Zettelstore

Check-in Differences
Login

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

Difference From version-0.0.14 To trunk

2021-09-17
14:55
Version 0.0.15 ... (Leaf check-in: 66498fc30a user: stern tags: trunk, release, version-0.0.15)
2021-09-16
18:02
Initial development zettel ... (check-in: 64e566f5db user: stern tags: trunk)
2021-07-23
16:43
Increase version to 0.0.15-dev to begin next development cycle ... (check-in: ba65777850 user: stern tags: trunk)
11:02
Version 0.0.14 ... (check-in: 6fe53d5db2 user: stern tags: trunk, release, version-0.0.14)
2021-07-22
13:50
Add client API for retrieving zettel links ... (check-in: e43ff68174 user: stern tags: trunk)

Changes to VERSION.

     1         -0.0.14
            1  +0.0.15

Changes to api/api.go.

    16     16   	Token   string `json:"token"`
    17     17   	Type    string `json:"token_type"`
    18     18   	Expires int    `json:"expires_in"`
    19     19   }
    20     20   
    21     21   // ZidJSON contains the identifier data of a zettel.
    22     22   type ZidJSON struct {
    23         -	ID  string `json:"id"`
    24         -	URL string `json:"url"`
           23  +	ID string `json:"id"`
    25     24   }
    26     25   
    27     26   // ZidMetaJSON contains the identifier and the metadata of a zettel.
    28     27   type ZidMetaJSON struct {
    29     28   	ID   string            `json:"id"`
    30         -	URL  string            `json:"url"`
    31     29   	Meta map[string]string `json:"meta"`
    32     30   }
    33     31   
    34     32   // ZidMetaRelatedList contains identifier/metadata of a zettel and the same for related zettel
    35     33   type ZidMetaRelatedList struct {
    36     34   	ID   string            `json:"id"`
    37         -	URL  string            `json:"url"`
    38     35   	Meta map[string]string `json:"meta"`
    39     36   	List []ZidMetaJSON     `json:"list"`
    40     37   }
    41     38   
    42     39   // ZettelLinksJSON store all links / connections from one zettel to other.
    43     40   type ZettelLinksJSON struct {
    44         -	ID    string `json:"id"`
    45         -	URL   string `json:"url"`
    46         -	Links struct {
    47         -		Incoming []ZidJSON `json:"incoming,omitempty"`
    48         -		Outgoing []ZidJSON `json:"outgoing,omitempty"`
    49         -		Local    []string  `json:"local,omitempty"`
    50         -		External []string  `json:"external,omitempty"`
    51         -		Meta     []string  `json:"meta,omitempty"`
    52         -	} `json:"links"`
    53         -	Images struct {
    54         -		Outgoing []ZidJSON `json:"outgoing,omitempty"`
    55         -		Local    []string  `json:"local,omitempty"`
    56         -		External []string  `json:"external,omitempty"`
    57         -	} `json:"images,omitempty"`
           41  +	ID     string `json:"id"`
           42  +	Linked struct {
           43  +		Incoming []string `json:"incoming,omitempty"`
           44  +		Outgoing []string `json:"outgoing,omitempty"`
           45  +		Local    []string `json:"local,omitempty"`
           46  +		External []string `json:"external,omitempty"`
           47  +		Meta     []string `json:"meta,omitempty"`
           48  +	} `json:"linked"`
           49  +	Embedded struct {
           50  +		Outgoing []string `json:"outgoing,omitempty"`
           51  +		Local    []string `json:"local,omitempty"`
           52  +		External []string `json:"external,omitempty"`
           53  +	} `json:"embedded,omitempty"`
    58     54   	Cites []string `json:"cites,omitempty"`
    59     55   }
    60     56   
    61     57   // ZettelDataJSON contains all data for a zettel.
    62     58   type ZettelDataJSON struct {
    63     59   	Meta     map[string]string `json:"meta"`
    64     60   	Encoding string            `json:"encoding"`
    65     61   	Content  string            `json:"content"`
    66     62   }
    67     63   
    68     64   // ZettelJSON contains all data for a zettel, the identifier, the metadata, and the content.
    69     65   type ZettelJSON struct {
    70     66   	ID       string            `json:"id"`
    71         -	URL      string            `json:"url"`
    72     67   	Meta     map[string]string `json:"meta"`
    73     68   	Encoding string            `json:"encoding"`
    74     69   	Content  string            `json:"content"`
    75     70   }
    76     71   
    77     72   // ZettelListJSON contains data for a zettel list.
    78     73   type ZettelListJSON struct {
    79         -	List []ZettelJSON `json:"list"`
           74  +	List []ZidMetaJSON `json:"list"`
    80     75   }
    81     76   
    82     77   // TagListJSON specifies the list/map of tags
    83     78   type TagListJSON struct {
    84     79   	Tags map[string][]string `json:"tags"`
    85     80   }
    86     81   
    87     82   // RoleListJSON specifies the list of roles.
    88     83   type RoleListJSON struct {
    89     84   	Roles []string `json:"role-list"`
    90     85   }

Changes to api/const.go.

     7      7   // Public License). Please see file LICENSE.txt for your rights and obligations
     8      8   // under this license.
     9      9   //-----------------------------------------------------------------------------
    10     10   
    11     11   // Package api contains common definition used for client and server.
    12     12   package api
    13     13   
    14         -import (
    15         -	"fmt"
    16         -)
           14  +import "fmt"
    17     15   
    18     16   // Additional HTTP constants used.
    19     17   const (
    20     18   	MethodMove = "MOVE" // HTTP method for renaming a zettel
    21     19   
    22     20   	HeaderAccept      = "Accept"
    23     21   	HeaderContentType = "Content-Type"
    24     22   	HeaderDestination = "Destination"
    25     23   	HeaderLocation    = "Location"
    26     24   )
    27     25   
    28     26   // Values for HTTP query parameter.
    29     27   const (
    30         -	QueryKeyDepth  = "depth"
    31         -	QueryKeyDir    = "dir"
    32         -	QueryKeyFormat = "_format"
    33         -	QueryKeyLimit  = "limit"
    34         -	QueryKeyPart   = "_part"
           28  +	QueryKeyDepth    = "depth"
           29  +	QueryKeyDir      = "dir"
           30  +	QueryKeyEncoding = "_enc"
           31  +	QueryKeyLimit    = "_limit"
           32  +	QueryKeyNegate   = "_negate"
           33  +	QueryKeyOffset   = "_offset"
           34  +	QueryKeyOrder    = "_order"
           35  +	QueryKeyPart     = "_part"
           36  +	QueryKeySearch   = "_s"
           37  +	QueryKeySort     = "_sort"
    35     38   )
    36     39   
    37     40   // Supported dir values.
    38     41   const (
    39     42   	DirBackward = "backward"
    40     43   	DirForward  = "forward"
    41     44   )
    42     45   
    43         -// Supported format values.
           46  +// Supported encoding values.
    44     47   const (
    45         -	FormatDJSON  = "djson"
    46         -	FormatHTML   = "html"
    47         -	FormatJSON   = "json"
    48         -	FormatNative = "native"
    49         -	FormatRaw    = "raw"
    50         -	FormatText   = "text"
    51         -	FormatZMK    = "zmk"
           48  +	EncodingDJSON  = "djson"
           49  +	EncodingHTML   = "html"
           50  +	EncodingNative = "native"
           51  +	EncodingText   = "text"
           52  +	EncodingZMK    = "zmk"
    52     53   )
    53     54   
    54         -var formatEncoder = map[string]EncodingEnum{
    55         -	FormatDJSON:  EncoderDJSON,
    56         -	FormatHTML:   EncoderHTML,
    57         -	FormatJSON:   EncoderJSON,
    58         -	FormatNative: EncoderNative,
    59         -	FormatRaw:    EncoderRaw,
    60         -	FormatText:   EncoderText,
    61         -	FormatZMK:    EncoderZmk,
           55  +var mapEncodingEnum = map[string]EncodingEnum{
           56  +	EncodingDJSON:  EncoderDJSON,
           57  +	EncodingHTML:   EncoderHTML,
           58  +	EncodingNative: EncoderNative,
           59  +	EncodingText:   EncoderText,
           60  +	EncodingZMK:    EncoderZmk,
    62     61   }
    63         -var encoderFormat = map[EncodingEnum]string{}
           62  +var mapEnumEncoding = map[EncodingEnum]string{}
    64     63   
    65     64   func init() {
    66         -	for k, v := range formatEncoder {
    67         -		encoderFormat[v] = k
           65  +	for k, v := range mapEncodingEnum {
           66  +		mapEnumEncoding[v] = k
    68     67   	}
    69     68   }
    70     69   
    71         -// Encoder returns the internal encoder code for the given format string.
    72         -func Encoder(format string) EncodingEnum {
    73         -	if e, ok := formatEncoder[format]; ok {
           70  +// Encoder returns the internal encoder code for the given encoding string.
           71  +func Encoder(encoding string) EncodingEnum {
           72  +	if e, ok := mapEncodingEnum[encoding]; ok {
    74     73   		return e
    75     74   	}
    76     75   	return EncoderUnknown
    77     76   }
    78     77   
    79     78   // EncodingEnum lists all valid encoder keys.
    80     79   type EncodingEnum uint8
    81     80   
    82     81   // Values for EncoderEnum
    83     82   const (
    84     83   	EncoderUnknown EncodingEnum = iota
    85     84   	EncoderDJSON
    86     85   	EncoderHTML
    87         -	EncoderJSON
    88     86   	EncoderNative
    89         -	EncoderRaw
    90     87   	EncoderText
    91     88   	EncoderZmk
    92     89   )
    93     90   
    94     91   // String representation of an encoder key.
    95     92   func (e EncodingEnum) String() string {
    96         -	if f, ok := encoderFormat[e]; ok {
           93  +	if f, ok := mapEnumEncoding[e]; ok {
    97     94   		return f
    98     95   	}
    99     96   	return fmt.Sprintf("*Unknown*(%d)", e)
   100     97   }
   101     98   
   102     99   // Supported part values.
   103    100   const (
   104         -	PartID      = "id"
   105    101   	PartMeta    = "meta"
   106    102   	PartContent = "content"
   107    103   	PartZettel  = "zettel"
   108    104   )

Changes to ast/ast.go.

    18     18   	"zettelstore.de/z/domain/id"
    19     19   	"zettelstore.de/z/domain/meta"
    20     20   )
    21     21   
    22     22   // ZettelNode is the root node of the abstract syntax tree.
    23     23   // It is *not* part of the visitor pattern.
    24     24   type ZettelNode struct {
    25         -	// Zettel  domain.Zettel
    26     25   	Meta    *meta.Meta     // Original metadata
    27     26   	Content domain.Content // Original content
    28     27   	Zid     id.Zid         // Zettel identification.
    29     28   	InhMeta *meta.Meta     // Metadata of the zettel, with inherited values.
    30         -	Ast     BlockSlice     // Zettel abstract syntax tree is a sequence of block nodes.
           29  +	Ast     *BlockListNode // Zettel abstract syntax tree is a sequence of block nodes.
    31     30   }
    32     31   
    33     32   // Node is the interface, all nodes must implement.
    34     33   type Node interface {
    35     34   	WalkChildren(v Visitor)
    36     35   }
    37     36   
    38     37   // BlockNode is the interface that all block nodes must implement.
    39     38   type BlockNode interface {
    40     39   	Node
    41     40   	blockNode()
    42     41   }
    43     42   
    44         -// BlockSlice is a slice of BlockNodes.
    45         -type BlockSlice []BlockNode
    46         -
    47     43   // ItemNode is a node that can occur as a list item.
    48     44   type ItemNode interface {
    49     45   	BlockNode
    50     46   	itemNode()
    51     47   }
    52     48   
    53     49   // ItemSlice is a slice of ItemNodes.
    54     50   type ItemSlice []ItemNode
           51  +
           52  +// ItemListNode is a list of BlockNodes.
           53  +type ItemListNode struct {
           54  +	List []ItemNode
           55  +}
           56  +
           57  +// WalkChildren walks down to the descriptions.
           58  +func (iln *ItemListNode) WalkChildren(v Visitor) {
           59  +	for _, bn := range iln.List {
           60  +		Walk(v, bn)
           61  +	}
           62  +}
    55     63   
    56     64   // DescriptionNode is a node that contains just textual description.
    57     65   type DescriptionNode interface {
    58     66   	ItemNode
    59     67   	descriptionNode()
    60     68   }
    61     69   
................................................................................
    64     72   
    65     73   // InlineNode is the interface that all inline nodes must implement.
    66     74   type InlineNode interface {
    67     75   	Node
    68     76   	inlineNode()
    69     77   }
    70     78   
    71         -// InlineSlice is a slice of InlineNodes.
    72         -type InlineSlice []InlineNode
    73         -
    74     79   // Reference is a reference to external or internal material.
    75     80   type Reference struct {
    76     81   	URL   *url.URL
    77     82   	Value string
    78     83   	State RefState
    79     84   }
    80     85   

Changes to ast/attr.go.

    16     16   )
    17     17   
    18     18   // Attributes store additional information about some node types.
    19     19   type Attributes struct {
    20     20   	Attrs map[string]string
    21     21   }
    22     22   
           23  +// IsEmpty returns true if there are no attributes.
           24  +func (a *Attributes) IsEmpty() bool { return a == nil || len(a.Attrs) == 0 }
           25  +
    23     26   // HasDefault returns true, if the default attribute "-" has been set.
    24     27   func (a *Attributes) HasDefault() bool {
    25     28   	if a != nil {
    26     29   		_, ok := a.Attrs["-"]
    27     30   		return ok
    28     31   	}
    29     32   	return false
................................................................................
    75     78   }
    76     79   
    77     80   // AddClass adds a value to the class attribute.
    78     81   func (a *Attributes) AddClass(class string) *Attributes {
    79     82   	if a == nil {
    80     83   		return &Attributes{map[string]string{"class": class}}
    81     84   	}
           85  +	if a.Attrs == nil {
           86  +		a.Attrs = make(map[string]string)
           87  +	}
    82     88   	classes := a.GetClasses()
    83     89   	for _, cls := range classes {
    84     90   		if cls == class {
    85     91   			return a
    86     92   		}
    87     93   	}
    88     94   	classes = append(classes, class)

Changes to ast/block.go.

     9      9   //-----------------------------------------------------------------------------
    10     10   
    11     11   // Package ast provides the abstract syntax tree.
    12     12   package ast
    13     13   
    14     14   // Definition of Block nodes.
    15     15   
           16  +// BlockListNode is a list of BlockNodes.
           17  +type BlockListNode struct {
           18  +	List []BlockNode
           19  +}
           20  +
           21  +// WalkChildren walks down to the descriptions.
           22  +func (bln *BlockListNode) WalkChildren(v Visitor) {
           23  +	for _, bn := range bln.List {
           24  +		Walk(v, bn)
           25  +	}
           26  +}
           27  +
           28  +//--------------------------------------------------------------------------
           29  +
    16     30   // ParaNode contains just a sequence of inline elements.
    17     31   // Another name is "paragraph".
    18     32   type ParaNode struct {
    19         -	Inlines InlineSlice
           33  +	Inlines *InlineListNode
    20     34   }
    21     35   
    22         -func (pn *ParaNode) blockNode()       { /* Just a marker */ }
    23         -func (pn *ParaNode) itemNode()        { /* Just a marker */ }
    24         -func (pn *ParaNode) descriptionNode() { /* Just a marker */ }
           36  +func (*ParaNode) blockNode()       { /* Just a marker */ }
           37  +func (*ParaNode) itemNode()        { /* Just a marker */ }
           38  +func (*ParaNode) descriptionNode() { /* Just a marker */ }
           39  +
           40  +// NewParaNode creates an empty ParaNode.
           41  +func NewParaNode() *ParaNode { return &ParaNode{Inlines: &InlineListNode{}} }
    25     42   
    26     43   // WalkChildren walks down the inline elements.
    27     44   func (pn *ParaNode) WalkChildren(v Visitor) {
    28         -	WalkInlineSlice(v, pn.Inlines)
           45  +	Walk(v, pn.Inlines)
    29     46   }
    30     47   
    31     48   //--------------------------------------------------------------------------
    32     49   
    33     50   // VerbatimNode contains lines of uninterpreted text
    34     51   type VerbatimNode struct {
    35     52   	Kind  VerbatimKind
................................................................................
    44     61   const (
    45     62   	_               VerbatimKind = iota
    46     63   	VerbatimProg                 // Program code.
    47     64   	VerbatimComment              // Block comment
    48     65   	VerbatimHTML                 // Block HTML, e.g. for Markdown
    49     66   )
    50     67   
    51         -func (vn *VerbatimNode) blockNode() { /* Just a marker */ }
    52         -func (vn *VerbatimNode) itemNode()  { /* Just a marker */ }
           68  +func (*VerbatimNode) blockNode() { /* Just a marker */ }
           69  +func (*VerbatimNode) itemNode()  { /* Just a marker */ }
    53     70   
    54     71   // WalkChildren does nothing.
    55         -func (vn *VerbatimNode) WalkChildren(v Visitor) { /* No children*/ }
           72  +func (*VerbatimNode) WalkChildren(Visitor) { /* No children*/ }
    56     73   
    57     74   //--------------------------------------------------------------------------
    58     75   
    59     76   // RegionNode encapsulates a region of block nodes.
    60     77   type RegionNode struct {
    61     78   	Kind    RegionKind
    62     79   	Attrs   *Attributes
    63         -	Blocks  BlockSlice
    64         -	Inlines InlineSlice // Additional text at the end of the region
           80  +	Blocks  *BlockListNode
           81  +	Inlines *InlineListNode // Optional text at the end of the region
    65     82   }
    66     83   
    67     84   // RegionKind specifies the actual region type.
    68     85   type RegionKind uint8
    69     86   
    70     87   // Values for RegionCode
    71     88   const (
    72     89   	_           RegionKind = iota
    73     90   	RegionSpan             // Just a span of blocks
    74     91   	RegionQuote            // A longer quotation
    75     92   	RegionVerse            // Line breaks matter
    76     93   )
    77     94   
    78         -func (rn *RegionNode) blockNode() { /* Just a marker */ }
    79         -func (rn *RegionNode) itemNode()  { /* Just a marker */ }
           95  +func (*RegionNode) blockNode() { /* Just a marker */ }
           96  +func (*RegionNode) itemNode()  { /* Just a marker */ }
    80     97   
    81     98   // WalkChildren walks down the blocks and the text.
    82     99   func (rn *RegionNode) WalkChildren(v Visitor) {
    83         -	WalkBlockSlice(v, rn.Blocks)
    84         -	WalkInlineSlice(v, rn.Inlines)
          100  +	Walk(v, rn.Blocks)
          101  +	if iln := rn.Inlines; iln != nil {
          102  +		Walk(v, iln)
          103  +	}
    85    104   }
    86    105   
    87    106   //--------------------------------------------------------------------------
    88    107   
    89    108   // HeadingNode stores the heading text and level.
    90    109   type HeadingNode struct {
    91         -	Level   int
    92         -	Inlines InlineSlice // Heading text, possibly formatted
    93         -	Slug    string      // Heading text, suitable to be used as an URL fragment
    94         -	Attrs   *Attributes
          110  +	Level    int
          111  +	Inlines  *InlineListNode // Heading text, possibly formatted
          112  +	Slug     string          // Heading text, normalized
          113  +	Fragment string          // Heading text, suitable to be used as an unique URL fragment
          114  +	Attrs    *Attributes
    95    115   }
    96    116   
    97         -func (hn *HeadingNode) blockNode() { /* Just a marker */ }
    98         -func (hn *HeadingNode) itemNode()  { /* Just a marker */ }
          117  +func (*HeadingNode) blockNode() { /* Just a marker */ }
          118  +func (*HeadingNode) itemNode()  { /* Just a marker */ }
    99    119   
   100    120   // WalkChildren walks the heading text.
   101    121   func (hn *HeadingNode) WalkChildren(v Visitor) {
   102         -	WalkInlineSlice(v, hn.Inlines)
          122  +	Walk(v, hn.Inlines)
   103    123   }
   104    124   
   105    125   //--------------------------------------------------------------------------
   106    126   
   107    127   // HRuleNode specifies a horizontal rule.
   108    128   type HRuleNode struct {
   109    129   	Attrs *Attributes
   110    130   }
   111    131   
   112         -func (hn *HRuleNode) blockNode() { /* Just a marker */ }
   113         -func (hn *HRuleNode) itemNode()  { /* Just a marker */ }
          132  +func (*HRuleNode) blockNode() { /* Just a marker */ }
          133  +func (*HRuleNode) itemNode()  { /* Just a marker */ }
   114    134   
   115    135   // WalkChildren does nothing.
   116         -func (hn *HRuleNode) WalkChildren(v Visitor) { /* No children*/ }
          136  +func (*HRuleNode) WalkChildren(Visitor) { /* No children*/ }
   117    137   
   118    138   //--------------------------------------------------------------------------
   119    139   
   120    140   // NestedListNode specifies a nestable list, either ordered or unordered.
   121    141   type NestedListNode struct {
   122    142   	Kind  NestedListKind
   123    143   	Items []ItemSlice
................................................................................
   131    151   const (
   132    152   	_                   NestedListKind = iota
   133    153   	NestedListOrdered                  // Ordered list.
   134    154   	NestedListUnordered                // Unordered list.
   135    155   	NestedListQuote                    // Quote list.
   136    156   )
   137    157   
   138         -func (ln *NestedListNode) blockNode() { /* Just a marker */ }
   139         -func (ln *NestedListNode) itemNode()  { /* Just a marker */ }
          158  +func (*NestedListNode) blockNode() { /* Just a marker */ }
          159  +func (*NestedListNode) itemNode()  { /* Just a marker */ }
   140    160   
   141    161   // WalkChildren walks down the items.
   142    162   func (ln *NestedListNode) WalkChildren(v Visitor) {
   143    163   	for _, item := range ln.Items {
   144    164   		WalkItemSlice(v, item)
   145    165   	}
   146    166   }
................................................................................
   150    170   // DescriptionListNode specifies a description list.
   151    171   type DescriptionListNode struct {
   152    172   	Descriptions []Description
   153    173   }
   154    174   
   155    175   // Description is one element of a description list.
   156    176   type Description struct {
   157         -	Term         InlineSlice
          177  +	Term         *InlineListNode
   158    178   	Descriptions []DescriptionSlice
   159    179   }
   160    180   
   161         -func (dn *DescriptionListNode) blockNode() {}
          181  +func (*DescriptionListNode) blockNode() { /* Just a marker */ }
   162    182   
   163    183   // WalkChildren walks down to the descriptions.
   164    184   func (dn *DescriptionListNode) WalkChildren(v Visitor) {
   165    185   	for _, desc := range dn.Descriptions {
   166         -		WalkInlineSlice(v, desc.Term)
          186  +		Walk(v, desc.Term)
   167    187   		for _, dns := range desc.Descriptions {
   168    188   			WalkDescriptionSlice(v, dns)
   169    189   		}
   170    190   	}
   171    191   }
   172    192   
   173    193   //--------------------------------------------------------------------------
................................................................................
   177    197   	Header TableRow    // The header row
   178    198   	Align  []Alignment // Default column alignment
   179    199   	Rows   []TableRow  // The slice of cell rows
   180    200   }
   181    201   
   182    202   // TableCell contains the data for one table cell
   183    203   type TableCell struct {
   184         -	Align   Alignment   // Cell alignment
   185         -	Inlines InlineSlice // Cell content
          204  +	Align   Alignment       // Cell alignment
          205  +	Inlines *InlineListNode // Cell content
   186    206   }
   187    207   
   188    208   // TableRow is a slice of cells.
   189    209   type TableRow []*TableCell
   190    210   
   191    211   // Alignment specifies text alignment.
   192    212   // Currently only for tables.
................................................................................
   197    217   	_            Alignment = iota
   198    218   	AlignDefault           // Default alignment, inherited
   199    219   	AlignLeft              // Left alignment
   200    220   	AlignCenter            // Center the content
   201    221   	AlignRight             // Right alignment
   202    222   )
   203    223   
   204         -func (tn *TableNode) blockNode() { /* Just a marker */ }
          224  +func (*TableNode) blockNode() { /* Just a marker */ }
   205    225   
   206    226   // WalkChildren walks down to the cells.
   207    227   func (tn *TableNode) WalkChildren(v Visitor) {
   208    228   	for _, cell := range tn.Header {
   209         -		WalkInlineSlice(v, cell.Inlines)
          229  +		Walk(v, cell.Inlines)
   210    230   	}
   211    231   	for _, row := range tn.Rows {
   212    232   		for _, cell := range row {
   213         -			WalkInlineSlice(v, cell.Inlines)
          233  +			Walk(v, cell.Inlines)
   214    234   		}
   215    235   	}
   216    236   }
   217    237   
   218    238   //--------------------------------------------------------------------------
   219    239   
   220    240   // BLOBNode contains just binary data that must be interpreted according to
................................................................................
   221    241   // a syntax.
   222    242   type BLOBNode struct {
   223    243   	Title  string
   224    244   	Syntax string
   225    245   	Blob   []byte
   226    246   }
   227    247   
   228         -func (bn *BLOBNode) blockNode() { /* Just a marker */ }
          248  +func (*BLOBNode) blockNode() { /* Just a marker */ }
   229    249   
   230    250   // WalkChildren does nothing.
   231         -func (bn *BLOBNode) WalkChildren(v Visitor) { /* No children*/ }
          251  +func (*BLOBNode) WalkChildren(Visitor) { /* No children*/ }

Changes to ast/inline.go.

     9      9   //-----------------------------------------------------------------------------
    10     10   
    11     11   // Package ast provides the abstract syntax tree.
    12     12   package ast
    13     13   
    14     14   // Definitions of inline nodes.
    15     15   
           16  +// InlineListNode is a list of BlockNodes.
           17  +type InlineListNode struct {
           18  +	List []InlineNode
           19  +}
           20  +
           21  +func (*InlineListNode) inlineNode() { /* Just a marker */ }
           22  +
           23  +// CreateInlineListNode make a new inline list node from nodes
           24  +func CreateInlineListNode(nodes ...InlineNode) *InlineListNode {
           25  +	return &InlineListNode{List: nodes}
           26  +}
           27  +
           28  +// CreateInlineListNodeFromWords makes a new inline list from words,
           29  +// that will be space-separated.
           30  +func CreateInlineListNodeFromWords(words ...string) *InlineListNode {
           31  +	inl := make([]InlineNode, 0, 2*len(words)-1)
           32  +	for i, word := range words {
           33  +		if i > 0 {
           34  +			inl = append(inl, &SpaceNode{Lexeme: " "})
           35  +		}
           36  +		inl = append(inl, &TextNode{Text: word})
           37  +	}
           38  +	return &InlineListNode{List: inl}
           39  +}
           40  +
           41  +// WalkChildren walks down to the descriptions.
           42  +func (iln *InlineListNode) WalkChildren(v Visitor) {
           43  +	for _, bn := range iln.List {
           44  +		Walk(v, bn)
           45  +	}
           46  +}
           47  +
           48  +// IsEmpty returns true if the list has no elements.
           49  +func (iln *InlineListNode) IsEmpty() bool { return iln == nil || len(iln.List) == 0 }
           50  +
           51  +// Append inline node(s) to the list.
           52  +func (iln *InlineListNode) Append(in ...InlineNode) {
           53  +	iln.List = append(iln.List, in...)
           54  +}
           55  +
           56  +// --------------------------------------------------------------------------
           57  +
    16     58   // TextNode just contains some text.
    17     59   type TextNode struct {
    18     60   	Text string // The text itself.
    19     61   }
    20     62   
    21         -func (tn *TextNode) inlineNode() { /* Just a marker */ }
           63  +func (*TextNode) inlineNode() { /* Just a marker */ }
    22     64   
    23     65   // WalkChildren does nothing.
    24         -func (tn *TextNode) WalkChildren(v Visitor) { /* No children*/ }
           66  +func (*TextNode) WalkChildren(Visitor) { /* No children*/ }
    25     67   
    26     68   // --------------------------------------------------------------------------
    27     69   
    28     70   // TagNode contains a tag.
    29     71   type TagNode struct {
    30     72   	Tag string // The text itself.
    31     73   }
    32     74   
    33         -func (tn *TagNode) inlineNode() { /* Just a marker */ }
           75  +func (*TagNode) inlineNode() { /* Just a marker */ }
    34     76   
    35     77   // WalkChildren does nothing.
    36         -func (tn *TagNode) WalkChildren(v Visitor) { /* No children*/ }
           78  +func (*TagNode) WalkChildren(Visitor) { /* No children*/ }
    37     79   
    38     80   // --------------------------------------------------------------------------
    39     81   
    40     82   // SpaceNode tracks inter-word space characters.
    41     83   type SpaceNode struct {
    42     84   	Lexeme string
    43     85   }
    44     86   
    45         -func (sn *SpaceNode) inlineNode() { /* Just a marker */ }
           87  +func (*SpaceNode) inlineNode() { /* Just a marker */ }
    46     88   
    47     89   // WalkChildren does nothing.
    48         -func (sn *SpaceNode) WalkChildren(v Visitor) { /* No children*/ }
           90  +func (*SpaceNode) WalkChildren(Visitor) { /* No children*/ }
    49     91   
    50     92   // --------------------------------------------------------------------------
    51     93   
    52     94   // BreakNode signals a new line that must / should be interpreted as a new line break.
    53     95   type BreakNode struct {
    54     96   	Hard bool // Hard line break?
    55     97   }
    56     98   
    57         -func (bn *BreakNode) inlineNode() { /* Just a marker */ }
           99  +func (*BreakNode) inlineNode() { /* Just a marker */ }
    58    100   
    59    101   // WalkChildren does nothing.
    60         -func (bn *BreakNode) WalkChildren(v Visitor) { /* No children*/ }
          102  +func (*BreakNode) WalkChildren(Visitor) { /* No children*/ }
    61    103   
    62    104   // --------------------------------------------------------------------------
    63    105   
    64    106   // LinkNode contains the specified link.
    65    107   type LinkNode struct {
    66    108   	Ref     *Reference
    67         -	Inlines InlineSlice // The text associated with the link.
    68         -	OnlyRef bool        // True if no text was specified.
    69         -	Attrs   *Attributes // Optional attributes
          109  +	Inlines *InlineListNode // The text associated with the link.
          110  +	OnlyRef bool            // True if no text was specified.
          111  +	Attrs   *Attributes     // Optional attributes
    70    112   }
    71    113   
    72         -func (ln *LinkNode) inlineNode() { /* Just a marker */ }
          114  +func (*LinkNode) inlineNode() { /* Just a marker */ }
    73    115   
    74    116   // WalkChildren walks to the link text.
    75    117   func (ln *LinkNode) WalkChildren(v Visitor) {
    76         -	WalkInlineSlice(v, ln.Inlines)
          118  +	if iln := ln.Inlines; iln != nil {
          119  +		Walk(v, iln)
          120  +	}
    77    121   }
    78    122   
    79    123   // --------------------------------------------------------------------------
    80    124   
    81         -// ImageNode contains the specified image reference.
    82         -type ImageNode struct {
    83         -	Ref     *Reference  // Reference to image
    84         -	Blob    []byte      // BLOB data of the image, as an alternative to Ref.
    85         -	Syntax  string      // Syntax of Blob
    86         -	Inlines InlineSlice // The text associated with the image.
    87         -	Attrs   *Attributes // Optional attributes
          125  +// EmbedNode contains the specified embedded material.
          126  +type EmbedNode struct {
          127  +	Material MaterialNode    // The material to be embedded
          128  +	Inlines  *InlineListNode // Optional text associated with the image.
          129  +	Attrs    *Attributes     // Optional attributes
    88    130   }
    89    131   
    90         -func (in *ImageNode) inlineNode() { /* Just a marker */ }
          132  +func (*EmbedNode) inlineNode() { /* Just a marker */ }
    91    133   
    92         -// WalkChildren walks to the image text.
    93         -func (in *ImageNode) WalkChildren(v Visitor) {
    94         -	WalkInlineSlice(v, in.Inlines)
          134  +// WalkChildren walks to the text that describes the embedded material.
          135  +func (en *EmbedNode) WalkChildren(v Visitor) {
          136  +	if iln := en.Inlines; iln != nil {
          137  +		Walk(v, iln)
          138  +	}
    95    139   }
    96    140   
    97    141   // --------------------------------------------------------------------------
    98    142   
    99    143   // CiteNode contains the specified citation.
   100    144   type CiteNode struct {
   101         -	Key     string      // The citation key
   102         -	Inlines InlineSlice // The text associated with the citation.
   103         -	Attrs   *Attributes // Optional attributes
          145  +	Key     string          // The citation key
          146  +	Inlines *InlineListNode // Optional text associated with the citation.
          147  +	Attrs   *Attributes     // Optional attributes
   104    148   }
   105    149   
   106         -func (cn *CiteNode) inlineNode() { /* Just a marker */ }
          150  +func (*CiteNode) inlineNode() { /* Just a marker */ }
   107    151   
   108    152   // WalkChildren walks to the cite text.
   109    153   func (cn *CiteNode) WalkChildren(v Visitor) {
   110         -	WalkInlineSlice(v, cn.Inlines)
          154  +	if iln := cn.Inlines; iln != nil {
          155  +		Walk(v, iln)
          156  +	}
   111    157   }
   112    158   
   113    159   // --------------------------------------------------------------------------
   114    160   
   115    161   // MarkNode contains the specified merked position.
   116    162   // It is a BlockNode too, because although it is typically parsed during inline
   117    163   // mode, it is moved into block mode afterwards.
   118    164   type MarkNode struct {
   119         -	Text string
          165  +	Text     string
          166  +	Slug     string // Slugified form of Text
          167  +	Fragment string // Unique form of Slug
   120    168   }
   121    169   
   122         -func (mn *MarkNode) inlineNode() { /* Just a marker */ }
          170  +func (*MarkNode) inlineNode() { /* Just a marker */ }
   123    171   
   124    172   // WalkChildren does nothing.
   125         -func (mn *MarkNode) WalkChildren(v Visitor) { /* No children*/ }
          173  +func (*MarkNode) WalkChildren(Visitor) { /* No children*/ }
   126    174   
   127    175   // --------------------------------------------------------------------------
   128    176   
   129    177   // FootnoteNode contains the specified footnote.
   130    178   type FootnoteNode struct {
   131         -	Inlines InlineSlice // The footnote text.
   132         -	Attrs   *Attributes // Optional attributes
          179  +	Inlines *InlineListNode // The footnote text.
          180  +	Attrs   *Attributes     // Optional attributes
   133    181   }
   134    182   
   135         -func (fn *FootnoteNode) inlineNode() { /* Just a marker */ }
          183  +func (*FootnoteNode) inlineNode() { /* Just a marker */ }
   136    184   
   137    185   // WalkChildren walks to the footnote text.
   138    186   func (fn *FootnoteNode) WalkChildren(v Visitor) {
   139         -	WalkInlineSlice(v, fn.Inlines)
          187  +	Walk(v, fn.Inlines)
   140    188   }
   141    189   
   142    190   // --------------------------------------------------------------------------
   143    191   
   144    192   // FormatNode specifies some inline formatting.
   145    193   type FormatNode struct {
   146    194   	Kind    FormatKind
   147    195   	Attrs   *Attributes // Optional attributes.
   148         -	Inlines InlineSlice
          196  +	Inlines *InlineListNode
   149    197   }
   150    198   
   151    199   // FormatKind specifies the format that is applied to the inline nodes.
   152    200   type FormatKind uint8
   153    201   
   154    202   // Constants for FormatCode
   155    203   const (
................................................................................
   167    215   	FormatQuote                // Quoted text.
   168    216   	FormatQuotation            // Quotation text.
   169    217   	FormatSmall                // Smaller text.
   170    218   	FormatSpan                 // Generic inline container.
   171    219   	FormatMonospace            // Monospaced text.
   172    220   )
   173    221   
   174         -func (fn *FormatNode) inlineNode() { /* Just a marker */ }
          222  +func (*FormatNode) inlineNode() { /* Just a marker */ }
   175    223   
   176    224   // WalkChildren walks to the formatted text.
   177    225   func (fn *FormatNode) WalkChildren(v Visitor) {
   178         -	WalkInlineSlice(v, fn.Inlines)
          226  +	Walk(v, fn.Inlines)
   179    227   }
   180    228   
   181    229   // --------------------------------------------------------------------------
   182    230   
   183    231   // LiteralNode specifies some uninterpreted text.
   184    232   type LiteralNode struct {
   185    233   	Kind  LiteralKind
................................................................................
   196    244   	LiteralProg                // Inline program code.
   197    245   	LiteralKeyb                // Keyboard strokes.
   198    246   	LiteralOutput              // Sample output.
   199    247   	LiteralComment             // Inline comment
   200    248   	LiteralHTML                // Inline HTML, e.g. for Markdown
   201    249   )
   202    250   
   203         -func (ln *LiteralNode) inlineNode() { /* Just a marker */ }
          251  +func (*LiteralNode) inlineNode() { /* Just a marker */ }
   204    252   
   205    253   // WalkChildren does nothing.
   206         -func (ln *LiteralNode) WalkChildren(v Visitor) { /* No children*/ }
          254  +func (*LiteralNode) WalkChildren(Visitor) { /* No children*/ }

Added ast/material.go.

            1  +//-----------------------------------------------------------------------------
            2  +// Copyright (c) 2021 Detlef Stern
            3  +//
            4  +// This file is part of zettelstore.
            5  +//
            6  +// Zettelstore is licensed under the latest version of the EUPL (European Union
            7  +// Public License). Please see file LICENSE.txt for your rights and obligations
            8  +// under this license.
            9  +//-----------------------------------------------------------------------------
           10  +
           11  +// Package ast provides the abstract syntax tree.
           12  +package ast
           13  +
           14  +// MaterialNode references the various types of zettel material.
           15  +type MaterialNode interface {
           16  +	Node
           17  +	materialNode()
           18  +}
           19  +
           20  +// --------------------------------------------------------------------------
           21  +
           22  +// ReferenceMaterialNode is material that can be retrieved by using a reference.
           23  +type ReferenceMaterialNode struct {
           24  +	Ref *Reference
           25  +}
           26  +
           27  +func (*ReferenceMaterialNode) materialNode() { /* Just a marker */ }
           28  +
           29  +// WalkChildren does nothing.
           30  +func (*ReferenceMaterialNode) WalkChildren(Visitor) { /* No children*/ }
           31  +
           32  +// --------------------------------------------------------------------------
           33  +
           34  +// BLOBMaterialNode represents itself.
           35  +type BLOBMaterialNode struct {
           36  +	Blob   []byte // BLOB data itself.
           37  +	Syntax string // Syntax of Blob
           38  +}
           39  +
           40  +func (*BLOBMaterialNode) materialNode() { /* Just a marker */ }
           41  +
           42  +// WalkChildren does nothing.
           43  +func (*BLOBMaterialNode) WalkChildren(Visitor) { /* No children*/ }

Changes to ast/walk.go.

    21     21   	if v = v.Visit(node); v == nil {
    22     22   		return
    23     23   	}
    24     24   	node.WalkChildren(v)
    25     25   	v.Visit(nil)
    26     26   }
    27     27   
    28         -// WalkBlockSlice traverse a block slice.
    29         -func WalkBlockSlice(v Visitor, bns BlockSlice) {
    30         -	for _, bn := range bns {
    31         -		Walk(v, bn)
    32         -	}
    33         -}
    34         -
    35         -// WalkInlineSlice traverses an inline slice.
    36         -func WalkInlineSlice(v Visitor, ins InlineSlice) {
    37         -	for _, in := range ins {
    38         -		Walk(v, in)
    39         -	}
    40         -}
    41         -
    42     28   // WalkItemSlice traverses an item slice.
    43     29   func WalkItemSlice(v Visitor, ins ItemSlice) {
    44     30   	for _, in := range ins {
    45     31   		Walk(v, in)
    46     32   	}
    47     33   }
    48     34   
    49     35   // WalkDescriptionSlice traverses an item slice.
    50     36   func WalkDescriptionSlice(v Visitor, dns DescriptionSlice) {
    51     37   	for _, dn := range dns {
    52     38   		Walk(v, dn)
    53     39   	}
    54     40   }

Changes to auth/policy/policy_test.go.

    41     41   			readOnly: ts.readonly,
    42     42   			withAuth: ts.withAuth,
    43     43   		}
    44     44   		pol := newPolicy(authzManager, &authConfig{ts.expert})
    45     45   		name := fmt.Sprintf("readonly=%v/withauth=%v/expert=%v",
    46     46   			ts.readonly, ts.withAuth, ts.expert)
    47     47   		t.Run(name, func(tt *testing.T) {
    48         -			testCreate(tt, pol, ts.withAuth, ts.readonly, ts.expert)
    49         -			testRead(tt, pol, ts.withAuth, ts.readonly, ts.expert)
           48  +			testCreate(tt, pol, ts.withAuth, ts.readonly)
           49  +			testRead(tt, pol, ts.withAuth, ts.expert)
    50     50   			testWrite(tt, pol, ts.withAuth, ts.readonly, ts.expert)
    51     51   			testRename(tt, pol, ts.withAuth, ts.readonly, ts.expert)
    52     52   			testDelete(tt, pol, ts.withAuth, ts.readonly, ts.expert)
    53     53   		})
    54     54   	}
    55     55   }
    56     56   
................................................................................
    90     90   func (ac *authConfig) GetVisibility(m *meta.Meta) meta.Visibility {
    91     91   	if vis, ok := m.Get(meta.KeyVisibility); ok {
    92     92   		return meta.GetVisibility(vis)
    93     93   	}
    94     94   	return meta.VisibilityLogin
    95     95   }
    96     96   
    97         -func testCreate(t *testing.T, pol auth.Policy, withAuth, readonly, isExpert bool) {
           97  +func testCreate(t *testing.T, pol auth.Policy, withAuth, readonly bool) {
    98     98   	t.Helper()
    99     99   	anonUser := newAnon()
   100    100   	creator := newCreator()
   101    101   	reader := newReader()
   102    102   	writer := newWriter()
   103    103   	owner := newOwner()
   104    104   	owner2 := newOwner2()
................................................................................
   137    137   			if tc.exp != got {
   138    138   				tt.Errorf("exp=%v, but got=%v", tc.exp, got)
   139    139   			}
   140    140   		})
   141    141   	}
   142    142   }
   143    143   
   144         -func testRead(t *testing.T, pol auth.Policy, withAuth, readonly, expert bool) {
          144  +func testRead(t *testing.T, pol auth.Policy, withAuth, expert bool) {
   145    145   	t.Helper()
   146    146   	anonUser := newAnon()
   147    147   	creator := newCreator()
   148    148   	reader := newReader()
   149    149   	writer := newWriter()
   150    150   	owner := newOwner()
   151    151   	owner2 := newOwner2()

Changes to box/box.go.

    39     39   
    40     40   	// GetZettel retrieves a specific zettel.
    41     41   	GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error)
    42     42   
    43     43   	// GetMeta retrieves just the meta data of a specific zettel.
    44     44   	GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error)
    45     45   
    46         -	// FetchZids returns the set of all zettel identifer managed by the box.
    47         -	FetchZids(ctx context.Context) (id.Set, error)
    48         -
    49     46   	// CanUpdateZettel returns true, if box could possibly update the given zettel.
    50     47   	CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool
    51     48   
    52     49   	// UpdateZettel updates an existing zettel.
    53     50   	UpdateZettel(ctx context.Context, zettel domain.Zettel) error
    54     51   
    55     52   	// AllowRenameZettel returns true, if box will not disallow renaming the zettel.
................................................................................
    60     57   
    61     58   	// CanDeleteZettel returns true, if box could possibly delete the given zettel.
    62     59   	CanDeleteZettel(ctx context.Context, zid id.Zid) bool
    63     60   
    64     61   	// DeleteZettel removes the zettel from the box.
    65     62   	DeleteZettel(ctx context.Context, zid id.Zid) error
    66     63   }
           64  +
           65  +// ZidFunc is a function that processes identifier of a zettel.
           66  +type ZidFunc func(id.Zid)
           67  +
           68  +// MetaFunc is a function that processes metadata of a zettel.
           69  +type MetaFunc func(*meta.Meta)
    67     70   
    68     71   // ManagedBox is the interface of managed boxes.
    69     72   type ManagedBox interface {
    70     73   	BaseBox
    71     74   
    72         -	// SelectMeta returns all zettel meta data that match the selection criteria.
    73         -	SelectMeta(ctx context.Context, match search.MetaMatchFunc) ([]*meta.Meta, error)
           75  +	// Apply identifier of every zettel to the given function.
           76  +	ApplyZid(context.Context, ZidFunc) error
           77  +
           78  +	// Apply metadata of every zettel to the given function.
           79  +	ApplyMeta(context.Context, MetaFunc) error
    74     80   
    75     81   	// ReadStats populates st with box statistics
    76     82   	ReadStats(st *ManagedBoxStats)
    77     83   }
    78     84   
    79     85   // ManagedBoxStats records statistics about the box.
    80     86   type ManagedBoxStats struct {
................................................................................
    94    100   	// Stop the started box. Now only the Start() function is allowed.
    95    101   	Stop(ctx context.Context) error
    96    102   }
    97    103   
    98    104   // Box is to be used outside the box package and its descendants.
    99    105   type Box interface {
   100    106   	BaseBox
          107  +
          108  +	// FetchZids returns the set of all zettel identifer managed by the box.
          109  +	FetchZids(ctx context.Context) (id.Set, error)
   101    110   
   102    111   	// SelectMeta returns a list of metadata that comply to the given selection criteria.
   103    112   	SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error)
   104    113   
   105    114   	// GetAllZettel retrieves a specific zettel from all managed boxes.
   106    115   	GetAllZettel(ctx context.Context, zid id.Zid) ([]domain.Zettel, error)
   107    116   

Changes to box/compbox/compbox.go.

    16     16   	"net/url"
    17     17   
    18     18   	"zettelstore.de/z/box"
    19     19   	"zettelstore.de/z/box/manager"
    20     20   	"zettelstore.de/z/domain"
    21     21   	"zettelstore.de/z/domain/id"
    22     22   	"zettelstore.de/z/domain/meta"
    23         -	"zettelstore.de/z/search"
    24     23   )
    25     24   
    26     25   func init() {
    27     26   	manager.Register(
    28     27   		" comp",
    29     28   		func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) {
    30     29   			return getCompBox(cdata.Number, cdata.Enricher), nil
................................................................................
    53     52   func getCompBox(boxNumber int, mf box.Enricher) box.ManagedBox {
    54     53   	return &compBox{number: boxNumber, enricher: mf}
    55     54   }
    56     55   
    57     56   // Setup remembers important values.
    58     57   func Setup(cfg *meta.Meta) { myConfig = cfg.Clone() }
    59     58   
    60         -func (pp *compBox) Location() string { return "" }
           59  +func (*compBox) Location() string { return "" }
    61     60   
    62         -func (pp *compBox) CanCreateZettel(ctx context.Context) bool { return false }
           61  +func (*compBox) CanCreateZettel(context.Context) bool { return false }
    63     62   
    64         -func (pp *compBox) CreateZettel(
    65         -	ctx context.Context, zettel domain.Zettel) (id.Zid, error) {
           63  +func (*compBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) {
    66     64   	return id.Invalid, box.ErrReadOnly
    67     65   }
    68     66   
    69         -func (pp *compBox) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) {
           67  +func (*compBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) {
    70     68   	if gen, ok := myZettel[zid]; ok && gen.meta != nil {
    71     69   		if m := gen.meta(zid); m != nil {
    72     70   			updateMeta(m)
    73     71   			if genContent := gen.content; genContent != nil {
    74     72   				return domain.Zettel{
    75     73   					Meta:    m,
    76     74   					Content: domain.NewContent(genContent(m)),
................................................................................
    78     76   			}
    79     77   			return domain.Zettel{Meta: m}, nil
    80     78   		}
    81     79   	}
    82     80   	return domain.Zettel{}, box.ErrNotFound
    83     81   }
    84     82   
    85         -func (pp *compBox) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) {
           83  +func (*compBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) {
    86     84   	if gen, ok := myZettel[zid]; ok {
    87     85   		if genMeta := gen.meta; genMeta != nil {
    88     86   			if m := genMeta(zid); m != nil {
    89     87   				updateMeta(m)
    90     88   				return m, nil
    91     89   			}
    92     90   		}
    93     91   	}
    94     92   	return nil, box.ErrNotFound
    95     93   }
    96     94   
    97         -func (pp *compBox) FetchZids(ctx context.Context) (id.Set, error) {
    98         -	result := id.NewSetCap(len(myZettel))
           95  +func (*compBox) ApplyZid(_ context.Context, handle box.ZidFunc) error {
    99     96   	for zid, gen := range myZettel {
   100     97   		if genMeta := gen.meta; genMeta != nil {
   101     98   			if genMeta(zid) != nil {
   102         -				result[zid] = true
           99  +				handle(zid)
   103    100   			}
   104    101   		}
   105    102   	}
   106         -	return result, nil
          103  +	return nil
   107    104   }
   108    105   
   109         -func (pp *compBox) SelectMeta(ctx context.Context, match search.MetaMatchFunc) (res []*meta.Meta, err error) {
          106  +func (pp *compBox) ApplyMeta(ctx context.Context, handle box.MetaFunc) error {
   110    107   	for zid, gen := range myZettel {
   111    108   		if genMeta := gen.meta; genMeta != nil {
   112    109   			if m := genMeta(zid); m != nil {
   113    110   				updateMeta(m)
   114    111   				pp.enricher.Enrich(ctx, m, pp.number)
   115         -				if match(m) {
   116         -					res = append(res, m)
   117         -				}
          112  +				handle(m)
   118    113   			}
   119    114   		}
   120    115   	}
   121         -	return res, nil
          116  +	return nil
   122    117   }
   123    118   
   124         -func (pp *compBox) CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool {
   125         -	return false
   126         -}
          119  +func (*compBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return false }
          120  +
          121  +func (*compBox) UpdateZettel(context.Context, domain.Zettel) error { return box.ErrReadOnly }
   127    122   
   128         -func (pp *compBox) UpdateZettel(ctx context.Context, zettel domain.Zettel) error {
   129         -	return box.ErrReadOnly
   130         -}
   131         -
   132         -func (pp *compBox) AllowRenameZettel(ctx context.Context, zid id.Zid) bool {
          123  +func (*compBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool {
   133    124   	_, ok := myZettel[zid]
   134    125   	return !ok
   135    126   }
   136    127   
   137         -func (pp *compBox) RenameZettel(ctx context.Context, curZid, newZid id.Zid) error {
          128  +func (*compBox) RenameZettel(_ context.Context, curZid, _ id.Zid) error {
   138    129   	if _, ok := myZettel[curZid]; ok {
   139    130   		return box.ErrReadOnly
   140    131   	}
   141    132   	return box.ErrNotFound
   142    133   }
   143    134   
   144         -func (pp *compBox) CanDeleteZettel(ctx context.Context, zid id.Zid) bool { return false }
          135  +func (*compBox) CanDeleteZettel(context.Context, id.Zid) bool { return false }
   145    136   
   146         -func (pp *compBox) DeleteZettel(ctx context.Context, zid id.Zid) error {
          137  +func (*compBox) DeleteZettel(_ context.Context, zid id.Zid) error {
   147    138   	if _, ok := myZettel[zid]; ok {
   148    139   		return box.ErrReadOnly
   149    140   	}
   150    141   	return box.ErrNotFound
   151    142   }
   152    143   
   153         -func (pp *compBox) ReadStats(st *box.ManagedBoxStats) {
          144  +func (*compBox) ReadStats(st *box.ManagedBoxStats) {
   154    145   	st.ReadOnly = true
   155    146   	st.Zettel = len(myZettel)
   156    147   }
   157    148   
   158    149   func updateMeta(m *meta.Meta) {
   159    150   	m.Set(meta.KeyNoIndex, meta.ValueTrue)
   160    151   	m.Set(meta.KeySyntax, meta.ValueSyntaxZmk)

Changes to box/constbox/base.css.

   252    252     }
   253    253     .zs-meta a {
   254    254       color:#444;
   255    255     }
   256    256     h1+.zs-meta {
   257    257       margin-top:-1rem;
   258    258     }
          259  +  nav > details {
          260  +    margin-top:1rem;
          261  +  }
   259    262     details > summary {
   260    263       width: 100%;
   261    264       background-color: #eee;
   262    265       font-family:sans-serif;
   263    266     }
   264    267     details > ul {
   265    268       margin-top:0;

Changes to box/constbox/base.mustache.

    22     22   {{#UserIsValid}}
    23     23   <a href="{{{UserZettelURL}}}">{{UserIdent}}</a>
    24     24   {{/UserIsValid}}
    25     25   {{^UserIsValid}}
    26     26   <a href="{{{LoginURL}}}">Login</a>
    27     27   {{/UserIsValid}}
    28     28   {{#UserIsValid}}
    29         -<a href="{{{UserLogoutURL}}}">Logout</a>
           29  +<a href="{{{LogoutURL}}}">Logout</a>
    30     30   {{/UserIsValid}}
    31     31   {{/WithAuth}}
    32     32   </nav>
    33     33   </div>
    34     34   {{/WithUser}}
    35     35   <div class="zs-dropdown">
    36     36   <button>Lists</button>
................................................................................
    47     47   {{#NewZettelLinks}}
    48     48   <a href="{{{URL}}}">{{Text}}</a>
    49     49   {{/NewZettelLinks}}
    50     50   </nav>
    51     51   </div>
    52     52   {{/HasNewZettelLinks}}
    53     53   <form action="{{{SearchURL}}}">
    54         -<input type="text" placeholder="Search.." name="s">
           54  +<input type="text" placeholder="Search.." name="{{QueryKeySearch}}">
    55     55   </form>
    56     56   </nav>
    57     57   <main class="content">
    58     58   {{{Content}}}
    59     59   </main>
    60     60   {{#FooterHTML}}
    61     61   <footer>
    62     62   {{{FooterHTML}}}
    63     63   </footer>
    64     64   {{/FooterHTML}}
    65     65   </body>
    66     66   </html>

Changes to box/constbox/constbox.go.

    17     17   	"net/url"
    18     18   
    19     19   	"zettelstore.de/z/box"
    20     20   	"zettelstore.de/z/box/manager"
    21     21   	"zettelstore.de/z/domain"
    22     22   	"zettelstore.de/z/domain/id"
    23     23   	"zettelstore.de/z/domain/meta"
    24         -	"zettelstore.de/z/search"
    25     24   )
    26     25   
    27     26   func init() {
    28     27   	manager.Register(
    29     28   		" const",
    30     29   		func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) {
    31     30   			return &constBox{
................................................................................
    34     33   				enricher: cdata.Enricher,
    35     34   			}, nil
    36     35   		})
    37     36   }
    38     37   
    39     38   type constHeader map[string]string
    40     39   
    41         -func makeMeta(zid id.Zid, h constHeader) *meta.Meta {
    42         -	m := meta.New(zid)
    43         -	for k, v := range h {
    44         -		m.Set(k, v)
    45         -	}
    46         -	return m
    47         -}
    48         -
    49     40   type constZettel struct {
    50     41   	header  constHeader
    51     42   	content domain.Content
    52     43   }
    53     44   
    54     45   type constBox struct {
    55     46   	number   int
    56     47   	zettel   map[id.Zid]constZettel
    57     48   	enricher box.Enricher
    58     49   }
    59     50   
    60         -func (cp *constBox) Location() string {
    61         -	return "const:"
    62         -}
           51  +func (*constBox) Location() string { return "const:" }
    63     52   
    64         -func (cp *constBox) CanCreateZettel(ctx context.Context) bool { return false }
           53  +func (*constBox) CanCreateZettel(context.Context) bool { return false }
    65     54   
    66         -func (cp *constBox) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) {
           55  +func (*constBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) {
    67     56   	return id.Invalid, box.ErrReadOnly
    68     57   }
    69     58   
    70         -func (cp *constBox) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) {
           59  +func (cp *constBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) {
    71     60   	if z, ok := cp.zettel[zid]; ok {
    72         -		return domain.Zettel{Meta: makeMeta(zid, z.header), Content: z.content}, nil
           61  +		return domain.Zettel{Meta: meta.NewWithData(zid, z.header), Content: z.content}, nil
    73     62   	}
    74     63   	return domain.Zettel{}, box.ErrNotFound
    75     64   }
    76     65   
    77         -func (cp *constBox) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) {
           66  +func (cp *constBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) {
    78     67   	if z, ok := cp.zettel[zid]; ok {
    79         -		return makeMeta(zid, z.header), nil
           68  +		return meta.NewWithData(zid, z.header), nil
           69  +	}
           70  +	return nil, box.ErrNotFound
           71  +}
           72  +
           73  +func (cp *constBox) ApplyZid(_ context.Context, handle box.ZidFunc) error {
           74  +	for zid := range cp.zettel {
           75  +		handle(zid)
    80     76   	}
    81         -	return nil, box.ErrNotFound
           77  +	return nil
    82     78   }
    83     79   
    84         -func (cp *constBox) FetchZids(ctx context.Context) (id.Set, error) {
    85         -	result := id.NewSetCap(len(cp.zettel))
    86         -	for zid := range cp.zettel {
    87         -		result[zid] = true
           80  +func (cp *constBox) ApplyMeta(ctx context.Context, handle box.MetaFunc) error {
           81  +	for zid, zettel := range cp.zettel {
           82  +		m := meta.NewWithData(zid, zettel.header)
           83  +		cp.enricher.Enrich(ctx, m, cp.number)
           84  +		handle(m)
    88     85   	}
    89         -	return result, nil
           86  +	return nil
    90     87   }
    91     88   
    92         -func (cp *constBox) SelectMeta(ctx context.Context, match search.MetaMatchFunc) (res []*meta.Meta, err error) {
    93         -	for zid, zettel := range cp.zettel {
    94         -		m := makeMeta(zid, zettel.header)
    95         -		cp.enricher.Enrich(ctx, m, cp.number)
    96         -		if match(m) {
    97         -			res = append(res, m)
    98         -		}
    99         -	}
   100         -	return res, nil
   101         -}
           89  +func (*constBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return false }
   102     90   
   103         -func (cp *constBox) CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool {
   104         -	return false
   105         -}
           91  +func (*constBox) UpdateZettel(context.Context, domain.Zettel) error { return box.ErrReadOnly }
   106     92   
   107         -func (cp *constBox) UpdateZettel(ctx context.Context, zettel domain.Zettel) error {
   108         -	return box.ErrReadOnly
   109         -}
   110         -
   111         -func (cp *constBox) AllowRenameZettel(ctx context.Context, zid id.Zid) bool {
           93  +func (cp *constBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool {
   112     94   	_, ok := cp.zettel[zid]
   113     95   	return !ok
   114     96   }
   115     97   
   116         -func (cp *constBox) RenameZettel(ctx context.Context, curZid, newZid id.Zid) error {
           98  +func (cp *constBox) RenameZettel(_ context.Context, curZid, _ id.Zid) error {
   117     99   	if _, ok := cp.zettel[curZid]; ok {
   118    100   		return box.ErrReadOnly
   119    101   	}
   120    102   	return box.ErrNotFound
   121    103   }
   122         -func (cp *constBox) CanDeleteZettel(ctx context.Context, zid id.Zid) bool { return false }
          104  +func (*constBox) CanDeleteZettel(context.Context, id.Zid) bool { return false }
   123    105   
   124         -func (cp *constBox) DeleteZettel(ctx context.Context, zid id.Zid) error {
          106  +func (cp *constBox) DeleteZettel(_ context.Context, zid id.Zid) error {
   125    107   	if _, ok := cp.zettel[zid]; ok {
   126    108   		return box.ErrReadOnly
   127    109   	}
   128    110   	return box.ErrNotFound
   129    111   }
   130    112   
   131    113   func (cp *constBox) ReadStats(st *box.ManagedBoxStats) {

Changes to box/constbox/home.zettel.

     1      1   === Thank you for using Zettelstore!
     2      2   
     3      3   You will find the lastest information about Zettelstore at [[https://zettelstore.de]].
     4      4   Check that website regulary for [[upgrades|https://zettelstore.de/home/doc/trunk/www/download.wiki]] to the latest version.
     5      5   You should consult the [[change log|https://zettelstore.de/home/doc/trunk/www/changes.wiki]] before upgrading.
     6      6   Sometimes, you have to edit some of your Zettelstore-related zettel before upgrading.
     7      7   Since Zettelstore is currently in a development state, every upgrade might fix some of your problems.
     8         -To check for versions, there is a zettel with the [[current version|00000000000001]] of your Zettelstore.
     9      8   
    10      9   If you have problems concerning Zettelstore,
    11     10   do not hesitate to get in [[contact with the main developer|mailto:ds@zettelstore.de]].
    12     11   
    13     12   === Reporting errors
    14     13   If you have encountered an error, please include the content of the following zettel in your mail (if possible):
    15         -* [[Zettelstore Version|00000000000001]]
           14  +* [[Zettelstore Version|00000000000001]]: {{00000000000001}}
    16     15   * [[Zettelstore Operating System|00000000000003]]
    17     16   * [[Zettelstore Startup Configuration|00000000000096]]
    18     17   * [[Zettelstore Runtime Configuration|00000000000100]]
    19     18   
    20     19   Additionally, you have to describe, what you have done before that error occurs
    21     20   and what you have expected instead.
    22     21   Please do not forget to include the error message, if there is one.

Changes to box/constbox/info.mustache.

    27     27   <ul>
    28     28   {{#ExtLinks}}
    29     29   <li><a href="{{{.}}}"{{{ExtNewWindow}}}>{{.}}</a></li>
    30     30   {{/ExtLinks}}
    31     31   </ul>
    32     32   {{/HasExtLinks}}
    33     33   {{/HasLinks}}
    34         -<h2>Parts and format</h3>
           34  +<h2>Parts and encodings</h2>
           35  +<table>
           36  +{{#EvalMatrix}}
           37  +<tr>
           38  +<th>{{Header}}</th>
           39  +{{#Elements}}<td><a href="{{{URL}}}">{{Text}}</td>
           40  +{{/Elements}}
           41  +</tr>
           42  +{{/EvalMatrix}}
           43  +</table>
           44  +<h3>Parsed (not evaluated)</h3>
    35     45   <table>
    36         -{{#Matrix}}
           46  +{{#ParseMatrix}}
    37     47   <tr>
    38         -{{#Elements}}{{#HasURL}}<td><a href="{{{URL}}}">{{Text}}</td>{{/HasURL}}{{^HasURL}}<th>{{Text}}</th>{{/HasURL}}
           48  +<th>{{Header}}</th>
           49  +{{#Elements}}<td><a href="{{{URL}}}">{{Text}}</td>
    39     50   {{/Elements}}
    40     51   </tr>
    41         -{{/Matrix}}
           52  +{{/ParseMatrix}}
    42     53   </table>
    43     54   {{#HasShadowLinks}}
    44     55   <h2>Shadowed Boxes</h2>
    45     56   <ul>{{#ShadowLinks}}<li>{{.}}</li>{{/ShadowLinks}}</ul>
    46     57   {{/HasShadowLinks}}
    47     58   {{#Endnotes}}{{{Endnotes}}}{{/Endnotes}}
    48     59   </article>

Changes to box/constbox/login.mustache.

     1      1   <article>
     2      2   <header>
     3      3   <h1>{{Title}}</h1>
     4      4   </header>
     5      5   {{#Retry}}
     6      6   <div class="zs-indication zs-error">Wrong user name / password. Try again.</div>
     7      7   {{/Retry}}
     8         -<form method="POST" action="?_format=html">
            8  +<form method="POST" action="">
     9      9   <div>
    10     10   <label for="username">User name</label>
    11     11   <input class="zs-input" type="text" id="username" name="username" placeholder="Your user name.." autofocus>
    12     12   </div>
    13     13   <div>
    14     14   <label for="password">Password</label>
    15     15   <input class="zs-input" type="password" id="password" name="password" placeholder="Your password..">
    16     16   </div>
    17     17   <input class="zs-button" type="submit" value="Login">
    18     18   </form>
    19     19   </article>

Changes to box/constbox/zettel.mustache.

     5      5   {{#CanWrite}}<a href="{{{EditURL}}}">Edit</a> &#183;{{/CanWrite}}
     6      6   {{Zid}} &#183;
     7      7   <a href="{{{InfoURL}}}">Info</a> &#183;
     8      8   (<a href="{{{RoleURL}}}">{{RoleText}}</a>)
     9      9   {{#HasTags}}&#183; {{#Tags}} <a href="{{{URL}}}">{{Text}}</a>{{/Tags}}{{/HasTags}}
    10     10   {{#CanCopy}}&#183; <a href="{{{CopyURL}}}">Copy</a>{{/CanCopy}}
    11     11   {{#CanFolge}}&#183; <a href="{{{FolgeURL}}}">Folge</a>{{/CanFolge}}
    12         -{{#FolgeRefs}}<br>Folge: {{{FolgeRefs}}}{{/FolgeRefs}}
    13     12   {{#PrecursorRefs}}<br>Precursor: {{{PrecursorRefs}}}{{/PrecursorRefs}}
    14     13   {{#HasExtURL}}<br>URL: <a href="{{{ExtURL}}}"{{{ExtNewWindow}}}>{{ExtURL}}</a>{{/HasExtURL}}
    15     14   </div>
    16     15   </header>
    17     16   {{{Content}}}
           17  +</article>
           18  +{{#HasFolgeLinks}}
           19  +<nav>
           20  +<details open>
           21  +<summary>Folgezettel</summary>
           22  +<ul>
           23  +{{#FolgeLinks}}
           24  +<li><a href="{{{URL}}}">{{Text}}</a></li>
           25  +{{/FolgeLinks}}
           26  +</ul>
           27  +</details>
           28  +</nav>
           29  +{{/HasFolgeLinks}}
    18     30   {{#HasBackLinks}}
           31  +<nav>
    19     32   <details>
    20     33   <summary>Additional links to this zettel</summary>
    21     34   <ul>
    22     35   {{#BackLinks}}
    23     36   <li><a href="{{{URL}}}">{{Text}}</a></li>
    24     37   {{/BackLinks}}
    25     38   </ul>
    26     39   </details>
           40  +</nav>
    27     41   {{/HasBackLinks}}
    28         -</article>

Changes to box/dirbox/dirbox.go.

    25     25   	"zettelstore.de/z/box"
    26     26   	"zettelstore.de/z/box/dirbox/directory"
    27     27   	"zettelstore.de/z/box/filebox"
    28     28   	"zettelstore.de/z/box/manager"
    29     29   	"zettelstore.de/z/domain"
    30     30   	"zettelstore.de/z/domain/id"
    31     31   	"zettelstore.de/z/domain/meta"
    32         -	"zettelstore.de/z/search"
    33     32   )
    34     33   
    35     34   func init() {
    36     35   	manager.Register("dir", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) {
    37     36   		path := getDirPath(u)
    38     37   		if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
    39     38   			return nil, err
................................................................................
   108    107   	mxCmds     sync.RWMutex
   109    108   }
   110    109   
   111    110   func (dp *dirBox) Location() string {
   112    111   	return dp.location
   113    112   }
   114    113   
   115         -func (dp *dirBox) Start(ctx context.Context) error {
          114  +func (dp *dirBox) Start(context.Context) error {
   116    115   	dp.mxCmds.Lock()
   117    116   	dp.fCmds = make([]chan fileCmd, 0, dp.fSrvs)
   118    117   	for i := uint32(0); i < dp.fSrvs; i++ {
   119    118   		cc := make(chan fileCmd)
   120         -		go fileService(i, cc)
          119  +		go fileService(cc)
   121    120   		dp.fCmds = append(dp.fCmds, cc)
   122    121   	}
   123    122   	dp.setupDirService()
   124    123   	dp.mxCmds.Unlock()
   125    124   	if dp.dirSrv == nil {
   126    125   		panic("No directory service")
   127    126   	}
   128    127   	return dp.dirSrv.Start()
   129    128   }
   130    129   
   131         -func (dp *dirBox) Stop(ctx context.Context) error {
          130  +func (dp *dirBox) Stop(_ context.Context) error {
   132    131   	dirSrv := dp.dirSrv
   133    132   	dp.dirSrv = nil
   134    133   	err := dirSrv.Stop()
   135    134   	for _, c := range dp.fCmds {
   136    135   		close(c)
   137    136   	}
   138    137   	return err
................................................................................
   154    153   	sum *= 16777619
   155    154   
   156    155   	dp.mxCmds.RLock()
   157    156   	defer dp.mxCmds.RUnlock()
   158    157   	return dp.fCmds[sum%dp.fSrvs]
   159    158   }
   160    159   
   161         -func (dp *dirBox) CanCreateZettel(ctx context.Context) bool {
          160  +func (dp *dirBox) CanCreateZettel(_ context.Context) bool {
   162    161   	return !dp.readonly
   163    162   }
   164    163   
   165         -func (dp *dirBox) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) {
          164  +func (dp *dirBox) CreateZettel(_ context.Context, zettel domain.Zettel) (id.Zid, error) {
   166    165   	if dp.readonly {
   167    166   		return id.Invalid, box.ErrReadOnly
   168    167   	}
   169    168   
   170    169   	entry, err := dp.dirSrv.GetNew()
   171    170   	if err != nil {
   172    171   		return id.Invalid, err
................................................................................
   179    178   	if err == nil {
   180    179   		dp.dirSrv.UpdateEntry(entry)
   181    180   	}
   182    181   	dp.notifyChanged(box.OnUpdate, meta.Zid)
   183    182   	return meta.Zid, err
   184    183   }
   185    184   
   186         -func (dp *dirBox) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) {
          185  +func (dp *dirBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) {
   187    186   	entry, err := dp.dirSrv.GetEntry(zid)
   188    187   	if err != nil || !entry.IsValid() {
   189    188   		return domain.Zettel{}, box.ErrNotFound
   190    189   	}
   191    190   	m, c, err := getMetaContent(dp, entry, zid)
   192    191   	if err != nil {
   193    192   		return domain.Zettel{}, err
   194    193   	}
   195         -	dp.cleanupMeta(ctx, m)
          194  +	dp.cleanupMeta(m)
   196    195   	zettel := domain.Zettel{Meta: m, Content: domain.NewContent(c)}
   197    196   	return zettel, nil
   198    197   }
   199    198   
   200         -func (dp *dirBox) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) {
          199  +func (dp *dirBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) {
   201    200   	entry, err := dp.dirSrv.GetEntry(zid)
   202    201   	if err != nil || !entry.IsValid() {
   203    202   		return nil, box.ErrNotFound
   204    203   	}
   205    204   	m, err := getMeta(dp, entry, zid)
   206    205   	if err != nil {
   207    206   		return nil, err
   208    207   	}
   209         -	dp.cleanupMeta(ctx, m)
          208  +	dp.cleanupMeta(m)
   210    209   	return m, nil
   211    210   }
   212    211   
   213         -func (dp *dirBox) FetchZids(ctx context.Context) (id.Set, error) {
          212  +func (dp *dirBox) ApplyZid(_ context.Context, handle box.ZidFunc) error {
   214    213   	entries, err := dp.dirSrv.GetEntries()
   215    214   	if err != nil {
   216         -		return nil, err
          215  +		return err
   217    216   	}
   218         -	result := id.NewSetCap(len(entries))
   219    217   	for _, entry := range entries {
   220         -		result[entry.Zid] = true
          218  +		handle(entry.Zid)
   221    219   	}
   222         -	return result, nil
          220  +	return nil
   223    221   }
   224    222   
   225         -func (dp *dirBox) SelectMeta(ctx context.Context, match search.MetaMatchFunc) (res []*meta.Meta, err error) {
          223  +func (dp *dirBox) ApplyMeta(ctx context.Context, handle box.MetaFunc) error {
   226    224   	entries, err := dp.dirSrv.GetEntries()
   227    225   	if err != nil {
   228         -		return nil, err
          226  +		return err
   229    227   	}
   230         -	res = make([]*meta.Meta, 0, len(entries))
   231    228   	// The following loop could be parallelized if needed for performance.
   232    229   	for _, entry := range entries {
   233    230   		m, err1 := getMeta(dp, entry, entry.Zid)
   234         -		err = err1
   235         -		if err != nil {
   236         -			continue
          231  +		if err1 != nil {
          232  +			return err1
   237    233   		}
   238         -		dp.cleanupMeta(ctx, m)
          234  +		dp.cleanupMeta(m)
   239    235   		dp.cdata.Enricher.Enrich(ctx, m, dp.number)
   240         -
   241         -		if match(m) {
   242         -			res = append(res, m)
   243         -		}
          236  +		handle(m)
   244    237   	}
   245         -	if err != nil {
   246         -		return nil, err
   247         -	}
   248         -	return res, nil
          238  +	return nil
   249    239   }
   250    240   
   251         -func (dp *dirBox) CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool {
          241  +func (dp *dirBox) CanUpdateZettel(context.Context, domain.Zettel) bool {
   252    242   	return !dp.readonly
   253    243   }
   254    244   
   255         -func (dp *dirBox) UpdateZettel(ctx context.Context, zettel domain.Zettel) error {
          245  +func (dp *dirBox) UpdateZettel(_ context.Context, zettel domain.Zettel) error {
   256    246   	if dp.readonly {
   257    247   		return box.ErrReadOnly
   258    248   	}
   259    249   
   260    250   	meta := zettel.Meta
   261    251   	if !meta.Zid.IsValid() {
   262    252   		return &box.ErrInvalidID{Zid: meta.Zid}
................................................................................
   315    305   		if s == syntax {
   316    306   			return directory.MetaSpecHeader, "zettel"
   317    307   		}
   318    308   	}
   319    309   	return directory.MetaSpecFile, syntax
   320    310   }
   321    311   
   322         -func (dp *dirBox) AllowRenameZettel(ctx context.Context, zid id.Zid) bool {
          312  +func (dp *dirBox) AllowRenameZettel(context.Context, id.Zid) bool {
   323    313   	return !dp.readonly
   324    314   }
   325    315   
   326    316   func (dp *dirBox) RenameZettel(ctx context.Context, curZid, newZid id.Zid) error {
   327    317   	if curZid == newZid {
   328    318   		return nil
   329    319   	}
................................................................................
   367    357   	if err == nil {
   368    358   		dp.notifyChanged(box.OnDelete, curZid)
   369    359   		dp.notifyChanged(box.OnUpdate, newZid)
   370    360   	}
   371    361   	return err
   372    362   }
   373    363   
   374         -func (dp *dirBox) CanDeleteZettel(ctx context.Context, zid id.Zid) bool {
          364  +func (dp *dirBox) CanDeleteZettel(_ context.Context, zid id.Zid) bool {
   375    365   	if dp.readonly {
   376    366   		return false
   377    367   	}
   378    368   	entry, err := dp.dirSrv.GetEntry(zid)
   379    369   	return err == nil && entry.IsValid()
   380    370   }
   381    371   
   382         -func (dp *dirBox) DeleteZettel(ctx context.Context, zid id.Zid) error {
          372  +func (dp *dirBox) DeleteZettel(_ context.Context, zid id.Zid) error {
   383    373   	if dp.readonly {
   384    374   		return box.ErrReadOnly
   385    375   	}
   386    376   
   387    377   	entry, err := dp.dirSrv.GetEntry(zid)
   388    378   	if err != nil || !entry.IsValid() {
   389    379   		return box.ErrNotFound
................................................................................
   397    387   }
   398    388   
   399    389   func (dp *dirBox) ReadStats(st *box.ManagedBoxStats) {
   400    390   	st.ReadOnly = dp.readonly
   401    391   	st.Zettel, _ = dp.dirSrv.NumEntries()
   402    392   }
   403    393   
   404         -func (dp *dirBox) cleanupMeta(ctx context.Context, m *meta.Meta) {
          394  +func (dp *dirBox) cleanupMeta(m *meta.Meta) {
   405    395   	if role, ok := m.Get(meta.KeyRole); !ok || role == "" {
   406    396   		m.Set(meta.KeyRole, dp.cdata.Config.GetDefaultRole())
   407    397   	}
   408    398   	if syntax, ok := m.Get(meta.KeySyntax); !ok || syntax == "" {
   409    399   		m.Set(meta.KeySyntax, dp.cdata.Config.GetDefaultSyntax())
   410    400   	}
   411    401   }

Changes to box/dirbox/service.go.

    18     18   	"zettelstore.de/z/box/filebox"
    19     19   	"zettelstore.de/z/domain"
    20     20   	"zettelstore.de/z/domain/id"
    21     21   	"zettelstore.de/z/domain/meta"
    22     22   	"zettelstore.de/z/input"
    23     23   )
    24     24   
    25         -func fileService(num uint32, cmds <-chan fileCmd) {
           25  +func fileService(cmds <-chan fileCmd) {
    26     26   	for cmd := range cmds {
    27     27   		cmd.run()
    28     28   	}
    29     29   }
    30     30   
    31     31   type fileCmd interface {
    32     32   	run()

Changes to box/filebox/zipbox.go.

    19     19   	"strings"
    20     20   
    21     21   	"zettelstore.de/z/box"
    22     22   	"zettelstore.de/z/domain"
    23     23   	"zettelstore.de/z/domain/id"
    24     24   	"zettelstore.de/z/domain/meta"
    25     25   	"zettelstore.de/z/input"
    26         -	"zettelstore.de/z/search"
    27     26   )
    28     27   
    29     28   var validFileName = regexp.MustCompile(`^(\d{14}).*(\.(.+))$`)
    30     29   
    31     30   func matchValidFileName(name string) []string {
    32     31   	return validFileName.FindStringSubmatch(name)
    33     32   }
................................................................................
    49     48   func (zp *zipBox) Location() string {
    50     49   	if strings.HasPrefix(zp.name, "/") {
    51     50   		return "file://" + zp.name
    52     51   	}
    53     52   	return "file:" + zp.name
    54     53   }
    55     54   
    56         -func (zp *zipBox) Start(ctx context.Context) error {
           55  +func (zp *zipBox) Start(context.Context) error {
    57     56   	reader, err := zip.OpenReader(zp.name)
    58     57   	if err != nil {
    59     58   		return err
    60     59   	}
    61     60   	defer reader.Close()
    62     61   	zp.zettel = make(map[id.Zid]*zipEntry)
    63     62   	for _, f := range reader.File {
................................................................................
    94     93   		if entry.contentExt == "" {
    95     94   			entry.contentExt = ext
    96     95   			entry.contentName = name
    97     96   		}
    98     97   	}
    99     98   }
   100     99   
   101         -func (zp *zipBox) Stop(ctx context.Context) error {
          100  +func (zp *zipBox) Stop(context.Context) error {
   102    101   	zp.zettel = nil
   103    102   	return nil
   104    103   }
   105    104   
   106         -func (zp *zipBox) CanCreateZettel(ctx context.Context) bool { return false }
          105  +func (*zipBox) CanCreateZettel(context.Context) bool { return false }
   107    106   
   108         -func (zp *zipBox) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) {
          107  +func (*zipBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) {
   109    108   	return id.Invalid, box.ErrReadOnly
   110    109   }
   111    110   
   112         -func (zp *zipBox) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) {
          111  +func (zp *zipBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) {
   113    112   	entry, ok := zp.zettel[zid]
   114    113   	if !ok {
   115    114   		return domain.Zettel{}, box.ErrNotFound
   116    115   	}
   117    116   	reader, err := zip.OpenReader(zp.name)
   118    117   	if err != nil {
   119    118   		return domain.Zettel{}, err
................................................................................
   144    143   	} else {
   145    144   		m = CalcDefaultMeta(zid, entry.contentExt)
   146    145   	}
   147    146   	CleanupMeta(m, zid, entry.contentExt, inMeta, false)
   148    147   	return domain.Zettel{Meta: m, Content: domain.NewContent(src)}, nil
   149    148   }
   150    149   
   151         -func (zp *zipBox) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) {
          150  +func (zp *zipBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) {
   152    151   	entry, ok := zp.zettel[zid]
   153    152   	if !ok {
   154    153   		return nil, box.ErrNotFound
   155    154   	}
   156    155   	reader, err := zip.OpenReader(zp.name)
   157    156   	if err != nil {
   158    157   		return nil, err
   159    158   	}
   160    159   	defer reader.Close()
   161    160   	return readZipMeta(reader, zid, entry)
   162    161   }
   163    162   
   164         -func (zp *zipBox) FetchZids(ctx context.Context) (id.Set, error) {
   165         -	result := id.NewSetCap(len(zp.zettel))
          163  +func (zp *zipBox) ApplyZid(_ context.Context, handle box.ZidFunc) error {
   166    164   	for zid := range zp.zettel {
   167         -		result[zid] = true
          165  +		handle(zid)
   168    166   	}
   169         -	return result, nil
          167  +	return nil
   170    168   }
   171    169   
   172         -func (zp *zipBox) SelectMeta(ctx context.Context, match search.MetaMatchFunc) (res []*meta.Meta, err error) {
          170  +func (zp *zipBox) ApplyMeta(ctx context.Context, handle box.MetaFunc) error {
   173    171   	reader, err := zip.OpenReader(zp.name)
   174    172   	if err != nil {
   175         -		return nil, err
          173  +		return err
   176    174   	}
   177    175   	defer reader.Close()
   178    176   	for zid, entry := range zp.zettel {
   179    177   		m, err := readZipMeta(reader, zid, entry)
   180    178   		if err != nil {
   181    179   			continue
   182    180   		}
   183    181   		zp.enricher.Enrich(ctx, m, zp.number)
   184         -		if match(m) {
   185         -			res = append(res, m)
   186         -		}
          182  +		handle(m)
   187    183   	}
   188         -	return res, nil
          184  +	return nil
   189    185   }
   190    186   
   191         -func (zp *zipBox) CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool {
   192         -	return false
   193         -}
          187  +func (*zipBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return false }
          188  +
          189  +func (*zipBox) UpdateZettel(context.Context, domain.Zettel) error { return box.ErrReadOnly }
   194    190   
   195         -func (zp *zipBox) UpdateZettel(ctx context.Context, zettel domain.Zettel) error {
   196         -	return box.ErrReadOnly
   197         -}
   198         -
   199         -func (zp *zipBox) AllowRenameZettel(ctx context.Context, zid id.Zid) bool {
          191  +func (zp *zipBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool {
   200    192   	_, ok := zp.zettel[zid]
   201    193   	return !ok
   202    194   }
   203    195   
   204         -func (zp *zipBox) RenameZettel(ctx context.Context, curZid, newZid id.Zid) error {
          196  +func (zp *zipBox) RenameZettel(_ context.Context, curZid, _ id.Zid) error {
   205    197   	if _, ok := zp.zettel[curZid]; ok {
   206    198   		return box.ErrReadOnly
   207    199   	}
   208    200   	return box.ErrNotFound
   209    201   }
   210    202   
   211         -func (zp *zipBox) CanDeleteZettel(ctx context.Context, zid id.Zid) bool { return false }
          203  +func (*zipBox) CanDeleteZettel(context.Context, id.Zid) bool { return false }
   212    204   
   213         -func (zp *zipBox) DeleteZettel(ctx context.Context, zid id.Zid) error {
          205  +func (zp *zipBox) DeleteZettel(_ context.Context, zid id.Zid) error {
   214    206   	if _, ok := zp.zettel[zid]; ok {
   215    207   		return box.ErrReadOnly
   216    208   	}
   217    209   	return box.ErrNotFound
   218    210   }
   219    211   
   220    212   func (zp *zipBox) ReadStats(st *box.ManagedBoxStats) {

Changes to box/manager/box.go.

    10     10   
    11     11   // Package manager coordinates the various boxes and indexes of a Zettelstore.
    12     12   package manager
    13     13   
    14     14   import (
    15     15   	"context"
    16     16   	"errors"
    17         -	"sort"
    18     17   	"strings"
    19     18   
    20     19   	"zettelstore.de/z/box"
    21     20   	"zettelstore.de/z/domain"
    22     21   	"zettelstore.de/z/domain/id"
    23     22   	"zettelstore.de/z/domain/meta"
    24     23   	"zettelstore.de/z/search"
................................................................................
   125    124   			result = append(result, m)
   126    125   		}
   127    126   	}
   128    127   	return result, nil
   129    128   }
   130    129   
   131    130   // FetchZids returns the set of all zettel identifer managed by the box.
   132         -func (mgr *Manager) FetchZids(ctx context.Context) (result id.Set, err error) {
          131  +func (mgr *Manager) FetchZids(ctx context.Context) (id.Set, error) {
   133    132   	mgr.mgrMx.RLock()
   134    133   	defer mgr.mgrMx.RUnlock()
   135    134   	if !mgr.started {
   136    135   		return nil, box.ErrStopped
   137    136   	}
          137  +	result := id.Set{}
   138    138   	for _, p := range mgr.boxes {
   139         -		zids, err := p.FetchZids(ctx)
          139  +		err := p.ApplyZid(ctx, func(zid id.Zid) { result[zid] = true })
   140    140   		if err != nil {
   141    141   			return nil, err
   142    142   		}
   143         -		if result == nil {
   144         -			result = zids
   145         -		} else if len(result) <= len(zids) {
   146         -			for zid := range result {
   147         -				zids[zid] = true
   148         -			}
   149         -			result = zids
   150         -		} else {
   151         -			for zid := range zids {
   152         -				result[zid] = true
   153         -			}
   154         -		}
   155    143   	}
   156    144   	return result, nil
   157    145   }
   158    146   
   159    147   // SelectMeta returns all zettel meta data that match the selection
   160    148   // criteria. The result is ordered by descending zettel id.
   161    149   func (mgr *Manager) SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) {
   162    150   	mgr.mgrMx.RLock()
   163    151   	defer mgr.mgrMx.RUnlock()
   164    152   	if !mgr.started {
   165    153   		return nil, box.ErrStopped
   166    154   	}
   167         -	var result []*meta.Meta
          155  +	selected, rejected := map[id.Zid]*meta.Meta{}, id.Set{}
   168    156   	match := s.CompileMatch(mgr)
   169         -	for _, p := range mgr.boxes {
   170         -		selected, err := p.SelectMeta(ctx, match)
   171         -		if err != nil {
   172         -			return nil, err
          157  +	handleMeta := func(m *meta.Meta) {
          158  +		zid := m.Zid
          159  +		if rejected[zid] {
          160  +			return
          161  +		}
          162  +		if _, ok := selected[zid]; ok {
          163  +			return
   173    164   		}
   174         -		sort.Slice(selected, func(i, j int) bool { return selected[i].Zid > selected[j].Zid })
   175         -		if len(result) == 0 {
   176         -			result = selected
          165  +		if match(m) {
          166  +			selected[zid] = m
   177    167   		} else {
   178         -			result = box.MergeSorted(result, selected)
          168  +			rejected[zid] = true
          169  +		}
          170  +	}
          171  +	for _, p := range mgr.boxes {
          172  +		if err := p.ApplyMeta(ctx, handleMeta); err != nil {
          173  +			return nil, err
   179    174   		}
   180    175   	}
   181         -	if s == nil {
   182         -		return result, nil
          176  +	result := make([]*meta.Meta, 0, len(selected))
          177  +	for _, m := range selected {
          178  +		result = append(result, m)
   183    179   	}
   184    180   	return s.Sort(result), nil
   185    181   }
   186    182   
   187    183   // CanUpdateZettel returns true, if box could possibly update the given zettel.
   188    184   func (mgr *Manager) CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool {
   189    185   	mgr.mgrMx.RLock()

Changes to box/manager/collect.go.

    20     20   	"zettelstore.de/z/strfun"
    21     21   )
    22     22   
    23     23   type collectData struct {
    24     24   	refs  id.Set
    25     25   	words store.WordSet
    26     26   	urls  store.WordSet
           27  +	itags store.WordSet
    27     28   }
    28     29   
    29     30   func (data *collectData) initialize() {
    30     31   	data.refs = id.NewSet()
    31     32   	data.words = store.NewWordSet()
    32     33   	data.urls = store.NewWordSet()
           34  +	data.itags = store.NewWordSet()
    33     35   }
    34     36   
    35     37   func collectZettelIndexData(zn *ast.ZettelNode, data *collectData) {
    36         -	ast.WalkBlockSlice(data, zn.Ast)
           38  +	ast.Walk(data, zn.Ast)
    37     39   }
    38     40   
    39         -func collectInlineIndexData(ins ast.InlineSlice, data *collectData) {
    40         -	ast.WalkInlineSlice(data, ins)
           41  +func collectInlineIndexData(iln *ast.InlineListNode, data *collectData) {
           42  +	ast.Walk(data, iln)
    41     43   }
    42     44   
    43     45   func (data *collectData) Visit(node ast.Node) ast.Visitor {
    44     46   	switch n := node.(type) {
    45     47   	case *ast.VerbatimNode:
    46     48   		for _, line := range n.Lines {
    47     49   			data.addText(line)
    48     50   		}
    49     51   	case *ast.TextNode:
    50     52   		data.addText(n.Text)
    51     53   	case *ast.TagNode:
    52     54   		data.addText(n.Tag)
           55  +		data.itags.Add("#" + strings.ToLower(n.Tag))
    53     56   	case *ast.LinkNode:
    54     57   		data.addRef(n.Ref)
    55         -	case *ast.ImageNode:
    56         -		data.addRef(n.Ref)
           58  +	case *ast.EmbedNode:
           59  +		if m, ok := n.Material.(*ast.ReferenceMaterialNode); ok {
           60  +			data.addRef(m.Ref)
           61  +		}
    57     62   	case *ast.LiteralNode:
    58     63   		data.addText(n.Text)
    59     64   	}
    60     65   	return data
    61     66   }
    62     67   
    63     68   func (data *collectData) addText(s string) {

Changes to box/manager/indexer.go.

   193    193   			zi.AddBackRef(ref)
   194    194   		} else {
   195    195   			zi.AddDeadRef(ref)
   196    196   		}
   197    197   	}
   198    198   	zi.SetWords(cData.words)
   199    199   	zi.SetUrls(cData.urls)
          200  +	zi.SetITags(cData.itags)
   200    201   }
   201    202   
   202    203   func (mgr *Manager) idxUpdateValue(ctx context.Context, inverseKey, value string, zi *store.ZettelIndex) {
   203    204   	zid, err := id.Parse(value)
   204    205   	if err != nil {
   205    206   		return
   206    207   	}

Changes to box/manager/memstore/memstore.go.

    32     32   type zettelIndex struct {
    33     33   	dead     id.Slice
    34     34   	forward  id.Slice
    35     35   	backward id.Slice
    36     36   	meta     map[string]metaRefs
    37     37   	words    []string
    38     38   	urls     []string
           39  +	itags    string // Inline tags
    39     40   }
    40     41   
    41     42   func (zi *zettelIndex) isEmpty() bool {
    42     43   	if len(zi.forward) > 0 || len(zi.backward) > 0 || len(zi.dead) > 0 || len(zi.words) > 0 {
    43     44   		return false
    44     45   	}
    45         -	return zi.meta == nil || len(zi.meta) == 0
           46  +	return len(zi.meta) == 0
    46     47   }
    47     48   
    48     49   type stringRefs map[string]id.Slice
    49     50   
    50     51   type memStore struct {
    51     52   	mx    sync.RWMutex
    52     53   	idx   map[id.Zid]*zettelIndex
................................................................................
    64     65   		idx:   make(map[id.Zid]*zettelIndex),
    65     66   		dead:  make(map[id.Zid]id.Slice),
    66     67   		words: make(stringRefs),
    67     68   		urls:  make(stringRefs),
    68     69   	}
    69     70   }
    70     71   
    71         -func (ms *memStore) Enrich(ctx context.Context, m *meta.Meta) {
    72         -	if ms.doEnrich(ctx, m) {
           72  +func (ms *memStore) Enrich(_ context.Context, m *meta.Meta) {
           73  +	if ms.doEnrich(m) {
    73     74   		ms.mx.Lock()
    74     75   		ms.updates++
    75     76   		ms.mx.Unlock()
    76     77   	}
    77     78   }
    78     79   
    79         -func (ms *memStore) doEnrich(ctx context.Context, m *meta.Meta) bool {
           80  +func (ms *memStore) doEnrich(m *meta.Meta) bool {
    80     81   	ms.mx.RLock()
    81     82   	defer ms.mx.RUnlock()
    82     83   	zi, ok := ms.idx[m.Zid]
    83     84   	if !ok {
    84     85   		return false
    85     86   	}
    86     87   	var updated bool
................................................................................
   106    107   				updated = true
   107    108   			}
   108    109   		}
   109    110   	}
   110    111   	if len(back) > 0 {
   111    112   		m.Set(meta.KeyBack, back.String())
   112    113   		updated = true
          114  +	}
          115  +	if zi.itags != "" {
          116  +		if tags, ok := m.Get(meta.KeyTags); ok {
          117  +			m.Set(meta.KeyAllTags, tags+" "+zi.itags)
          118  +		} else {
          119  +			m.Set(meta.KeyAllTags, zi.itags)
          120  +		}
          121  +		updated = true
          122  +	} else if tags, ok := m.Get(meta.KeyTags); ok {
          123  +		m.Set(meta.KeyAllTags, tags)
          124  +		updated = true
   113    125   	}
   114    126   	return updated
   115    127   }
   116    128   
   117    129   // SearchEqual returns all zettel that contains the given exact word.
   118    130   // The word must be normalized through Unicode NKFD, trimmed and not empty.
   119    131   func (ms *memStore) SearchEqual(word string) id.Set {
................................................................................
   252    264   				}
   253    265   			}
   254    266   		}
   255    267   	}
   256    268   	return back
   257    269   }
   258    270   
   259         -func (ms *memStore) UpdateReferences(ctx context.Context, zidx *store.ZettelIndex) id.Set {
          271  +func (ms *memStore) UpdateReferences(_ context.Context, zidx *store.ZettelIndex) id.Set {
   260    272   	ms.mx.Lock()
   261    273   	defer ms.mx.Unlock()
   262    274   	zi, ziExist := ms.idx[zidx.Zid]
   263    275   	if !ziExist || zi == nil {
   264    276   		zi = &zettelIndex{}
   265    277   		ziExist = false
   266    278   	}
................................................................................
   274    286   	}
   275    287   
   276    288   	ms.updateDeadReferences(zidx, zi)
   277    289   	ms.updateForwardBackwardReferences(zidx, zi)
   278    290   	ms.updateMetadataReferences(zidx, zi)
   279    291   	zi.words = updateWordSet(zidx.Zid, ms.words, zi.words, zidx.GetWords())
   280    292   	zi.urls = updateWordSet(zidx.Zid, ms.urls, zi.urls, zidx.GetUrls())
          293  +	zi.itags = setITags(zidx.GetITags())
   281    294   
   282    295   	// Check if zi must be inserted into ms.idx
   283    296   	if !ziExist && !zi.isEmpty() {
   284    297   		ms.idx[zidx.Zid] = zi
   285    298   	}
   286    299   
   287    300   	return toCheck
................................................................................
   366    379   			delete(srefs, word)
   367    380   			continue
   368    381   		}
   369    382   		srefs[word] = refs2
   370    383   	}
   371    384   	return next.Words()
   372    385   }
          386  +
          387  +func setITags(next store.WordSet) string {
          388  +	itags := next.Words()
          389  +	if len(itags) == 0 {
          390  +		return ""
          391  +	}
          392  +	sort.Strings(itags)
          393  +	return strings.Join(itags, " ")
          394  +}
   373    395   
   374    396   func (ms *memStore) getEntry(zid id.Zid) *zettelIndex {
   375    397   	// Must only be called if ms.mx is write-locked!
   376    398   	if zi, ok := ms.idx[zid]; ok {
   377    399   		return zi
   378    400   	}
   379    401   	zi := &zettelIndex{}
   380    402   	ms.idx[zid] = zi
   381    403   	return zi
   382    404   }
   383    405   
   384         -func (ms *memStore) DeleteZettel(ctx context.Context, zid id.Zid) id.Set {
          406  +func (ms *memStore) DeleteZettel(_ context.Context, zid id.Zid) id.Set {
   385    407   	ms.mx.Lock()
   386    408   	defer ms.mx.Unlock()
   387    409   
   388    410   	zi, ok := ms.idx[zid]
   389    411   	if !ok {
   390    412   		return nil
   391    413   	}

Changes to box/manager/store/zettel.go.

    17     17   type ZettelIndex struct {
    18     18   	Zid      id.Zid            // zid of the indexed zettel
    19     19   	backrefs id.Set            // set of back references
    20     20   	metarefs map[string]id.Set // references to inverse keys
    21     21   	deadrefs id.Set            // set of dead references
    22     22   	words    WordSet
    23     23   	urls     WordSet
           24  +	itags    WordSet
    24     25   }
    25     26   
    26     27   // NewZettelIndex creates a new zettel index.
    27     28   func NewZettelIndex(zid id.Zid) *ZettelIndex {
    28     29   	return &ZettelIndex{
    29     30   		Zid:      zid,
    30     31   		backrefs: id.NewSet(),
................................................................................
    56     57   
    57     58   // SetWords sets the words to the given value.
    58     59   func (zi *ZettelIndex) SetWords(words WordSet) { zi.words = words }
    59     60   
    60     61   // SetUrls sets the words to the given value.
    61     62   func (zi *ZettelIndex) SetUrls(urls WordSet) { zi.urls = urls }
    62     63   
           64  +// SetITags sets the words to the given value.
           65  +func (zi *ZettelIndex) SetITags(itags WordSet) { zi.itags = itags }
           66  +
    63     67   // GetDeadRefs returns all dead references as a sorted list.
    64     68   func (zi *ZettelIndex) GetDeadRefs() id.Slice {
    65     69   	return zi.deadrefs.Sorted()
    66     70   }
    67     71   
    68     72   // GetBackRefs returns all back references as a sorted list.
    69     73   func (zi *ZettelIndex) GetBackRefs() id.Slice {
................................................................................
    83     87   }
    84     88   
    85     89   // GetWords returns a reference to the set of words. It must not be modified.
    86     90   func (zi *ZettelIndex) GetWords() WordSet { return zi.words }
    87     91   
    88     92   // GetUrls returns a reference to the set of URLs. It must not be modified.
    89     93   func (zi *ZettelIndex) GetUrls() WordSet { return zi.urls }
           94  +
           95  +// GetITags returns a reference to the set of internal tags. It must not be modified.
           96  +func (zi *ZettelIndex) GetITags() WordSet { return zi.itags }

Changes to box/membox/membox.go.

    17     17   	"sync"
    18     18   
    19     19   	"zettelstore.de/z/box"
    20     20   	"zettelstore.de/z/box/manager"
    21     21   	"zettelstore.de/z/domain"
    22     22   	"zettelstore.de/z/domain/id"
    23     23   	"zettelstore.de/z/domain/meta"
    24         -	"zettelstore.de/z/search"
    25     24   )
    26     25   
    27     26   func init() {
    28     27   	manager.Register(
    29     28   		"mem",
    30     29   		func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) {
    31     30   			return &memBox{u: u, cdata: *cdata}, nil
................................................................................
    45     44   	}
    46     45   }
    47     46   
    48     47   func (mp *memBox) Location() string {
    49     48   	return mp.u.String()
    50     49   }
    51     50   
    52         -func (mp *memBox) Start(ctx context.Context) error {
           51  +func (mp *memBox) Start(context.Context) error {
    53     52   	mp.mx.Lock()
    54     53   	mp.zettel = make(map[id.Zid]domain.Zettel)
    55     54   	mp.mx.Unlock()
    56     55   	return nil
    57     56   }
    58     57   
    59         -func (mp *memBox) Stop(ctx context.Context) error {
           58  +func (mp *memBox) Stop(context.Context) error {
    60     59   	mp.mx.Lock()
    61     60   	mp.zettel = nil
    62     61   	mp.mx.Unlock()
    63     62   	return nil
    64     63   }
    65     64   
    66         -func (mp *memBox) CanCreateZettel(ctx context.Context) bool { return true }
           65  +func (*memBox) CanCreateZettel(context.Context) bool { return true }
    67     66   
    68         -func (mp *memBox) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) {
           67  +func (mp *memBox) CreateZettel(_ context.Context, zettel domain.Zettel) (id.Zid, error) {
    69     68   	mp.mx.Lock()
    70     69   	zid, err := box.GetNewZid(func(zid id.Zid) (bool, error) {
    71     70   		_, ok := mp.zettel[zid]
    72     71   		return !ok, nil
    73     72   	})
    74     73   	if err != nil {
    75     74   		mp.mx.Unlock()
................................................................................
    80     79   	zettel.Meta = meta
    81     80   	mp.zettel[zid] = zettel
    82     81   	mp.mx.Unlock()
    83     82   	mp.notifyChanged(box.OnUpdate, zid)
    84     83   	return zid, nil
    85     84   }
    86     85   
    87         -func (mp *memBox) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) {
           86  +func (mp *memBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) {
    88     87   	mp.mx.RLock()
    89     88   	zettel, ok := mp.zettel[zid]
    90     89   	mp.mx.RUnlock()
    91     90   	if !ok {
    92     91   		return domain.Zettel{}, box.ErrNotFound
    93     92   	}
    94     93   	zettel.Meta = zettel.Meta.Clone()
    95     94   	return zettel, nil
    96     95   }
    97     96   
    98         -func (mp *memBox) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) {
           97  +func (mp *memBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) {
    99     98   	mp.mx.RLock()
   100     99   	zettel, ok := mp.zettel[zid]
   101    100   	mp.mx.RUnlock()
   102    101   	if !ok {
   103    102   		return nil, box.ErrNotFound
   104    103   	}
   105    104   	return zettel.Meta.Clone(), nil
   106    105   }
   107    106   
   108         -func (mp *memBox) FetchZids(ctx context.Context) (id.Set, error) {
          107  +func (mp *memBox) ApplyZid(_ context.Context, handle box.ZidFunc) error {
   109    108   	mp.mx.RLock()
   110         -	result := id.NewSetCap(len(mp.zettel))
          109  +	defer mp.mx.RUnlock()
   111    110   	for zid := range mp.zettel {
   112         -		result[zid] = true
          111  +		handle(zid)
   113    112   	}
   114         -	mp.mx.RUnlock()
   115         -	return result, nil
          113  +	return nil
   116    114   }
   117    115   
   118         -func (mp *memBox) SelectMeta(ctx context.Context, match search.MetaMatchFunc) ([]*meta.Meta, error) {
   119         -	result := make([]*meta.Meta, 0, len(mp.zettel))
          116  +func (mp *memBox) ApplyMeta(ctx context.Context, handle box.MetaFunc) error {
   120    117   	mp.mx.RLock()
          118  +	defer mp.mx.RUnlock()
   121    119   	for _, zettel := range mp.zettel {
   122    120   		m := zettel.Meta.Clone()
   123    121   		mp.cdata.Enricher.Enrich(ctx, m, mp.cdata.Number)
   124         -		if match(m) {
   125         -			result = append(result, m)
   126         -		}
          122  +		handle(m)
   127    123   	}
   128         -	mp.mx.RUnlock()
   129         -	return result, nil
          124  +	return nil
   130    125   }
   131    126   
   132         -func (mp *memBox) CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool {
   133         -	return true
   134         -}
          127  +func (*memBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return true }
   135    128   
   136         -func (mp *memBox) UpdateZettel(ctx context.Context, zettel domain.Zettel) error {
          129  +func (mp *memBox) UpdateZettel(_ context.Context, zettel domain.Zettel) error {
   137    130   	mp.mx.Lock()
   138    131   	meta := zettel.Meta.Clone()
   139    132   	if !meta.Zid.IsValid() {
   140    133   		return &box.ErrInvalidID{Zid: meta.Zid}
   141    134   	}
   142    135   	zettel.Meta = meta
   143    136   	mp.zettel[meta.Zid] = zettel
   144    137   	mp.mx.Unlock()
   145    138   	mp.notifyChanged(box.OnUpdate, meta.Zid)
   146    139   	return nil
   147    140   }
   148    141   
   149         -func (mp *memBox) AllowRenameZettel(ctx context.Context, zid id.Zid) bool { return true }
          142  +func (*memBox) AllowRenameZettel(context.Context, id.Zid) bool { return true }
   150    143   
   151         -func (mp *memBox) RenameZettel(ctx context.Context, curZid, newZid id.Zid) error {
          144  +func (mp *memBox) RenameZettel(_ context.Context, curZid, newZid id.Zid) error {
   152    145   	mp.mx.Lock()
   153    146   	zettel, ok := mp.zettel[curZid]
   154    147   	if !ok {
   155    148   		mp.mx.Unlock()
   156    149   		return box.ErrNotFound
   157    150   	}
   158    151   
................................................................................
   169    162   	delete(mp.zettel, curZid)
   170    163   	mp.mx.Unlock()
   171    164   	mp.notifyChanged(box.OnDelete, curZid)
   172    165   	mp.notifyChanged(box.OnUpdate, newZid)
   173    166   	return nil
   174    167   }
   175    168   
   176         -func (mp *memBox) CanDeleteZettel(ctx context.Context, zid id.Zid) bool {
          169  +func (mp *memBox) CanDeleteZettel(_ context.Context, zid id.Zid) bool {
   177    170   	mp.mx.RLock()
   178    171   	_, ok := mp.zettel[zid]
   179    172   	mp.mx.RUnlock()
   180    173   	return ok
   181    174   }
   182    175   
   183         -func (mp *memBox) DeleteZettel(ctx context.Context, zid id.Zid) error {
          176  +func (mp *memBox) DeleteZettel(_ context.Context, zid id.Zid) error {
   184    177   	mp.mx.Lock()
   185    178   	if _, ok := mp.zettel[zid]; !ok {
   186    179   		mp.mx.Unlock()
   187    180   		return box.ErrNotFound
   188    181   	}
   189    182   	delete(mp.zettel, zid)
   190    183   	mp.mx.Unlock()

Deleted box/merge.go.

     1         -//-----------------------------------------------------------------------------
     2         -// Copyright (c) 2020-2021 Detlef Stern
     3         -//
     4         -// This file is part of zettelstore.
     5         -//
     6         -// Zettelstore is licensed under the latest version of the EUPL (European Union
     7         -// Public License). Please see file LICENSE.txt for your rights and obligations
     8         -// under this license.
     9         -//-----------------------------------------------------------------------------
    10         -
    11         -// Package box provides a generic interface to zettel boxes.
    12         -package box
    13         -
    14         -import "zettelstore.de/z/domain/meta"
    15         -
    16         -// MergeSorted returns a merged sequence of metadata, sorted by Zid.
    17         -// The lists first and second must be sorted descending by Zid.
    18         -func MergeSorted(first, second []*meta.Meta) []*meta.Meta {
    19         -	lenFirst := len(first)
    20         -	lenSecond := len(second)
    21         -	result := make([]*meta.Meta, 0, lenFirst+lenSecond)
    22         -	iFirst := 0
    23         -	iSecond := 0
    24         -	for iFirst < lenFirst && iSecond < lenSecond {
    25         -		zidFirst := first[iFirst].Zid
    26         -		zidSecond := second[iSecond].Zid
    27         -		if zidFirst > zidSecond {
    28         -			result = append(result, first[iFirst])
    29         -			iFirst++
    30         -		} else if zidFirst < zidSecond {
    31         -			result = append(result, second[iSecond])
    32         -			iSecond++
    33         -		} else { // zidFirst == zidSecond
    34         -			result = append(result, first[iFirst])
    35         -			iFirst++
    36         -			iSecond++
    37         -		}
    38         -	}
    39         -	if iFirst < lenFirst {
    40         -		result = append(result, first[iFirst:]...)
    41         -	} else {
    42         -		result = append(result, second[iSecond:]...)
    43         -	}
    44         -
    45         -	return result
    46         -}

Changes to client/client.go.

    13     13   
    14     14   import (
    15     15   	"bytes"
    16     16   	"context"
    17     17   	"encoding/json"
    18     18   	"errors"
    19     19   	"io"
           20  +	"net"
    20     21   	"net/http"
    21     22   	"net/url"
    22     23   	"strconv"
    23     24   	"strings"
    24     25   	"time"
    25     26   
    26     27   	"zettelstore.de/z/api"
................................................................................
    31     32   type Client struct {
    32     33   	baseURL   string
    33     34   	username  string
    34     35   	password  string
    35     36   	token     string
    36     37   	tokenType string
    37     38   	expires   time.Time
           39  +	client    http.Client
    38     40   }
    39     41   
    40     42   // NewClient create a new client.
    41     43   func NewClient(baseURL string) *Client {
    42     44   	if !strings.HasSuffix(baseURL, "/") {
    43     45   		baseURL += "/"
    44     46   	}
    45         -	c := Client{baseURL: baseURL}
           47  +	c := Client{
           48  +		baseURL: baseURL,
           49  +		client: http.Client{
           50  +			Timeout: 10 * time.Second,
           51  +			Transport: &http.Transport{
           52  +				DialContext: (&net.Dialer{
           53  +					Timeout: 5 * time.Second, // TCP connect timeout
           54  +				}).DialContext,
           55  +				TLSHandshakeTimeout: 5 * time.Second,
           56  +			},
           57  +		},
           58  +	}
    46     59   	return &c
    47     60   }
    48     61   
    49     62   func (c *Client) newURLBuilder(key byte) *api.URLBuilder {
    50     63   	return api.NewURLBuilder(c.baseURL, key)
    51     64   }
    52         -func (c *Client) newRequest(ctx context.Context, method string, ub *api.URLBuilder, body io.Reader) (*http.Request, error) {
           65  +func (*Client) newRequest(ctx context.Context, method string, ub *api.URLBuilder, body io.Reader) (*http.Request, error) {
    53     66   	return http.NewRequestWithContext(ctx, method, ub.String(), body)
    54     67   }
    55     68   
    56     69   func (c *Client) executeRequest(req *http.Request) (*http.Response, error) {
    57     70   	if c.token != "" {
    58     71   		req.Header.Add("Authorization", c.tokenType+" "+c.token)
    59     72   	}
    60         -	client := http.Client{}
    61         -	resp, err := client.Do(req)
           73  +	resp, err := c.client.Do(req)
    62     74   	if err != nil {
    63     75   		if resp != nil && resp.Body != nil {
    64     76   			resp.Body.Close()
    65     77   		}
    66     78   		return nil, err
    67     79   	}
    68     80   	return resp, err
................................................................................
   123    135   	}
   124    136   	return c.RefreshToken(ctx)
   125    137   }
   126    138   
   127    139   // Authenticate sets a new token by sending user name and password.
   128    140   func (c *Client) Authenticate(ctx context.Context) error {
   129    141   	authData := url.Values{"username": {c.username}, "password": {c.password}}
   130         -	req, err := c.newRequest(ctx, http.MethodPost, c.newURLBuilder('v'), strings.NewReader(authData.Encode()))
          142  +	req, err := c.newRequest(ctx, http.MethodPost, c.newURLBuilder('a'), strings.NewReader(authData.Encode()))
   131    143   	if err != nil {
   132    144   		return err
   133    145   	}
   134    146   	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   135    147   	return c.executeAuthRequest(req)
   136    148   }
   137    149   
   138    150   // RefreshToken updates the access token
   139    151   func (c *Client) RefreshToken(ctx context.Context) error {
   140         -	req, err := c.newRequest(ctx, http.MethodPut, c.newURLBuilder('v'), nil)
          152  +	req, err := c.newRequest(ctx, http.MethodPut, c.newURLBuilder('a'), nil)
   141    153   	if err != nil {
   142    154   		return err
   143    155   	}
   144    156   	return c.executeAuthRequest(req)
   145    157   }
   146    158   
   147    159   // CreateZettel creates a new zettel and returns its URL.
   148         -func (c *Client) CreateZettel(ctx context.Context, data *api.ZettelDataJSON) (id.Zid, error) {
          160  +func (c *Client) CreateZettel(ctx context.Context, data string) (id.Zid, error) {
          161  +	ub := c.jsonZettelURLBuilder('z', nil)
          162  +	resp, err := c.buildAndExecuteRequest(ctx, http.MethodPost, ub, strings.NewReader(data), nil)
          163  +	if err != nil {
          164  +		return id.Invalid, err
          165  +	}
          166  +	defer resp.Body.Close()
          167  +	if resp.StatusCode != http.StatusCreated {
          168  +		return id.Invalid, errors.New(resp.Status)
          169  +	}
          170  +	b, err := io.ReadAll(resp.Body)
          171  +	if err != nil {
          172  +		return id.Invalid, err
          173  +	}
          174  +	zid, err := id.Parse(string(b))
          175  +	if err != nil {
          176  +		return id.Invalid, err
          177  +	}
          178  +	return zid, nil
          179  +}
          180  +
          181  +// CreateZettelJSON creates a new zettel and returns its URL.
          182  +func (c *Client) CreateZettelJSON(ctx context.Context, data *api.ZettelDataJSON) (id.Zid, error) {
   149    183   	var buf bytes.Buffer
   150    184   	if err := encodeZettelData(&buf, data); err != nil {
   151    185   		return id.Invalid, err
   152    186   	}
   153         -	ub := c.jsonZettelURLBuilder(nil)
          187  +	ub := c.jsonZettelURLBuilder('j', nil)
   154    188   	resp, err := c.buildAndExecuteRequest(ctx, http.MethodPost, ub, &buf, nil)
   155    189   	if err != nil {
   156    190   		return id.Invalid, err
   157    191   	}
   158    192   	defer resp.Body.Close()
   159    193   	if resp.StatusCode != http.StatusCreated {
   160    194   		return id.Invalid, errors.New(resp.Status)
................................................................................
   175    209   func encodeZettelData(buf *bytes.Buffer, data *api.ZettelDataJSON) error {
   176    210   	enc := json.NewEncoder(buf)
   177    211   	enc.SetEscapeHTML(false)
   178    212   	return enc.Encode(&data)
   179    213   }
   180    214   
   181    215   // ListZettel returns a list of all Zettel.
   182         -func (c *Client) ListZettel(ctx context.Context, query url.Values) ([]api.ZettelJSON, error) {
   183         -	ub := c.jsonZettelURLBuilder(query)
          216  +func (c *Client) ListZettel(ctx context.Context, query url.Values) (string, error) {
          217  +	ub := c.jsonZettelURLBuilder('z', query)
          218  +	resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil)
          219  +	if err != nil {
          220  +		return "", err
          221  +	}
          222  +	defer resp.Body.Close()
          223  +	if resp.StatusCode != http.StatusOK {
          224  +		return "", errors.New(resp.Status)
          225  +	}
          226  +	data, err := io.ReadAll(resp.Body)
          227  +	if err != nil {
          228  +		return "", err
          229  +	}
          230  +	return string(data), nil
          231  +}
          232  +
          233  +// ListZettelJSON returns a list of all Zettel.
          234  +func (c *Client) ListZettelJSON(ctx context.Context, query url.Values) ([]api.ZidMetaJSON, error) {
          235  +	ub := c.jsonZettelURLBuilder('j', query)
   184    236   	resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil)
   185    237   	if err != nil {
   186    238   		return nil, err
   187    239   	}
   188    240   	defer resp.Body.Close()
   189    241   	if resp.StatusCode != http.StatusOK {
   190    242   		return nil, errors.New(resp.Status)
................................................................................
   193    245   	var zl api.ZettelListJSON
   194    246   	err = dec.Decode(&zl)
   195    247   	if err != nil {
   196    248   		return nil, err
   197    249   	}
   198    250   	return zl.List, nil
   199    251   }
          252  +
          253  +// GetZettel returns a zettel as a string.
          254  +func (c *Client) GetZettel(ctx context.Context, zid id.Zid, part string) (string, error) {
          255  +	ub := c.jsonZettelURLBuilder('z', nil).SetZid(zid)
          256  +	if part != "" && part != api.PartContent {
          257  +		ub.AppendQuery(api.QueryKeyPart, part)
          258  +	}
          259  +	resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil)
          260  +	if err != nil {
          261  +		return "", err
          262  +	}
          263  +	defer resp.Body.Close()
          264  +	if resp.StatusCode != http.StatusOK {
          265  +		return "", errors.New(resp.Status)
          266  +	}
          267  +	data, err := io.ReadAll(resp.Body)
          268  +	if err != nil {
          269  +		return "", err
          270  +	}
          271  +	return string(data), nil
          272  +}
   200    273   
   201    274   // GetZettelJSON returns a zettel as a JSON struct.
   202         -func (c *Client) GetZettelJSON(ctx context.Context, zid id.Zid, query url.Values) (*api.ZettelDataJSON, error) {
   203         -	ub := c.jsonZettelURLBuilder(query).SetZid(zid)
          275  +func (c *Client) GetZettelJSON(ctx context.Context, zid id.Zid) (*api.ZettelDataJSON, error) {
          276  +	ub := c.jsonZettelURLBuilder('j', nil).SetZid(zid)
   204    277   	resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil)
   205    278   	if err != nil {
   206    279   		return nil, err
   207    280   	}
   208    281   	defer resp.Body.Close()
   209    282   	if resp.StatusCode != http.StatusOK {
   210    283   		return nil, errors.New(resp.Status)
................................................................................
   214    287   	err = dec.Decode(&out)
   215    288   	if err != nil {
   216    289   		return nil, err
   217    290   	}
   218    291   	return &out, nil
   219    292   }
   220    293   
   221         -// GetEvaluatedZettel return a zettel in a defined encoding.
          294  +// GetParsedZettel return a parsed zettel in a defined encoding.
          295  +func (c *Client) GetParsedZettel(ctx context.Context, zid id.Zid, enc api.EncodingEnum) (string, error) {
          296  +	return c.getZettelString(ctx, 'p', zid, enc)
          297  +}
          298  +
          299  +// GetEvaluatedZettel return an evaluated zettel in a defined encoding.
   222    300   func (c *Client) GetEvaluatedZettel(ctx context.Context, zid id.Zid, enc api.EncodingEnum) (string, error) {
   223         -	ub := c.jsonZettelURLBuilder(nil).SetZid(zid)
   224         -	ub.AppendQuery(api.QueryKeyFormat, enc.String())
          301  +	return c.getZettelString(ctx, 'v', zid, enc)
          302  +}
          303  +
          304  +func (c *Client) getZettelString(ctx context.Context, key byte, zid id.Zid, enc api.EncodingEnum) (string, error) {
          305  +	ub := c.jsonZettelURLBuilder(key, nil).SetZid(zid)
          306  +	ub.AppendQuery(api.QueryKeyEncoding, enc.String())
   225    307   	ub.AppendQuery(api.QueryKeyPart, api.PartContent)
   226    308   	resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil)
   227    309   	if err != nil {
   228    310   		return "", err
   229    311   	}
   230    312   	defer resp.Body.Close()
   231    313   	if resp.StatusCode != http.StatusOK {
................................................................................
   302    384   	err = dec.Decode(&out)
   303    385   	if err != nil {
   304    386   		return nil, err
   305    387   	}
   306    388   	return &out, nil
   307    389   }
   308    390   
   309         -// GetZettelLinks returns connections to ohter zettel, images, externals URLs.
          391  +// GetZettelLinks returns connections to other zettel, embedded material, externals URLs.
   310    392   func (c *Client) GetZettelLinks(ctx context.Context, zid id.Zid) (*api.ZettelLinksJSON, error) {
   311    393   	ub := c.newURLBuilder('l').SetZid(zid)
   312    394   	resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil)
   313    395   	if err != nil {
   314    396   		return nil, err
   315    397   	}
   316    398   	defer resp.Body.Close()
................................................................................
   323    405   	if err != nil {
   324    406   		return nil, err
   325    407   	}
   326    408   	return &out, nil
   327    409   }
   328    410   
   329    411   // UpdateZettel updates an existing zettel.
   330         -func (c *Client) UpdateZettel(ctx context.Context, zid id.Zid, data *api.ZettelDataJSON) error {
          412  +func (c *Client) UpdateZettel(ctx context.Context, zid id.Zid, data string) error {
          413  +	ub := c.jsonZettelURLBuilder('z', nil).SetZid(zid)
          414  +	resp, err := c.buildAndExecuteRequest(ctx, http.MethodPut, ub, strings.NewReader(data), nil)
          415  +	if err != nil {
          416  +		return err
          417  +	}
          418  +	defer resp.Body.Close()
          419  +	if resp.StatusCode != http.StatusNoContent {
          420  +		return errors.New(resp.Status)
          421  +	}
          422  +	return nil
          423  +}
          424  +
          425  +// UpdateZettelJSON updates an existing zettel.
          426  +func (c *Client) UpdateZettelJSON(ctx context.Context, zid id.Zid, data *api.ZettelDataJSON) error {
   331    427   	var buf bytes.Buffer
   332    428   	if err := encodeZettelData(&buf, data); err != nil {
   333    429   		return err
   334    430   	}
   335         -	ub := c.jsonZettelURLBuilder(nil).SetZid(zid)
          431  +	ub := c.jsonZettelURLBuilder('j', nil).SetZid(zid)
   336    432   	resp, err := c.buildAndExecuteRequest(ctx, http.MethodPut, ub, &buf, nil)
   337    433   	if err != nil {
   338    434   		return err
   339    435   	}
   340    436   	defer resp.Body.Close()
   341    437   	if resp.StatusCode != http.StatusNoContent {
   342    438   		return errors.New(resp.Status)
   343    439   	}
   344    440   	return nil
   345    441   }
   346    442   
   347    443   // RenameZettel renames a zettel.
   348    444   func (c *Client) RenameZettel(ctx context.Context, oldZid, newZid id.Zid) error {
   349         -	ub := c.jsonZettelURLBuilder(nil).SetZid(oldZid)
          445  +	ub := c.jsonZettelURLBuilder('z', nil).SetZid(oldZid)
   350    446   	h := http.Header{
   351         -		api.HeaderDestination: {c.jsonZettelURLBuilder(nil).SetZid(newZid).String()},
          447  +		api.HeaderDestination: {c.jsonZettelURLBuilder('z', nil).SetZid(newZid).String()},
   352    448   	}
   353    449   	resp, err := c.buildAndExecuteRequest(ctx, api.MethodMove, ub, nil, h)
   354    450   	if err != nil {
   355    451   		return err
   356    452   	}
   357    453   	defer resp.Body.Close()
   358    454   	if resp.StatusCode != http.StatusNoContent {
................................................................................
   359    455   		return errors.New(resp.Status)
   360    456   	}
   361    457   	return nil
   362    458   }
   363    459   
   364    460   // DeleteZettel deletes a zettel with the given identifier.
   365    461   func (c *Client) DeleteZettel(ctx context.Context, zid id.Zid) error {
   366         -	ub := c.jsonZettelURLBuilder(nil).SetZid(zid)
          462  +	ub := c.jsonZettelURLBuilder('z', nil).SetZid(zid)
   367    463   	resp, err := c.buildAndExecuteRequest(ctx, http.MethodDelete, ub, nil, nil)
   368    464   	if err != nil {
   369    465   		return err
   370    466   	}
   371    467   	defer resp.Body.Close()
   372    468   	if resp.StatusCode != http.StatusNoContent {
   373    469   		return errors.New(resp.Status)
   374    470   	}
   375    471   	return nil
   376    472   }
   377    473   
   378         -func (c *Client) jsonZettelURLBuilder(query url.Values) *api.URLBuilder {
   379         -	ub := c.newURLBuilder('z')
          474  +func (c *Client) jsonZettelURLBuilder(key byte, query url.Values) *api.URLBuilder {
          475  +	ub := c.newURLBuilder(key)
   380    476   	for key, values := range query {
   381         -		if key == api.QueryKeyFormat {
          477  +		if key == api.QueryKeyEncoding {
   382    478   			continue
   383    479   		}
   384    480   		for _, val := range values {
   385    481   			ub.AppendQuery(key, val)
   386    482   		}
   387    483   	}
   388    484   	return ub

Changes to client/client_test.go.

    12     12   package client_test
    13     13   
    14     14   import (
    15     15   	"context"
    16     16   	"flag"
    17     17   	"fmt"
    18     18   	"net/url"
           19  +	"strings"
    19     20   	"testing"
    20     21   
    21     22   	"zettelstore.de/z/api"
    22     23   	"zettelstore.de/z/client"
    23     24   	"zettelstore.de/z/domain/id"
    24     25   	"zettelstore.de/z/domain/meta"
    25     26   )
    26     27   
    27         -func TestCreateRenameDeleteZettel(t *testing.T) {
           28  +func TestCreateGetRenameDeleteZettel(t *testing.T) {
           29  +	// Is not to be allowed to run in parallel with other tests.
           30  +	zettel := `title: A Test
           31  +
           32  +Example content.`
           33  +	c := getClient()
           34  +	c.SetAuth("owner", "owner")
           35  +	zid, err := c.CreateZettel(context.Background(), zettel)
           36  +	if err != nil {
           37  +		t.Error("Cannot create zettel:", err)
           38  +		return
           39  +	}
           40  +	if !zid.IsValid() {
           41  +		t.Error("Invalid zettel ID", zid)
           42  +		return
           43  +	}
           44  +	data, err := c.GetZettel(context.Background(), zid, api.PartZettel)
           45  +	if err != nil {
           46  +		t.Error("Cannot read zettel", zid, err)
           47  +		return
           48  +	}
           49  +	exp := `title: A Test
           50  +role: zettel
           51  +syntax: zmk
           52  +
           53  +Example content.`
           54  +	if data != exp {
           55  +		t.Errorf("Expected zettel data: %q, but got %q", exp, data)
           56  +	}
           57  +	newZid := zid + 1
           58  +	err = c.RenameZettel(context.Background(), zid, newZid)
           59  +	if err != nil {
           60  +		t.Error("Cannot rename", zid, ":", err)
           61  +		newZid = zid
           62  +	}
           63  +	err = c.DeleteZettel(context.Background(), newZid)
           64  +	if err != nil {
           65  +		t.Error("Cannot delete", zid, ":", err)
           66  +		return
           67  +	}
           68  +}
           69  +
           70  +func TestCreateRenameDeleteZettelJSON(t *testing.T) {
    28     71   	// Is not to be allowed to run in parallel with other tests.
    29     72   	c := getClient()
    30     73   	c.SetAuth("creator", "creator")
    31         -	zid, err := c.CreateZettel(context.Background(), &api.ZettelDataJSON{
           74  +	zid, err := c.CreateZettelJSON(context.Background(), &api.ZettelDataJSON{
    32     75   		Meta:     nil,
    33     76   		Encoding: "",
    34     77   		Content:  "Example",
    35     78   	})
    36     79   	if err != nil {
    37     80   		t.Error("Cannot create zettel:", err)
    38     81   		return
................................................................................
    53     96   		t.Error("Cannot delete", zid, ":", err)
    54     97   		return
    55     98   	}
    56     99   }
    57    100   
    58    101   func TestUpdateZettel(t *testing.T) {
    59    102   	t.Parallel()
          103  +	c := getClient()
          104  +	c.SetAuth("owner", "owner")
          105  +	z, err := c.GetZettel(context.Background(), id.DefaultHomeZid, api.PartZettel)
          106  +	if err != nil {
          107  +		t.Error(err)
          108  +		return
          109  +	}
          110  +	if !strings.HasPrefix(z, "title: Home\n") {
          111  +		t.Error("Got unexpected zettel", z)
          112  +		return
          113  +	}
          114  +	newZettel := `title: New Home
          115  +role: zettel
          116  +syntax: zmk
          117  +
          118  +Empty`
          119  +	err = c.UpdateZettel(context.Background(), id.DefaultHomeZid, newZettel)
          120  +	if err != nil {
          121  +		t.Error(err)
          122  +		return
          123  +	}
          124  +	zt, err := c.GetZettel(context.Background(), id.DefaultHomeZid, api.PartZettel)
          125  +	if err != nil {
          126  +		t.Error(err)
          127  +		return
          128  +	}
          129  +	if zt != newZettel {
          130  +		t.Errorf("Expected zettel %q, got %q", newZettel, zt)
          131  +	}
          132  +	// Must delete to clean up for next tests
          133  +	err = c.DeleteZettel(context.Background(), id.DefaultHomeZid)
          134  +	if err != nil {
          135  +		t.Error("Cannot delete", id.DefaultHomeZid, ":", err)
          136  +		return
          137  +	}
          138  +}
          139  +
          140  +func TestUpdateZettelJSON(t *testing.T) {
          141  +	t.Parallel()
    60    142   	c := getClient()
    61    143   	c.SetAuth("writer", "writer")
    62         -	z, err := c.GetZettelJSON(context.Background(), id.DefaultHomeZid, nil)
          144  +	z, err := c.GetZettelJSON(context.Background(), id.DefaultHomeZid)
    63    145   	if err != nil {
    64    146   		t.Error(err)
    65    147   		return
    66    148   	}
    67    149   	if got := z.Meta[meta.KeyTitle]; got != "Home" {
    68    150   		t.Errorf("Title of zettel is not \"Home\", but %q", got)
    69    151   		return
    70    152   	}
    71    153   	newTitle := "New Home"
    72    154   	z.Meta[meta.KeyTitle] = newTitle
    73         -	err = c.UpdateZettel(context.Background(), id.DefaultHomeZid, z)
          155  +	err = c.UpdateZettelJSON(context.Background(), id.DefaultHomeZid, z)
    74    156   	if err != nil {
    75    157   		t.Error(err)
    76    158   		return
    77    159   	}
    78         -	zt, err := c.GetZettelJSON(context.Background(), id.DefaultHomeZid, nil)
          160  +	zt, err := c.GetZettelJSON(context.Background(), id.DefaultHomeZid)
    79    161   	if err != nil {
    80    162   		t.Error(err)
    81    163   		return
    82    164   	}
    83    165   	if got := zt.Meta[meta.KeyTitle]; got != newTitle {
    84    166   		t.Errorf("Title of zettel is not %q, but %q", newTitle, got)
    85    167   	}
          168  +	// No need to clean up, because we just changed the title.
    86    169   }
    87    170   
    88         -func TestList(t *testing.T) {
          171  +func TestListZettel(t *testing.T) {
    89    172   	testdata := []struct {
    90    173   		user string
    91    174   		exp  int
    92    175   	}{
    93    176   		{"", 7},
    94    177   		{"creator", 10},
    95    178   		{"reader", 12},
    96    179   		{"writer", 12},
    97    180   		{"owner", 34},
    98    181   	}
    99    182   
   100    183   	t.Parallel()
   101    184   	c := getClient()
   102         -	query := url.Values{api.QueryKeyFormat: {"html"}} // Client must remove "html"
          185  +	query := url.Values{api.QueryKeyEncoding: {api.EncodingHTML}} // Client must remove "html"
   103    186   	for i, tc := range testdata {
   104    187   		t.Run(fmt.Sprintf("User %d/%q", i, tc.user), func(tt *testing.T) {
   105    188   			c.SetAuth(tc.user, tc.user)
   106         -			l, err := c.ListZettel(context.Background(), query)
          189  +			l, err := c.ListZettelJSON(context.Background(), query)
   107    190   			if err != nil {
   108    191   				tt.Error(err)
   109    192   				return
   110    193   			}
   111    194   			got := len(l)
   112    195   			if got != tc.exp {
   113    196   				tt.Errorf("List of length %d expected, but got %d\n%v", tc.exp, got, l)
   114    197   			}
   115    198   		})
   116    199   	}
   117         -	l, err := c.ListZettel(context.Background(), url.Values{meta.KeyRole: {meta.ValueRoleConfiguration}})
          200  +	l, err := c.ListZettelJSON(context.Background(), url.Values{meta.KeyRole: {meta.ValueRoleConfiguration}})
   118    201   	if err != nil {
   119    202   		t.Error(err)
   120    203   		return
   121    204   	}
   122    205   	got := len(l)
   123    206   	if got != 27 {
   124    207   		t.Errorf("List of length %d expected, but got %d\n%v", 27, got, l)
   125    208   	}
          209  +
          210  +	pl, err := c.ListZettel(context.Background(), url.Values{meta.KeyRole: {meta.ValueRoleConfiguration}})
          211  +	if err != nil {
          212  +		t.Error(err)
          213  +		return
          214  +	}
          215  +	lines := strings.Split(pl, "\n")
          216  +	if lines[len(lines)-1] == "" {
          217  +		lines = lines[:len(lines)-1]
          218  +	}
          219  +	if len(lines) != len(l) {
          220  +		t.Errorf("Different list lenght: Plain=%d, JSON=%d", len(lines), len(l))
          221  +	} else {
          222  +		for i, line := range lines {
          223  +			if got := line[:14]; got != l[i].ID {
          224  +				t.Errorf("%d: JSON=%q, got=%q", i, l[i].ID, got)
          225  +			}
          226  +		}
          227  +	}
   126    228   }
   127         -func TestGetZettel(t *testing.T) {
          229  +
          230  +func TestGetZettelJSON(t *testing.T) {
   128    231   	t.Parallel()
   129    232   	c := getClient()
   130    233   	c.SetAuth("owner", "owner")
   131         -	z, err := c.GetZettelJSON(context.Background(), id.DefaultHomeZid, url.Values{api.QueryKeyPart: {api.PartContent}})
          234  +	z, err := c.GetZettelJSON(context.Background(), id.DefaultHomeZid)
   132    235   	if err != nil {
   133    236   		t.Error(err)
   134    237   		return
   135    238   	}
   136         -	if m := z.Meta; len(m) > 0 {
   137         -		t.Errorf("Exptected empty meta, but got %v", z.Meta)
          239  +	if m := z.Meta; len(m) == 0 {
          240  +		t.Errorf("Exptected non-empty meta, but got %v", z.Meta)
   138    241   	}
   139    242   	if z.Content == "" || z.Encoding != "" {
   140    243   		t.Errorf("Expect non-empty content, but empty encoding (got %q)", z.Encoding)
   141    244   	}
   142    245   }
   143    246   
   144         -func TestGetEvaluatedZettel(t *testing.T) {
          247  +func TestGetParsedEvaluatedZettel(t *testing.T) {
   145    248   	t.Parallel()
   146    249   	c := getClient()
   147    250   	c.SetAuth("owner", "owner")
   148    251   	encodings := []api.EncodingEnum{
   149    252   		api.EncoderDJSON,
   150    253   		api.EncoderHTML,
   151    254   		api.EncoderNative,
   152    255   		api.EncoderText,
   153    256   	}
   154    257   	for _, enc := range encodings {
   155         -		content, err := c.GetEvaluatedZettel(context.Background(), id.DefaultHomeZid, enc)
          258  +		content, err := c.GetParsedZettel(context.Background(), id.DefaultHomeZid, enc)
          259  +		if err != nil {
          260  +			t.Error(err)
          261  +			continue
          262  +		}
          263  +		if len(content) == 0 {
          264  +			t.Errorf("Empty content for parsed encoding %v", enc)
          265  +		}
          266  +		content, err = c.GetEvaluatedZettel(context.Background(), id.DefaultHomeZid, enc)
   156    267   		if err != nil {
   157    268   			t.Error(err)
   158    269   			continue
   159    270   		}
   160    271   		if len(content) == 0 {
   161         -			t.Errorf("Empty content for encoding %v", enc)
          272  +			t.Errorf("Empty content for evaluated encoding %v", enc)
   162    273   		}
   163    274   	}
   164    275   }
          276  +
          277  +func checkZid(t *testing.T, expected id.Zid, got string) bool {
          278  +	t.Helper()
          279  +	if exp := expected.String(); exp != got {
          280  +		t.Errorf("Expected a Zid %q, but got %q", exp, got)
          281  +		return false
          282  +	}
          283  +	return true
          284  +}
          285  +
          286  +func checkListZid(t *testing.T, l []api.ZidMetaJSON, pos int, expected id.Zid) {
          287  +	t.Helper()
          288  +	exp := expected.String()
          289  +	if got := l[pos].ID; got != exp {
          290  +		t.Errorf("Expected result[%d]=%v, but got %v", pos, exp, got)
          291  +	}
          292  +}
   165    293   
   166    294   func TestGetZettelOrder(t *testing.T) {
   167    295   	t.Parallel()
   168    296   	c := getClient()
   169    297   	c.SetAuth("owner", "owner")
   170    298   	rl, err := c.GetZettelOrder(context.Background(), id.TOCNewTemplateZid)
   171    299   	if err != nil {
   172    300   		t.Error(err)
   173    301   		return
   174    302   	}
   175         -	if rl.ID != id.TOCNewTemplateZid.String() {
   176         -		t.Errorf("Expected an Zid %v, but got %v", id.TOCNewTemplateZid, rl.ID)
          303  +	if !checkZid(t, id.TOCNewTemplateZid, rl.ID) {
   177    304   		return
   178    305   	}
   179    306   	l := rl.List
   180    307   	if got := len(l); got != 2 {
   181    308   		t.Errorf("Expected list fo length 2, got %d", got)
   182    309   		return
   183    310   	}
   184         -	if got := l[0].ID; got != id.TemplateNewZettelZid.String() {
   185         -		t.Errorf("Expected result[0]=%v, but got %v", id.TemplateNewZettelZid, got)
   186         -	}
   187         -	if got := l[1].ID; got != id.TemplateNewUserZid.String() {
   188         -		t.Errorf("Expected result[1]=%v, but got %v", id.TemplateNewUserZid, got)
   189         -	}
          311  +	checkListZid(t, l, 0, id.TemplateNewZettelZid)
          312  +	checkListZid(t, l, 1, id.TemplateNewUserZid)
   190    313   }
   191    314   
   192    315   func TestGetZettelContext(t *testing.T) {
   193    316   	t.Parallel()
   194    317   	c := getClient()
   195    318   	c.SetAuth("owner", "owner")
   196    319   	rl, err := c.GetZettelContext(context.Background(), id.VersionZid, client.DirBoth, 0, 3)
   197    320   	if err != nil {
   198    321   		t.Error(err)
   199    322   		return
   200    323   	}
   201         -	if rl.ID != id.VersionZid.String() {
   202         -		t.Errorf("Expected an Zid %v, but got %v", id.VersionZid, rl.ID)
          324  +	if !checkZid(t, id.VersionZid, rl.ID) {
   203    325   		return
   204    326   	}
   205    327   	l := rl.List
   206    328   	if got := len(l); got != 3 {
   207    329   		t.Errorf("Expected list fo length 3, got %d", got)
   208    330   		return
   209    331   	}
   210         -	if got := l[0].ID; got != id.DefaultHomeZid.String() {
   211         -		t.Errorf("Expected result[0]=%v, but got %v", id.DefaultHomeZid, got)
   212         -	}
   213         -	if got := l[1].ID; got != id.OperatingSystemZid.String() {
   214         -		t.Errorf("Expected result[1]=%v, but got %v", id.OperatingSystemZid, got)
   215         -	}
   216         -	if got := l[2].ID; got != id.StartupConfigurationZid.String() {
   217         -		t.Errorf("Expected result[2]=%v, but got %v", id.StartupConfigurationZid, got)
   218         -	}
          332  +	checkListZid(t, l, 0, id.DefaultHomeZid)
          333  +	checkListZid(t, l, 1, id.OperatingSystemZid)
          334  +	checkListZid(t, l, 2, id.StartupConfigurationZid)
   219    335   
   220    336   	rl, err = c.GetZettelContext(context.Background(), id.VersionZid, client.DirBackward, 0, 0)
   221    337   	if err != nil {
   222    338   		t.Error(err)
   223    339   		return
   224    340   	}
   225         -	if rl.ID != id.VersionZid.String() {
   226         -		t.Errorf("Expected an Zid %v, but got %v", id.VersionZid, rl.ID)
          341  +	if !checkZid(t, id.VersionZid, rl.ID) {
   227    342   		return
   228    343   	}
   229    344   	l = rl.List
   230    345   	if got := len(l); got != 1 {
   231    346   		t.Errorf("Expected list fo length 1, got %d", got)
   232    347   		return
   233    348   	}
   234         -	if got := l[0].ID; got != id.DefaultHomeZid.String() {
   235         -		t.Errorf("Expected result[0]=%v, but got %v", id.DefaultHomeZid, got)
   236         -	}
          349  +	checkListZid(t, l, 0, id.DefaultHomeZid)
   237    350   }
   238    351   
   239    352   func TestGetZettelLinks(t *testing.T) {
   240    353   	t.Parallel()
   241    354   	c := getClient()
   242    355   	c.SetAuth("owner", "owner")
   243    356   	zl, err := c.GetZettelLinks(context.Background(), id.DefaultHomeZid)
   244    357   	if err != nil {
   245    358   		t.Error(err)
   246    359   		return
   247    360   	}
   248         -	if zl.ID != id.DefaultHomeZid.String() {
   249         -		t.Errorf("Expected an Zid %v, but got %v", id.DefaultHomeZid, zl.ID)
          361  +	if !checkZid(t, id.DefaultHomeZid, zl.ID) {
   250    362   		return
   251    363   	}
   252         -	if len(zl.Links.Incoming) != 0 {
   253         -		t.Error("No incomings expected", zl.Links.Incoming)
          364  +	if len(zl.Linked.Incoming) != 0 {
          365  +		t.Error("No incomings expected", zl.Linked.Incoming)
   254    366   	}
   255         -	if got := len(zl.Links.Outgoing); got != 4 {
          367  +	if got := len(zl.Linked.Outgoing); got != 4 {
   256    368   		t.Errorf("Expected 4 outgoing links, got %d", got)
   257    369   	}
   258         -	if got := len(zl.Links.Local); got != 1 {
          370  +	if got := len(zl.Linked.Local); got != 1 {
   259    371   		t.Errorf("Expected 1 local link, got %d", got)
   260    372   	}
   261         -	if got := len(zl.Links.External); got != 4 {
          373  +	if got := len(zl.Linked.External); got != 4 {
   262    374   		t.Errorf("Expected 4 external link, got %d", got)
   263    375   	}
   264    376   }
   265    377   
   266    378   func TestListTags(t *testing.T) {
   267    379   	t.Parallel()
   268    380   	c := getClient()

Changes to cmd/cmd_file.go.

    23     23   	"zettelstore.de/z/encoder"
    24     24   	"zettelstore.de/z/input"
    25     25   	"zettelstore.de/z/parser"
    26     26   )
    27     27   
    28     28   // ---------- Subcommand: file -----------------------------------------------
    29     29   
    30         -func cmdFile(fs *flag.FlagSet, cfg *meta.Meta) (int, error) {
    31         -	format := fs.Lookup("t").Value.String()
           30  +func cmdFile(fs *flag.FlagSet, _ *meta.Meta) (int, error) {
           31  +	enc := fs.Lookup("t").Value.String()
    32     32   	m, inp, err := getInput(fs.Args())
    33     33   	if m == nil {
    34     34   		return 2, err
    35     35   	}
    36     36   	z := parser.ParseZettel(
    37     37   		domain.Zettel{
    38     38   			Meta:    m,
    39     39   			Content: domain.NewContent(inp.Src[inp.Pos:]),
    40     40   		},
    41     41   		m.GetDefault(meta.KeySyntax, meta.ValueSyntaxZmk),
    42     42   		nil,
    43     43   	)
    44         -	enc := encoder.Create(api.Encoder(format), &encoder.Environment{Lang: m.GetDefault(meta.KeyLang, meta.ValueLangEN)})
    45         -	if enc == nil {
    46         -		fmt.Fprintf(os.Stderr, "Unknown format %q\n", format)
           44  +	encdr := encoder.Create(api.Encoder(enc), &encoder.Environment{
           45  +		Lang: m.GetDefault(meta.KeyLang, meta.ValueLangEN),
           46  +	})
           47  +	if encdr == nil {
           48  +		fmt.Fprintf(os.Stderr, "Unknown format %q\n", enc)
    47     49   		return 2, nil
    48     50   	}
    49         -	_, err = enc.WriteZettel(os.Stdout, z, format != "raw")
           51  +	_, err = encdr.WriteZettel(os.Stdout, z, parser.ParseMetadata)
    50     52   	if err != nil {
    51     53   		return 2, err
    52     54   	}
    53     55   	fmt.Println()
    54     56   
    55     57   	return 0, nil
    56     58   }

Changes to cmd/cmd_run.go.

     8      8   // under this license.
     9      9   //-----------------------------------------------------------------------------
    10     10   
    11     11   package cmd
    12     12   
    13     13   import (
    14     14   	"flag"
    15         -	"net/http"
    16     15   
    17         -	zsapi "zettelstore.de/z/api"
    18     16   	"zettelstore.de/z/auth"
    19     17   	"zettelstore.de/z/box"
    20     18   	"zettelstore.de/z/config"
    21     19   	"zettelstore.de/z/domain/meta"
    22     20   	"zettelstore.de/z/kernel"
    23     21   	"zettelstore.de/z/usecase"
    24     22   	"zettelstore.de/z/web/adapter/api"
................................................................................
    39     37   }
    40     38   
    41     39   func withDebug(fs *flag.FlagSet) bool {
    42     40   	dbg := fs.Lookup("debug")
    43     41   	return dbg != nil && dbg.Value.String() == "true"
    44     42   }
    45     43   
    46         -func runFunc(fs *flag.FlagSet, cfg *meta.Meta) (int, error) {
           44  +func runFunc(fs *flag.FlagSet, _ *meta.Meta) (int, error) {
    47     45   	exitCode, err := doRun(withDebug(fs))
    48     46   	kernel.Main.WaitForShutdown()
    49     47   	return exitCode, err
    50     48   }
    51     49   
    52     50   func doRun(debug bool) (int, error) {
    53     51   	kern := kernel.Main
................................................................................
    56     54   		return 1, err
    57     55   	}
    58     56   	return 0, nil
    59     57   }
    60     58   
    61     59   func setupRouting(webSrv server.Server, boxManager box.Manager, authManager auth.Manager, rtConfig config.Config) {
    62     60   	protectedBoxManager, authPolicy := authManager.BoxWithPolicy(webSrv, boxManager, rtConfig)
    63         -	api := api.New(webSrv, authManager, authManager, webSrv, rtConfig)
           61  +	a := api.New(webSrv, authManager, authManager, webSrv, rtConfig)
    64     62   	wui := webui.New(webSrv, authManager, rtConfig, authManager, boxManager, authPolicy)
    65     63   
    66     64   	ucAuthenticate := usecase.NewAuthenticate(authManager, authManager, boxManager)
    67     65   	ucCreateZettel := usecase.NewCreateZettel(rtConfig, protectedBoxManager)
    68     66   	ucGetMeta := usecase.NewGetMeta(protectedBoxManager)
    69     67   	ucGetAllMeta := usecase.NewGetAllMeta(protectedBoxManager)
    70     68   	ucGetZettel := usecase.NewGetZettel(protectedBoxManager)
    71     69   	ucParseZettel := usecase.NewParseZettel(rtConfig, ucGetZettel)
           70  +	ucEvaluate := usecase.NewEvaluate(rtConfig, ucGetZettel, ucGetMeta)
    72     71   	ucListMeta := usecase.NewListMeta(protectedBoxManager)
    73     72   	ucListRoles := usecase.NewListRole(protectedBoxManager)
    74     73   	ucListTags := usecase.NewListTags(protectedBoxManager)
    75     74   	ucZettelContext := usecase.NewZettelContext(protectedBoxManager)
    76     75   	ucDelete := usecase.NewDeleteZettel(protectedBoxManager)
    77     76   	ucUpdate := usecase.NewUpdateZettel(protectedBoxManager)
    78     77   	ucRename := usecase.NewRenameZettel(protectedBoxManager)
    79     78   
    80     79   	webSrv.Handle("/", wui.MakeGetRootHandler(protectedBoxManager))
    81     80   
    82     81   	// Web user interface
    83         -	webSrv.AddListRoute('a', http.MethodGet, wui.MakeGetLoginHandler())
    84         -	webSrv.AddListRoute('a', http.MethodPost, wui.MakePostLoginHandler(ucAuthenticate))
    85         -	webSrv.AddZettelRoute('a', http.MethodGet, wui.MakeGetLogoutHandler())
    86     82   	if !authManager.IsReadonly() {
    87         -		webSrv.AddZettelRoute('b', http.MethodGet, wui.MakeGetRenameZettelHandler(ucGetMeta))
    88         -		webSrv.AddZettelRoute('b', http.MethodPost, wui.MakePostRenameZettelHandler(ucRename))
    89         -		webSrv.AddZettelRoute('c', http.MethodGet, wui.MakeGetCopyZettelHandler(
           83  +		webSrv.AddZettelRoute('b', server.MethodGet, wui.MakeGetRenameZettelHandler(ucGetMeta))
           84  +		webSrv.AddZettelRoute('b', server.MethodPost, wui.MakePostRenameZettelHandler(ucRename))
           85  +		webSrv.AddZettelRoute('c', server.MethodGet, wui.MakeGetCopyZettelHandler(
    90     86   			ucGetZettel, usecase.NewCopyZettel()))
    91         -		webSrv.AddZettelRoute('c', http.MethodPost, wui.MakePostCreateZettelHandler(ucCreateZettel))
    92         -		webSrv.AddZettelRoute('d', http.MethodGet, wui.MakeGetDeleteZettelHandler(ucGetZettel))
    93         -		webSrv.AddZettelRoute('d', http.MethodPost, wui.MakePostDeleteZettelHandler(ucDelete))
    94         -		webSrv.AddZettelRoute('e', http.MethodGet, wui.MakeEditGetZettelHandler(ucGetZettel))
    95         -		webSrv.AddZettelRoute('e', http.MethodPost, wui.MakeEditSetZettelHandler(ucUpdate))
    96         -		webSrv.AddZettelRoute('f', http.MethodGet, wui.MakeGetFolgeZettelHandler(
           87  +		webSrv.AddZettelRoute('c', server.MethodPost, wui.MakePostCreateZettelHandler(ucCreateZettel))
           88  +		webSrv.AddZettelRoute('d', server.MethodGet, wui.MakeGetDeleteZettelHandler(ucGetZettel))
           89  +		webSrv.AddZettelRoute('d', server.MethodPost, wui.MakePostDeleteZettelHandler(ucDelete))
           90  +		webSrv.AddZettelRoute('e', server.MethodGet, wui.MakeEditGetZettelHandler(ucGetZettel))
           91  +		webSrv.AddZettelRoute('e', server.MethodPost, wui.MakeEditSetZettelHandler(ucUpdate))
           92  +		webSrv.AddZettelRoute('f', server.MethodGet, wui.MakeGetFolgeZettelHandler(
    97     93   			ucGetZettel, usecase.NewFolgeZettel(rtConfig)))
    98         -		webSrv.AddZettelRoute('f', http.MethodPost, wui.MakePostCreateZettelHandler(ucCreateZettel))
    99         -		webSrv.AddZettelRoute('g', http.MethodGet, wui.MakeGetNewZettelHandler(
           94  +		webSrv.AddZettelRoute('f', server.MethodPost, wui.MakePostCreateZettelHandler(ucCreateZettel))
           95  +		webSrv.AddZettelRoute('g', server.MethodGet, wui.MakeGetNewZettelHandler(
   100     96   			ucGetZettel, usecase.NewNewZettel()))
   101         -		webSrv.AddZettelRoute('g', http.MethodPost, wui.MakePostCreateZettelHandler(ucCreateZettel))
           97  +		webSrv.AddZettelRoute('g', server.MethodPost, wui.MakePostCreateZettelHandler(ucCreateZettel))
   102     98   	}
   103         -	webSrv.AddListRoute('f', http.MethodGet, wui.MakeSearchHandler(
   104         -		usecase.NewSearch(protectedBoxManager), ucGetMeta, ucGetZettel))
   105         -	webSrv.AddListRoute('h', http.MethodGet, wui.MakeListHTMLMetaHandler(
   106         -		ucListMeta, ucListRoles, ucListTags))
   107         -	webSrv.AddZettelRoute('h', http.MethodGet, wui.MakeGetHTMLZettelHandler(
   108         -		ucParseZettel, ucGetMeta))
   109         -	webSrv.AddZettelRoute('i', http.MethodGet, wui.MakeGetInfoHandler(
   110         -		ucParseZettel, ucGetMeta, ucGetAllMeta))
   111         -	webSrv.AddZettelRoute('j', http.MethodGet, wui.MakeZettelContextHandler(ucZettelContext))
           99  +	webSrv.AddListRoute('f', server.MethodGet, wui.MakeSearchHandler(
          100  +		usecase.NewSearch(protectedBoxManager), &ucEvaluate))
          101  +	webSrv.AddListRoute('h', server.MethodGet, wui.MakeListHTMLMetaHandler(
          102  +		ucListMeta, ucListRoles, ucListTags, &ucEvaluate))
          103  +	webSrv.AddZettelRoute('h', server.MethodGet, wui.MakeGetHTMLZettelHandler(
          104  +		&ucEvaluate, ucGetMeta))
          105  +	webSrv.AddListRoute('i', server.MethodGet, wui.MakeGetLoginOutHandler())
          106  +	webSrv.AddListRoute('i', server.MethodPost, wui.MakePostLoginHandler(ucAuthenticate))
          107  +	webSrv.AddZettelRoute('i', server.MethodGet, wui.MakeGetInfoHandler(
          108  +		ucParseZettel, &ucEvaluate, ucGetMeta, ucGetAllMeta))
          109  +	webSrv.AddListRoute('i', server.MethodGet, wui.MakeGetLoginOutHandler())
          110  +	webSrv.AddZettelRoute('k', server.MethodGet, wui.MakeZettelContextHandler(
          111  +		ucZettelContext, &ucEvaluate))
   112    112   
   113    113   	// API
   114         -	webSrv.AddZettelRoute('l', http.MethodGet, api.MakeGetLinksHandler(ucParseZettel))
   115         -	webSrv.AddZettelRoute('o', http.MethodGet, api.MakeGetOrderHandler(
   116         -		usecase.NewZettelOrder(protectedBoxManager, ucParseZettel)))
   117         -	webSrv.AddListRoute('r', http.MethodGet, api.MakeListRoleHandler(ucListRoles))
   118         -	webSrv.AddListRoute('t', http.MethodGet, api.MakeListTagsHandler(ucListTags))
   119         -	webSrv.AddListRoute('v', http.MethodPost, api.MakePostLoginHandler(ucAuthenticate))
   120         -	webSrv.AddListRoute('v', http.MethodPut, api.MakeRenewAuthHandler())
   121         -	webSrv.AddZettelRoute('x', http.MethodGet, api.MakeZettelContextHandler(ucZettelContext))
   122         -	webSrv.AddListRoute('z', http.MethodGet, api.MakeListMetaHandler(
   123         -		usecase.NewListMeta(protectedBoxManager), ucGetMeta, ucParseZettel))
   124         -	webSrv.AddZettelRoute('z', http.MethodGet, api.MakeGetZettelHandler(
   125         -		ucParseZettel, ucGetMeta))
          114  +	webSrv.AddListRoute('a', server.MethodPost, a.MakePostLoginHandler(ucAuthenticate))
          115  +	webSrv.AddListRoute('a', server.MethodPut, a.MakeRenewAuthHandler())
          116  +	webSrv.AddListRoute('j', server.MethodGet, api.MakeListMetaHandler(ucListMeta))
          117  +	webSrv.AddZettelRoute('j', server.MethodGet, api.MakeGetZettelHandler(ucGetZettel))
          118  +	webSrv.AddZettelRoute('l', server.MethodGet, api.MakeGetLinksHandler(ucEvaluate))
          119  +	webSrv.AddZettelRoute('o', server.MethodGet, api.MakeGetOrderHandler(
          120  +		usecase.NewZettelOrder(protectedBoxManager, ucEvaluate)))
          121  +	webSrv.AddZettelRoute('p', server.MethodGet, a.MakeGetParsedZettelHandler(ucParseZettel))
          122  +	webSrv.AddListRoute('r', server.MethodGet, api.MakeListRoleHandler(ucListRoles))
          123  +	webSrv.AddListRoute('t', server.MethodGet, api.MakeListTagsHandler(ucListTags))
          124  +	webSrv.AddZettelRoute('v', server.MethodGet, a.MakeGetEvalZettelHandler(ucEvaluate))
          125  +	webSrv.AddZettelRoute('x', server.MethodGet, api.MakeZettelContextHandler(ucZettelContext))
          126  +	webSrv.AddListRoute('z', server.MethodGet, a.MakeListPlainHandler(ucListMeta))
          127  +	webSrv.AddZettelRoute('z', server.MethodGet, a.MakeGetPlainZettelHandler(ucGetZettel))
   126    128   	if !authManager.IsReadonly() {
   127         -		webSrv.AddListRoute('z', http.MethodPost, api.MakePostCreateZettelHandler(ucCreateZettel))
   128         -		webSrv.AddZettelRoute('z', http.MethodDelete, api.MakeDeleteZettelHandler(ucDelete))
   129         -		webSrv.AddZettelRoute('z', http.MethodPut, api.MakeUpdateZettelHandler(ucUpdate))
   130         -		webSrv.AddZettelRoute('z', zsapi.MethodMove, api.MakeRenameZettelHandler(ucRename))
          129  +		webSrv.AddListRoute('j', server.MethodPost, a.MakePostCreateZettelHandler(ucCreateZettel))
          130  +		webSrv.AddZettelRoute('j', server.MethodPut, api.MakeUpdateZettelHandler(ucUpdate))
          131  +		webSrv.AddZettelRoute('j', server.MethodDelete, api.MakeDeleteZettelHandler(ucDelete))
          132  +		webSrv.AddZettelRoute('j', server.MethodMove, api.MakeRenameZettelHandler(ucRename))
          133  +		webSrv.AddListRoute('z', server.MethodPost, a.MakePostCreatePlainZettelHandler(ucCreateZettel))
          134  +		webSrv.AddZettelRoute('z', server.MethodPut, api.MakeUpdatePlainZettelHandler(ucUpdate))
          135  +		webSrv.AddZettelRoute('z', server.MethodDelete, api.MakeDeleteZettelHandler(ucDelete))
          136  +		webSrv.AddZettelRoute('z', server.MethodMove, api.MakeRenameZettelHandler(ucRename))
   131    137   	}
   132    138   
   133    139   	if authManager.WithAuth() {
   134    140   		webSrv.SetUserRetriever(usecase.NewGetUserByZid(boxManager))
   135    141   	}
   136    142   }

Changes to cmd/fd_limit.go.

     4      4   // This file is part of zettelstore.
     5      5   //
     6      6   // Zettelstore is licensed under the latest version of the EUPL (European Union
     7      7   // Public License). Please see file LICENSE.txt for your rights and obligations
     8      8   // under this license.
     9      9   //-----------------------------------------------------------------------------
    10     10   
           11  +//go:build !darwin
    11     12   // +build !darwin
    12     13   
    13     14   package cmd
    14     15   
    15     16   func raiseFdLimit() error { return nil }

Changes to cmd/fd_limit_raise.go.

     4      4   // This file is part of zettelstore.
     5      5   //
     6      6   // Zettelstore is licensed under the latest version of the EUPL (European Union
     7      7   // Public License). Please see file LICENSE.txt for your rights and obligations
     8      8   // under this license.
     9      9   //-----------------------------------------------------------------------------
    10     10   
           11  +//go:build darwin
    11     12   // +build darwin
    12     13   
    13     14   package cmd
    14     15   
    15     16   import (
    16     17   	"log"
    17     18   	"syscall"

Changes to cmd/main.go.

    16     16   	"fmt"
    17     17   	"net"
    18     18   	"net/url"
    19     19   	"os"
    20     20   	"strconv"
    21     21   	"strings"
    22     22   
           23  +	"zettelstore.de/z/api"
    23     24   	"zettelstore.de/z/auth"
    24     25   	"zettelstore.de/z/auth/impl"
    25     26   	"zettelstore.de/z/box"
    26     27   	"zettelstore.de/z/box/compbox"
    27     28   	"zettelstore.de/z/box/manager"
    28     29   	"zettelstore.de/z/config"
    29     30   	"zettelstore.de/z/domain/id"
................................................................................
    68     69   		Header: true,
    69     70   		Flags:  flgSimpleRun,
    70     71   	})
    71     72   	RegisterCommand(Command{
    72     73   		Name: "file",
    73     74   		Func: cmdFile,
    74     75   		Flags: func(fs *flag.FlagSet) {
    75         -			fs.String("t", "html", "target output format")
           76  +			fs.String("t", api.EncoderHTML.String(), "target output encoding")
    76     77   		},
    77     78   	})
    78     79   	RegisterCommand(Command{
    79     80   		Name: "password",
    80     81   		Func: cmdPassword,
    81     82   	})
    82     83   }

Changes to cmd/register.go.

    14     14   // Mention all needed encoders, parsers and stores to have them registered.
    15     15   import (
    16     16   	_ "zettelstore.de/z/box/compbox"       // Allow to use computed box.
    17     17   	_ "zettelstore.de/z/box/constbox"      // Allow to use global internal box.
    18     18   	_ "zettelstore.de/z/box/dirbox"        // Allow to use directory box.
    19     19   	_ "zettelstore.de/z/box/filebox"       // Allow to use file box.
    20     20   	_ "zettelstore.de/z/box/membox"        // Allow to use in-memory box.
           21  +	_ "zettelstore.de/z/encoder/djsonenc"  // Allow to use DJSON encoder.
    21     22   	_ "zettelstore.de/z/encoder/htmlenc"   // Allow to use HTML encoder.
    22         -	_ "zettelstore.de/z/encoder/jsonenc"   // Allow to use JSON encoder.
    23     23   	_ "zettelstore.de/z/encoder/nativeenc" // Allow to use native encoder.
    24         -	_ "zettelstore.de/z/encoder/rawenc"    // Allow to use raw encoder.
    25     24   	_ "zettelstore.de/z/encoder/textenc"   // Allow to use text encoder.
    26     25   	_ "zettelstore.de/z/encoder/zmkenc"    // Allow to use zmk encoder.
    27     26   	_ "zettelstore.de/z/kernel/impl"       // Allow kernel implementation to create itself
    28     27   	_ "zettelstore.de/z/parser/blob"       // Allow to use BLOB parser.
    29     28   	_ "zettelstore.de/z/parser/markdown"   // Allow to use markdown parser.
    30     29   	_ "zettelstore.de/z/parser/none"       // Allow to use none parser.
    31     30   	_ "zettelstore.de/z/parser/plain"      // Allow to use plain parser.
    32     31   	_ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser.
    33     32   )

Changes to collect/collect.go.

    11     11   // Package collect provides functions to collect items from a syntax tree.
    12     12   package collect
    13     13   
    14     14   import "zettelstore.de/z/ast"
    15     15   
    16     16   // Summary stores the relevant parts of the syntax tree
    17     17   type Summary struct {
    18         -	Links  []*ast.Reference // list of all referenced links
    19         -	Images []*ast.Reference // list of all referenced images
           18  +	Links  []*ast.Reference // list of all linked material
           19  +	Embeds []*ast.Reference // list of all embedded material
    20     20   	Cites  []*ast.CiteNode  // list of all referenced citations
    21     21   }
    22     22   
    23     23   // References returns all references mentioned in the given zettel. This also
    24     24   // includes references to images.
    25     25   func References(zn *ast.ZettelNode) (s Summary) {
    26         -	ast.WalkBlockSlice(&s, zn.Ast)
           26  +	if zn.Ast != nil {
           27  +		ast.Walk(&s, zn.Ast)
           28  +	}
    27     29   	return s
    28     30   }
    29     31   
    30     32   // Visit all node to collect data for the summary.
    31     33   func (s *Summary) Visit(node ast.Node) ast.Visitor {
    32     34   	switch n := node.(type) {
    33     35   	case *ast.LinkNode:
    34     36   		s.Links = append(s.Links, n.Ref)
    35         -	case *ast.ImageNode:
    36         -		if n.Ref != nil {
    37         -			s.Images = append(s.Images, n.Ref)
           37  +	case *ast.EmbedNode:
           38  +		if m, ok := n.Material.(*ast.ReferenceMaterialNode); ok {
           39  +			s.Embeds = append(s.Embeds, m.Ref)
    38     40   		}
    39     41   	case *ast.CiteNode:
    40     42   		s.Cites = append(s.Cites, n)
    41     43   	}
    42     44   	return s
    43     45   }

Changes to collect/collect_test.go.

    26     26   	return r
    27     27   }
    28     28   
    29     29   func TestLinks(t *testing.T) {
    30     30   	t.Parallel()
    31     31   	zn := &ast.ZettelNode{}
    32     32   	summary := collect.References(zn)
    33         -	if summary.Links != nil || summary.Images != nil {
    34         -		t.Error("No links/images expected, but got:", summary.Links, "and", summary.Images)
           33  +	if summary.Links != nil || summary.Embeds != nil {
           34  +		t.Error("No links/images expected, but got:", summary.Links, "and", summary.Embeds)
    35     35   	}
    36     36   
    37     37   	intNode := &ast.LinkNode{Ref: parseRef("01234567890123")}
    38     38   	para := &ast.ParaNode{
    39         -		Inlines: ast.InlineSlice{
           39  +		Inlines: ast.CreateInlineListNode(
    40     40   			intNode,
    41     41   			&ast.LinkNode{Ref: parseRef("https://zettelstore.de/z")},
    42         -		},
           42  +		),
    43     43   	}
    44         -	zn.Ast = ast.BlockSlice{para}
           44  +	zn.Ast = &ast.BlockListNode{List: []ast.BlockNode{para}}
    45     45   	summary = collect.References(zn)
    46         -	if summary.Links == nil || summary.Images != nil {
    47         -		t.Error("Links expected, and no images, but got:", summary.Links, "and", summary.Images)
           46  +	if summary.Links == nil || summary.Embeds != nil {
           47  +		t.Error("Links expected, and no images, but got:", summary.Links, "and", summary.Embeds)
    48     48   	}
    49     49   
    50         -	para.Inlines = append(para.Inlines, intNode)
           50  +	para.Inlines.Append(intNode)
    51     51   	summary = collect.References(zn)
    52     52   	if cnt := len(summary.Links); cnt != 3 {
    53     53   		t.Error("Link count does not work. Expected: 3, got", summary.Links)
    54     54   	}
    55     55   }
    56     56   
    57         -func TestImage(t *testing.T) {
           57  +func TestEmbed(t *testing.T) {
    58     58   	t.Parallel()
    59     59   	zn := &ast.ZettelNode{
    60         -		Ast: ast.BlockSlice{
           60  +		Ast: &ast.BlockListNode{List: []ast.BlockNode{
    61     61   			&ast.ParaNode{
    62         -				Inlines: ast.InlineSlice{
    63         -					&ast.ImageNode{Ref: parseRef("12345678901234")},
    64         -				},
           62  +				Inlines: ast.CreateInlineListNode(
           63  +					&ast.EmbedNode{Material: &ast.ReferenceMaterialNode{Ref: parseRef("12345678901234")}},
           64  +				),
    65     65   			},
    66         -		},
           66  +		}},
    67     67   	}
    68     68   	summary := collect.References(zn)
    69         -	if summary.Images == nil {
    70         -		t.Error("Only image expected, but got: ", summary.Images)
           69  +	if summary.Embeds == nil {
           70  +		t.Error("Only image expected, but got: ", summary.Embeds)
    71     71   	}
    72     72   }

Changes to collect/order.go.

    11     11   // Package collect provides functions to collect items from a syntax tree.
    12     12   package collect
    13     13   
    14     14   import "zettelstore.de/z/ast"
    15     15   
    16     16   // Order of internal reference within the given zettel.
    17     17   func Order(zn *ast.ZettelNode) (result []*ast.Reference) {
    18         -	for _, bn := range zn.Ast {
    19         -		if ln, ok := bn.(*ast.NestedListNode); ok {
    20         -			switch ln.Kind {
    21         -			case ast.NestedListOrdered, ast.NestedListUnordered:
    22         -				for _, is := range ln.Items {
    23         -					if ref := firstItemZettelReference(is); ref != nil {
    24         -						result = append(result, ref)
    25         -					}
           18  +	if zn.Ast == nil {
           19  +		return nil
           20  +	}
           21  +	for _, bn := range zn.Ast.List {
           22  +		ln, ok := bn.(*ast.NestedListNode)
           23  +		if !ok {
           24  +			continue
           25  +		}
           26  +		switch ln.Kind {
           27  +		case ast.NestedListOrdered, ast.NestedListUnordered:
           28  +			for _, is := range ln.Items {
           29  +				if ref := firstItemZettelReference(is); ref != nil {
           30  +					result = append(result, ref)
    26     31   				}
    27     32   			}
    28     33   		}
    29     34   	}
    30     35   	return result
    31     36   }
    32     37   
................................................................................
    37     42   				return ref
    38     43   			}
    39     44   		}
    40     45   	}
    41     46   	return nil
    42     47   }
    43     48   
    44         -func firstInlineZettelReference(ins ast.InlineSlice) (result *ast.Reference) {
    45         -	for _, inl := range ins {
           49  +func firstInlineZettelReference(iln *ast.InlineListNode) (result *ast.Reference) {
           50  +	if iln == nil {
           51  +		return nil
           52  +	}
           53  +	for _, inl := range iln.List {
    46     54   		switch in := inl.(type) {
    47     55   		case *ast.LinkNode:
    48     56   			if ref := in.Ref; ref.IsZettel() {
    49     57   				return ref
    50     58   			}
    51     59   			result = firstInlineZettelReference(in.Inlines)
    52         -		case *ast.ImageNode:
           60  +		case *ast.EmbedNode:
    53     61   			result = firstInlineZettelReference(in.Inlines)
    54     62   		case *ast.CiteNode:
    55     63   			result = firstInlineZettelReference(in.Inlines)
    56     64   		case *ast.FootnoteNode:
    57     65   			// Ignore references in footnotes
    58     66   			continue
    59     67   		case *ast.FormatNode:

Changes to config/config.go.

    39     39   	GetSiteName() string
    40     40   
    41     41   	// GetHomeZettel returns the value of the "home-zettel" key.
    42     42   	GetHomeZettel() id.Zid
    43     43   
    44     44   	// GetDefaultVisibility returns the default value for zettel visibility.
    45     45   	GetDefaultVisibility() meta.Visibility
           46  +
           47  +	// GetMaxTransclusions return the maximum number of indirect transclusions.
           48  +	GetMaxTransclusions() int
    46     49   
    47     50   	// GetYAMLHeader returns the current value of the "yaml-header" key.
    48     51   	GetYAMLHeader() bool
    49     52   
    50     53   	// GetZettelFileSyntax returns the current value of the "zettel-file-syntax" key.
    51     54   	GetZettelFileSyntax() []string
    52     55   
................................................................................
    69     72   
    70     73   // GetTitle returns the value of the "title" key of the given meta. If there
    71     74   // is no such value, GetDefaultTitle is returned.
    72     75   func GetTitle(m *meta.Meta, cfg Config) string {
    73     76   	if val, ok := m.Get(meta.KeyTitle); ok {
    74     77   		return val
    75     78   	}
    76         -	return cfg.GetDefaultTitle()
           79  +	if cfg != nil {
           80  +		return cfg.GetDefaultTitle()
           81  +	}
           82  +	return "Untitled"
    77     83   }
    78     84   
    79     85   // GetRole returns the value of the "role" key of the given meta. If there
    80     86   // is no such value, GetDefaultRole is returned.
    81     87   func GetRole(m *meta.Meta, cfg Config) string {
    82     88   	if val, ok := m.Get(meta.KeyRole); ok {
    83     89   		return val

Added docs/development/00010000000000.zettel.

            1  +id: 00010000000000
            2  +title: Developments Notes
            3  +role: zettel
            4  +syntax: zmk
            5  +modified: 20210916194954
            6  +
            7  +* [[Required Software|20210916193200]]
            8  +* [[Checklist for Release|20210916194900]]

Added docs/development/20210916193200.zettel.

            1  +id: 20210916193200
            2  +title: Required Software
            3  +role: zettel
            4  +syntax: zmk
            5  +modified: 20210916194748
            6  +
            7  +The following software must be installed:
            8  +
            9  +* A current, supported [[release of Go|https://golang.org/doc/devel/release.html]],
           10  +* [[golint|https://github.com/golang/lint]],
           11  +* [[shadow|https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow]] via ``go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest``,
           12  +* [[staticcheck|https://staticcheck.io/]] via ``go get honnef.co/go/tools/cmd/staticcheck``,
           13  +* [[unparam|mvdan.cc/unparam]][^[[GitHub|https://github.com/mvdan/unparam]]] via ``go install mvdan.cc/unparam@latest``
           14  +
           15  +Make sure that the software is in your path, e.g. via:
           16  +
           17  +```sh
           18  +export PATH=$PATH:/usr/local/go/bin
           19  +export PATH=$PATH:$(go env GOPATH)/bin
           20  +```

Added docs/development/20210916194900.zettel.

            1  +id: 20210916194900
            2  +title: Checklist for Release
            3  +role: zettel
            4  +syntax: zmk
            5  +modified: 20210917165448
            6  +
            7  +# Check for unused parameters:
            8  +#* ``unparam ./...``
            9  +#* ``unparam -exported -tests ./...``
           10  +# Clean up your Go workspace:
           11  +#* ``go run tools/build.go clean`` (alternatively: ``make clean``).
           12  +# All internal tests must succeed:
           13  +#* ``go run tools/build.go check`` (alternatively: ``make check``).
           14  +# The API tests must succeed on every development platform:
           15  +#* ``go run tools/build.go testapi`` (alternatively: ``make api``).
           16  +# Run [[linkchecker|https://linkchecker.github.io/linkchecker/]] with the manual:
           17  +#* ``go run -race cmd/zettelstore/main.go run -d docs/manual``
           18  +#* ``linkchecker  http://127.0.0.1:23123 2>&1 | tee lc.txt``
           19  +#* Check all ""Error: 404 Not Found""
           20  +#* Check all ""Error: 403 Forbidden"": allowed for endpoint ''/p'' with encoding ''html'' for those zettel that are accessible only in ''expert-mode''.
           21  +#* Try to resolve other error messages and warnings
           22  +#* Warnings about empty content can be ignored
           23  +# On every development platform, the box with 10.000 zettel must run, with ''-race'' enabled:
           24  +#* ``go run -race cmd/zettelstore/main.go run -d DIR``.
           25  +# Create a development release:
           26  +#* ``go run tools/build.go release`` (alternatively: ``make release``).
           27  +# On every platform (esp. macOS), the box with 10.000 zettel must run properly:
           28  +#* ``./zettelstore -d DIR``
           29  +# Update files in directory ''www''
           30  +#* index.wiki
           31  +#* download.wiki
           32  +#* changes.wiki
           33  +#* plan.wiki
           34  +# Set file ''VERSION'' to the new release version
           35  +# Disable Fossil autosync mode:
           36  +#* ``fossil setting autosync off``
           37  +# Commit the new release version:
           38  +#* ``fossil commit --tag release --tag version-VERSION -m "Version VERSION"``
           39  +# Clean up your Go workspace:
           40  +#* ``go run tools/build.go clean`` (alternatively: ``make clean``).
           41  +# Create the release:
           42  +#* ``go run tools/build.go release`` (alternatively: ``make release``).
           43  +# Remove previous executables:
           44  +#* ``fossil uv remove --glob '*-PREVVERSION*'``
           45  +# Add executables for release:
           46  +#* ``cd release``
           47  +#* ``fossil uv add *.zip``
           48  +#* ``cd ..``
           49  +#* Synchronize with main repository:
           50  +#* ``fossil sync -u``
           51  +# Enable autosync:
           52  +#* ``fossil setting autosync on``

Changes to docs/manual/00001004020000.zettel.

     1      1   id: 00001004020000
     2      2   title: Configure the running Zettelstore
     3      3   role: manual
     4      4   tags: #configuration #manual #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210611213730
            6  +modified: 20210810103936
     7      7   
     8      8   You can configure a running Zettelstore by modifying the special zettel with the ID [[00000000000100]].
     9      9   This zettel is called ""configuration zettel"".
    10     10   The following metadata keys change the appearance / behavior of Zettelstore:
    11     11   
    12     12   ; [!default-copyright]''default-copyright''
    13     13   : Copyright value to be used when rendering content.
................................................................................
    51     51     Default: (the empty string).
    52     52   ; [!home-zettel]''home-zettel''
    53     53   : Specifies the identifier of the zettel, that should be presented for the default view / home view.
    54     54     If not given or if the identifier does not identify a zettel, the zettel with the identifier ''00010000000000'' is shown.
    55     55   ; [!marker-external]''marker-external''
    56     56   : Some HTML code that is displayed after a reference to external material.
    57     57     Default: ''&\#10138;'', to display a ""&#10138;"" sign.
           58  +; [!max-transclusions]''max-transclusions''
           59  +: Maximum number of indirect transclusion.
           60  +  This is used to avoid an exploding ""transclusion bomb"", a form of a [[billion laughs attack|https://en.wikipedia.org/wiki/Billion_laughs_attack]].
           61  +  Default: 1024.
    58     62   ; [!site-name]''site-name''
    59     63   : Name of the Zettelstore instance.
    60     64     Will be used when displaying some lists.
    61     65     Default: ''Zettelstore''.
    62     66   ; [!yaml-header]''yaml-header''
    63     67   : If true, metadata and content will be separated by ''-\--\\n'' instead of an empty line (''\\n\\n'').
    64     68     Default: ''false''.

Changes to docs/manual/00001004051200.zettel.

     1      1   id: 00001004051200
     2      2   title: The ''file'' sub-command
     3      3   role: manual
     4      4   tags: #command #configuration #manual #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210712234222
            6  +modified: 20210727120507
     7      7   
     8      8   Reads zettel data from a file (or from standard input / stdin) and renders it to standard output / stdout.
     9      9   This allows Zettelstore to render files manually.
    10     10   ```
    11     11   zettelstore file [-t FORMAT] [file-1 [file-2]]
    12     12   ```
    13     13   
    14     14   ; ''-t FORMAT''
    15     15   : Specifies the output format.
    16     16     Supported values are:
    17     17     [[''html''|00001012920510]] (default),
    18     18     [[''djson''|00001012920503]],
    19         -  [[''json''|00001012920501]],
    20     19     [[''native''|00001012920513]],
    21         -  [[''raw''|00001012920516]],
    22     20     [[''text''|00001012920519]],
    23     21     and [[''zmk''|00001012920522]].
    24     22   ; ''file-1''
    25     23   : Specifies the file name, where at least metadata is read.
    26     24     If ''file-2'' is not given, the zettel content is also read from here.
    27     25   ; ''file-2''
    28     26   : File name where the zettel content is stored.
    29     27   
    30     28   If neither ''file-1'' nor ''file-2'' are given, metadata and zettel content are read from standard input / stdin.

Changes to docs/manual/00001006000000.zettel.

     1      1   id: 00001006000000
     2      2   title: Layout of a Zettel
            3  +role: manual
     3      4   tags: #design #manual #zettelstore
     4      5   syntax: zmk
     5         -role: manual
            6  +modified: 20210903210655
     6      7   
     7         -A zettel consists of two part: the metadata and the zettel content.
            8  +A zettel consists of two parts: the metadata and the zettel content.
     8      9   Metadata gives some information mostly about the zettel content, how it should be interpreted, how it is sorted within Zettelstore.
     9     10   The zettel content is, well, the actual content.
    10     11   In many cases, the content is in plain text form.
    11     12   Plain text is long-lasting.
    12     13   However, content in binary format is also possible.
    13     14   
    14     15   Metadata has to conform to a [[special syntax|00001006010000]].
................................................................................
    21     22   The zettel content is your valuable content.
    22     23   Zettelstore contains some predefined parsers that interpret the zettel content to the syntax of the zettel.
    23     24   This includes markup languages, like [[Zettelmarkup|00001007000000]] and [[CommonMark|https://commonmark.org/]].
    24     25   Other text formats are also supported, like CSS and HTML templates.
    25     26   Plain text content is always Unicode, encoded as UTF-8.
    26     27   Other character encodings are not supported and will never be[^This is not a real problem, since every modern software should support UTF-8 as an encoding.].
    27     28   There is support for a graphical format with a text represenation: SVG.
    28         -And the is support for some binary image formats, like GIF, PNG, and JPEG.
           29  +And there is support for some binary image formats, like GIF, PNG, and JPEG.

Changes to docs/manual/00001006020000.zettel.

     1      1   id: 00001006020000
     2      2   title: Supported Metadata Keys
     3      3   role: manual
     4      4   tags: #manual #meta #reference #zettel #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210709162756
            6  +modified: 20210822234119
     7      7   
     8      8   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.
     9      9   See the [[computed list of supported metadata keys|00000000000090]] for details.
    10     10   
    11     11   Most keys conform to a [[type|00001006030000]].
    12     12   
           13  +; [!all-tags]''all-tags''
           14  +: A property (a computed values that is not stored) that contains both the value of [[''tags''|#tags]] together with all [[tags|00001007040000#tag]] that are specified within the content.
    13     15   ; [!back]''back''
    14     16   : Is a property that contains the identifier of all zettel that reference the zettel of this metadata, that are not referenced by this zettel.
    15     17     Basically, it is the value of [[''backward''|#bachward]], but without any zettel identifier that is contained in [[''forward''|#forward]].
    16     18   ; [!backward]''backward''
    17     19   : Is a property that contains the identifier of all zettel that reference the zettel of this metadata.
    18     20     References within inversable values are not included here, e.g. [[''precursor''|#precursor]].
    19     21   ; [!copyright]''copyright''

Changes to docs/manual/00001006020100.zettel.

     1      1   id: 00001006020100
     2      2   title: Supported Zettel Roles
     3      3   role: manual
     4      4   tags: #manual #meta #reference #zettel #zettelstore
     5      5   syntax: zmk
            6  +modified: 20210727120817
     6      7   
     7      8   The [[''role'' key|00001006020000#role]] defines what kind of zettel you are writing.
     8      9   The following values are used internally by Zettelstore and must exist:
     9     10   
    10     11   ; [!user]''user''
    11     12   : If you want to use [[authentication|00001010000000]], all zettel that identify users of the zettel store must have this role.
    12     13   
................................................................................
    31     32     Think of them as Post-it notes.
    32     33   ; [!literature]''literature''
    33     34   : Contains some remarks about a book, a paper, a web page, etc.
    34     35     You should add a citation key for citing it.
    35     36   ; [!zettel]''zettel''
    36     37   : A real zettel that contains your own thoughts.
    37     38   
    38         -However, you are free to define additional roles, e.g. ''material'' for literature that is web-based only, ''slide'' for presentation slides, ''paper'' for the raw text of a scientific paper, ''project'' to define a project, ...
           39  +However, you are free to define additional roles, e.g. ''material'' for literature that is web-based only, ''slide'' for presentation slides, ''paper'' for the text of a scientific paper, ''project'' to define a project, ...

Changes to docs/manual/00001006030000.zettel.

     1      1   id: 00001006030000
     2      2   title: Supported Key Types
     3      3   role: manual
     4      4   tags: #manual #meta #reference #zettel #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210627170437
            6  +modified: 20210830132855
     7      7   
     8      8   All [[supported metadata keys|00001006020000]] conform to a type.
     9      9   
    10     10   User-defined metadata keys conform also to a type, based on the suffix of the key.
    11     11   |=Suffix|Type
    12     12   | ''-number'' | [[Number|00001006033000]]
           13  +| ''-role'' | [[Word|00001006035500]]
    13     14   | ''-url'' | [[URL|00001006035000]]
    14     15   | ''-zid''  | [[Identifier|00001006032000]]
    15     16   | any other suffix | [[EString|00001006031500]]
    16     17   
    17     18   The name of the metadata key is bound to the key type
    18     19   
    19     20   Every key type has an associated validation rule to check values of the given type.

Changes to docs/manual/00001006034000.zettel.

     1      1   id: 00001006034000
     2      2   title: TagSet Key Type
     3      3   role: manual
     4      4   tags: #manual #meta #reference #zettel #zettelstore
     5      5   syntax: zmk
            6  +modified: 20210817201410
     6      7   
     7      8   Values of this type denote a (sorted) set of tags.
     8      9   
     9     10   A set is different to a list, as no duplicates are allowed.
    10     11   
    11     12   === Allowed values
    12     13   Every tag must must begin with the number sign character (""''#''"", ''U+0023''), followed by at least one printable character.
    13     14   Tags are separated by space characters.
    14     15   
           16  +All characters are mapped to their lower case values.
           17  +
    15     18   === Match operator
    16     19   It depends of the first character of a search string how it is matched against a tag set value:
    17     20   
    18     21   * If the first character of the search string is a number sign character,
    19     22     it must exactly match one of the values of a tag.
    20     23   * In other cases, the search string must be the prefix of at least one tag.
    21     24   
    22         -Conpectually, all number sign characters are removed at the beginning of the search string
    23         -and of all tags.
           25  +Conceptually, all number sign characters are removed at the beginning of the search string and of all tags.
    24     26   
    25     27   === Sorting
    26     28   Sorting is done by comparing the [[String|00001006033500]] values.

Changes to docs/manual/00001006035500.zettel.

     1      1   id: 00001006035500
     2      2   title: Word Key Type
     3      3   role: manual
     4      4   tags: #manual #meta #reference #zettel #zettelstore
     5      5   syntax: zmk
            6  +modified: 20210817201304
     6      7   
     7      8   Values of this type denote a single word.
     8      9   
     9     10   === Allowed values
    10     11   Must be a non-empty sequence of characters, but without the space character.
    11     12   
           13  +All characters are mapped to their lower case values.
           14  +
    12     15   === Match operator
    13         -A value matches a word value, if both value are character-wise equal.
           16  +A value matches a word value, if both value are character-wise equal, ignoring upper / lower case.
    14     17   
    15     18   === Sorting
    16     19   Sorting is done by comparing the [[String|00001006033500]] values.

Changes to docs/manual/00001006055000.zettel.

     1      1   id: 00001006055000
     2      2   title: Reserved zettel identifier
     3      3   role: manual
     4      4   tags: #design #manual #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210721125518
            6  +modified: 20210917154229
     7      7   
     8      8   [[Zettel identifier|00001006050000]] are typically created by examine the current date and time.
     9      9   By renaming a zettel, you are able to provide any sequence of 14 digits.
    10     10   If no other zettel has the same identifier, you are allowed to rename a zettel.
    11     11   
    12     12   To make things easier, you normally should not use zettel identifier that begin with four zeroes (''0000'').
    13     13   
................................................................................
    36     36   | 00000200000000 | 0000899999999 | Reserved for future use
    37     37   | 00009000000000 | 0000999999999 | Reserved for applications
    38     38   
    39     39   This list may change in the future.
    40     40   
    41     41   ==== External Applications
    42     42   |= From | To | Description
    43         -| 00009000001000 | 00009000001000 | ZS Slides, an application to display zettel as a HTML-based slideshow
           43  +| 00009000001000 | 00009000001999 | ZS Slides, an application to display zettel as a HTML-based slideshow

Changes to docs/manual/00001007040000.zettel.

     1      1   id: 00001007040000
     2      2   title: Zettelmarkup: Inline-Structured Elements
            3  +role: manual
     3      4   tags: #manual #zettelmarkup #zettelstore
     4      5   syntax: zmk
     5         -role: manual
            6  +modified: 20210822234205
     6      7   
     7      8   Most characters you type is concerned with inline-structured elements.
     8      9   The content of a zettel contains is many cases just ordinary text, lightly formatted.
     9     10   Inline-structured elements allow to format your text and add some helpful links or images.
    10     11   Sometimes, you want to enter characters that have no representation on your keyboard.
    11     12   
    12         -=== Text formatting
    13         -Every [[text formatting|00001007040100]] element begins with two same characters at the beginning.
    14         -It lasts until the same two characters occurred the second time.
    15         -Some of these elements explicitly support [[attributes|00001007050000]].
           13  +; Text formatting
           14  +: Every [[text formatting|00001007040100]] element begins with two same characters at the beginning.
           15  +  It lasts until the same two characters occurred the second time.
           16  +  Some of these elements explicitly support [[attributes|00001007050000]].
    16     17   
    17         -=== Literal-like formatting
    18         -Sometime you want to render the text as it is.
    19         -This is the core motivation of [[literal-like formatting|00001007040200]].
           18  +; Literal-like formatting
           19  +: Sometime you want to render the text as it is.
           20  +: This is the core motivation of [[literal-like formatting|00001007040200]].
    20     21   
    21         -=== Reference-like text
    22         -You can reference other zettel and (external) material within one zettel.
    23         -This kind of reference may be a link, or an images that is display inline when the zettel is rendered.
    24         -Footnotes sometimes factor out some useful text that hinders the flow of reading text.
    25         -Internal marks allow to reference something within a zettel.
    26         -An important aspect of all knowledge work is to reference others work, e.g. with citation keys.
    27         -All these elements can be subsumed under [[reference-like text|00001007040300]].
           22  +; Reference-like text
           23  +: You can reference other zettel and (external) material within one zettel.
           24  +  This kind of reference may be a link, or an images that is display inline when the zettel is rendered.
           25  +  Footnotes sometimes factor out some useful text that hinders the flow of reading text.
           26  +  Internal marks allow to reference something within a zettel.
           27  +  An important aspect of all knowledge work is to reference others work, e.g. with citation keys.
           28  +  All these elements can be subsumed under [[reference-like text|00001007040300]].
    28     29   
    29     30   === Other inline elements
    30     31   ==== Comments
    31     32   A comment begins with two consecutive percent sign characters (""''%''"", ''U+0025'').
    32     33   It ends at the end of the line where it begins.
    33     34   
    34     35   ==== Backslash
................................................................................
    36     37   * If a space character follows, it is converted in a non-breaking space (''U+00A0'').
    37     38   * If a line ending follows the backslash character, the line break is converted from a //soft break// into a //hard break//.
    38     39   * Every other character is taken as itself, but without the interpretation of a Zettelmarkup element.
    39     40     For example, if you want to enter a ""'']''"" into a footnote text, you should escape it with a backslash.
    40     41   
    41     42   ==== Tag
    42     43   Any text that begins with a number sign character (""''#''"", ''U+0023''), followed by a non-empty sequence of Unicode letters, Unicode digits, the hyphen-minus character (""''-''"", ''U+002D''), or the low line character (""''_''"", ''U+005F'') is interpreted as an //inline tag//.
    43         -They will be considered equivalent to tags in metadata.
           44  +They are be considered equivalent to tags in metadata.
    44     45   
    45     46   ==== Entities & more
    46     47   Sometimes it is not easy to enter special characters.
    47     48   If you know the Unicode code point of that character, or its name according to the [[HTML standard|https://html.spec.whatwg.org/multipage/named-characters.html]], you can enter it by number or by name.
    48     49   
    49     50   Regardless which method you use, an entity always begins with an ampersand character (""''&''"", ''U+0026'') and ends with a semicolon character (""'';''"", ''U+003B'').
    50     51   If you know the HTML name of the character you want to enter, put it between these two character.

Changes to docs/manual/00001007040300.zettel.

     1      1   id: 00001007040300
     2      2   title: Zettelmarkup: Reference-like text
     3      3   role: manual
     4      4   tags: #manual #zettelmarkup #zettelstore
     5      5   syntax: zmk
            6  +modified: 20210810172531
     6      7   
     7      8   An important aspect of knowledge work is to interconnect your zettel as well as provide links to (external) material.
     8      9   
     9     10   There are several kinds of references that are allowed in Zettelmarkup:
    10         -* Links to other zettel.
    11         -* Links to (external material).
    12         -* Embed images that are stored within your Zettelstore.
    13         -* Embed external images.
    14         -* Reference via a footnote.
    15         -* Reference via a citation key.
    16         -* Put a mark within your zettel that you can reference later with a link.
    17         -
    18         -=== Links
    19         -There are two kinds of links, regardless of links to (internal) other zettel or to (external) material.
    20         -Both kinds begin with two consecutive left square bracket characters (""''[''"", ''U+005B'') and ends with two consecutive right square bracket characters (""'']''"", ''U+005D'').
    21         -
    22         -The first form provides some text plus the link specification, delimited by a vertical bar character (""''|''"", ''U+007C''): ``[[text|linkspecification]]``.
    23         -
    24         -The second form just provides a link specification between the square brackets.
    25         -Its text is derived from the link specification, e.g. by interpreting the link specification as text: ``[[linkspecification]]``.
    26         -
    27         -The link specification for another zettel within the same Zettelstore is just the [[zettel identifier|00001006050000]].
    28         -To reference some content within a zettel, you can append a number sign character (""''#''"", ''U+0023'') and the name of the mark to the zettel identifier.
    29         -The resulting reference is called ""zettel reference"".
    30         -
    31         -To specify some material outside the Zettelstore, just use an normal Uniform Resource Identifier (URI) as defined by [[RFC\ 3986|https://tools.ietf.org/html/rfc3986]].
    32         -If the URL begins with the slash character (""/"", ''U+002F''), or if it begins with ""./"" or with ""../"", i.e. without scheme, user info, and host name, the reference will be treated as a ""local reference"", otherwise as an ""external reference"".
    33         -If the URL begins with two slash characters, it will be interpreted relative to the value of [[''url-prefix''|00001004010000#url-prefix]].
    34         -
    35         -The text in the second form is just a sequence of inline elements.
    36         -
    37         -=== Images
    38         -To some degree, an image specification is conceptually not too far away from a link specification.
    39         -Both contain a link specification and optionally some text.
    40         -In contrast to a link, the link specification of an image must resolve to actual graphical image data.
    41         -That data is read when rendered as HTML, and is embedded inside the zettel as an inline image.
    42         -
    43         -An image specification begins with two consecutive left curly bracket characters (""''{''"", ''U+007B'') and ends with two consecutive right curly bracket characters (""''}''"", ''U+007D'').
    44         -The curly brackets delimits either a link specification or some text, a vertical bar character and the link specification, similar to a link.
    45         -
    46         -One difference to a link: if the text was not given, an empty string is assumed.
    47         -
    48         -The link specification must reference a graphical image representation if the image is about to be rendered.
    49         -Supported formats are:
    50         -
    51         -* Portable Network Graphics (""PNG""), as defined by [[RFC\ 2083|https://tools.ietf.org/html/rfc2083]].
    52         -* Graphics Interchange Format (""GIF"), as defined by [[https://www.w3.org/Graphics/GIF/spec-gif89a.txt]].
    53         -* JPEG / JPG, defined by the //Joint Photographic Experts Group//.
    54         -* Scalable Vector Graphics (SVG), defined by [[https://www.w3.org/Graphics/SVG/]]
    55         -
    56         -If the text is given, it will be interpreted as an alternative textual representation, to help persons with some visual disabilities.
    57         -
    58         -[[Attributes|00001007050000]] are supported.
    59         -They must follow the last right curly bracket character immediately.
    60         -One prominent example is to specify an explicit title attribute that is shown on certain web browsers when the zettel is rendered in HTML:
    61         -
    62         -Examples:
    63         -* ``{{Spinning Emoji|00000000040001}}{title=Emoji width=30}`` is rendered as ::{{Spinning Emoji|00000000040001}}{title=Emoji width=30}::{=example}.
    64         -* The above image is also a placeholder for a non-existent image:
    65         -** ``{{00000000000000}}`` will be rendered as ::{{00000000000000}}::{=example}.
    66         -** ``{{00000000009999}}`` will be rendered as ::{{00000000009999}}::{=example}.
    67         -
    68         -=== Footnotes
    69         -
    70         -A footnote begins with a left square bracket, followed by a circumflex accent (""''^''"", ''U+005E''), followed by some text, and ends with a right square bracket.
    71         -
    72         -Example:
    73         -
    74         -``Main text[^Footnote text.].`` is rendered in HTML as: ::Main text[^Footnote text.].::{=example}.
    75         -
    76         -=== Citation key
    77         -
    78         -A citation key references some external material that is part of a bibliografical collection.
    79         -
    80         -Currently, Zettelstore implements this only partially, it is ""work in progress"".
    81         -
    82         -However, the syntax is: beginning with a left square bracket and followed by an at sign character (""''@''"", ''U+0040''), a the citation key is given.
    83         -The key is typically a sequence of letters and digits.
    84         -If a comma character (""'',''"", ''U+002C'') or a vertical bar character is given, the following is interpreted as inline elements.
    85         -A right square bracket ends the text and the citation key element.
    86         -
    87         -=== Mark
    88         -A mark allows to name a point within a zettel.
    89         -This is useful if you want to reference some content in a bigger-sized zettel[^Other uses of marks will be given, if Zettelmarkup is extended by a concept called //transclusion//.].
    90         -
    91         -A mark begins with a left square bracket, followed by an exclamation mark character (""''!''"", ''U+0021'').
    92         -Now the optional mark name follows.
    93         -It is a (possibly empty) sequence of Unicode letters, Unicode digits, the hyphen-minus character (""''-''"", ''U+002D''), or the low-line character (""''_''"", ''U+005F'').
    94         -The mark element ends with a right square bracket.
    95         -
    96         -Examples:
    97         -* ``[!]`` is a mark without a name, the empty mark.
    98         -* ``[!mark]`` is a mark with the name ""mark"".
           11  +* [[Links to other zettel or to (external) material|00001007040310]]
           12  +* [[Embedded zettel or (external) material|00001007040320]] (""inline transclusion"")
           13  +* [[Reference via a footnote|00001007040330]]
           14  +* [[Reference via a citation key|00001007040340]]
           15  +* Put a [[mark|00001007040350]] within your zettel that you can reference later with a link.

Added docs/manual/00001007040310.zettel.

            1  +id: 00001007040310
            2  +title: Zettelmarkup: Links
            3  +role: manual
            4  +tags: #manual #zettelmarkup #zettelstore
            5  +syntax: zmk
            6  +
            7  +There are two kinds of links, regardless of links to (internal) other zettel or to (external) material.
            8  +Both kinds begin with two consecutive left square bracket characters (""''[''"", ''U+005B'') and ends with two consecutive right square bracket characters (""'']''"", ''U+005D'').
            9  +
           10  +The first form provides some text plus the link specification, delimited by a vertical bar character (""''|''"", ''U+007C''): ``[[text|linkspecification]]``.
           11  +
           12  +The second form just provides a link specification between the square brackets.
           13  +Its text is derived from the link specification, e.g. by interpreting the link specification as text: ``[[linkspecification]]``.
           14  +
           15  +The link specification for another zettel within the same Zettelstore is just the [[zettel identifier|00001006050000]].
           16  +To reference some content within a zettel, you can append a number sign character (""''#''"", ''U+0023'') and the name of the mark to the zettel identifier.
           17  +The resulting reference is called ""zettel reference"".
           18  +
           19  +To specify some material outside the Zettelstore, just use an normal Uniform Resource Identifier (URI) as defined by [[RFC\ 3986|https://tools.ietf.org/html/rfc3986]].
           20  +If the URL begins with the slash character (""/"", ''U+002F''), or if it begins with ""./"" or with ""../"", i.e. without scheme, user info, and host name, the reference will be treated as a ""local reference"", otherwise as an ""external reference"".
           21  +If the URL begins with two slash characters, it will be interpreted relative to the value of [[''url-prefix''|00001004010000#url-prefix]].
           22  +
           23  +The text in the second form is just a sequence of inline elements.

Added docs/manual/00001007040320.zettel.

            1  +id: 00001007040320
            2  +title: Zettelmarkup: Inline Embedding / Transclusion
            3  +role: manual
            4  +tags: #manual #zettelmarkup #zettelstore
            5  +syntax: zmk
            6  +modified: 20210811162201
            7  +
            8  +To some degree, an specification for embedded material is conceptually not too far away from a specification for [[linked material|00001007040310]].
            9  +Both contain a reference specification and optionally some text.
           10  +In contrast to a link, the specification of embedded material must currently resolve to some kind of real content.
           11  +This content replaces the embed specification.
           12  +
           13  +An image specification begins with two consecutive left curly bracket characters (""''{''"", ''U+007B'') and ends with two consecutive right curly bracket characters (""''}''"", ''U+007D'').
           14  +The curly brackets delimits either a reference specification or some text, a vertical bar character and the link specification, similar to a link.
           15  +
           16  +One difference to a link: if the text was not given, an empty string is assumed.
           17  +
           18  +The reference must point to some content, either zettel content or URL-referenced content.
           19  +If the referenced zettel does not exist, or is not readable, a spinning emoji is presented as a visual hint:
           20  + 
           21  +Example: ``{{00000000000000}}`` will be rendered as ::{{00000000000000}}::{=example}.
           22  +
           23  +There are two kind of content:
           24  +# [[image content|00001007040322]],
           25  +# [[textual content|00001007040324]].

Added docs/manual/00001007040322.zettel.

            1  +id: 00001007040322
            2  +title: Zettelmarkup: Image Embedding
            3  +role: manual
            4  +tags: #manual #zettelmarkup #zettelstore
            5  +syntax: zmk
            6  +modified: 20210811165719
            7  +
            8  +Image content is assumed, if an URL is used or if the referenced zettel contains an image.
            9  +
           10  +Supported formats are:
           11  +
           12  +* Portable Network Graphics (""PNG""), as defined by [[RFC\ 2083|https://tools.ietf.org/html/rfc2083]].
           13  +* Graphics Interchange Format (""GIF"), as defined by [[https://www.w3.org/Graphics/GIF/spec-gif89a.txt]].
           14  +* JPEG / JPG, defined by the //Joint Photographic Experts Group//.
           15  +* Scalable Vector Graphics (SVG), defined by [[https://www.w3.org/Graphics/SVG/]]
           16  +
           17  +If the text is given, it will be interpreted as an alternative textual representation, to help persons with some visual disabilities.
           18  +
           19  +[[Attributes|00001007050000]] are supported.
           20  +They must follow the last right curly bracket character immediately.
           21  +One prominent example is to specify an explicit title attribute that is shown on certain web browsers when the zettel is rendered in HTML:
           22  +
           23  +Examples:
           24  +* [!spin] ``{{Spinning Emoji|00000000040001}}{title=Emoji width=30}`` is rendered as ::{{Spinning Emoji|00000000040001}}{title=Emoji width=30}::{=example}.
           25  +* The above image is also the placeholder for a non-existent zettel:
           26  +** ``{{00000000009999}}`` will be rendered as ::{{00000000009999}}::{=example}.

Added docs/manual/00001007040324.zettel.

            1  +id: 00001007040324
            2  +title: Zettelmarkup: Inline Transclusion of Text
            3  +role: manual
            4  +tags: #manual #zettelmarkup #zettelstore
            5  +syntax: zmk
            6  +modified: 20210811172440
            7  +
            8  +Inline transclusion applies to all zettel that are parsed in an non-trivial way.
            9  +For example, textual content is assumed if the [[syntax|00001006020000#syntax]] of a zettel is ''zmk'' ([[Zettelmarkup|00001007000000]]), or ''markdown'' / ''md'' ([[Markdown|00001008010000]]).
           10  +
           11  +Since the transclusion is at the level of [[inline-structured elements|00001007040000]], the transclude specification must be replaced with some inline-structured elements.
           12  +
           13  +First, the referenced zettel is read.
           14  +If it contains inline transclusions, these will be expanded, recursively.
           15  +When an endless recursion is detected, expansion does not take place.
           16  +Instead an error message replaces the transclude specification.
           17  +
           18  +The result of this (indirect) transclusion is searched for inline-structured elements.
           19  +
           20  +* If only the zettel identifier was specified, the first top-level [[paragraph|00001007030000#paragraphs]] is used.
           21  +  Since a paragraph is basically a sequence of inline-structured elements, these elements will replace the transclude specification.
           22  +
           23  +  Example: ``{{00010000000000}}`` (see [[00010000000000]]) is rendered as ::{{00010000000000}}::{=example}.
           24  +
           25  +* If a fragment identifier was additionally specified, the element with the given fragment is searched:
           26  +** If it specifies a [[heading|00001007030300]], the next top-level paragraph is used.
           27  +
           28  +   Example: ``{{00010000000000#reporting-errors}}`` is rendered as ::{{00010000000000#reporting-errors}}::{=example}.
           29  +
           30  +** In case the fragment names a [[mark|00001007040350]], the inline-structured elements after the mark are used.
           31  +   Initial spaces and line breaks are ignored in this case.
           32  +
           33  +   Example: ``{{00001007040322#spin}}`` is rendered as ::{{00001007040322#spin}}::{=example}.
           34  +
           35  +** Just specifying the fragment identifier will reference something in the current page.
           36  +   This is not allowed, to prevent a possible endless recursion.
           37  +
           38  +If no inline-structured elements are found, the transclude specification is replaced by an error message.
           39  +
           40  +To avoid an exploding ""transclusion bomb"", a form of a [[billion laughs attack|https://en.wikipedia.org/wiki/Billion_laughs_attack]] (also known as ""XML bomb""), the total number of transclusions / expansions is limited.
           41  +The limit can be controlled by setting the value [[''max-transclusions''|00001004020000#max-transclusions]] of the runtime configuration zettel.

Added docs/manual/00001007040330.zettel.

            1  +id: 00001007040330
            2  +title: Zettelmarkup: Footnotes
            3  +role: manual
            4  +tags: #manual #zettelmarkup #zettelstore
            5  +syntax: zmk
            6  +
            7  +A footnote begins with a left square bracket, followed by a circumflex accent (""''^''"", ''U+005E''), followed by some text, and ends with a right square bracket.
            8  +
            9  +Example:
           10  +
           11  +``Main text[^Footnote text.].`` is rendered in HTML as: ::Main text[^Footnote text.].::{=example}.

Added docs/manual/00001007040340.zettel.

            1  +id: 00001007040340
            2  +title: Zettelmarkup: Citation Key
            3  +role: manual
            4  +tags: #manual #zettelmarkup #zettelstore
            5  +syntax: zmk
            6  +modified: 20210810172339
            7  +
            8  +A citation key references some external material that is part of a bibliografical collection.
            9  +
           10  +Currently, Zettelstore implements this only partially, it is ""work in progress"".
           11  +
           12  +However, the syntax is: beginning with a left square bracket and followed by an at sign character (""''@''"", ''U+0040''), a the citation key is given.
           13  +The key is typically a sequence of letters and digits.
           14  +If a comma character (""'',''"", ''U+002C'') or a vertical bar character is given, the following is interpreted as inline elements.
           15  +A right square bracket ends the text and the citation key element.

Added docs/manual/00001007040350.zettel.

            1  +id: 00001007040350
            2  +title: Zettelmarkup: Mark
            3  +role: manual
            4  +tags: #manual #zettelmarkup #zettelstore
            5  +syntax: zmk
            6  +
            7  +A mark allows to name a point within a zettel.
            8  +This is useful if you want to reference some content in a bigger-sized zettel, currently with a [[link|00001007040310]] only[^Other uses of marks will be given, if Zettelmarkup is extended by a concept called //transclusion//.].
            9  +
           10  +A mark begins with a left square bracket, followed by an exclamation mark character (""''!''"", ''U+0021'').
           11  +Now the optional mark name follows.
           12  +It is a (possibly empty) sequence of Unicode letters, Unicode digits, the hyphen-minus character (""''-''"", ''U+002D''), or the low-line character (""''_''"", ''U+005F'').
           13  +The mark element ends with a right square bracket.
           14  +
           15  +Examples:
           16  +* ``[!]`` is a mark without a name, the empty mark.
           17  +* ``[!mark]`` is a mark with the name ""mark"".

Changes to docs/manual/00001007050000.zettel.

     1      1   id: 00001007050000
     2      2   title: Zettelmarkup: Attributes
            3  +role: manual
     3      4   tags: #manual #zettelmarkup #zettelstore
     4      5   syntax: zmk
     5         -role: manual
            6  +modified: 20210830161438
     6      7   
     7      8   Attributes allows to modify the way how material is presented.
     8      9   Alternatively, they provide additional information to markup elements.
     9     10   To some degree, attributes are similar to [[HTML attributes|https://html.spec.whatwg.org/multipage/dom.html#global-attributes]].
    10     11   
    11         -Typical use cases for attributes are to specify the (natural) [[language|00001007050100]] for a text region, to specify the [[programming language|00001007050200]] for highlighting program code, or to make white space visible in raw text.
           12  +Typical use cases for attributes are to specify the (natural) [[language|00001007050100]] for a text region, to specify the [[programming language|00001007050200]] for highlighting program code, or to make white space visible in plain text.
    12     13   
    13     14   Attributes are specified within curly brackets ``{...}``.
    14     15   Of course, more than one attribute can be specified.
    15     16   Attributes are separated by a sequence of space characters or by a comma character.
    16     17   
    17     18   An attribute normally consists of an optional key and an optional value.
    18     19   The key is a sequence of letters, digits, a hyphen-minus (""''-''"", ''U+002D'', and a low line / underscore (""''_''"", ''U+005D'').
................................................................................
    43     44   
    44     45   This is not true for the generic attribute.
    45     46   In ``{=key1 =key2}``, the first key is ignored.
    46     47   Therefore it is equivalent to ``{=key2}``.
    47     48   
    48     49   The key ""''-''"" (just hyphen-minus) is special.
    49     50   It is called //default attribute//{-} and has a markup specific meaning.
    50         -For example, when used for raw text, it replaces the non-visible space with a visible representation:
           51  +For example, when used for plain text, it replaces the non-visible space with a visible representation:
    51     52   
    52     53   * ++``Hello, world``{-}++ produces ==Hello, world=={-}.
    53     54   * ++``Hello, world``++ produces ==Hello, world==.
    54     55   
    55     56   For some block elements, there is a syntax variant if you only want to specify a generic attribute.
    56     57   For all line-range blocks you can specify the generic attributes directly in the first line, after the three (or more) block characters.
    57     58   

Changes to docs/manual/00001007060000.zettel.

     1      1   id: 00001007060000
     2      2   title: Zettelmarkup: Summary of Formatting Characters
            3  +role: manual
     3      4   tags: #manual #reference #zettelmarkup #zettelstore
     4      5   syntax: zmk
     5         -role: manual
            6  +modified: 20210728162830
     6      7   
     7      8   The following table gives an overview about the use of all characters that begin a markup element.
     8      9   
     9     10   |= Character :|= Blocks <|= Inlines <
    10     11   | ''!''  | (free) | (free)
    11     12   | ''"''  | [[Verse block|00001007030700]] | [[Typographic quotation mark|00001007040100]]
    12     13   | ''#''  | [[Ordered list|00001007030200]] | [[Tag|00001007040000]]
................................................................................
    25     26   | '':''  | [[Region block|00001007030800]] / [[description text|00001007030100]] | [[Inline region|00001007040100]]
    26     27   | '';''  | [[Description term|00001007030100]] | [[Small text|00001007040100]]
    27     28   | ''<''  | [[Quotation block|00001007030600]] | [[Short inline quote|00001007040100]]
    28     29   | ''=''  | [[Headings|00001007030300]] | [[Computer output|00001007040200]]
    29     30   | ''>''  | [[Quotation lists|00001007030200]] | (blocked: to remove anyambiguity with quotation lists)
    30     31   | ''?''  | (free) | (free)
    31     32   | ''@''  | (free) | (reserved)
    32         -| ''[''  | (reserved)  | [[Link|00001007040300]], [[citation key|00001007040300]], [[footnote|00001007040300]], [[mark|00001007040300]]
           33  +| ''[''  | (reserved)  | [[Linked material|00001007040300]], [[citation key|00001007040300]], [[footnote|00001007040300]], [[mark|00001007040300]]
    33     34   | ''\\'' | (blocked by inline meaning) | [[Escape character|00001007040000]]
    34     35   | '']''  | (reserved) | End of link, citation key, footnote, mark
    35     36   | ''^''  | (free) | [[Superscripted text|00001007040100]]
    36     37   | ''_''  | (free) | [[Underlined text|00001007040100]]
    37     38   | ''`''  | [[Verbatim block|00001007030500]] | [[Literal text|00001007040200]]
    38         -| ''{''  | (reserved) | [[Image|00001007040300]], [[Attribute|00001007050000]]
    39         -| ''|''  | [[Table row / table cell|00001007031000]] | Separator within link and image formatting
    40         -| ''}''  | (reserved) | End of Image, End of Attribute
           39  +| ''{''  | (reserved) | [[Embedded material|00001007040300]], [[Attribute|00001007050000]]
           40  +| ''|''  | [[Table row / table cell|00001007031000]] | Separator within link and embed formatting
           41  +| ''}''  | (reserved) | End of embedded material, End of Attribute
    41     42   | ''~''  | (free) | [[Strike-through text|00001007040100]]

Changes to docs/manual/00001008010000.zettel.

     1      1   id: 00001008010000
     2      2   title: Use Markdown within Zettelstore
     3      3   role: manual
     4      4   tags: #manual #markdown #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210518102155
            6  +modified: 20210726191610
     7      7   
     8      8   If you are customized to use Markdown as your markup language, you can configure Zettelstore to support your decision.
     9      9   Zettelstore supports [[CommonMark|https://commonmark.org/]], an [[attempt|https://xkcd.com/927/]] to unify all the different, divergent dialects of Markdown.
    10     10   
    11     11   === Use Markdown as the default markup language of Zettelstore
    12     12   
    13     13   Add the key ''default-syntax'' with a value of ''md'' or ''markdown'' to the [[configuration zettel|00000000000100]].
................................................................................
    38     38   An attacker might include malicious HTML code in your zettel.
    39     39   For example, HTML allows to embed JavaScript, a full-sized programming language that drives many web sites.
    40     40   When a zettel is displayed, JavaScript code might be executed, sometimes with harmful results.
    41     41   
    42     42   Zettelstore mitigates this problem by ignoring suspicious text when it encodes a zettel as HTML.
    43     43   Any HTML text that might contain the ``<script>`` tag or the ``<iframe>`` tag is ignored.
    44     44   This may lead to unexpected results if you depend on these.
    45         -Other encoding [[formats|00001012920500]] may still contain the full HTML text.
           45  +Other [[encodings|00001012920500]] may still contain the full HTML text.
    46     46   
    47     47   Any external client of Zettelstore, which does not use Zettelstore's HTML encoding, must be programmed to take care of malicious code.

Changes to docs/manual/00001010070200.zettel.

     1      1   id: 00001010070200
     2      2   title: Visibility rules for zettel
     3      3   role: manual
     4      4   tags: #authorization #configuration #manual #security #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210510194652
            6  +modified: 20210728162201
     7      7   
     8      8   For every zettel you can specify under which condition the zettel is visible to others.
     9      9   This is controlled with the metadata key [[''visibility''|00001006020000#visibility]].
    10     10   The following values are supported:
    11     11   
    12     12   ; [!public]""public""
    13     13   : The zettel is visible to everybody, even if the user is not authenticated.
................................................................................
    26     26     Computed zettel with internal runtime information are examples for such a zettel.
    27     27   
    28     28   When you install a Zettelstore, only some zettel have visibility ""public"".
    29     29   One is the zettel that contains CSS for displaying the web interface.
    30     30   This is to ensure that the web interface looks nice even for not authenticated users.
    31     31   Another is the zettel containing the [[version|00000000000001]] of the Zettelstore.
    32     32   Yet other zettel lists the Zettelstore [[license|00000000000004]], its [[contributors|00000000000005]], and external, licensed [[dependencies|00000000000006]], such as program code written by others or graphics designed by others.
    33         -The [[default image|00000000040001]], used if an image reference is invalid,is also public visible.
           33  +The [[default image|00000000040001]], used if an image reference is invalid, is also public visible.
    34     34   
    35     35   Please note: if authentication is not enabled, every user has the same rights as the owner of a Zettelstore.
    36     36   This is also true, if the Zettelstore runs additionally in [[read-only mode|00001004010000#read-only-mode]].
    37     37   In this case, the [[runtime configuration zettel|00001004020000]] is shown (its visibility is ""owner"").
    38     38   The [[startup configuration|00001004010000]] is not shown, because the associated computed zettel with identifier ''00000000000096'' is stored with the visibility ""expert"".
    39     39   If you want to show such a zettel, you must set ''expert-mode'' to true.

Changes to docs/manual/00001012000000.zettel.

     1      1   id: 00001012000000
     2      2   title: API
     3      3   role: manual
     4      4   tags: #api #manual #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210721120820
            6  +modified: 20210908220502
     7      7   
     8      8   The API (short for ""**A**pplication **P**rogramming **I**nterface"") is the primary way to communicate with a running Zettelstore.
     9      9   Most integration with other systems and services is done through the API.
    10     10   The [[web user interface|00001014000000]] is just an alternative, secondary way of interacting with a Zettelstore.
    11     11   
    12     12   === Background
    13         -The API is HTTP-based and uses JSON as its main encoding format for exchanging messages between a Zettelstore and its client software.
           13  +The API is HTTP-based and uses plain text and JSON as its main encoding format for exchanging messages between a Zettelstore and its client software.
    14     14   
    15     15   There is an [[overview zettel|00001012920000]] that shows the structure of the endpoints used by the API and gives an indication about its use.
    16     16   
    17         -While JSON is the main encoding format, it is possible to retrieve zettel representations in other formats.
    18         -If you want to create a new zettel or to change an existing one, you have to use JSON.
    19         -There is an [[overview zettel for encoding formats|00001012920500]] that describes the valid formats.
    20         -
    21         -Various parts of a zettel can be retrieved.
    22         -There are the [[possible values to specify zettel parts|00001012920800]].
    23         -
    24     17   === Authentication
    25     18   If [[authentication is enabled|00001010040100]], most API calls must include an [[access token|00001010040700]] that proves the identity of the caller.
    26     19   * [[Authenticate an user|00001012050200]] to obtain an access token
    27     20   * [[Renew an access token|00001012050400]] without costly re-authentication
    28     21   * [[Provide an access token|00001012050600]] when doing an API call
    29     22   
    30     23   === Zettel lists
    31     24   * [[List metadata of all zettel|00001012051200]]
    32         -* [[List all zettel, but in different encoding formats|00001012051400]]
    33         -* [[List all zettel, but include different parts of a zettel|00001012051600]]
    34     25   * [[Shape the list of zettel metadata|00001012051800]]
    35     26   ** [[Selection of zettel|00001012051810]]
    36         -** [[Zettel parts|00001012051820]]
    37     27   ** [[Limit the list length|00001012051830]]
    38     28   ** [[Content search|00001012051840]]
    39     29   ** [[Sort the list of zettel metadata|00001012052000]]
    40         -* [[List all tags|00001012052200]]
    41         -* [[List all roles|00001012052400]]
           30  +* [[List all tags|00001012052400]]
           31  +* [[List all roles|00001012052600]]
    42     32   
    43     33   === Working with zettel
    44     34   * [[Create a new zettel|00001012053200]]
    45     35   * [[Retrieve metadata and content of an existing zettel|00001012053400]]
    46         -* [[Retrieve references of an existing zettel|00001012053600]]
           36  +* [[Retrieve evaluated metadata and content of an existing zettel in various encodings|00001012053500]]
           37  +* [[Retrieve parsed metadata and content of an existing zettel in various encodings|00001012053600]]
           38  +* [[Retrieve references of an existing zettel|00001012053700]]
    47     39   * [[Retrieve context of an existing zettel|00001012053800]]
    48     40   * [[Retrieve zettel order within an existing zettel|00001012054000]]
    49     41   * [[Update metadata and content of a zettel|00001012054200]]
    50     42   * [[Rename a zettel|00001012054400]]
    51     43   * [[Delete a zettel|00001012054600]]

Changes to docs/manual/00001012050200.zettel.

     1      1   id: 00001012050200
     2      2   title: API: Authenticate a client
     3      3   role: manual
     4      4   tags: #api #manual #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210712221945
            6  +modified: 20210726123709
     7      7   
     8      8   Authentication for future API calls is done by sending a [[user identification|00001010040200]] and a password to the Zettelstore to obtain an [[access token|00001010040700]].
     9      9   This token has to be used for other API calls.
    10     10   It is valid for a relatively short amount of time, as configured with the key ''token-timeout-api'' of the [[startup configuration|00001004010000]] (typically 10 minutes).
    11     11   
    12         -The simplest way is to send user identification (''IDENT'') and password (''PASSWORD'') via [[HTTP Basic Authentication|https://tools.ietf.org/html/rfc7617]] and send them to the [[endpoint|00001012920000]] ''/v''[^The endpoint ''/a'' is already taken for the [[web user interface|00001014000000]], ""v"" stands for the German word ""verb├╝rgen""] with a POST request:
           12  +The simplest way is to send user identification (''IDENT'') and password (''PASSWORD'') via [[HTTP Basic Authentication|https://tools.ietf.org/html/rfc7617]] and send them to the [[endpoint|00001012920000]] ''/a'' with a POST request:
    13     13   ```sh
    14         -# curl -X POST -u IDENT:PASSWORD http://127.0.0.1:23123/v
           14  +# curl -X POST -u IDENT:PASSWORD http://127.0.0.1:23123/a
    15     15   {"access_token":"eyJhbGciOiJIUzUxMiJ9.eyJfdGsiOjEsImV4cCI6MTYwMTczMTI3NSwiaWF0IjoxNjAxNzMwNjc1LCJzdWIiOiJhYmMiLCJ6aWQiOiIyMDIwMTAwMzE1MDEwMCJ9.ekhXkvn146P2bMKFQcU-bNlvgbeO6sS39hs6U5EKfjIqnSInkuHYjYAIfUqf_clYRfr6YBlX5izii8XfxV8jhg","token_type":"Bearer","expires_in":600}
    16     16   ```
    17     17   
    18     18   Some tools, like [[curl|https://curl.haxx.se/]], also allow to specify user identification and password as part of the URL:
    19     19   ```sh
    20         -# curl -X POST http://IDENT:PASSWORD@127.0.0.1:23123/v
           20  +# curl -X POST http://IDENT:PASSWORD@127.0.0.1:23123/a
    21     21   {"access_token":"eyJhbGciOiJIUzUxMiJ9.eyJfdGsiOjEsImV4cCI6MTYwMTczMTI3NSwiaWF0IjoxNjAxNzMwNjc1LCJzdWIiOiJhYmMiLCJ6aWQiOiIyMDIwMTAwMzE1MDEwMCJ9.ekhXkvn146P2bMKFQcU-bNlvgbeO6sS39hs6U5EKfjIqnSInkuHYjYAIfUqf_clYRfr6YBlX5izii8XfxV8jhg","token_type":"Bearer","expires_in":600}
    22     22   ```
    23     23   
    24     24   If you do not want to use Basic Authentication, you can also send user identification and password as HTML form data:
    25     25   ```sh
    26         -# curl -X POST -d 'username=IDENT&password=PASSWORD' http://127.0.0.1:23123/v
           26  +# curl -X POST -d 'username=IDENT&password=PASSWORD' http://127.0.0.1:23123/a
    27     27   {"access_token":"eyJhbGciOiJIUzUxMiJ9.eyJfdGsiOjEsImV4cCI6MTYwMTczMTI3NSwiaWF0IjoxNjAxNzMwNjc1LCJzdWIiOiJhYmMiLCJ6aWQiOiIyMDIwMTAwMzE1MDEwMCJ9.ekhXkvn146P2bMKFQcU-bNlvgbeO6sS39hs6U5EKfjIqnSInkuHYjYAIfUqf_clYRfr6YBlX5izii8XfxV8jhg","token_type":"Bearer","expires_in":600}
    28     28   ```
    29     29   
    30     30   In all cases, you will receive an JSON object will all [[relevant data|00001012921000]] to be used for further API calls.
    31     31   
    32     32   **Important:** obtaining a token is a time-intensive process.
    33     33   Zettelstore will delay every request to obtain a token for a certain amount of time.

Changes to docs/manual/00001012050400.zettel.

     1      1   id: 00001012050400
     2      2   title: API: Renew an access token
     3      3   role: manual
     4      4   tags: #api #manual #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210712222135
            6  +modified: 20210726123745
     7      7   
     8      8   An access token is only valid for a certain duration.
     9      9   Since the [[authentication process|00001012050200]] will need some processing time, there is a way to renew the token without providing full authentication data.
    10     10   
    11         -Send a HTTP PUT request to the [[endpoint|00001012920000]] ''/v'' and include the current access token in the ''Authorization'' header:
           11  +Send a HTTP PUT request to the [[endpoint|00001012920000]] ''/a'' and include the current access token in the ''Authorization'' header:
    12     12   
    13     13   ```sh
    14         -# curl -X PUT -H 'Authorization: Bearer TOKEN' http://127.0.0.1:23123/v
           14  +# curl -X PUT -H 'Authorization: Bearer TOKEN' http://127.0.0.1:23123/a
    15     15   {"access_token":"eyJhbGciOiJIUzUxMiJ9.eyJfdGsiOjEsImV4cCI6MTYwMTczMTI3NSwiaWF0IjoxNjAxNzMwNjc1LCJzdWIiOiJhYmMiLCJ6aWQiOiIyMDIwMTAwMzE1MDEwMCJ9.ekhXkvn146P2bMKFQcU-bNlvgbeO6sS39hs6U5EKfjIqnSInkuHYjYAIfUqf_clYRfr6YBlX5izii8XfxV8jhg","token_type":"Bearer","expires_in":456}
    16     16   ```
    17     17   You may receive a new access token, or the current one if it was obtained not a long time ago.
    18     18   However, the lifetime of the returned [[access token|00001012921000]] is accurate.
    19     19   
    20     20   === HTTP Status codes
    21     21   ; ''200''

Changes to docs/manual/00001012050600.zettel.

     1      1   id: 00001012050600
     2      2   title: API: Provide an access token
            3  +role: manual
     3      4   tags: #api #manual #zettelstore
     4      5   syntax: zmk
     5         -role: manual
            6  +modified: 20210830161320
     6      7   
     7      8   The [[authentication process|00001012050200]] provides you with an [[access token|00001012921000]].
     8      9   Most API calls need such an access token, so that they know the identity of the caller.
     9     10   
    10     11   You send the access token in the ""Authorization"" request header field, as described in [[RFC 6750, section 2.1|https://tools.ietf.org/html/rfc6750#section-2.1]].
    11     12   You need to use the ""Bearer"" authentication scheme to transmit the access token.
    12     13   
    13         -For example (raw HTTP):
           14  +For example (in plain text HTTP):
    14     15   ```
    15     16   GET /z HTTP/1.0
    16     17   Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJfdGsiOjEsImV4cCI6MTYwMTczMTI3NSwiaWF0IjoxNjAxNzMwNjc1LCJzdWIiOiJhYmMiLCJ6aWQiOiIyMDIwMTAwMzE1MDEwMCJ9.ekhXkvn146P2bMKFQcU-bNlvgbeO6sS39hs6U5EKfjIqnSInkuHYjYAIfUqf_clYRfr6YBlX5izii8XfxV8jhg
    17     18   ```
    18     19   Note, that there is exactly one space character (''U+0020'') between the string ""Bearer"" and the access token: ``Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.ey...``{-}.
    19     20   
    20     21   If you use the [[curl|https://curl.haxx.se/]] tool, you can use the ++-H++ command line parameter to set this header field.

Changes to docs/manual/00001012051200.zettel.

     1      1   id: 00001012051200
     2      2   title: API: List metadata of all zettel
     3      3   role: manual
     4      4   tags: #api #manual #zettelstore
     5      5   syntax: zmk
            6  +modified: 20210905203616
     6      7   
     7         -To list the metadata of all zettel just send a HTTP GET request to the [[endpoint|00001012920000]] ''/z''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].
            8  +To list the metadata of all zettel just send a HTTP GET request to the [[endpoint|00001012920000]] ''/j''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].
     8      9   If successful, the output is a JSON object:
     9     10   
    10     11   ```sh
    11         -# curl http://127.0.0.1:23123/z
    12         -{"list":[{"id":"00001012051200","url":"/z/00001012051200","meta":{"title":"API: Renew an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012050600","url":"/z/00001012050600","meta":{"title":"API: Provide an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012050400","url":"/z/00001012050400","meta":{"title":"API: Renew an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012050200","url":"/z/00001012050200","meta":{"title":"API: Authenticate a client","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012000000","url":"/z/00001012000000","meta":{"title":"API","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"}}]}
           12  +# curl http://127.0.0.1:23123/j
           13  +{"list":[{"id":"00001012051200","meta":{"title":"API: Renew an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012050600","meta":{"title":"API: Provide an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012050400","meta":{"title":"API: Renew an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012050200","meta":{"title":"API: Authenticate a client","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012000000","meta":{"title":"API","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"}}]}
    13     14   ```
    14     15   
    15     16   The JSON object contains a key ''"list"'' where its value is a list of zettel JSON objects.
    16         -These zettel JSON objects themself contains the keys ''"id"'' (value is a string containing the zettel identifier), ''"url"'' (value is a string containing the URL of the zettel), and ''"meta"'' (value ss a JSON object).
    17         -The vlaue of key ''"meta"'' effectively contains all metadata of the identified zettel, where metadata keys are encoded as JSON object keys and metadata values encoded as JSON strings.
           17  +These zettel JSON objects themself contains the keys ''"id"'' (value is a string containing the zettel identifier), , and ''"meta"'' (value as a JSON object).
           18  +The value of key ''"meta"'' effectively contains all metadata of the identified zettel, where metadata keys are encoded as JSON object keys and metadata values encoded as JSON strings.
    18     19   
    19         -If you reformat the JSON output from the ''GET /z'' call, you'll see its structure better:
           20  +If you reformat the JSON output from the ''GET /j'' call, you'll see its structure better:
    20     21   
    21     22   ```json
    22     23   {
    23     24     "list": [
    24     25       {
    25     26         "id": "00001012051200",
    26         -      "url": "/z/00001012051200",
    27     27         "meta": {
    28     28           "title": "API: List for all zettel some data",
    29     29           "tags": "#api #manual #zettelstore",
    30     30           "syntax": "zmk",
    31     31           "role": "manual"
    32     32         }
    33     33       },
    34     34       {
    35     35         "id": "00001012050600",
    36         -      "url": "/z/00001012050600",
    37     36         "meta": {
    38     37           "title": "API: Provide an access token",
    39     38           "tags": "#api #manual #zettelstore",
    40     39           "syntax": "zmk",
    41     40           "role": "manual"
    42     41         }
    43     42       },
    44     43       {
    45     44         "id": "00001012050400",
    46         -      "url": "/z/00001012050400",
    47     45         "meta": {
    48     46           "title": "API: Renew an access token",
    49     47           "tags": "#api #manual #zettelstore",
    50     48           "syntax": "zmk",
    51     49           "role": "manual"
    52     50         }
    53     51       },
    54     52       {
    55     53         "id": "00001012050200",
    56         -      "url": "/z/00001012050200",
    57     54         "meta": {
    58     55           "title": "API: Authenticate a client",
    59     56           "tags": "#api #manual #zettelstore",
    60     57           "syntax": "zmk",
    61     58           "role": "manual"
    62     59         }
    63     60       },
    64     61       {
    65     62         "id": "00001012000000",
    66         -      "url": "/z/00001012000000",
    67     63         "meta": {
    68     64           "title": "API",
    69     65           "tags": "#api #manual #zettelstore",
    70     66           "syntax": "zmk",
    71     67           "role": "manual"
    72     68         }
    73     69       }
    74     70     ]
    75     71   }
    76     72   ```
    77     73   In this special case, the metadata of each zettel just contains the four default keys ''title'', ''tags'', ''syntax'', and ''role''.
    78     74   
    79         -If you specify the request in above simple way, you will always get a JSON object that contains all zettel maintained by your Zettelstore and which contains just the metadata of each zettel.
    80         -There are several ways to change this behavior.
           75  +[!plain]Alternatively, you can retrieve the list of zettel in a simple, plain format using the [[endpoint|00001012920000]] ''/z''.
           76  +In this case, a plain text document is returned, with one line per zettel.
           77  +Each line contains in the first 14 characters the zettel identifier.
           78  +Separated by a space character, the title of the zettel follows:
    81     79   
    82         -* [[Specify a different encoding format|00001012051400]]
    83         -* [[Specify which detail information of a zettel should be included|00001012051600]]
           80  +```sh
           81  +# curl http://127.0.0.1:23123/z
           82  +00001012051200 API: Renew an access token
           83  +00001012050600 API: Provide an access token
           84  +00001012050400 API: Renew an access token
           85  +00001012050200 API: Authenticate a client
           86  +00001012000000 API
           87  +```
    84     88   
    85     89   === HTTP Status codes
    86     90   ; ''200''
    87     91   : Retrieval was successful, the body contains an [[appropriate JSON object|00001012921000]].
    88     92   ; ''400''
    89     93   : Request was not valid. 
    90     94     There are several reasons for this.
    91     95     Maybe the access bearer token was not valid.

Deleted docs/manual/00001012051400.zettel.

     1         -id: 00001012051400
     2         -title: API: List all zettel, but in different encoding formats
     3         -tags: #api #manual #zettelstore
     4         -syntax: zmk
     5         -role: manual
     6         -
     7         -You can add a query parameter ''_format=[[FORMAT|00001012920500]]'' to select the encoding format when [[retrieving all zettel|00001012051200]].
     8         -Probably some formats are not very useful and may not make sense.
     9         -Currently implemented are the formats [[''json''|00001012920501]] (default), [[''djson''|00001012920503]], and [[''html''|00001012920510]].
    10         -
    11         -The format ''json'' will be selected, if no ''_format'' is specified.
    12         -''djson'' will return a JSON object with some more detailed information.
    13         -For example, the [[Zettelmarkup|00001007000000]] structure will be returned and tags are returned as a list of strings.
    14         -''html'' will return an HTML encoding of the zettel list, using a HTML unnumbered list.
    15         -
    16         -```sh
    17         -# curl 'http://127.0.0.1:23123/z?_format=djson'
    18         -{"list":[{"id":"00001012051400","url":"/z/00001012051400?_format=djson","meta":{"title":[{"t":"Text","s":"API:"},{"t":"Space"},{"t":"Text","s":"List"},{"t":"Space"},{"t":"Text","s":"all"},{"t":"Space"},{"t":"Text","s":"zettel,"},{"t":"Space"},{"t":"Text","s":"but"},{"t":"Space"},{"t":"Text","s":"in"},{"t":"Space"},{"t":"Text","s":"different"},{"t":"Space"},{"t":"Text","s":"encoding"},{"t":"Space"},{"t":"Text","s":"formats"}],"tags":["#api","#manual","#zettelstore"],"syntax":"zmk","role":"manual"}}, ...
    19         -```
    20         -Or reformatted:
    21         -````json
    22         -{
    23         -  "list": [
    24         -    {
    25         -      "id": "00001012051400",
    26         -      "url": "/z/00001012051400?_format=djson",
    27         -      "meta": {
    28         -        "title": [
    29         -          {
    30         -            "t": "Text",
    31         -            "s": "API:"
    32         -          },
    33         -          {
    34         -            "t": "Space"
    35         -          },
    36         -          {
    37         -            "t": "Text",
    38         -            "s": "List"
    39         -          },
    40         -          {
    41         -            "t": "Space"
    42         -          },
    43         -          {
    44         -            "t": "Text",
    45         -            "s": "all"
    46         -          },
    47         -          {
    48         -            "t": "Space"
    49         -          },
    50         -          {
    51         -            "t": "Text",
    52         -            "s": "zettel,"
    53         -          },
    54         -...
    55         -````
    56         -
    57         -```sh
    58         -# curl 'http://127.0.0.1:23123/z?_format=html'
    59         -<html lang="en">
    60         -<body>
    61         -<ul>
    62         -<li><a href="/z/00001012051400?_format=html">API: List all zettel, but in different encoding formats</a></li>
    63         -<li><a href="/z/00001012051200?_format=html">API: List metadata of all zettel</a></li>
    64         -<li><a href="/z/00001012050600?_format=html">API: Provide an access token</a></li>
    65         -<li><a href="/z/00001012050400?_format=html">API: Renew an access token</a></li>
    66         -<li><a href="/z/00001012050200?_format=html">API: Authenticate a client</a></li>
    67         -<li><a href="/z/00001012000000?_format=html">API</a></li>
    68         -</ul>
    69         -</body>
    70         -</html>```
    71         -
    72         -If you request a JSON-based encoding format, you can request additional [[parts of a zettel|00001012051600]].
    73         -
    74         -=== HTTP Status codes
    75         -; ''200''
    76         -: Retrieval was successful, the body contains an [[appropriate JSON object|00001012921000]].
    77         -; ''400''
    78         -: Request was not valid.
    79         -  There are several reasons for this.
    80         -  Maybe you provided a wrong value for ''_format''.
    81         -; ''501''
    82         -: Encoding format is not yet implemented, but will be in the future.

Deleted docs/manual/00001012051600.zettel.

     1         -id: 00001012051600
     2         -title: API: List all zettel, but include different parts of a zettel
     3         -tags: #api #manual #zettelstore
     4         -syntax: zmk
     5         -role: manual
     6         -
     7         -For JSON-based formats[^[[''json''|00001012920501]] and [[''djson''|00001012920503]]] you can add a query parameter ''_part=[[PART|00001012920800]]'' to select which parts of a zettel must be encoded.
     8         -
     9         -All allowed parts are implemented.
    10         -The JSON keys ''"id"'' and ''"url"'' are always included.
    11         -The default value is ''_part=meta''.
    12         -
    13         -If you just want to know the zettel identifier and the zettel url, specify ''_part=id'':
    14         -
    15         -```sh
    16         -# curl 'http://127.0.0.1:23123/z?_format=djson&_part=id'
    17         -{"list":[{"id":"00001012051600","url":"/z/00001012051600?_format=djson"},{"id":"00001012051400","url":"/z/00001012051400?_format=djson"},{"id":"00001012051200","url":"/z/00001012051200?_format=djson"},{"id":"00001012050600","url":"/z/00001012050600?_format=djson"},{"id":"00001012050400","url":"/z/00001012050400?_format=djson"},{"id":"00001012050200","url":"/z/00001012050200?_format=djson"},{"id":"00001012000000","url":"/z/00001012000000?_format=djson"}]}
    18         -```
    19         -Or reformatted:
    20         -````json
    21         -{
    22         -  "list": [
    23         -    {
    24         -      "id": "00001012051600",
    25         -      "url": "/z/00001012051600?_format=djson"
    26         -    },
    27         -    {
    28         -      "id": "00001012051400",
    29         -      "url": "/z/00001012051400?_format=djson"
    30         -    },
    31         -    {
    32         -      "id": "00001012051200",
    33         -      "url": "/z/00001012051200?_format=djson"
    34         -    },
    35         -    {
    36         -      "id": "00001012050600",
    37         -      "url": "/z/00001012050600?_format=djson"
    38         -    },
    39         -    {
    40         -      "id": "00001012050400",
    41         -      "url": "/z/00001012050400?_format=djson"
    42         -    },
    43         -    {
    44         -      "id": "00001012050200",
    45         -      "url": "/z/00001012050200?_format=djson"
    46         -    },
    47         -    {
    48         -      "id": "00001012000000",
    49         -      "url": "/z/00001012000000?_format=djson"
    50         -    }
    51         -  ]
    52         -}
    53         -````
    54         -
    55         -A value ''_part=content'' will include the zettel content.
    56         -If ''_format=json'', the content is a string value.
    57         -For ''_format=djson'', a detailed JSON value will be returned.
    58         -```sh
    59         -# curl 'http://127.0.0.1:23123/z?_part=content'
    60         -{"list":[{"id":"00001012051600","url":"/z/00001012051600","content":"For JSON-based formats[^[[''json''|00001012920501]] and [[''djson''|00001012920503]]] you can add a query parameter ''_part=[[PART|00001012920800]]'' to select the encoding format when [[retrieving all zettel|00001012051200]].\n\nAll given parts are implemented.\nThe JSON keys ''\"id\"'' ...
    61         -```
    62         -
    63         -The value ''_part=zettel'' will include both the metadata of a zettel //and// its zettel content.
    64         -Metadata will additionally include all autogenerated default values, such as the key ''"copyright"'' and ''"license"''.
    65         -
    66         -=== HTTP Status codes
    67         -; ''200''
    68         -: Retrieval was successful, the body contains an [[appropriate JSON object|00001012921000]].
    69         -; ''400''
    70         -: Request was not valid.
    71         -  There are several reasons for this.
    72         -  Maybe you provided a wrong value for ''_part''.

Changes to docs/manual/00001012051800.zettel.

     1      1   id: 00001012051800
     2      2   title: API: Shape the list of zettel metadata
     3      3   role: manual
     4      4   tags: #api #manual #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210721120658
            6  +modified: 20210905203719
     7      7   
     8      8   In most cases, it is not essential to list //all// zettel.
     9      9   Typically, you are interested only in a subset of the zettel maintained by your Zettelstore.
    10         -This is done by adding some query parameters to the general ''GET /z'' request.
           10  +This is done by adding some query parameters to the general ''GET /j'' request.
    11     11   
    12     12   * [[Select|00001012051810]] just some zettel, based on metadata.
    13         -* You can specify which [[parts of a zettel|00001012051820]] must be returned.
    14     13   * Only a specific amount of zettel will be selected by specifying [[a length and/or an offset|00001012051830]].
    15     14   * [[Searching for specific content|00001012051840]], not just the metadata, is another way of selecting some zettel.
    16     15   * The resulting list can be [[sorted|00001012052000]] according to various criteria.

Changes to docs/manual/00001012051810.zettel.

     1      1   id: 00001012051810
     2      2   title: API: Select zettel based on their metadata
     3      3   role: manual
     4      4   tags: #api #manual #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210721121038
            6  +modified: 20210905203929
     7      7   
     8      8   Every query parameter that does //not// begin with the low line character (""_"", ''U+005F'') is treated as the name of a [[metadata|00001006010000]] key.
     9      9   According to the [[type|00001006030000]] of a metadata key, zettel are possibly selected.
    10     10   All [[supported|00001006020000]] metadata keys have a well-defined type.
    11     11   User-defined keys have the type ''e'' (string, possibly empty).
    12     12   
    13     13   For example, if you want to retrieve all zettel that contain the string ""API"" in its title, your request will be:
    14     14   ```sh
    15         -# curl 'http://127.0.0.1:23123/z?title=API'
    16         -{"list":[{"id":"00001012921000","url":"/z/00001012921000","meta":{"title":"API: JSON structure of an access token","tags":"#api #manual #reference #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012920500","url":"/z/00001012920500","meta":{"title":"Formats available by the API","tags":"#api #manual #reference #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012920000","url":"/z/00001012920000","meta":{"title":"Endpoints used by the API","tags":"#api #manual #reference #zettelstore","syntax":"zmk","role":"manual"}}, ...
           15  +# curl 'http://127.0.0.1:23123/j?title=API'
           16  +{"list":[{"id":"00001012921000","meta":{"title":"API: JSON structure of an access token","tags":"#api #manual #reference #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012920500","meta":{"title":"Formats available by the API","tags":"#api #manual #reference #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012920000","meta":{"title":"Endpoints used by the API","tags":"#api #manual #reference #zettelstore","syntax":"zmk","role":"manual"}}, ...
    17     17   ```
    18     18   
    19     19   However, if you want all zettel that does //not// match a given value, you must prefix the value with the exclamation mark character (""!"", ''U+0021'').
    20     20   For example, if you want to retrieve all zettel that do not contain the string ""API"" in their title, your request will be:
    21     21   ```sh
    22         -# curl 'http://127.0.0.1:23123/z?title=!API'
    23         -{"list":[{"id":"00010000000000","url":"/z/00010000000000","meta":{"back":"00001003000000 00001005090000","backward":"00001003000000 00001005090000","copyright":"(c) 2020-2021 by Detlef Stern <ds@zettelstore.de>","forward":"00000000000001 00000000000003 00000000000096 00000000000100","lang":"en","license":"EUPL-1.2-or-later","role":"zettel","syntax":"zmk","title":"Home"}},{"id":"00001014000000","url":"/z/00001014000000","meta":{"back":"00001000000000 00001004020000 00001012920510","backward":"00001000000000 00001004020000 00001012000000 00001012920510","copyright":"(c) 2020-2021 by Detlef Stern <ds@zettelstore.de>","forward":"00001012000000","lang":"en","license":"EUPL-1.2-or-later","published":"00001014000000","role":"manual","syntax":"zmk","tags":"#manual #webui #zettelstore","title":"Web user interface"}},
           22  +# curl 'http://127.0.0.1:23123/j?title=!API'
           23  +{"list":[{"id":"00010000000000","meta":{"back":"00001003000000 00001005090000","backward":"00001003000000 00001005090000","copyright":"(c) 2020-2021 by Detlef Stern <ds@zettelstore.de>","forward":"00000000000001 00000000000003 00000000000096 00000000000100","lang":"en","license":"EUPL-1.2-or-later","role":"zettel","syntax":"zmk","title":"Home"}},{"id":"00001014000000","meta":{"back":"00001000000000 00001004020000 00001012920510","backward":"00001000000000 00001004020000 00001012000000 00001012920510","copyright":"(c) 2020-2021 by Detlef Stern <ds@zettelstore.de>","forward":"00001012000000","lang":"en","license":"EUPL-1.2-or-later","published":"00001014000000","role":"manual","syntax":"zmk","tags":"#manual #webui #zettelstore","title":"Web user interface"}},
    24     24   ...
    25     25   ```
    26     26   
    27     27   In both cases, an implicit precondition is that the zettel must contain the given metadata key.
    28     28   For a metadata key like [[''title''|00001006020000#title]], which has a default value, this precondition should always be true.
    29     29   But the situation is different for a key like [[''url''|00001006020000#url]].
    30         -Both ``curl 'http://localhost:23123/z?url='`` and ``curl 'http://localhost:23123/z?url=!'`` may result in an empty list.
           30  +Both ``curl 'http://localhost:23123/j?url='`` and ``curl 'http://localhost:23123/j?url=!'`` may result in an empty list.
    31     31   
    32     32   The empty query parameter values matches all zettel that contain the given metadata key.
    33     33   Similar, if you specify just the exclamation mark character as a query parameter value, only those zettel match that does //not// contain the given metadata key.
    34     34   This is in contrast to above rule that the metadata value must exist before a match is done.
    35         -For example ``curl 'http://localhost:23123/z?back=!&backward='`` returns all zettel that are reachable via other zettel, but also references these zettel.
           35  +For example ``curl 'http://localhost:23123/j?back=!&backward='`` returns all zettel that are reachable via other zettel, but also references these zettel.
    36     36   
    37     37   Above example shows that all sub-expressions of a select specification must be true so that no zettel is rejected from the final list.
    38     38   
    39     39   If you specify the query parameter ''_negate'', either with or without a value, the whole selection will be negated.
    40         -Because of the precondition described above, ``curl 'http://127.0.0.1:23123/z?url=!com'`` and ``curl 'http://127.0.0.1:23123/z?url=com&_negate'`` may produce different lists.
           40  +Because of the precondition described above, ``curl 'http://127.0.0.1:23123/j?url=!com'`` and ``curl 'http://127.0.0.1:23123/j?url=com&_negate'`` may produce different lists.
    41     41   The first query produces a zettel list, where each zettel does have a ''url'' metadata value, which does not contain the characters ""com"".
    42     42   The second query produces a zettel list, that excludes any zettel containing a ''url'' metadata value that contains the characters ""com""; this also includes all zettel that do not contain the metadata key ''url''.
           43  +
           44  +Alternatively, you also can use the [[endpoint|00001012920000]] ''/z'' for a simpler result format.
           45  +The first example translates to:
           46  +```sh
           47  +# curl 'http://127.0.0.1:23123/z?title=API'
           48  +00001012921000 API: JSON structure of an access token
           49  +00001012920500 Formats available by the API
           50  +00001012920000 Endpoints used by the API
           51  +...
           52  +```

Deleted docs/manual/00001012051820.zettel.

     1         -id: 00001012051820
     2         -title: API: Shape the list of zettel metadata by returning specific parts of a zettel
     3         -role: manual
     4         -tags: #api #manual #zettelstore
     5         -syntax: zmk
     6         -modified: 20210721120743
     7         -
     8         -=== Basic usage
     9         -If you are just interested in the zettel identifier, you should add the [[''_part''|00001012920800]] query parameter:
    10         -```sh
    11         -# curl 'http://127.0.0.1:23123/z?title=API&_part=id'
    12         -{"list":[{"id":"00001012921000","url":"/z/00001012921000"},{"id":"00001012920500","url":"/z/00001012920500"},{"id":"00001012920000","url":"/z/00001012920000"},{"id":"00001012051800","url":"/z/00001012051800"},{"id":"00001012051600","url":"/z/00001012051600"},{"id":"00001012051400","url":"/z/00001012051400"},{"id":"00001012051200","url":"/z/00001012051200"},{"id":"00001012050600","url":"/z/00001012050600"},{"id":"00001012050400","url":"/z/00001012050400"},{"id":"00001012050200","url":"/z/00001012050200"},{"id":"00001012000000","url":"/z/00001012000000"}]}
    13         -```
    14         -
    15         -=== Combined usage with select options
    16         -If you want only those zettel that additionally must contain the string ""JSON"", you have to add an additional query parameter:
    17         -```sh
    18         -# curl 'http://127.0.0.1:23123/z?title=API&_part=id&title=JSON'
    19         -{"list":[{"id":"00001012921000","url":"/z/00001012921000"}]}
    20         -```
    21         -Similarly, if you add another query parameter, the intersection of both results is returned:
    22         -```sh
    23         -# curl 'http://127.0.0.1:23123/z?title=API&_part=id&id=00001012050'
    24         -{"list":[{"id":"00001012050600","url":"/z/00001012050600"},{"id":"00001012050400","url":"/z/00001012050400"},{"id":"00001012050200","url":"/z/00001012050200"}]}
    25         -```

Changes to docs/manual/00001012051830.zettel.

     1      1   id: 00001012051830
     2      2   title: API: Shape the list of zettel metadata by limiting its length
     3      3   role: manual
     4      4   tags: #api #manual #zettelstore
     5      5   syntax: zmk
            6  +modified: 20210905204006
     6      7   
     7      8   === Limit
     8      9   By using the query parameter ""''_limit''"", which must have an integer value, you specifying an upper limit of list elements:
     9     10   ```sh
    10         -# curl 'http://192.168.17.7:23121/z?title=API&_part=id&_sort=id&_limit=2'
    11         -{"list":[{"id":"00001012000000","url":"/z/00001012000000"},{"id":"00001012050200","url":"/z/00001012050200"}]}
           11  +# curl 'http://127.0.0.1:23123/j?title=API&_sort=id&_limit=2'
           12  +{"list":[{"id":"00001012000000","meta":{"all-tags":"#api #manual #zettelstore","back":"00001000000000 00001004020000","backward":"00001000000000 00001004020000 00001012053200 00001012054000 00001014000000","box-number":"1","forward":"00001010040100 00001010040700 00001012050200 00001012050400 00001012050600 00001012051200 00001012051800 00001012051810 00001012051830 00001012051840 00001012052000 00001012052200 00001012052400 00001012052600 00001012053200 00001012053400 00001012053500 00001012053600 00001012053700 00001012053800 00001012054000 00001012054200 00001012054400 00001012054600 00001012920000 00001014000000","modified":"20210817160844","published":"20210817160844","role":"manual","syntax":"zmk","tags":"#api #manual #zettelstore","title":"API"}},{"id":"00001012050200","meta":{"all-tags":"#api #manual #zettelstore","back":"00001012000000 00001012050400 00001012050600 00001012051200 00001012053400 00001012053500 00001012053600","backward":"00001010040700 00001012000000 00001012050400 00001012050600 00001012051200 00001012053400 00001012053500 00001012053600 00001012920000 00001012921000","box-number":"1","forward":"00001004010000 00001010040200 00001010040700 00001012920000 00001012921000","modified":"20210726123709","published":"20210726123709","role":"manual","syntax":"zmk","tags":"#api #manual #zettelstore","title":"API: Authenticate a client"}}]}
           13  +```
           14  +
           15  +```sh
           16  +# curl 'http://127.0.0.1:23123/z?title=API&_sort=id&_limit=2'
           17  +00001012000000 API
           18  +00001012050200 API: Authenticate a client
    12     19   ```
    13     20   
    14     21   === Offset
    15     22   The query parameter ""''_offset''"" allows to list not only the first elements, but to begin at a specific element:
    16     23   ```sh
    17         -# curl 'http://192.168.17.7:23121/z?title=API&_part=id&_sort=id&_limit=2&_offset=1'
    18         -{"list":[{"id":"00001012050200","url":"/z/00001012050200"},{"id":"00001012050400","url":"/z/00001012050400"}]}
           24  +# curl 'http://127.0.0.1:23123/j?title=API&_sort=id&_limit=2&_offset=1'
           25  +{"list":[{"id":"00001012050200","meta":{"all-tags":"#api #manual #zettelstore","back":"00001012000000 00001012050400 00001012050600 00001012051200 00001012053400 00001012053500 00001012053600","backward":"00001010040700 00001012000000 00001012050400 00001012050600 00001012051200 00001012053400 00001012053500 00001012053600 00001012920000 00001012921000","box-number":"1","forward":"00001004010000 00001010040200 00001010040700 00001012920000 00001012921000","modified":"20210726123709","published":"20210726123709","role":"manual","syntax":"zmk","tags":"#api #manual #zettelstore","title":"API: Authenticate a client"}},{"id":"00001012050400","meta":{"all-tags":"#api #manual #zettelstore","back":"00001010040700 00001012000000","backward":"00001010040700 00001012000000 00001012920000 00001012921000","box-number":"1","forward":"00001010040100 00001012050200 00001012920000 00001012921000","modified":"20210726123745","published":"20210726123745","role":"manual","syntax":"zmk","tags":"#api #manual #zettelstore","title":"API: Renew an access token"}}]}
           26  +```
           27  +
           28  +```sh
           29  +# curl 'http://127.0.0.1:23123/z?title=API&_sort=id&_limit=2&_offset=1'
           30  +00001012050200 API: Authenticate a client
           31  +00001012050400 API: Renew an access token
    19     32   ```

Deleted docs/manual/00001012052200.zettel.

     1         -id: 00001012052200
     2         -title: API: List all tags
     3         -role: manual
     4         -tags: #api #manual #zettelstore
     5         -syntax: zmk
     6         -
     7         -To list all [[tags|00001006020000#tags]] used in the Zettelstore just send a HTTP GET request to the [[endpoint|00001012920000]] ''/t''.
     8         -If successful, the output is a JSON object:
     9         -
    10         -```sh
    11         -# curl http://127.0.0.1:23123/t
    12         -{"tags":{"#api":[:["00001012921000","00001012920800","00001012920522",...],"#authorization":["00001010040700","00001010040400",...],...,"#zettelstore":["00010000000000","00001014000000",...,"00001001000000"]}}
    13         -```
    14         -
    15         -The JSON object only contains the key ''"tags"'' with the value of another object.
    16         -This second object contains all tags as keys and the list of identifier of those zettel with this tag as a value.
    17         -
    18         -Please note that this structure will likely change in the future to be more compliant with other API calls.

Changes to docs/manual/00001012052400.zettel.

     1      1   id: 00001012052400
     2         -title: API: List all roles
            2  +title: API: List all tags
     3      3   role: manual
     4      4   tags: #api #manual #zettelstore
     5      5   syntax: zmk
     6      6   
     7         -To list all [[roles|00001006020100]] used in the Zettelstore just send a HTTP GET request to the [[endpoint|00001012920000]] ''/r''.
            7  +To list all [[tags|00001006020000#tags]] used in the Zettelstore just send a HTTP GET request to the [[endpoint|00001012920000]] ''/t''.
     8      8   If successful, the output is a JSON object:
     9      9   
    10     10   ```sh
    11         -# curl http://127.0.0.1:23123/r
    12         -{"role-list":["configuration","manual","user","zettel"]}
           11  +# curl http://127.0.0.1:23123/t
           12  +{"tags":{"#api":[:["00001012921000","00001012920800","00001012920522",...],"#authorization":["00001010040700","00001010040400",...],...,"#zettelstore":["00010000000000","00001014000000",...,"00001001000000"]}}
    13     13   ```
    14     14   
    15         -The JSON object only contains the key ''"role-list"'' with the value of a sorted string list.
    16         -Each string names one role.
           15  +The JSON object only contains the key ''"tags"'' with the value of another object.
           16  +This second object contains all tags as keys and the list of identifier of those zettel with this tag as a value.
    17     17   
    18     18   Please note that this structure will likely change in the future to be more compliant with other API calls.

Added docs/manual/00001012052600.zettel.

            1  +id: 00001012052600
            2  +title: API: List all roles
            3  +role: manual
            4  +tags: #api #manual #zettelstore
            5  +syntax: zmk
            6  +
            7  +To list all [[roles|00001006020100]] used in the Zettelstore just send a HTTP GET request to the [[endpoint|00001012920000]] ''/r''.
            8  +If successful, the output is a JSON object:
            9  +
           10  +```sh
           11  +# curl http://127.0.0.1:23123/r
           12  +{"role-list":["configuration","manual","user","zettel"]}
           13  +```
           14  +
           15  +The JSON object only contains the key ''"role-list"'' with the value of a sorted string list.
           16  +Each string names one role.
           17  +
           18  +Please note that this structure will likely change in the future to be more compliant with other API calls.

Changes to docs/manual/00001012053200.zettel.

     1      1   id: 00001012053200
     2      2   title: API: Create a new zettel
     3      3   role: manual
     4      4   tags: #api #manual #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210713163927
            6  +modified: 20210905204325
     7      7   
     8      8   A zettel is created by adding it to the [[list of zettel|00001012000000#zettel-lists]].
     9         -Therefore, the [[endpoint|00001012920000]] to create a new zettel is also ''/z'', but you must send the data of the new zettel via a HTTP POST request.
            9  +Therefore, the [[endpoint|00001012920000]] to create a new zettel is also ''/j'', but you must send the data of the new zettel via a HTTP POST request.
    10     10   
    11     11   The body of the POST request must contain a JSON object that specifies metadata and content of the zettel to be created.
    12     12   The following keys of the JSON object are used:
    13     13   ; ''"meta"''
    14     14   : References an embedded JSON object with only string values.
    15     15     The name/value pairs of this objects are interpreted as the metadata of the new zettel.
    16     16     Please consider the [[list of supported metadata keys|00001006020000]] (and their value types).
................................................................................
    26     26   Even these three keys are just optional.
    27     27   The body of the HTTP POST request must not be empty and it must contain a JSON object.
    28     28   
    29     29   Therefore, a body containing just ''{}'' is perfectly valid.
    30     30   The new zettel will have no content, its title will be set to the value of [[''default-title''|00001004020000#default-title]] (default: ""Untitled""), its role is set to the value of [[''default-role''|00001004020000#default-role]] (default: ""zettel""), and its syntax is set to the value of [[''default-syntax''|00001004020000#default-syntax]] (default: ""zmk"").
    31     31   
    32     32   ```
    33         -# curl -X POST --data '{}' http://127.0.0.1:23123/z
    34         -{"id":"20210713161000","url":"/z/20210713161000"}
           33  +# curl -X POST --data '{}' http://127.0.0.1:23123/j
           34  +{"id":"20210713161000"}
    35     35   ```
    36         -If creating the zettel was successful, the HTTP response will contain a JSON object with two keys:
           36  +If creating the zettel was successful, the HTTP response will contain a JSON object with one key:
    37     37   ; ''"id"''
    38     38   : Contains the zettel identifier of the created zettel for further usage.
    39         -; ''"url"''
    40         -: The URL for [[reading metadata and content|00001012053400]] of the new zettel.
    41         -  In most cases, the URL is a relative one.
    42         -  A client must prepend the HTTP protocol scheme, the host name, and (optional, but often needed) the post number to make it an absolute URL.
    43     39   
    44         -In addition, the HTTP response header contains a key ''Location'' with the same value of the relative URL.
           40  +In addition, the HTTP response header contains a key ''Location'' with a relative URL for the new zettel.
           41  +A client must prepend the HTTP protocol scheme, the host name, and (optional, but often needed) the post number to make it an absolute URL.
    45     42   
    46     43   As an example, a zettel with title ""Note"" and content ""Important content."" can be created by issuing:
    47     44   ```
    48         -# curl -X POST --data '{"meta":{"title":"Note"},"content":"Important content."}' http://127.0.0.1:23123/z
    49         -{"id":"20210713163100","url":"/z/20210713163100"}
           45  +# curl -X POST --data '{"meta":{"title":"Note"},"content":"Important content."}' http://127.0.0.1:23123/j
           46  +{"id":"20210713163100"}
           47  +```
           48  +
           49  +[!plain]Alternatively, you can use the [[endpoint|00001012920000]] ''/z'' to create a new zettel.
           50  +In this case, the zettel must be encoded in a [[plain|00001006000000]] format: first comes the [[metadata|00001006010000]] and the following content is separated by an empty line.
           51  +This is the same format as used by storing zettel within a [[directory box|00001006010000]].
           52  +```
           53  +# curl -X POST --data $'title: Note\n\nImportant content.' http://127.0.0.1:23123/z
           54  +20210903211500
    50     55   ```
           56  +
    51     57   === HTTP Status codes
    52     58   ; ''201''
    53     59   : Zettel creation was successful, the body contains a JSON object that contains its zettel identifier.
    54     60   ; ''400''
    55     61   : Request was not valid. 
    56     62     There are several reasons for this.
    57     63     Most likely, the JSON was not formed according to above rules.
    58     64   ; ''403''
    59     65   : You are not allowed to create a new zettel.

Changes to docs/manual/00001012053400.zettel.

     1      1   id: 00001012053400
     2      2   title: API: Retrieve metadata and content of an existing zettel
     3      3   role: manual
     4      4   tags: #api #manual #zettelstore
     5      5   syntax: zmk
            6  +modified: 20210905204428
     6      7   
     7         -The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the zettel identifier (14 digits).
            8  +The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/j/{ID}'', where ''{ID}'' is a placeholder for the zettel identifier (14 digits).
     8      9   
     9         -For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/z/00001012053400''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].
           10  +For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/j/00001012053400''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].
    10     11   If successful, the output is a JSON object:
    11     12   ```sh
    12         -# curl http://127.0.0.1:23123/z/00001012053400
    13         -{"id":"00001012053400","url":"/z/00001012053400","meta":{"title":"API: Retrieve data for an exisiting zettel","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual","copyright":"(c) 2020 by Detlef Stern <ds@zettelstore.de>","lang":"en","license":"CC BY-SA 4.0"},"content":"The endpoint to work with a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the zettel identifier (14 digits).\n\nFor example, ...
           13  +# curl http://127.0.0.1:23123/j/00001012053400
           14  +{"id":"00001012053400","meta":{"title":"API: Retrieve data for an exisiting zettel","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual","copyright":"(c) 2020 by Detlef Stern <ds@zettelstore.de>","lang":"en","license":"CC BY-SA 4.0"},"content":"The endpoint to work with a specific zettel is ''/j/{ID}'', where ''{ID}'' is a placeholder for the zettel identifier (14 digits).\n\nFor example, ...
           15  +```
           16  +
           17  +Pretty-printed, this results in:
    14     18   ```
    15         -Similar to listing all or some zettel, you can provide a query parameter ''_format=[[FORMAT|00001012920500]]'' to select a different encoding format.
    16         -The default encoding format is ""[[json|00001012920501]]"".
    17         -Others are ""[[djson|00001012920503]]"", ""[[html|00001012920510]]"", and some more.
    18         -```sh
    19         -# curl 'http://127.0.0.1:23123/z/00001012053400?_format=html'
    20         -<!DOCTYPE html>
    21         -<html lang="en">
    22         -<head>
    23         -<meta charset="utf-8">
    24         -<title>API: Retrieve data for an exisiting zettel</title>
    25         -<meta name="keywords" content="api, manual, zettelstore">
    26         -<meta name="zs-syntax" content="zmk">
    27         -<meta name="zs-role" content="manual">
    28         -<meta name="copyright" content="(c) 2020 by Detlef Stern <ds@zettelstore.de>">
    29         -<meta name="license" content="CC BY-SA 4.0">
    30         -</head>
    31         -<body>
    32         -<p>The endpoint to work with a specific zettel is <span style="font-family:monospace">/z/{ID}</span>, where <span style="font-family:monospace">{ID}</span> is a placeholder for the zettel identifier (14 digits).</p>
    33         -...
           19  +{
           20  +  "id": "00001012053400",
           21  +  "meta": {
           22  +    "back": "00001012000000 00001012053200 00001012054400",
           23  +    "backward": "00001012000000 00001012053200 00001012054400 00001012920000",
           24  +    "box-number": "1",
           25  +    "forward": "00001010040100 00001012050200 00001012920000 00001012920800",
           26  +    "modified": "20210726190012",
           27  +    "published": "20210726190012",
           28  +    "role": "manual",
           29  +    "syntax": "zmk",
           30  +    "tags": "#api #manual #zettelstore",
           31  +    "title": "API: Retrieve metadata and content of an existing zettel"
           32  +  },
           33  +  "encoding": "",
           34  +  "content": "The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/j/{ID}'', where ''{ID}'' (...)
           35  +}
    34     36   ```
    35     37   
    36         -You also can use the query parameter ''_part=[[PART|00001012920800]]'' to specify which parts of a zettel must be encoded.
    37         -In this case, its default value is ''zettel''.
           38  +[!plain]Additionally, you can retrieve the plain zettel, without using JSON.
           39  +Just change the [[endpoint|00001012920000]] to ''/z/{ID}''
           40  +Optionally, you may provide which parts of the zettel you are requesting.
           41  +In this case, add an additional query parameter ''_part=[[PART|00001012920800]]''.
           42  +Valid values are ""zettel"", ""meta"", and ""content"" (the default value).
           43  +
           44  +````sh
           45  +# curl 'http://127.0.0.1:23123/z/00001012053400'
           46  +The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/j/{ID}'', where ''{ID}'' is a placeholder for the zettel identifier (14 digits).
           47  +
           48  +For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/j/00001012053400''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].
           49  +If successful, the output is a JSON object:
    38     50   ```sh
    39         -# curl 'http://192.168.17.7:23121/z/00001012053400?_format=html&_part=meta'
    40         -<meta name="zs-title" content="API: Retrieve data for an exisiting zettel">
    41         -<meta name="keywords" content="api, manual, zettelstore">
    42         -<meta name="zs-syntax" content="zmk">
    43         -<meta name="zs-role" content="manual">
    44         -```
    45         -(Note that metadata of the whole zettel contains some computed values, such as ''"copyright"'' and ''"license"'', while the metadata for ''_part=meta'' just contain the explicitly specified metadata.)
           51  +...
           52  +````
           53  +
           54  +````sh
           55  +# curl 'http://127.0.0.1:23123/z/00001012053400?_part=meta'
           56  +title: API: Retrieve metadata and content of an existing zettel
           57  +role: manual
           58  +tags: #api #manual #zettelstore
           59  +syntax: zmk
           60  +````
    46     61   
    47     62   === HTTP Status codes
    48     63   ; ''200''
    49     64   : Retrieval was successful, the body contains an appropriate JSON object.
    50     65   ; ''400''
    51     66   : Request was not valid. 
    52     67     There are several reasons for this.
    53         -  Maybe the zettel identifier did not consist of exactly 14 digits or ''_format'' / ''_part'' contained illegal values.
           68  +  Maybe the zettel identifier did not consist of exactly 14 digits.
    54     69   ; ''403''
    55     70   : You are not allowed to retrieve data of the given zettel.
    56     71   ; ''404''
    57     72   : Zettel not found.
    58     73     You probably used a zettel identifier that is not used in the Zettelstore.

Added docs/manual/00001012053500.zettel.

            1  +id: 00001012053500
            2  +title: API: Retrieve evaluated metadata and content of an existing zettel in various encodings
            3  +role: manual
            4  +tags: #api #manual #zettelstore
            5  +syntax: zmk
            6  +modified: 20210826224328
            7  +
            8  +The [[endpoint|00001012920000]] to work with evaluated metadata and content of a specific zettel is ''/v/{ID}'', where ''{ID}'' is a placeholder for the zettel identifier (14 digits).
            9  +
           10  +For example, to retrieve some evaluated data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/v/00001012053500''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].
           11  +If successful, the output is a JSON object:
           12  +```sh
           13  +# curl http://127.0.0.1:23123/v/00001012053500
           14  +{"meta":{"title":[{"t":"Text","s":"API:"},{"t":"Space"},{"t":"Text","s":"Retrieve"},{"t":"Space"},{"t":"Text","s":"evaluated"},{"t":"Space"},{"t":"Text","s":"metadata"},{"t":"Space"},{"t":"Text","s":"and"},{"t":"Space"},{"t":"Text","s":"content"},{"t":"Space"},{"t":"Text","s":"of"},{"t":"Space"},{"t":"Text","s":"an"},{"t":"Space"},{"t":"Text","s":"existing"},{"t":"Space"},{"t":"Text","s":"zettel"},{"t":"Space"},{"t":"Text","s":"in"},{"t":"Space"}, ...
           15  +```
           16  +
           17  +To select another encoding, you can provide a query parameter ''_enc=[[ENCODING|00001012920500]]''.
           18  +The default encoding is ""[[djson|00001012920503]]"".
           19  +Others are ""[[html|00001012920510]]"", ""[[text|00001012920519]]"", and some more.
           20  +```sh
           21  +# curl 'http://127.0.0.1:23123/v/00001012053500?_enc=html'
           22  +<!DOCTYPE html>
           23  +<html lang="en">
           24  +<head>
           25  +<meta charset="utf-8">
           26  +<title>API: Retrieve evaluated metadata and content of an existing zettel in various encodings</title>
           27  +<meta name="zs-role" content="manual">
           28  +<meta name="keywords" content="api, manual, zettelstore">
           29  +<meta name="zs-syntax" content="zmk">
           30  +<meta name="zs-back" content="00001012000000">
           31  +<meta name="zs-backward" content="00001012000000">
           32  +<meta name="zs-box-number" content="1">
           33  +<meta name="copyright" content="(c) 2020-2021 by Detlef Stern <ds@zettelstore.de>">
           34  +<meta name="zs-forward" content="00001010040100 00001012050200 00001012920000 00001012920800">
           35  +<meta name="zs-published" content="00001012053500">
           36  +</head>
           37  +<body>
           38  +<p>The <a href="/v/00001012920000?_enc=html">endpoint</a> to work with metadata and content of a specific zettel is <span style="font-family:monospace">/v/{ID}</span>, where <span style="font-family:monospace">{ID}</span> is a placeholder for the zettel identifier (14 digits).</p>
           39  +...
           40  +```
           41  +
           42  +You also can use the query parameter ''_part=[[PART|00001012920800]]'' to specify which parts of a zettel must be encoded.
           43  +In this case, its default value is ''content''.
           44  +```sh
           45  +# curl 'http://127.0.0.1:23123/v/00001012053500?_enc=html&_part=meta'
           46  +<meta name="zs-title" content="API: Retrieve evaluated metadata and content of an existing zettel in various encodings">
           47  +<meta name="zs-role" content="manual">
           48  +<meta name="keywords" content="api, manual, zettelstore">
           49  +<meta name="zs-syntax" content="zmk">
           50  +<meta name="zs-back" content="00001012000000">
           51  +<meta name="zs-backward" content="00001012000000">
           52  +<meta name="zs-box-number" content="1">
           53  +<meta name="copyright" content="(c) 2020-2021 by Detlef Stern <ds@zettelstore.de>">
           54  +<meta name="zs-forward" content="00001010040100 00001012050200 00001012920000 00001012920800">
           55  +<meta name="zs-lang" content="en">
           56  +<meta name="zs-published" content="00001012053500">
           57  +```
           58  +
           59  +=== HTTP Status codes
           60  +; ''200''
           61  +: Retrieval was successful, the body contains an appropriate JSON object.
           62  +; ''400''
           63  +: Request was not valid. 
           64  +  There are several reasons for this.
           65  +  Maybe the zettel identifier did not consist of exactly 14 digits or ''_enc'' / ''_part'' contained illegal values.
           66  +; ''403''
           67  +: You are not allowed to retrieve data of the given zettel.
           68  +; ''404''
           69  +: Zettel not found.
           70  +  You probably used a zettel identifier that is not used in the Zettelstore.

Changes to docs/manual/00001012053600.zettel.

     1      1   id: 00001012053600
     2         -title: API: Retrieve references of an existing zettel
            2  +title: API: Retrieve parsed metadata and content of an existing zettel in various encodings
     3      3   role: manual
     4      4   tags: #api #manual #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210702183357
            6  +modified: 20210830165056
            7  +
            8  +The [[endpoint|00001012920000]] to work with parsed metadata and content of a specific zettel is ''/p/{ID}'', where ''{ID}'' is a placeholder for the zettel identifier (14 digits).
     7      9   
     8         -The web of zettel is one important value of a Zettelstore.
     9         -Many zettel references other zettel, images, external/local material or, via citations, external literature.
    10         -By using the [[endpoint|00001012920000]] ''/l/{ID}'' you are able to retrieve these references.
           10  +A //parsed// zettel is basically an [[unevaluated|00001012053500]] zettel: the zettel is read and analyzed, but its content is //not evaluated//.
           11  +By using this endpoint, you are able to retrieve the structure of a zettel before it is evaluated.
    11     12   
    12         -````
    13         -# curl http://127.0.0.1:23123/l/00001012053600
    14         -{"id":"00001012053600","url":"/z/00001012053600","links":{"outgoing":[{"id":"00001012920000","url":"/z/00001012920000"},{"id":"00001007040300","url":"/z/00001007040300#links"},{"id":"00001007040300","url":"/z/00001007040300#images"},{"id":"00001007040300","url":"/z/00001007040300#citation-key"}]},"images":{}}
    15         -````
    16         -Formatted, this translates into:
    17         -````json
    18         -{
    19         -  "id": "00001012053600",
    20         -  "url": "/z/00001012053600",
    21         -  "links": {
    22         -    "outgoing": [
    23         -      {
    24         -        "id": "00001012920000",
    25         -        "url": "/z/00001012920000"
    26         -      },
    27         -      {
    28         -        "id": "00001007040300",
    29         -        "url": "/z/00001007040300#links"
    30         -      },
    31         -      {
    32         -        "id": "00001007040300",
    33         -        "url": "/z/00001007040300#images"
    34         -      },
    35         -      {
    36         -        "id": "00001007040300",
    37         -        "url": "/z/00001007040300#citation-key"
    38         -      }
    39         -    ],
    40         -  },
    41         -  "images": {},
    42         -}
    43         -````
    44         -=== Kind
    45         -The following to-level JSON keys are returned:
    46         -; ''id''
    47         -: The zettel identifier for which the references were requested.
    48         -; ''url''
    49         -: The API endpoint to fetch more information about the zettel.
    50         -; ''link''
    51         -: A JSON object that contains information about incoming and outgoing [[links|00001007040300#links]].
    52         -; ''image''
    53         -: A JSON object that contains information about referenced [[images|00001007040300#images]].
    54         -; ''cite''
    55         -: A JSON list of [[citation keys|00001007040300#citation-key]] (as JSON strings).
           13  +For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/v/00001012053600''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].
           14  +If successful, the output is a JSON object:
           15  +```sh
           16  +# curl http://127.0.0.1:23123/p/00001012053600
           17  +{"meta":{"title":[{"t":"Text","s":"Retrieve"},{"t":"Space"},{"t":"Text","s":"parsed"},{"t":"Space"},{"t":"Text","s":"metadata"},{"t":"Space"},{"t":"Text","s":"and"},{"t":"Space"},{"t":"Text","s":"content"},{"t":"Space"},{"t":"Text","s":"of"},{"t":"Space"},{"t":"Text","s":"an"},{"t":"Space"},{"t":"Text","s":"existing"},{"t":"Space"},{"t":"Text","s":"zettel"},{"t":"Space"},{"t":"Text","s":"in"},{"t":"Space"},{"t":"Text","s":"various"},{"t":"Space"},{"t":"Text","s":"encodings"}],"role":"manual","tags":["#api", ...
           18  +```
    56     19   
    57         -The query parameter ''kind'' controls which of these values is retrieved:
           20  +Similar to [[retrieving an encoded zettel|00001012053500]], you can specify an [[encoding|00001012920500]] and state which [[part|00001012920800]] of a zettel you are interested in.
           21  +The same default values applies to this endpoint.
    58     22   
    59         -|= ''kind'' <| Number >| Returned values
    60         -| (nothing) | (14) |links, images, and citation keys
    61         -| ''link'' | 2|links 
    62         -| ''image'' | 4|images
    63         -| ''cite'' | 8|citation keys
    64         -| ''both'' | (6)|links and images
    65         -| ''all'' | (14)|links, images, and citation keys
    66         -
    67         -The ""Number"" column gives an indication about an alternative way of specifying the kind.
    68         -Every kind is given a number, which you also can use to specify the requested kind of reference.
    69         -To get more than one kind, just add the numbers.
    70         -If you want to retrieve only images and citations, which was not given a ''kind'' value, just specify ''kind=12''.
    71         -=== Matter
    72         -If you request to retrieve referenced links and/or referenced images, you can further control, which matter of references should be retrieved.
    73         -This is controlled by the value of the query parameter ''matter'':
    74         -|= ''matter''| Number >| Returned reference list
    75         -| (nothing) | (30) | incoming, outgoing, local, and external references
    76         -| ''incoming'' | 2|incoming reference, not allowed for images (aka ""backlinks"", not yet implemented)
    77         -| ''outgoing'' | 4|outgoing references
    78         -| ''local'' | 8|local references, i.e. local, non-zettel material
    79         -| ''external'' |16| external references, i.e. on the web
    80         -| ''meta'' |32| external reference, stored in metadata
    81         -| ''zettel'' | (6)|incoming and outgoing references
    82         -| ''material'' |(56)| local and external references
    83         -| ''all'' | (62)| incoming, outgoing, local, and external references
    84         -
    85         -Incoming and outgoing references are basically zettel.
    86         -Therefore the list elements are JSON objects with keys ''id'' and ''url''.
    87         -Local and external references are strings.
    88         -
    89         -Similar to the ''kind'' query parameter, each matter is associated with a number.
    90         -To retrieve a combination of matter values that does not have a name, just add the numbers.
    91         -For example, if you want to retrieve only the outgoing and the external references, specify ''matter=20''.
    92         -
    93         -If a list is not going to retrieved, the associated value is ``null``{=json} instead of an empty list.
    94     23   === HTTP Status codes
    95     24   ; ''200''
    96     25   : Retrieval was successful, the body contains an appropriate JSON object.
    97     26   ; ''400''
    98     27   : Request was not valid. 
    99     28     There are several reasons for this.
   100         -  Maybe the zettel identifier did not consist of exactly 14 digits or ''_format'' / ''_part'' contained illegal values.
           29  +  Maybe the zettel identifier did not consist of exactly 14 digits or ''_enc'' / ''_part'' contained illegal values.
   101     30   ; ''403''
   102     31   : You are not allowed to retrieve data of the given zettel.
   103     32   ; ''404''
   104     33   : Zettel not found.
   105     34     You probably used a zettel identifier that is not used in the Zettelstore.

Added docs/manual/00001012053700.zettel.

            1  +id: 00001012053700
            2  +title: API: Retrieve references of an existing zettel
            3  +role: manual
            4  +tags: #api #manual #zettelstore
            5  +syntax: zmk
            6  +modified: 20210825225643
            7  +
            8  +The web of zettel is one important value of a Zettelstore.
            9  +Many zettel references other zettel, embedded material, external/local material or, via citations, external literature.
           10  +By using the [[endpoint|00001012920000]] ''/l/{ID}'' you are able to retrieve these references.
           11  +
           12  +````
           13  +# curl http://127.0.0.1:23123/l/00001012053700
           14  +{"id":"00001012053700","linked":{"outgoing":["00001012920000","00001007040300#links","00001007040300#embedded-material","00001007040300#citation-key"]},"embedded":{}}
           15  +````
           16  +Formatted, this translates into:
           17  +````json
           18  +{
           19  +  "id": "00001012053700",
           20  +  "linked": {
           21  +    "outgoing": [
           22  +      "00001012920000",
           23  +      "00001007040300#links",
           24  +      "00001007040300#embedded-material",
           25  +      "00001007040300#citation-key"
           26  +    ]
           27  +  },
           28  +  "embedded": {}
           29  +}
           30  +````
           31  +=== Kind
           32  +The following top-level JSON keys are returned:
           33  +; ''id''
           34  +: The zettel identifier for which the references were requested.
           35  +; ''linked''
           36  +: A JSON object that contains information about incoming and outgoing [[links|00001007040300#links]].
           37  +; ''embedded''
           38  +: A JSON object that contains information about referenced [[embedded material|00001007040300#embedded-material]].
           39  +; ''cite''
           40  +: A JSON list of [[citation keys|00001007040300#citation-key]] (as JSON strings).
           41  +
           42  +Incoming and outgoing references are basically zettel.
           43  +Therefore the list elements are JSON objects with key ''id'', optionally with an appended fragment.
           44  +Local and external references are strings.
           45  +
           46  +=== HTTP Status codes
           47  +; ''200''
           48  +: Retrieval was successful, the body contains an appropriate JSON object.
           49  +; ''400''
           50  +: Request was not valid. 
           51  +  There are several reasons for this.
           52  +  Maybe the zettel identifier did not consist of exactly 14 digits.
           53  +; ''403''
           54  +: You are not allowed to retrieve data of the given zettel.
           55  +; ''404''
           56  +: Zettel not found.
           57  +  You probably used a zettel identifier that is not used in the Zettelstore.

Changes to docs/manual/00001012053800.zettel.

     1      1   id: 00001012053800
     2      2   title: API: Retrieve context of an existing zettel
     3      3   role: manual
     4      4   tags: #api #manual #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210712223623
            6  +modified: 20210825194347
     7      7   
     8      8   The context of an origin zettel consists of those zettel that are somehow connected to the origin zettel.
     9      9   Direct connections of an origin zettel to other zettel are visible via [[metadata values|00001006020000]], such as ''backward'', ''forward'' or other values with type [[identifier|00001006032000]] or [[set of identifier|00001006032500]].
    10     10   Zettel are also connected by using same [[tags|00001006020000#tags]].
    11     11   
    12     12   The context is defined by a //direction//, a //depth//, and a //limit//:
    13     13   * Direction: connections are directed.
................................................................................
    27     27   Currently, only some of the newest zettel with a given tag are considered a connection.[^The number of zettel is given by the value of parameter ''depth''.]
    28     28   Otherwise the context would become too big and therefore unusable.
    29     29   
    30     30   To retrieve the context of an existing zettel, use the [[endpoint|00001012920000]] ''/x/{ID}''[^Mnemonic: conte**X**t].
    31     31   
    32     32   ````
    33     33   # curl 'http://127.0.0.1:23123/x/00001012053800?limit=3&dir=forward&depth=2'
    34         -{"id": "00001012053800","url": "/z/00001012053800","meta": {...},"list": [{"id": "00001012921000","url": "/z/00001012921000","meta": {...}},{"id": "00001012920800","url": "/z/00001012920800","meta": {...}},{"id": "00010000000000","url": "/z/00010000000000","meta": {...}}]}
           34  +{"id": "00001012053800","meta": {...},"list": [{"id": "00001012921000","meta": {...}},{"id": "00001012920800","meta": {...}},{"id": "00010000000000","meta": {...}}]}
    35     35   ````
    36     36   Formatted, this translates into:[^Metadata (key ''meta'') are hidden to make the overall structure easier to read.]
    37     37   ````json
    38     38   {
    39     39     "id": "00001012053800",
    40         -  "url": "/z/00001012053800",
    41     40     "meta": {...},
    42     41     "list": [
    43     42       {
    44     43         "id": "00001012921000",
    45         -      "url": "/z/00001012921000",
    46     44         "meta": {...}
    47     45       },
    48     46       {
    49     47         "id": "00001012920800",
    50         -      "url": "/z/00001012920800",
    51     48         "meta": {...}
    52     49       },
    53     50       {
    54     51         "id": "00010000000000",
    55         -      "url": "/z/00010000000000",
    56     52         "meta": {...}
    57     53       }
    58     54     ]
    59     55   }
    60     56   ````
    61     57   === Keys
    62     58   The following top-level JSON keys are returned:
    63     59   ; ''id''
    64     60   : The zettel identifier for which the context was requested.
    65         -; ''url''
    66         -: The API endpoint to fetch more information about the zettel.
    67     61   ; ''meta'':
    68     62   : The metadata of the zettel, encoded as a JSON object.
    69     63   ; ''list''
    70         -: A list of JSON objects with keys ''id'', ''url'' and ''meta'' that contains the zettel of the context.
           64  +: A list of JSON objects with keys ''id'' and ''meta'' that contains the zettel of the context.
    71     65   
    72     66   === HTTP Status codes
    73     67   ; ''200''
    74     68   : Retrieval was successful, the body contains an appropriate JSON object.
    75     69   ; ''400''
    76     70   : Request was not valid.
    77     71   ; ''403''
    78     72   : You are not allowed to retrieve data of the given zettel.
    79     73   ; ''404''
    80     74   : Zettel not found.
    81     75     You probably used a zettel identifier that is not used in the Zettelstore.

Changes to docs/manual/00001012054000.zettel.

     1      1   id: 00001012054000
     2      2   title: API: Retrieve zettel order within an existing zettel
     3      3   role: manual
     4      4   tags: #api #manual #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210721184434
            6  +modified: 20210825194515
     7      7   
     8      8   Some zettel act as a ""table of contents"" for other zettel.
     9      9   The [[initial zettel|00001000000000]] of this manual is one example, the [[general API description|00001012000000]] is another.
    10     10   Every zettel with a certain internal structure can act as the ""table of contents"" for others.
    11     11   
    12     12   What is a ""table of contents""?
    13     13   Basically, it is just a list of references to other zettel.
................................................................................
    19     19   Only the first reference to a valid zettel is collected for the table of contents.
    20     20   Following references to zettel within such an list item are ignored.
    21     21   
    22     22   To retrieve the zettel order of an existing zettel, use the [[endpoint|00001012920000]] ''/o/{ID}''.
    23     23   
    24     24   ````
    25     25   # curl http://127.0.0.1:23123/o/00001000000000
    26         -{"id":"00001000000000","url":"/z/00001000000000","meta":{...},"list":[{"id":"00001001000000","url":"/z/00001001000000","meta":{...}},{"id":"00001002000000","url":"/z/00001002000000","meta":{...}},{"id":"00001003000000","url":"/z/00001003000000","meta":{...}},{"id":"00001004000000","url":"/z/00001004000000","meta":{...}},...,{"id":"00001014000000","url":"/z/00001014000000","meta":{...}}]}
           26  +{"id":"00001000000000","meta":{...},"list":[{"id":"00001001000000","meta":{...}},{"id":"00001002000000","meta":{...}},{"id":"00001003000000","meta":{...}},{"id":"00001004000000","meta":{...}},...,{"id":"00001014000000","meta":{...}}]}
    27     27   ````
    28     28   Formatted, this translates into:[^Metadata (key ''meta'') are hidden to make the overall structure easier to read.]
    29     29   ````json
    30     30   {
    31     31     "id": "00001000000000",
    32         -  "url": "/z/00001000000000",
    33     32     "list": [
    34     33       {
    35     34         "id": "00001001000000",
    36         -      "url": "/z/00001001000000",
    37     35         "meta": {...}
    38     36       },
    39     37       {
    40     38         "id": "00001002000000",
    41         -      "url": "/z/00001002000000",
    42     39         "meta": {...}
    43     40       },
    44     41       {
    45     42         "id": "00001003000000",
    46         -      "url": "/z/00001003000000",
    47     43         "meta": {...}
    48     44       },
    49     45       {
    50     46         "id": "00001004000000",
    51         -      "url": "/z/00001004000000",
    52     47         "meta": {...}
    53     48       },
    54     49       ...
    55     50       {
    56     51         "id": "00001014000000",
    57         -      "url": "/z/00001014000000",
    58     52         "meta": {...}
    59     53       }
    60     54     ]
    61     55   }
    62     56   ````
    63         -=== Kind
           57  +
    64     58   The following top-level JSON keys are returned:
    65     59   ; ''id''
    66     60   : The zettel identifier for which the references were requested.
    67         -; ''url''
    68         -: The API endpoint to fetch more information about the zettel.
    69     61   ; ''meta'':
    70     62   : The metadata of the zettel, encoded as a JSON object.
    71     63   ; ''list''
    72         -: A list of JSON objects with keys ''id'', ''url'', and ''meta'' that describe other zettel in the defined order.
           64  +: A list of JSON objects with keys ''id'' and ''meta'' that describe other zettel in the defined order.
    73     65   
    74     66   === HTTP Status codes
    75     67   ; ''200''
    76     68   : Retrieval was successful, the body contains an appropriate JSON object.
    77     69   ; ''400''
    78     70   : Request was not valid.
    79     71   ; ''403''
    80     72   : You are not allowed to retrieve data of the given zettel.
    81     73   ; ''404''
    82     74   : Zettel not found.
    83     75     You probably used a zettel identifier that is not used in the Zettelstore.

Changes to docs/manual/00001012054200.zettel.

     1      1   id: 00001012054200
     2      2   title: API: Update a zettel
     3      3   role: manual
     4      4   tags: #api #manual #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210713163606
            6  +modified: 20210905204628
     7      7   
     8      8   Updating metadata and content of a zettel is technically quite similar to [[creating a new zettel|00001012053200]].
     9      9   In both cases you must provide the data for the new or updated zettel in the body of the HTTP request.
    10     10   
    11     11   One difference is the endpoint.
    12         -The [[endpoint|00001012920000]] to update a zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the zettel identifier (14 digits).
           12  +The [[endpoint|00001012920000]] to update a zettel is ''/j/{ID}'', where ''{ID}'' is a placeholder for the zettel identifier (14 digits).
    13     13   You must send a HTTP PUT request to that endpoint:
    14     14   
    15     15   ```
    16         -# curl -X PUT --data '{}' http://127.0.0.1:23123/z/00001012054200
           16  +# curl -X PUT --data '{}' http://127.0.0.1:23123/j/00001012054200
    17     17   ```
    18     18   This will put some empty content and metadata to the zettel you are currently reading.
    19     19   As usual, some metadata will be calculated if it is empty.
    20     20   
    21     21   The body of the HTTP response is empty, if the request was successful.
           22  +
           23  +[!plain]Alternatively, you can use the [[endpoint|00001012920000]] ''/z/{ID}'' to update a zettel.
           24  +In this case, the zettel must be encoded in a [[plain|00001006000000]] format: first comes the [[metadata|00001006010000]] and the following content is separated by an empty line.
           25  +This is the same format as used by storing zettel within a [[directory box|00001006010000]].
           26  +```
           27  +# curl -X POST --data $'title: Updated Note\n\nUpdated content.' http://127.0.0.1:23123/z/00001012054200
           28  +```
    22     29   
    23     30   === HTTP Status codes
    24     31   ; ''204''
    25     32   : Update was successful, there is no body in the response.
    26     33   ; ''400''
    27     34   : Request was not valid.
    28     35     For example, the request body was not valid.
    29     36   ; ''403''
    30     37   : You are not allowed to delete the given zettel.
    31     38   ; ''404''
    32     39   : Zettel not found.
    33     40     You probably used a zettel identifier that is not used in the Zettelstore.

Changes to docs/manual/00001012054400.zettel.

     1      1   id: 00001012054400
     2      2   title: API: Rename a zettel
     3      3   role: manual
     4      4   tags: #api #manual #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210713163708
            6  +modified: 20210905204715
     7      7   
     8      8   Renaming a zettel is effectively just specifying a new identifier for the zettel.
     9      9   Since more than one [[box|00001004011200]] might contain a zettel with the old identifier, the rename operation must success in every relevant box to be overall successful.
    10     10   If the rename operation fails in one box, Zettelstore tries to rollback previous successful operations.
    11     11   
    12     12   As a consequence, you cannot rename a zettel when its identifier is used in a read-only box.
    13     13   This applies to all predefined zettel, for example.
    14     14   
    15         -The [[endpoint|00001012920000]] to rename a zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the zettel identifier (14 digits).
           15  +The [[endpoint|00001012920000]] to rename a zettel is ''/j/{ID}'', where ''{ID}'' is a placeholder for the zettel identifier (14 digits).
    16     16   You must send a HTTP MOVE request to this endpoint, and you must specify the new zettel identifier as an URL, placed under the HTTP request header key ''Destination''.
    17     17   ```
    18         -# curl -X MOVE -H "Destination: 10000000000001" http://127.0.0.1:23123/z/00001000000000
           18  +# curl -X MOVE -H "Destination: 10000000000001" http://127.0.0.1:23123/j/00001000000000
    19     19   ```
    20     20   
    21     21   Only the last 14 characters of the value of ''Destination'' are taken into account and those must form an unused zettel identifier.
    22     22   If the value contains less than 14 characters that do not form an unused zettel identifier, the response will contain a HTTP status code ''400''.
    23     23   All other characters, besides those 14 digits, are effectively ignored.
    24     24   However, the value should form a valid URL that could be used later to [[read the content|00001012053400]] of the freshly renamed zettel.
           25  +
           26  +[!plain]Alternatively, you can also use the [[endpoint|00001012920000]] ''/z/{ID}''.
           27  +Both endpoints behave identical.
    25     28   
    26     29   === HTTP Status codes
    27     30   ; ''204''
    28     31   : Rename was successful, there is no body in the response.
    29     32   ; ''400''
    30     33   : Request was not valid.
    31     34     For example, the HTTP header did not contain a valid ''Destination'' key, or the new identifier is already in use.

Changes to docs/manual/00001012054600.zettel.

     1      1   id: 00001012054600
     2      2   title: API: Delete a zettel
     3      3   role: manual
     4      4   tags: #api #manual #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210713163722
            6  +modified: 20210905204749
     7      7   
     8      8   Deleting a zettel within the Zettelstore is executed on the first [[box|00001004011200]] that contains that zettel.
     9      9   Zettel with the same identifier, but in subsequent boxes remain.
    10     10   If the first box containing the zettel is read-only, deleting that zettel will fail, as well for a Zettelstore in [[read-only mode|00001004010000#read-only-mode]] or if authentication is enabled and the user has no [[access right|00001010070600]] to do so.
    11     11   
    12         -The [[endpoint|00001012920000]] to delete a zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the zettel identifier (14 digits).
           12  +The [[endpoint|00001012920000]] to delete a zettel is ''/j/{ID}'', where ''{ID}'' is a placeholder for the zettel identifier (14 digits).
    13     13   You must send a HTTP DELETE request to this endpoint:
    14     14   ```
    15         -# curl -X DELETE http://127.0.0.1:23123/z/00001000000000
           15  +# curl -X DELETE http://127.0.0.1:23123/j/00001000000000
    16     16   ```
           17  +
           18  +[!plain]Alternatively, you can also use the [[endpoint|00001012920000]] ''/z/{ID}''.
           19  +Both endpoints behave identical.
    17     20   
    18     21   === HTTP Status codes
    19     22   ; ''204''
    20     23   : Delete was successful, there is no body in the response.
    21     24   ; ''403''
    22     25   : You are not allowed to delete the given zettel.
    23     26     Maybe you do not have enough access rights, or either the box or Zettelstore itself operate in read-only mode.
    24     27   ; ''404''
    25     28   : Zettel not found.
    26     29     You probably specified a zettel identifier that is not used in the Zettelstore.

Changes to docs/manual/00001012920000.zettel.

     1      1   id: 00001012920000
     2      2   title: Endpoints used by the API
     3      3   role: manual
     4      4   tags: #api #manual #reference #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210712225257
            6  +modified: 20210908220438
     7      7   
     8      8   All API endpoints conform to the pattern ''[PREFIX]LETTER[/ZETTEL-ID]'', where:
     9      9   ; ''PREFIX''
    10     10   : is the URL prefix (default: ''/''), configured via the ''url-prefix'' [[startup configuration|00001004010000]],
    11     11   ; ''LETTER''
    12     12   : is a single letter that specifies the ressource type,
    13     13   ; ''ZETTEL-ID''
    14     14   : is an optional 14 digits string that uniquely [[identify a zettel|00001006050000]].
    15     15   
    16     16   The following letters are currently in use:
    17     17   
    18     18   |= Letter:| Without zettel identifier | With [[zettel identifier|00001006050000]] | Mnemonic
    19         -| ''l'' |  | GET: [[list references|00001012053600]] | **L**inks
    20         -| ''o'' |  | GET: [[list zettel order|00001012054000]] | **O**rder
    21         -| ''r'' | GET: [[list roles|00001012052400]] | | **R**oles
    22         -| ''t'' | GET: [[list tags|00001012052200]] || **T**ags
    23         -| ''v'' | POST: [[client authentication|00001012050200]] | | **V**erb├╝rgen[^German translation for ""authentication"", since ''/a'' is already in use by the [[web user interface|00001014000000]].]
           19  +| ''a'' | POST: [[client authentication|00001012050200]] | | **A**uthenticate
    24     20   |       | PUT: [[renew access token|00001012050400]] |
    25         -| ''x'' |  | GET: [[list zettel context|00001012053800]] | Conte**x**t
    26         -| ''z'' | GET: [[list zettel|00001012051200]] | GET: [[retrieve zettel|00001012053400]] | **Z**ettel
           21  +| ''j'' | GET: [[list zettel AS JSON|00001012051200]] | GET: [[retrieve zettel AS JSON|00001012053400]] | **J**SON
    27     22   |       | POST: [[create new zettel|00001012053200]] | PUT: [[update a zettel|00001012054200]]
    28     23   |       |  | DELETE: [[delete the zettel|00001012054600]]
    29     24   |       |  | MOVE: [[rename the zettel|00001012054400]]
           25  +| ''l'' |  | GET: [[list references|00001012053700]] | **L**inks
           26  +| ''o'' |  | GET: [[list zettel order|00001012054000]] | **O**rder
           27  +| ''p'' |  | GET: [[retrieve parsed zettel|00001012053600]]| **P**arsed
           28  +| ''r'' | GET: [[list roles|00001012052600]] | | **R**oles
           29  +| ''t'' | GET: [[list tags|00001012052400]] || **T**ags
           30  +| ''v'' |  | GET: [[retrieve evaluated zettel|00001012053500]] | E**v**aluated
           31  +| ''x'' |  | GET: [[list zettel context|00001012053800]] | Conte**x**t
           32  +| ''z'' | GET: [[list zettel|00001012051200#plain]] | GET: [[retrieve zettel|00001012053400#plain]] | **Z**ettel
           33  +|       | POST: [[create new zettel|00001012053200#plain]] | PUT: [[update a zettel|00001012054200#plain]]
           34  +|       |  | DELETE: [[delete zettel|00001012054600#plain]]
           35  +|       |  | MOVE: [[rename zettel|00001012054400#plain]]
    30     36   
    31     37   The full URL will contain either the ''http'' oder ''https'' scheme, a host name, and an optional port number.
    32     38   
    33     39   The API examples will assume the ''http'' schema, the local host ''127.0.0.1'', the default port ''23123'', and the default empty ''PREFIX''.
    34     40   Therefore, all URLs in the API documentation will begin with ''http://127.0.0.1:23123''.

Changes to docs/manual/00001012920500.zettel.

     1      1   id: 00001012920500
     2         -title: Formats available by the API
            2  +title: Encodings available by the API
            3  +role: manual
     3      4   tags: #api #manual #reference #zettelstore
     4      5   syntax: zmk
     5         -role: manual
            6  +modified: 20210727120214
     6      7   
     7      8   A zettel representation can be encoded in various formats for further processing.
     8      9   
     9         -* [[json|00001012920501]] (default)
    10         -* [[djson|00001012920503]]
           10  +* [[djson|00001012920503]] (default)
    11     11   * [[html|00001012920510]]
    12     12   * [[native|00001012920513]]
    13         -* [[raw|00001012920516]]
    14     13   * [[text|00001012920519]]
    15     14   * [[zmk|00001012920522]]
    16         -
    17         -Planned formats:
    18         -; ''pjson''
    19         -: Encoder that emits JSON data that can be fed into [[pandoc|https://pandoc.org]] for further processing.

Deleted docs/manual/00001012920501.zettel.

     1         -id: 00001012920501
     2         -title: JSON Format
     3         -role: manual
     4         -tags: #api #manual #reference #zettelstore
     5         -syntax: zmk
     6         -
     7         -This is the default representation of a zettel or a list of zettel.
     8         -Basically, user provided data is encoded as a string (zettel content and metadata values),
     9         -The metadata and some other structuring data is encoded a JSON object.
    10         -
    11         -The JSON objects contains various name/value pairs, depending which content should be encoded:
    12         -
    13         -* ''"id"'': the [[zettel identifier|00001006050000]], as a string.
    14         -* ''"url"'': the base URL to the zettel, as a string.
    15         -* ''"meta"'': the metadata of the zettel, as an JSON object. See below for details.
    16         -* ''"encoding"'' and ''"content"'': the actual content of the zettel. See below for details.
    17         -
    18         -''"id"'' and ''"url"'' are always sent to the client.
    19         -It depends on the value of the required [[zettel part|00001012920800]], whether ''"meta"'' or ''"content"'' or both are sent.
    20         -
    21         -For an example, take a look at the JSON encoding of this page, which is available via the ""Info"" sub-page of this zettel: 
    22         -
    23         -* [[//z/00001012920501?_part=id]],
    24         -* [[//z/00001012920501?_part=zettel]],
    25         -* [[//z/00001012920501?_part=meta]],
    26         -* [[//z/00001012920501?_part=content]].
    27         -
    28         -If transferred via HTTP, the content type will be ''application/json''.
    29         -
    30         -=== Metadata
    31         -This ia a JSON object, that maps [[metadata keys|00001006010000]] to their values.
    32         -Their values are encoded as strings, even if they contain a number (or something else).
    33         -
    34         -You can always expect the keys ''"title"'', ''"tags"'', ''"syntax"'', and ''"role"'', together with their values.
    35         -The Zettelstore provides default values for these values, if they are not set for a zettel.
    36         -
    37         -There is a list of [[supported metadata keys|00001006020000]].
    38         -
    39         -=== Content
    40         -When the content is text-only, it is encoded as a plain string with an ''"encoding"'' value of ''""'' (empty string).
    41         -
    42         -If the content contains binary content:
    43         -
    44         -* ''"encoding"'' specifies the string encoding of the content.
    45         -  Currently, only the value ''"base64"'' is supported (as described in [[RFC4648, section 4|https://tools.ietf.org/html/rfc4648#section-4]].
    46         -* ''"value"'' is a string that contains the encoded binary content.
    47         -
    48         -For example, if the content just consists of three zero bytes, it will be encoded as ``(...),"encoding":"base64","value":"AAAA"``{=json}.

Changes to docs/manual/00001012920503.zettel.

     1      1   id: 00001012920503
     2         -title: DJSON Format
            2  +title: DJSON Encoding
     3      3   role: manual
     4      4   tags: #api #manual #reference #zettelstore
     5      5   syntax: zmk
            6  +modified: 20210727120128
     6      7   
     7      8   A zettel representation that allows to process the syntactic structure of a zettel.
     8         -It is a JSON-based encoding format, but different to [[json|00001012920501]].
            9  +It is a JSON-based encoding format, but different to the structures returned by [[endpoint|00001012920000]] ''/j/{ID}''.
     9     10   
    10     11   For an example, take a look at the JSON encoding of this page, which is available via the ""Info"" sub-page of this zettel: 
    11     12   
    12         -* [[../z/00001012920503?_format=djson&_part=id]],
    13         -* [[../z/00001012920503?_format=djson&_part=zettel]],
    14         -* [[../z/00001012920503?_format=djson&_part=meta]],
    15         -* [[../z/00001012920503?_format=djson&_part=content]].
           13  +* [[//v/00001012920503?_enc=djson&_part=id]],
           14  +* [[//v/00001012920503?_enc=djson&_part=zettel]],
           15  +* [[//v/00001012920503?_enc=djson&_part=meta]],
           16  +* [[//v/00001012920503?_enc=djson&_part=content]].
    16     17   
    17     18   If transferred via HTTP, the content type will be ''application/json''.
    18     19   
    19     20   TODO: detailed description.

Changes to docs/manual/00001012920510.zettel.

     1      1   id: 00001012920510
     2         -title: HTML Format
            2  +title: HTML Encoding
            3  +role: manual
     3      4   tags: #api #manual #reference #zettelstore
     4      5   syntax: zmk
     5         -role: manual
            6  +modified: 20210726193034
     6      7   
     7      8   A zettel representation in HTML.
     8      9   This representation is different form the [[web user interface|00001014000000]] as it contains the zettel representation only and no additional data such as the menu bar.
     9     10   
    10     11   It is intended to be used by external clients.
    11     12   
    12     13   If transferred via HTTP, the content type will be ''text/html''.

Changes to docs/manual/00001012920513.zettel.

     1      1   id: 00001012920513
     2         -title: Native Format
            2  +title: Native Encoding
            3  +role: manual
     3      4   tags: #api #manual #reference #zettelstore
     4      5   syntax: zmk
     5         -role: manual
            6  +modified: 20210726193049
     6      7   
     7      8   A zettel representation shows the structure of a zettel in a more user-friendly way.
     8      9   Mostly used for debugging.
     9     10   
    10     11   If transferred via HTTP, the content type will be ''text/plain''.
    11     12   
    12     13   TODO: formal description

Deleted docs/manual/00001012920516.zettel.

     1         -id: 00001012920516
     2         -title: Raw Format
     3         -tags: #api #manual #reference #zettelstore
     4         -syntax: zmk
     5         -role: manual
     6         -
     7         -A zettel representation as it was loaded from the zettel content.
     8         -
     9         -Often used to have access to the bytes of an image.
    10         -
    11         -If transferred via HTTP, the content type will depend on various factors:
    12         -
    13         -* If the whole zettel should be transferred and it contains textual data only, ''text/plain'' will be used.
    14         -Otherwise the content type of the whole zettel will be ''application/octet-stream''.
    15         -* If just metadata is transferred, ''text/plain'' will be used.
    16         -* If just the content has to be transferred, the content type depends on the actual dats.
    17         -  A PNG image will be transferred as ''image/png'', HTML content as ''text/html'', and so on.

Changes to docs/manual/00001012920519.zettel.

     1      1   id: 00001012920519
     2         -title: Text Format
            2  +title: Text Encoding
            3  +role: manual
     3      4   tags: #api #manual #reference #zettelstore
     4      5   syntax: zmk
     5         -role: manual
            6  +modified: 20210726193119
     6      7   
     7      8   A zettel representation contains just all textual data of a zettel.
     8      9   Could be used for creating a search index.
     9     10   
    10     11   Every line may contain zero, one, or more words, spearated by space character.
    11     12   
    12     13   If transferred via HTTP, the content type will be ''text/plain''.

Changes to docs/manual/00001012920522.zettel.

     1      1   id: 00001012920522
     2         -title: Zmk Format
            2  +title: Zmk Encoding
            3  +role: manual
     3      4   tags: #api #manual #reference #zettelstore
     4      5   syntax: zmk
     5         -role: manual
            6  +modified: 20210726193136
     6      7   
     7      8   A zettel representation that tries to recreate a [[Zettelmarkup|00001007000000]] representation of the zettel.
     8      9   Useful if you want to convert [[other markup languages|00001008000000]] to Zettelmarkup (e.g. Markdown).
     9     10   
    10     11   If transferred via HTTP, the content type will be ''text/plain''.

Changes to docs/manual/00001012920800.zettel.

     1      1   id: 00001012920800
     2      2   title: Values to specify zettel parts
            3  +role: manual
     3      4   tags: #api #manual #reference #zettelstore
     4      5   syntax: zmk
     5         -role: manual
            6  +modified: 20210727125306
     6      7   
     7      8   When working with [[zettel|00001006000000]], you could work with the whole zettel, with its metadata, or with its content:
     8      9   ; [!zettel]''zettel''
     9     10   : Specifies that you work with a zettel as a whole.
    10     11     Contains identifier, metadata, and content of a zettel.
    11     12   ; [!meta]''meta''
    12     13   : Specifies that you only want to cope with the metadata of a zettel.
    13     14     Contains identifier and metadata of a zettel.
    14     15   ; [!content]''content''
    15     16   : Specifies that you are only interested in the zettel content.
    16     17     Contains identifier and content of a zettel.
    17         -; [!id]''id''
    18         -: States that you just want the zettel identifier and the URL of the zettel.
    19         -  Only valid for JSON-based encoding formats[^[[json|00001012920501]] and [[djson|00001012920503]].].

Changes to domain/content.go.

    10     10   
    11     11   // Package domain provides domain specific types, constants, and functions.
    12     12   package domain
    13     13   
    14     14   import (
    15     15   	"encoding/base64"
    16     16   	"errors"
           17  +	"io"
    17     18   	"unicode/utf8"
    18     19   
    19     20   	"zettelstore.de/z/strfun"
    20     21   )
    21     22   
    22     23   // Content is just the content of a zettel.
    23     24   type Content struct {
................................................................................
    31     32   }
    32     33   
    33     34   // Set content to new string value.
    34     35   func (zc *Content) Set(s string) {
    35     36   	zc.data = s
    36     37   	zc.isBinary = calcIsBinary(s)
    37     38   }
           39  +
           40  +// Write it to a Writer
           41  +func (zc *Content) Write(w io.Writer) (int, error) {
           42  +	return io.WriteString(w, zc.data)
           43  +}
    38     44   
    39     45   // AsString returns the content itself is a string.
    40     46   func (zc *Content) AsString() string { return zc.data }
    41     47   
    42     48   // AsBytes returns the content itself is a byte slice.
    43     49   func (zc *Content) AsBytes() []byte { return []byte(zc.data) }
    44     50   

Changes to domain/meta/meta.go.

   109    109   // Supported keys.
   110    110   var (
   111    111   	KeyID                = registerKey("id", TypeID, usageComputed, "")
   112    112   	KeyTitle             = registerKey("title", TypeZettelmarkup, usageUser, "")
   113    113   	KeyRole              = registerKey("role", TypeWord, usageUser, "")
   114    114   	KeyTags              = registerKey("tags", TypeTagSet, usageUser, "")
   115    115   	KeySyntax            = registerKey("syntax", TypeWord, usageUser, "")
          116  +	KeyAllTags           = registerKey("all-"+KeyTags, TypeTagSet, usageProperty, "")
   116    117   	KeyBack              = registerKey("back", TypeIDSet, usageProperty, "")
   117    118   	KeyBackward          = registerKey("backward", TypeIDSet, usageProperty, "")
   118    119   	KeyBoxNumber         = registerKey("box-number", TypeNumber, usageComputed, "")
   119    120   	KeyCopyright         = registerKey("copyright", TypeString, usageUser, "")
   120    121   	KeyCredential        = registerKey("credential", TypeCredential, usageUser, "")
   121    122   	KeyDead              = registerKey("dead", TypeIDSet, usageProperty, "")
   122    123   	KeyDefaultCopyright  = registerKey("default-copyright", TypeString, usageUser, "")
................................................................................
   131    132   	KeyFolge             = registerKey("folge", TypeIDSet, usageProperty, "")
   132    133   	KeyFooterHTML        = registerKey("footer-html", TypeString, usageUser, "")
   133    134   	KeyForward           = registerKey("forward", TypeIDSet, usageProperty, "")
   134    135   	KeyHomeZettel        = registerKey("home-zettel", TypeID, usageUser, "")
   135    136   	KeyLang              = registerKey("lang", TypeWord, usageUser, "")
   136    137   	KeyLicense           = registerKey("license", TypeEmpty, usageUser, "")
   137    138   	KeyMarkerExternal    = registerKey("marker-external", TypeEmpty, usageUser, "")
          139  +	KeyMaxTransclusions  = registerKey("max-transclusions", TypeNumber, usageUser, "")
   138    140   	KeyModified          = registerKey("modified", TypeTimestamp, usageComputed, "")
   139    141   	KeyNoIndex           = registerKey("no-index", TypeBool, usageUser, "")
   140    142   	KeyPrecursor         = registerKey("precursor", TypeIDSet, usageUser, KeyFolge)
   141    143   	KeyPublished         = registerKey("published", TypeTimestamp, usageProperty, "")
   142    144   	KeyReadOnly          = registerKey("read-only", TypeWord, usageUser, "")
   143    145   	KeySiteName          = registerKey("site-name", TypeString, usageUser, "")
   144    146   	KeyURL               = registerKey("url", TypeURL, usageUser, "")
................................................................................
   178    180   // Meta contains all meta-data of a zettel.
   179    181   type Meta struct {
   180    182   	Zid     id.Zid
   181    183   	pairs   map[string]string
   182    184   	YamlSep bool
   183    185   }
   184    186   
   185         -// New creates a new chunk for storing meta-data
          187  +// New creates a new chunk for storing metadata.
   186    188   func New(zid id.Zid) *Meta {
   187    189   	return &Meta{Zid: zid, pairs: make(map[string]string, 5)}
   188    190   }
          191  +
          192  +// NewWithData creates metadata object with given data.
          193  +func NewWithData(zid id.Zid, data map[string]string) *Meta {
          194  +	pairs := make(map[string]string, len(data))
          195  +	for k, v := range data {
          196  +		pairs[k] = v
          197  +	}
          198  +	return &Meta{Zid: zid, pairs: pairs}
          199  +}
   189    200   
   190    201   // Clone returns a new copy of the metadata.
   191    202   func (m *Meta) Clone() *Meta {
   192    203   	return &Meta{
   193    204   		Zid:     m.Zid,
   194    205   		pairs:   m.Map(),
   195    206   		YamlSep: m.YamlSep,

Changes to domain/meta/parse.go.

   158    158   
   159    159   	switch Type(key) {
   160    160   	case TypeString, TypeZettelmarkup:
   161    161   		if v != "" {
   162    162   			addData(m, key, v)
   163    163   		}
   164    164   	case TypeTagSet:
   165         -		addSet(m, key, v, func(s string) bool { return s[0] == '#' })
          165  +		addSet(m, key, strings.ToLower(v), func(s string) bool { return s[0] == '#' })
   166    166   	case TypeWord:
   167    167   		m.Set(key, strings.ToLower(v))
   168    168   	case TypeWordSet:
   169    169   		addSet(m, key, strings.ToLower(v), func(s string) bool { return true })
   170    170   	case TypeID:
   171    171   		if _, err := id.Parse(v); err == nil {
   172    172   			m.Set(key, v)

Changes to domain/meta/type.go.

    53     53   	TypeWord         = registerType("Word", false)
    54     54   	TypeWordSet      = registerType("WordSet", true)
    55     55   	TypeZettelmarkup = registerType("Zettelmarkup", false)
    56     56   )
    57     57   
    58     58   // Type returns a type hint for the given key. If no type hint is specified,
    59     59   // TypeUnknown is returned.
    60         -func (m *Meta) Type(key string) *DescriptionType {
           60  +func (*Meta) Type(key string) *DescriptionType {
    61     61   	return Type(key)
    62     62   }
    63     63   
    64     64   var (
    65     65   	cachedTypedKeys = make(map[string]*DescriptionType)
    66     66   	mxTypedKey      sync.RWMutex
           67  +	suffixTypes     = map[string]*DescriptionType{
           68  +		"-number": TypeNumber,
           69  +		"-role":   TypeWord,
           70  +		"-url":    TypeURL,
           71  +		"-zid":    TypeID,
           72  +	}
    67     73   )
    68     74   
    69         -func typedKey(key string, t *DescriptionType) *DescriptionType {
    70         -	mxTypedKey.Lock()
    71         -	defer mxTypedKey.Unlock()
    72         -	cachedTypedKeys[key] = t
    73         -	return t
    74         -}
    75         -
    76     75   // Type returns a type hint for the given key. If no type hint is specified,
    77     76   // TypeUnknown is returned.
    78     77   func Type(key string) *DescriptionType {
    79     78   	if k, ok := registeredKeys[key]; ok {
    80     79   		return k.Type
    81     80   	}
    82     81   	mxTypedKey.RLock()
    83     82   	k, ok := cachedTypedKeys[key]
    84     83   	mxTypedKey.RUnlock()
    85     84   	if ok {
    86     85   		return k
    87     86   	}
    88         -	if strings.HasSuffix(key, "-url") {
    89         -		return typedKey(key, TypeURL)
    90         -	}
    91         -	if strings.HasSuffix(key, "-number") {
    92         -		return typedKey(key, TypeNumber)
    93         -	}
    94         -	if strings.HasSuffix(key, "-zid") {
    95         -		return typedKey(key, TypeID)
           87  +	for suffix, t := range suffixTypes {
           88  +		if strings.HasSuffix(key, suffix) {
           89  +			mxTypedKey.Lock()
           90  +			defer mxTypedKey.Unlock()
           91  +			cachedTypedKeys[key] = t
           92  +			return t
           93  +		}
    96     94   	}
    97     95   	return TypeEmpty
    98     96   }
    99     97   
   100     98   // SetList stores the given string list value under the given key.
   101     99   func (m *Meta) SetList(key string, values []string) {
   102    100   	if key != KeyID {
................................................................................
   103    101   		for i, val := range values {
   104    102   			values[i] = trimValue(val)
   105    103   		}
   106    104   		m.pairs[key] = strings.Join(values, " ")
   107    105   	}
   108    106   }
   109    107   
   110         -// CleanTag removes the number charachter ('#') from a tag value.
   111         -func CleanTag(tag string) string {
   112         -	if len(tag) > 1 && tag[0] == '#' {
   113         -		return tag[1:]
   114         -	}
   115         -	return tag
   116         -}
   117         -
   118    108   // SetNow stores the current timestamp under the given key.
   119    109   func (m *Meta) SetNow(key string) {
   120    110   	m.Set(key, time.Now().Format("20060102150405"))
   121    111   }
   122    112   
   123    113   // BoolValue returns the value interpreted as a bool.
   124    114   func BoolValue(value string) bool {
................................................................................
   169    159   	}
   170    160   	return ListFromValue(value), true
   171    161   }
   172    162   
   173    163   // GetTags returns the list of tags as a string list. Each tag does not begin
   174    164   // with the '#' character, in contrast to `GetList`.
   175    165   func (m *Meta) GetTags(key string) ([]string, bool) {
   176         -	tags, ok := m.GetList(key)
          166  +	tagsValue, ok := m.Get(key)
   177    167   	if !ok {
   178    168   		return nil, false
   179    169   	}
          170  +	tags := ListFromValue(strings.ToLower(tagsValue))
   180    171   	for i, tag := range tags {
   181    172   		tags[i] = CleanTag(tag)
   182    173   	}
   183    174   	return tags, len(tags) > 0
   184    175   }
          176  +
          177  +// CleanTag removes the number character ('#') from a tag value and lowercases it.
          178  +func CleanTag(tag string) string {
          179  +	if len(tag) > 1 && tag[0] == '#' {
          180  +		return tag[1:]
          181  +	}
          182  +	return tag
          183  +}
   185    184   
   186    185   // GetListOrNil retrieves the string list value of a given key. If there was
   187    186   // nothing stores, a nil list is returned.
   188    187   func (m *Meta) GetListOrNil(key string) []string {
   189    188   	if value, ok := m.GetList(key); ok {
   190    189   		return value
   191    190   	}

Added encoder/djsonenc/djsonenc.go.

            1  +//-----------------------------------------------------------------------------
            2  +// Copyright (c) 2020-2021 Detlef Stern
            3  +//
            4  +// This file is part of zettelstore.
            5  +//
            6  +// Zettelstore is licensed under the latest version of the EUPL (European Union
            7  +// Public License). Please see file LICENSE.txt for your rights and obligations
            8  +// under this license.
            9  +//-----------------------------------------------------------------------------
           10  +
           11  +// Package jsonenc encodes the abstract syntax tree into JSON.
           12  +package jsonenc
           13  +
           14  +import (
           15  +	"fmt"
           16  +	"io"
           17  +	"sort"
           18  +	"strconv"
           19  +
           20  +	"zettelstore.de/z/api"
           21  +	"zettelstore.de/z/ast"
           22  +	"zettelstore.de/z/domain/meta"
           23  +	"zettelstore.de/z/encoder"
           24  +	"zettelstore.de/z/strfun"
           25  +)
           26  +
           27  +func init() {
           28  +	encoder.Register(api.EncoderDJSON, encoder.Info{
           29  +		Create: func(env *encoder.Environment) encoder.Encoder { return &jsonDetailEncoder{env: env} },
           30  +	})
           31  +}
           32  +
           33  +type jsonDetailEncoder struct {
           34  +	env *encoder.Environment
           35  +}
           36  +
           37  +// WriteZettel writes the encoded zettel to the writer.
           38  +func (je *jsonDetailEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) {
           39  +	v := newDetailVisitor(w, je)
           40  +	v.b.WriteString("{\"meta\":{")
           41  +	v.writeMeta(zn.InhMeta, evalMeta)
           42  +	v.b.WriteByte('}')
           43  +	v.b.WriteString(",\"content\":")
           44  +	ast.Walk(v, zn.Ast)
           45  +	v.b.WriteByte('}')
           46  +	length, err := v.b.Flush()
           47  +	return length, err
           48  +}
           49  +
           50  +// WriteMeta encodes meta data as JSON.
           51  +func (je *jsonDetailEncoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) {
           52  +	v := newDetailVisitor(w, je)
           53  +	v.b.WriteByte('{')
           54  +	v.writeMeta(m, evalMeta)
           55  +	v.b.WriteByte('}')
           56  +	length, err := v.b.Flush()
           57  +	return length, err
           58  +}
           59  +
           60  +func (je *jsonDetailEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
           61  +	return je.WriteBlocks(w, zn.Ast)
           62  +}
           63  +
           64  +// WriteBlocks writes a block slice to the writer
           65  +func (je *jsonDetailEncoder) WriteBlocks(w io.Writer, bln *ast.BlockListNode) (int, error) {
           66  +	v := newDetailVisitor(w, je)
           67  +	ast.Walk(v, bln)
           68  +	length, err := v.b.Flush()
           69  +	return length, err
           70  +}
           71  +
           72  +// WriteInlines writes an inline slice to the writer
           73  +func (je *jsonDetailEncoder) WriteInlines(w io.Writer, iln *ast.InlineListNode) (int, error) {
           74  +	v := newDetailVisitor(w, je)
           75  +	ast.Walk(v, iln)
           76  +	length, err := v.b.Flush()
           77  +	return length, err
           78  +}
           79  +
           80  +// visitor writes the abstract syntax tree to an io.Writer.
           81  +type visitor struct {
           82  +	b   encoder.BufWriter
           83  +	env *encoder.Environment
           84  +}
           85  +
           86  +func newDetailVisitor(w io.Writer, je *jsonDetailEncoder) *visitor {
           87  +	return &visitor{b: encoder.NewBufWriter(w), env: je.env}
           88  +}
           89  +
           90  +func (v *visitor) Visit(node ast.Node) ast.Visitor {
           91  +	switch n := node.(type) {
           92  +	case *ast.BlockListNode:
           93  +		v.visitBlockList(n)
           94  +		return nil
           95  +	case *ast.InlineListNode:
           96  +		v.walkInlineList(n)
           97  +		return nil
           98  +	case *ast.ParaNode:
           99  +		v.writeNodeStart("Para")
          100  +		v.writeContentStart('i')
          101  +		ast.Walk(v, n.Inlines)
          102  +	case *ast.VerbatimNode:
          103  +		v.visitVerbatim(n)
          104  +	case *ast.RegionNode:
          105  +		v.visitRegion(n)
          106  +	case *ast.HeadingNode:
          107  +		v.visitHeading(n)
          108  +	case *ast.HRuleNode:
          109  +		v.writeNodeStart("Hrule")
          110  +		v.visitAttributes(n.Attrs)
          111  +	case *ast.NestedListNode:
          112  +		v.visitNestedList(n)
          113  +	case *ast.DescriptionListNode:
          114  +		v.visitDescriptionList(n)
          115  +	case *ast.TableNode:
          116  +		v.visitTable(n)
          117  +	case *ast.BLOBNode:
          118  +		v.writeNodeStart("Blob")
          119  +		v.writeContentStart('q')
          120  +		writeEscaped(&v.b, n.Title)
          121  +		v.writeContentStart('s')
          122  +		writeEscaped(&v.b, n.Syntax)
          123  +		v.writeContentStart('o')
          124  +		v.b.WriteBase64(n.Blob)
          125  +		v.b.WriteByte('"')
          126  +	case *ast.TextNode:
          127  +		v.writeNodeStart("Text")
          128  +		v.writeContentStart('s')
          129  +		writeEscaped(&v.b, n.Text)
          130  +	case *ast.TagNode:
          131  +		v.writeNodeStart("Tag")
          132  +		v.writeContentStart('s')
          133  +		writeEscaped(&v.b, n.Tag)
          134  +	case *ast.SpaceNode:
          135  +		v.writeNodeStart("Space")
          136  +		if l := len(n.Lexeme); l > 1 {
          137  +			v.writeContentStart('n')
          138  +			v.b.WriteString(strconv.Itoa(l))
          139  +		}
          140  +	case *ast.BreakNode:
          141  +		if n.Hard {
          142  +			v.writeNodeStart("Hard")
          143  +		} else {
          144  +			v.writeNodeStart("Soft")
          145  +		}
          146  +	case *ast.LinkNode:
          147  +		v.writeNodeStart("Link")
          148  +		v.visitAttributes(n.Attrs)
          149  +		v.writeContentStart('q')
          150  +		writeEscaped(&v.b, mapRefState[n.Ref.State])
          151  +		v.writeContentStart('s')
          152  +		writeEscaped(&v.b, n.Ref.String())
          153  +		v.writeContentStart('i')
          154  +		ast.Walk(v, n.Inlines)
          155  +	case *ast.EmbedNode:
          156  +		v.visitEmbed(n)
          157  +	case *ast.CiteNode:
          158  +		v.writeNodeStart("Cite")
          159  +		v.visitAttributes(n.Attrs)
          160  +		v.writeContentStart('s')
          161  +		writeEscaped(&v.b, n.Key)
          162  +		if n.Inlines != nil {
          163  +			v.writeContentStart('i')
          164  +			ast.Walk(v, n.Inlines)
          165  +		}
          166  +	case *ast.FootnoteNode:
          167  +		v.writeNodeStart("Footnote")
          168  +		v.visitAttributes(n.Attrs)
          169  +		v.writeContentStart('i')
          170  +		ast.Walk(v, n.Inlines)
          171  +	case *ast.MarkNode:
          172  +		v.visitMark(n)
          173  +	case *ast.FormatNode:
          174  +		v.writeNodeStart(mapFormatKind[n.Kind])
          175  +		v.visitAttributes(n.Attrs)
          176  +		v.writeContentStart('i')
          177  +		ast.Walk(v, n.Inlines)
          178  +	case *ast.LiteralNode:
          179  +		kind, ok := mapLiteralKind[n.Kind]
          180  +		if !ok {
          181  +			panic(fmt.Sprintf("Unknown literal kind %v", n.Kind))
          182  +		}
          183  +		v.writeNodeStart(kind)
          184  +		v.visitAttributes(n.Attrs)
          185  +		v.writeContentStart('s')
          186  +		writeEscaped(&v.b, n.Text)
          187  +	default:
          188  +		return v
          189  +	}
          190  +	v.b.WriteByte('}')
          191  +	return nil
          192  +}
          193  +
          194  +var mapVerbatimKind = map[ast.VerbatimKind]string{
          195  +	ast.VerbatimProg:    "CodeBlock",
          196  +	ast.VerbatimComment: "CommentBlock",
          197  +	ast.VerbatimHTML:    "HTMLBlock",
          198  +}
          199  +
          200  +func (v *visitor) visitVerbatim(vn *ast.VerbatimNode) {
          201  +	kind, ok := mapVerbatimKind[vn.Kind]
          202  +	if !ok {
          203  +		panic(fmt.Sprintf("Unknown verbatim kind %v", vn.Kind))
          204  +	}
          205  +	v.writeNodeStart(kind)
          206  +	v.visitAttributes(vn.Attrs)
          207  +	v.writeContentStart('l')
          208  +	for i, line := range vn.Lines {
          209  +		v.writeComma(i)
          210  +		writeEscaped(&v.b, line)
          211  +	}
          212  +	v.b.WriteByte(']')
          213  +}
          214  +
          215  +var mapRegionKind = map[ast.RegionKind]string{
          216  +	ast.RegionSpan:  "SpanBlock",
          217  +	ast.RegionQuote: "QuoteBlock",
          218  +	ast.RegionVerse: "VerseBlock",
          219  +}
          220  +
          221  +func (v *visitor) visitRegion(rn *ast.RegionNode) {
          222  +	kind, ok := mapRegionKind[rn.Kind]
          223  +	if !ok {
          224  +		panic(fmt.Sprintf("Unknown region kind %v", rn.Kind))
          225  +	}
          226  +	v.writeNodeStart(kind)
          227  +	v.visitAttributes(rn.Attrs)
          228  +	v.writeContentStart('b')
          229  +	ast.Walk(v, rn.Blocks)
          230  +	if rn.Inlines != nil {
          231  +		v.writeContentStart('i')
          232  +		ast.Walk(v, rn.Inlines)
          233  +	}
          234  +}
          235  +
          236  +func (v *visitor) visitHeading(hn *ast.HeadingNode) {
          237  +	v.writeNodeStart("Heading")
          238  +	v.visitAttributes(hn.Attrs)
          239  +	v.writeContentStart('n')
          240  +	v.b.WriteString(strconv.Itoa(hn.Level))
          241  +	if fragment := hn.Fragment; fragment != "" {
          242  +		v.writeContentStart('s')
          243  +		v.b.WriteStrings("\"", fragment, "\"")
          244  +	}
          245  +	v.writeContentStart('i')
          246  +	ast.Walk(v, hn.Inlines)
          247  +}
          248  +
          249  +var mapNestedListKind = map[ast.NestedListKind]string{
          250  +	ast.NestedListOrdered:   "OrderedList",
          251  +	ast.NestedListUnordered: "BulletList",
          252  +	ast.NestedListQuote:     "QuoteList",
          253  +}
          254  +
          255  +func (v *visitor) visitNestedList(ln *ast.NestedListNode) {
          256  +	v.writeNodeStart(mapNestedListKind[ln.Kind])
          257  +	v.writeContentStart('c')
          258  +	for i, item := range ln.Items {
          259  +		v.writeComma(i)
          260  +		v.b.WriteByte('[')
          261  +		for j, in := range item {
          262  +			v.writeComma(j)
          263  +			ast.Walk(v, in)
          264  +		}
          265  +		v.b.WriteByte(']')
          266  +	}
          267  +	v.b.WriteByte(']')
          268  +}
          269  +
          270  +func (v *visitor) visitDescriptionList(dn *ast.DescriptionListNode) {
          271  +	v.writeNodeStart("DescriptionList")
          272  +	v.writeContentStart('g')
          273  +	for i, def := range dn.Descriptions {
          274  +		v.writeComma(i)
          275  +		v.b.WriteByte('[')
          276  +		ast.Walk(v, def.Term)
          277  +
          278  +		if len(def.Descriptions) > 0 {
          279  +			for _, b := range def.Descriptions {
          280  +				v.b.WriteString(",[")
          281  +				for j, dn := range b {
          282  +					v.writeComma(j)
          283  +					ast.Walk(v, dn)
          284  +				}
          285  +				v.b.WriteByte(']')
          286  +			}
          287  +		}
          288  +		v.b.WriteByte(']')
          289  +	}
          290  +	v.b.WriteByte(']')
          291  +}
          292  +
          293  +func (v *visitor) visitTable(tn *ast.TableNode) {
          294  +	v.writeNodeStart("Table")
          295  +	v.writeContentStart('p')
          296  +
          297  +	// Table header
          298  +	v.b.WriteByte('[')
          299  +	for i, cell := range tn.Header {
          300  +		v.writeComma(i)
          301  +		v.writeCell(cell)
          302  +	}
          303  +	v.b.WriteString("],")
          304  +
          305  +	// Table rows
          306  +	v.b.WriteByte('[')
          307  +	for i, row := range tn.Rows {
          308  +		v.writeComma(i)
          309  +		v.b.WriteByte('[')
          310  +		for j, cell := range row {
          311  +			v.writeComma(j)
          312  +			v.writeCell(cell)
          313  +		}
          314  +		v.b.WriteByte(']')
          315  +	}
          316  +	v.b.WriteString("]]")
          317  +}
          318  +
          319  +var alignmentCode = map[ast.Alignment]string{
          320  +	ast.AlignDefault: "[\"\",",
          321  +	ast.AlignLeft:    "[\"<\",",
          322  +	ast.AlignCenter:  "[\":\",",
          323  +	ast.AlignRight:   "[\">\",",
          324  +}
          325  +
          326  +func (v *visitor) writeCell(cell *ast.TableCell) {
          327  +	v.b.WriteString(alignmentCode[cell.Align])
          328  +	ast.Walk(v, cell.Inlines)
          329  +	v.b.WriteByte(']')
          330  +}
          331  +
          332  +var mapRefState = map[ast.RefState]string{
          333  +	ast.RefStateInvalid:  "invalid",
          334  +	ast.RefStateZettel:   "zettel",
          335  +	ast.RefStateSelf:     "self",
          336  +	ast.RefStateFound:    "zettel",
          337  +	ast.RefStateBroken:   "broken",
          338  +	ast.RefStateHosted:   "local",
          339  +	ast.RefStateBased:    "based",
          340  +	ast.RefStateExternal: "external",
          341  +}
          342  +
          343  +func (v *visitor) visitEmbed(en *ast.EmbedNode) {
          344  +	v.writeNodeStart("Embed")
          345  +	v.visitAttributes(en.Attrs)
          346  +	switch m := en.Material.(type) {
          347  +	case *ast.ReferenceMaterialNode:
          348  +		v.writeContentStart('s')
          349  +		writeEscaped(&v.b, m.Ref.String())
          350  +	case *ast.BLOBMaterialNode:
          351  +		v.writeContentStart('j')
          352  +		v.b.WriteString("\"s\":")
          353  +		writeEscaped(&v.b, m.Syntax)
          354  +		switch m.Syntax {
          355  +		case "svg":
          356  +			v.writeContentStart('q')
          357  +			writeEscaped(&v.b, string(m.Blob))
          358  +		default:
          359  +			v.writeContentStart('o')
          360  +			v.b.WriteBase64(m.Blob)
          361  +			v.b.WriteByte('"')
          362  +		}
          363  +		v.b.WriteByte('}')
          364  +	default:
          365  +		panic(fmt.Sprintf("Unknown material type %t for %v", en.Material, en.Material))
          366  +	}
          367  +
          368  +	if en.Inlines != nil {
          369  +		v.writeContentStart('i')
          370  +		ast.Walk(v, en.Inlines)
          371  +	}
          372  +}
          373  +
          374  +func (v *visitor) visitMark(mn *ast.MarkNode) {
          375  +	v.writeNodeStart("Mark")
          376  +	if text := mn.Text; text != "" {
          377  +		v.writeContentStart('s')
          378  +		writeEscaped(&v.b, text)
          379  +	}
          380  +	if fragment := mn.Fragment; fragment != "" {
          381  +		v.writeContentStart('q')
          382  +		v.b.WriteByte('"')
          383  +		v.b.WriteString(fragment)
          384  +		v.b.WriteByte('"')
          385  +	}
          386  +}
          387  +
          388  +var mapFormatKind = map[ast.FormatKind]string{
          389  +	ast.FormatItalic:    "Italic",
          390  +	ast.FormatEmph:      "Emph",
          391  +	ast.FormatBold:      "Bold",
          392  +	ast.FormatStrong:    "Strong",
          393  +	ast.FormatMonospace: "Mono",
          394  +	ast.FormatStrike:    "Strikethrough",
          395  +	ast.FormatDelete:    "Delete",
          396  +	ast.FormatUnder:     "Underline",
          397  +	ast.FormatInsert:    "Insert",
          398  +	ast.FormatSuper:     "Super",
          399  +	ast.FormatSub:       "Sub",
          400  +	ast.FormatQuote:     "Quote",
          401  +	ast.FormatQuotation: "Quotation",
          402  +	ast.FormatSmall:     "Small",
          403  +	ast.FormatSpan:      "Span",
          404  +}
          405  +
          406  +var mapLiteralKind = map[ast.LiteralKind]string{
          407  +	ast.LiteralProg:    "Code",
          408  +	ast.LiteralKeyb:    "Input",
          409  +	ast.LiteralOutput:  "Output",
          410  +	ast.LiteralComment: "Comment",
          411  +	ast.LiteralHTML:    "HTML",
          412  +}
          413  +
          414  +func (v *visitor) visitBlockList(bln *ast.BlockListNode) {
          415  +	v.b.WriteByte('[')
          416  +	for i, bn := range bln.List {
          417  +		v.writeComma(i)
          418  +		ast.Walk(v, bn)
          419  +	}
          420  +	v.b.WriteByte(']')
          421  +}
          422  +
          423  +func (v *visitor) walkInlineList(iln *ast.InlineListNode) {
          424  +	v.b.WriteByte('[')
          425  +	for i, in := range iln.List {
          426  +		v.writeComma(i)
          427  +		ast.Walk(v, in)
          428  +	}
          429  +	v.b.WriteByte(']')
          430  +}
          431  +
          432  +// visitAttributes write JSON attributes
          433  +func (v *visitor) visitAttributes(a *ast.Attributes) {
          434  +	if a.IsEmpty() {
          435  +		return
          436  +	}
          437  +	keys := make([]string, 0, len(a.Attrs))
          438  +	for k := range a.Attrs {
          439  +		keys = append(keys, k)
          440  +	}
          441  +	sort.Strings(keys)
          442  +
          443  +	v.b.WriteString(",\"a\":{\"")
          444  +	for i, k := range keys {
          445  +		if i > 0 {
          446  +			v.b.WriteString("\",\"")
          447  +		}
          448  +		strfun.JSONEscape(&v.b, k)
          449  +		v.b.WriteString("\":\"")
          450  +		strfun.JSONEscape(&v.b, a.Attrs[k])
          451  +	}
          452  +	v.b.WriteString("\"}")
          453  +}
          454  +
          455  +func (v *visitor) writeNodeStart(t string) {
          456  +	v.b.WriteStrings("{\"t\":\"", t, "\"")
          457  +}
          458  +
          459  +var contentCode = map[rune][]byte{
          460  +	'b': []byte(",\"b\":"),   // List of blocks
          461  +	'c': []byte(",\"c\":["),  // List of list of blocks
          462  +	'g': []byte(",\"g\":["),  // General list
          463  +	'i': []byte(",\"i\":"),   // List of inlines
          464  +	'j': []byte(",\"j\":{"),  // Embedded JSON object
          465  +	'l': []byte(",\"l\":["),  // List of lines
          466  +	'n': []byte(",\"n\":"),   // Number
          467  +	'o': []byte(",\"o\":\""), // Byte object
          468  +	'p': []byte(",\"p\":["),  // Generic tuple
          469  +	'q': []byte(",\"q\":"),   // String, if 's' is also needed
          470  +	's': []byte(",\"s\":"),   // String
          471  +	't': []byte("Content code 't' is not allowed"),
          472  +	'y': []byte("Content code 'y' is not allowed"), // field after 'j'
          473  +}
          474  +
          475  +func (v *visitor) writeContentStart(code rune) {
          476  +	if b, ok := contentCode[code]; ok {
          477  +		v.b.Write(b)
          478  +		return
          479  +	}
          480  +	panic("Unknown content code " + strconv.Itoa(int(code)))
          481  +}
          482  +
          483  +func (v *visitor) writeMeta(m *meta.Meta, evalMeta encoder.EvalMetaFunc) {
          484  +	for i, p := range m.Pairs(true) {
          485  +		if i > 0 {
          486  +			v.b.WriteByte(',')
          487  +		}
          488  +		v.b.WriteByte('"')
          489  +		key := p.Key
          490  +		strfun.JSONEscape(&v.b, key)
          491  +		v.b.WriteString("\":")
          492  +		t := m.Type(key)
          493  +		if t.IsSet {
          494  +			v.writeSetValue(p.Value)
          495  +			continue
          496  +		}
          497  +		if t == meta.TypeZettelmarkup {
          498  +			ast.Walk(v, evalMeta(p.Value))
          499  +			continue
          500  +		}
          501  +		writeEscaped(&v.b, p.Value)
          502  +	}
          503  +}
          504  +
          505  +func (v *visitor) writeSetValue(value string) {
          506  +	v.b.WriteByte('[')
          507  +	for i, val := range meta.ListFromValue(value) {
          508  +		v.writeComma(i)
          509  +		writeEscaped(&v.b, val)
          510  +	}
          511  +	v.b.WriteByte(']')
          512  +}
          513  +
          514  +func (v *visitor) writeComma(pos int) {
          515  +	if pos > 0 {
          516  +		v.b.WriteByte(',')
          517  +	}
          518  +}
          519  +
          520  +func writeEscaped(b *encoder.BufWriter, s string) {
          521  +	b.WriteByte('"')
          522  +	strfun.JSONEscape(b, s)
          523  +	b.WriteByte('"')
          524  +}

Deleted encoder/encfun/encfun.go.

     1         -//-----------------------------------------------------------------------------
     2         -// Copyright (c) 2021 Detlef Stern
     3         -//
     4         -// This file is part of zettelstore.
     5         -//
     6         -// Zettelstore is licensed under the latest version of the EUPL (European Union
     7         -// Public License). Please see file LICENSE.txt for your rights and obligations
     8         -// under this license.
     9         -//-----------------------------------------------------------------------------
    10         -
    11         -// Package encfun provides some helper function to work with encodings.
    12         -package encfun
    13         -
    14         -import (
    15         -	"strings"
    16         -
    17         -	"zettelstore.de/z/api"
    18         -	"zettelstore.de/z/ast"
    19         -	"zettelstore.de/z/domain/meta"
    20         -	"zettelstore.de/z/encoder"
    21         -	"zettelstore.de/z/parser"
    22         -)
    23         -
    24         -// MetaAsInlineSlice returns the value of the given metadata key as an inlince slice.
    25         -func MetaAsInlineSlice(m *meta.Meta, key string) ast.InlineSlice {
    26         -	return parser.ParseMetadata(m.GetDefault(key, ""))
    27         -}
    28         -
    29         -// MetaAsText returns the value of given metadata as text.
    30         -func MetaAsText(m *meta.Meta, key string) string {
    31         -	textEncoder := encoder.Create(api.EncoderText, nil)
    32         -	var sb strings.Builder
    33         -	_, err := textEncoder.WriteInlines(&sb, MetaAsInlineSlice(m, key))
    34         -	if err == nil {
    35         -		return sb.String()
    36         -	}
    37         -	return ""
    38         -}

Changes to encoder/encoder.go.

    10     10   
    11     11   // Package encoder provides a generic interface to encode the abstract syntax
    12     12   // tree into some text form.
    13     13   package encoder
    14     14   
    15     15   import (
    16     16   	"errors"
           17  +	"fmt"
    17     18   	"io"
    18         -	"log"
    19     19   
    20     20   	"zettelstore.de/z/api"
    21     21   	"zettelstore.de/z/ast"
    22     22   	"zettelstore.de/z/domain/meta"
    23     23   )
    24     24   
    25     25   // Encoder is an interface that allows to encode different parts of a zettel.
    26     26   type Encoder interface {
    27         -	WriteZettel(io.Writer, *ast.ZettelNode, bool) (int, error)
    28         -	WriteMeta(io.Writer, *meta.Meta) (int, error)
           27  +	WriteZettel(io.Writer, *ast.ZettelNode, EvalMetaFunc) (int, error)
           28  +	WriteMeta(io.Writer, *meta.Meta, EvalMetaFunc) (int, error)
    29     29   	WriteContent(io.Writer, *ast.ZettelNode) (int, error)
    30         -	WriteBlocks(io.Writer, ast.BlockSlice) (int, error)
    31         -	WriteInlines(io.Writer, ast.InlineSlice) (int, error)
           30  +	WriteBlocks(io.Writer, *ast.BlockListNode) (int, error)
           31  +	WriteInlines(io.Writer, *ast.InlineListNode) (int, error)
    32     32   }
           33  +
           34  +// EvalMetaFunc is a function that takes a string of metadata and returns
           35  +// a list of syntax elements.
           36  +type EvalMetaFunc func(string) *ast.InlineListNode
    33     37   
    34     38   // Some errors to signal when encoder methods are not implemented.
    35     39   var (
    36     40   	ErrNoWriteZettel  = errors.New("method WriteZettel is not implemented")
    37     41   	ErrNoWriteMeta    = errors.New("method WriteMeta is not implemented")
    38     42   	ErrNoWriteContent = errors.New("method WriteContent is not implemented")
    39     43   	ErrNoWriteBlocks  = errors.New("method WriteBlocks is not implemented")
    40     44   	ErrNoWriteInlines = errors.New("method WriteInlines is not implemented")
    41     45   )
    42     46   
    43     47   // Create builds a new encoder with the given options.
    44         -func Create(format api.EncodingEnum, env *Environment) Encoder {
    45         -	if info, ok := registry[format]; ok {
           48  +func Create(enc api.EncodingEnum, env *Environment) Encoder {
           49  +	if info, ok := registry[enc]; ok {
    46     50   		return info.Create(env)
    47     51   	}
    48     52   	return nil
    49     53   }
    50     54   
    51     55   // Info stores some data about an encoder.
    52     56   type Info struct {
    53     57   	Create  func(*Environment) Encoder
    54     58   	Default bool
    55     59   }
    56     60   
    57     61   var registry = map[api.EncodingEnum]Info{}
    58         -var defFormat api.EncodingEnum
           62  +var defEncoding api.EncodingEnum
    59     63   
    60     64   // Register the encoder for later retrieval.
    61         -func Register(format api.EncodingEnum, info Info) {
    62         -	if _, ok := registry[format]; ok {
    63         -		log.Fatalf("Writer with format %q already registered", format)
           65  +func Register(enc api.EncodingEnum, info Info) {
           66  +	if _, ok := registry[enc]; ok {
           67  +		panic(fmt.Sprintf("Encoder %q already registered", enc))
    64     68   	}
    65     69   	if info.Default {
    66         -		if defFormat != api.EncoderUnknown && defFormat != format {
    67         -			log.Fatalf("Default format already set: %q, new format: %q", defFormat, format)
           70  +		if defEncoding != api.EncoderUnknown && defEncoding != enc {
           71  +			panic(fmt.Sprintf("Default encoder already set: %q, new encoding: %q", defEncoding, enc))
    68     72   		}
    69         -		defFormat = format
           73  +		defEncoding = enc
    70     74   	}
    71         -	registry[format] = info
           75  +	registry[enc] = info
    72     76   }
    73     77   
    74         -// GetFormats returns all registered formats, ordered by format code.
    75         -func GetFormats() []api.EncodingEnum {
           78  +// GetEncodings returns all registered encodings, ordered by encoding value.
           79  +func GetEncodings() []api.EncodingEnum {
    76     80   	result := make([]api.EncodingEnum, 0, len(registry))
    77         -	for format := range registry {
    78         -		result = append(result, format)
           81  +	for enc := range registry {
           82  +		result = append(result, enc)
    79     83   	}
    80     84   	return result
    81     85   }
    82     86   
    83         -// GetDefaultFormat returns the format that should be used as default.
    84         -func GetDefaultFormat() api.EncodingEnum {
    85         -	if defFormat != api.EncoderUnknown {
    86         -		return defFormat
           87  +// GetDefaultEncoding returns the encoding that should be used as default.
           88  +func GetDefaultEncoding() api.EncodingEnum {
           89  +	if defEncoding != api.EncoderUnknown {
           90  +		return defEncoding
    87     91   	}
    88         -	if _, ok := registry[api.EncoderJSON]; ok {
    89         -		return api.EncoderJSON
           92  +	if _, ok := registry[api.EncoderDJSON]; ok {
           93  +		return api.EncoderDJSON
    90     94   	}
    91         -	log.Fatalf("No default format given")
    92         -	return api.EncoderUnknown
           95  +	panic("No default encoding given")
    93     96   }

Changes to encoder/env.go.

    12     12   // tree into some text form.
    13     13   package encoder
    14     14   
    15     15   import "zettelstore.de/z/ast"
    16     16   
    17     17   // Environment specifies all data and functions that affects encoding.
    18     18   type Environment struct {
    19         -	// Important for many encoder.
    20         -	LinkAdapter  func(*ast.LinkNode) ast.InlineNode
    21         -	ImageAdapter func(*ast.ImageNode) ast.InlineNode
    22         -	CiteAdapter  func(*ast.CiteNode) ast.InlineNode
    23         -
    24     19   	// Important for HTML encoder
    25     20   	Lang           string // default language
    26     21   	Interactive    bool   // Encoded data will be placed in interactive content
    27     22   	Xhtml          bool   // use XHTML syntax instead of HTML syntax
    28     23   	MarkerExternal string // Marker after link to (external) material.
    29     24   	NewWindow      bool   // open link in new window
    30     25   	IgnoreMeta     map[string]bool
    31     26   	footnotes      []*ast.FootnoteNode // Stores footnotes detected while encoding
    32     27   }
    33     28   
    34         -// AdaptLink helps to call the link adapter.
    35         -func (env *Environment) AdaptLink(ln *ast.LinkNode) (*ast.LinkNode, ast.InlineNode) {
    36         -	if env == nil || env.LinkAdapter == nil {
    37         -		return ln, nil
    38         -	}
    39         -	n := env.LinkAdapter(ln)
    40         -	if n == nil {
    41         -		return ln, nil
    42         -	}
    43         -	if ln2, ok := n.(*ast.LinkNode); ok {
    44         -		return ln2, nil
    45         -	}
    46         -	return nil, n
    47         -}
    48         -
    49         -// AdaptImage helps to call the link adapter.
    50         -func (env *Environment) AdaptImage(in *ast.ImageNode) (*ast.ImageNode, ast.InlineNode) {
    51         -	if env == nil || env.ImageAdapter == nil {
    52         -		return in, nil
    53         -	}
    54         -	n := env.ImageAdapter(in)
    55         -	if n == nil {
    56         -		return in, nil
    57         -	}
    58         -	if in2, ok := n.(*ast.ImageNode); ok {
    59         -		return in2, nil
    60         -	}
    61         -	return nil, n
    62         -}
    63         -
    64         -// AdaptCite helps to call the link adapter.
    65         -func (env *Environment) AdaptCite(cn *ast.CiteNode) (*ast.CiteNode, ast.InlineNode) {
    66         -	if env == nil || env.CiteAdapter == nil {
    67         -		return cn, nil
    68         -	}
    69         -	n := env.CiteAdapter(cn)
    70         -	if n == nil {
    71         -		return cn, nil
    72         -	}
    73         -	if cn2, ok := n.(*ast.CiteNode); ok {
    74         -		return cn2, nil
    75         -	}
    76         -	return nil, n
    77         -}
    78         -
    79     29   // IsInteractive returns true, if Interactive is enabled and currently embedded
    80     30   // interactive encoding will take place.
    81     31   func (env *Environment) IsInteractive(inInteractive bool) bool {
    82     32   	return inInteractive && env != nil && env.Interactive
    83     33   }
    84     34   
    85     35   // IsXHTML return true, if XHTML is enabled.

Changes to encoder/htmlenc/block.go.

   114    114   
   115    115   	v.lang.push(attrs)
   116    116   	defer v.lang.pop()
   117    117   
   118    118   	v.b.WriteStrings("<", code)
   119    119   	v.visitAttributes(attrs)
   120    120   	v.b.WriteString(">\n")
   121         -	ast.WalkBlockSlice(v, rn.Blocks)
   122         -	if len(rn.Inlines) > 0 {
          121  +	ast.Walk(v, rn.Blocks)
          122  +	if rn.Inlines != nil {
   123    123   		v.b.WriteString("<cite>")
   124         -		ast.WalkInlineSlice(v, rn.Inlines)
          124  +		ast.Walk(v, rn.Inlines)
   125    125   		v.b.WriteString("</cite>\n")
   126    126   	}
   127    127   	v.b.WriteStrings("</", code, ">\n")
   128    128   	v.inVerse = oldVerse
   129    129   }
   130    130   
   131    131   func (v *visitor) visitHeading(hn *ast.HeadingNode) {
................................................................................
   135    135   	lvl := hn.Level
   136    136   	if lvl > 6 {
   137    137   		lvl = 6 // HTML has H1..H6
   138    138   	}
   139    139   	strLvl := strconv.Itoa(lvl)
   140    140   	v.b.WriteStrings("<h", strLvl)
   141    141   	v.visitAttributes(hn.Attrs)
   142         -	if slug := hn.Slug; len(slug) > 0 {
   143         -		v.b.WriteStrings(" id=\"", slug, "\"")
          142  +	if _, ok := hn.Attrs.Get("id"); !ok {
          143  +		if fragment := hn.Fragment; fragment != "" {
          144  +			v.b.WriteStrings(" id=\"", fragment, "\"")
          145  +		}
   144    146   	}
   145    147   	v.b.WriteByte('>')
   146         -	ast.WalkInlineSlice(v, hn.Inlines)
          148  +	ast.Walk(v, hn.Inlines)
   147    149   	v.b.WriteStrings("</h", strLvl, ">\n")
   148    150   }
   149    151   
   150    152   var mapNestedListKind = map[ast.NestedListKind]string{
   151    153   	ast.NestedListOrdered:   "ol",
   152    154   	ast.NestedListUnordered: "ul",
   153    155   }
................................................................................
   186    188   		if pn := getParaItem(item); pn != nil {
   187    189   			if inPara {
   188    190   				v.b.WriteByte('\n')
   189    191   			} else {
   190    192   				v.b.WriteString("<p>")
   191    193   				inPara = true
   192    194   			}
   193         -			ast.WalkInlineSlice(v, pn.Inlines)
          195  +			ast.Walk(v, pn.Inlines)
   194    196   		} else {
   195    197   			if inPara {
   196    198   				v.writeEndPara()
   197    199   				inPara = false
   198    200   			}
   199    201   			ast.WalkItemSlice(v, item)
   200    202   		}
................................................................................
   241    243   
   242    244   // writeItemSliceOrPara emits the content of a paragraph if the paragraph is
   243    245   // the only element of the block slice and if compact mode is true. Otherwise,
   244    246   // the item slice is emitted normally.
   245    247   func (v *visitor) writeItemSliceOrPara(ins ast.ItemSlice, compact bool) {
   246    248   	if compact && len(ins) == 1 {
   247    249   		if para, ok := ins[0].(*ast.ParaNode); ok {
   248         -			ast.WalkInlineSlice(v, para.Inlines)
          250  +			ast.Walk(v, para.Inlines)
   249    251   			return
   250    252   		}
   251    253   	}
   252    254   	ast.WalkItemSlice(v, ins)
   253    255   }
   254    256   
   255    257   func (v *visitor) writeDescriptionsSlice(ds ast.DescriptionSlice) {
   256    258   	if len(ds) == 1 {
   257    259   		if para, ok := ds[0].(*ast.ParaNode); ok {
   258         -			ast.WalkInlineSlice(v, para.Inlines)
          260  +			ast.Walk(v, para.Inlines)
   259    261   			return
   260    262   		}
   261    263   	}
   262    264   	ast.WalkDescriptionSlice(v, ds)
   263    265   }
   264    266   
   265    267   func (v *visitor) visitDescriptionList(dn *ast.DescriptionListNode) {
   266    268   	v.b.WriteString("<dl>\n")
   267    269   	for _, descr := range dn.Descriptions {
   268    270   		v.b.WriteString("<dt>")
   269         -		ast.WalkInlineSlice(v, descr.Term)
          271  +		ast.Walk(v, descr.Term)
   270    272   		v.b.WriteString("</dt>\n")
   271    273   
   272    274   		for _, b := range descr.Descriptions {
   273    275   			v.b.WriteString("<dd>")
   274    276   			v.writeDescriptionsSlice(b)
   275    277   			v.b.WriteString("</dd>\n")
   276    278   		}
................................................................................
   302    304   	ast.AlignRight:   " style=\"text-align:right\">",
   303    305   }
   304    306   
   305    307   func (v *visitor) writeRow(row ast.TableRow, cellStart, cellEnd string) {
   306    308   	v.b.WriteString("<tr>")
   307    309   	for _, cell := range row {
   308    310   		v.b.WriteString(cellStart)
   309         -		if len(cell.Inlines) == 0 {
          311  +		if cell.Inlines.IsEmpty() {
   310    312   			v.b.WriteByte('>')
   311    313   		} else {
   312    314   			v.b.WriteString(alignStyle[cell.Align])
   313         -			ast.WalkInlineSlice(v, cell.Inlines)
          315  +			ast.Walk(v, cell.Inlines)
   314    316   		}
   315    317   		v.b.WriteString(cellEnd)
   316    318   	}
   317    319   	v.b.WriteString("</tr>\n")
   318    320   }
   319    321   
   320    322   func (v *visitor) visitBLOB(bn *ast.BLOBNode) {

Changes to encoder/htmlenc/htmlenc.go.

     9      9   //-----------------------------------------------------------------------------
    10     10   
    11     11   // Package htmlenc encodes the abstract syntax tree into HTML5.
    12     12   package htmlenc
    13     13   
    14     14   import (
    15     15   	"io"
    16         -	"strings"
    17     16   
    18     17   	"zettelstore.de/z/api"
    19     18   	"zettelstore.de/z/ast"
    20     19   	"zettelstore.de/z/domain/meta"
    21     20   	"zettelstore.de/z/encoder"
    22         -	"zettelstore.de/z/encoder/encfun"
    23         -	"zettelstore.de/z/parser"
    24     21   )
    25     22   
    26     23   func init() {
    27     24   	encoder.Register(api.EncoderHTML, encoder.Info{
    28     25   		Create: func(env *encoder.Environment) encoder.Encoder { return &htmlEncoder{env: env} },
    29     26   	})
    30     27   }
    31     28   
    32     29   type htmlEncoder struct {
    33     30   	env *encoder.Environment
    34     31   }
    35     32   
    36     33   // WriteZettel encodes a full zettel as HTML5.
    37         -func (he *htmlEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) {
           34  +func (he *htmlEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) {
    38     35   	v := newVisitor(he, w)
    39     36   	if !he.env.IsXHTML() {
    40     37   		v.b.WriteString("<!DOCTYPE html>\n")
    41     38   	}
    42     39   	if env := he.env; env != nil && env.Lang == "" {
    43     40   		v.b.WriteStrings("<html>\n<head>")
    44     41   	} else {
    45     42   		v.b.WriteStrings("<html lang=\"", env.Lang, "\">")
    46     43   	}
    47     44   	v.b.WriteString("\n<head>\n<meta charset=\"utf-8\">\n")
    48         -	v.b.WriteStrings("<title>", encfun.MetaAsText(zn.InhMeta, meta.KeyTitle), "</title>")
    49         -	if inhMeta {
    50         -		v.acceptMeta(zn.InhMeta)
    51         -	} else {
    52         -		v.acceptMeta(zn.Meta)
           45  +	plainTitle, hasTitle := zn.InhMeta.Get(meta.KeyTitle)
           46  +	if hasTitle {
           47  +		v.b.WriteStrings("<title>", v.evalValue(plainTitle, evalMeta), "</title>")
    53     48   	}
           49  +	v.acceptMeta(zn.InhMeta, evalMeta)
    54     50   	v.b.WriteString("\n</head>\n<body>\n")
    55         -	ast.WalkBlockSlice(v, zn.Ast)
           51  +	if hasTitle {
           52  +		if ilnTitle := evalMeta(plainTitle); ilnTitle != nil {
           53  +			v.b.WriteString("<h1>")
           54  +			ast.Walk(v, ilnTitle)
           55  +			v.b.WriteString("</h1>\n")
           56  +		}
           57  +	}
           58  +	ast.Walk(v, zn.Ast)
    56     59   	v.writeEndnotes()
    57     60   	v.b.WriteString("</body>\n</html>")
    58     61   	length, err := v.b.Flush()
    59     62   	return length, err
    60     63   }
    61     64   
    62     65   // WriteMeta encodes meta data as HTML5.
    63         -func (he *htmlEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
           66  +func (he *htmlEncoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) {
    64     67   	v := newVisitor(he, w)
    65     68   
    66     69   	// Write title
    67     70   	if title, ok := m.Get(meta.KeyTitle); ok {
    68         -		textEnc := encoder.Create(api.EncoderText, nil)
    69         -		var sb strings.Builder
    70         -		textEnc.WriteInlines(&sb, parser.ParseMetadata(title))
    71     71   		v.b.WriteStrings("<meta name=\"zs-", meta.KeyTitle, "\" content=\"")
    72         -		v.writeQuotedEscaped(sb.String())
           72  +		v.writeQuotedEscaped(v.evalValue(title, evalMeta))
    73     73   		v.b.WriteString("\">")
    74     74   	}
    75     75   
    76     76   	// Write other metadata
    77         -	v.acceptMeta(m)
           77  +	v.acceptMeta(m, evalMeta)
    78     78   	length, err := v.b.Flush()
    79     79   	return length, err
    80     80   }
    81     81   
    82     82   func (he *htmlEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
    83     83   	return he.WriteBlocks(w, zn.Ast)
    84     84   }
    85     85   
    86     86   // WriteBlocks encodes a block slice.
    87         -func (he *htmlEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
           87  +func (he *htmlEncoder) WriteBlocks(w io.Writer, bln *ast.BlockListNode) (int, error) {
    88     88   	v := newVisitor(he, w)
    89         -	ast.WalkBlockSlice(v, bs)
           89  +	ast.Walk(v, bln)
    90     90   	v.writeEndnotes()
    91     91   	length, err := v.b.Flush()
    92     92   	return length, err
    93     93   }
    94     94   
    95     95   // WriteInlines writes an inline slice to the writer
    96         -func (he *htmlEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
           96  +func (he *htmlEncoder) WriteInlines(w io.Writer, iln *ast.InlineListNode) (int, error) {
    97     97   	v := newVisitor(he, w)
    98     98   	if env := he.env; env != nil {
    99     99   		v.inInteractive = env.Interactive
   100    100   	}
   101         -	ast.WalkInlineSlice(v, is)
          101  +	ast.Walk(v, iln)
   102    102   	length, err := v.b.Flush()
   103    103   	return length, err
   104    104   }

Changes to encoder/htmlenc/inline.go.

    29     29   		}
    30     30   	} else {
    31     31   		v.b.WriteByte('\n')
    32     32   	}
    33     33   }
    34     34   
    35     35   func (v *visitor) visitLink(ln *ast.LinkNode) {
    36         -	ln, n := v.env.AdaptLink(ln)
    37         -	if n != nil {
    38         -		ast.Walk(v, n)
    39         -		return
    40         -	}
    41     36   	v.lang.push(ln.Attrs)
    42     37   	defer v.lang.pop()
    43     38   
    44     39   	switch ln.Ref.State {
    45     40   	case ast.RefStateSelf, ast.RefStateFound, ast.RefStateHosted, ast.RefStateBased:
    46     41   		v.writeAHref(ln.Ref, ln.Attrs, ln.Inlines)
    47     42   	case ast.RefStateBroken:
................................................................................
    66     61   		}
    67     62   		v.b.WriteString("<a href=\"")
    68     63   		v.writeQuotedEscaped(ln.Ref.Value)
    69     64   		v.b.WriteByte('"')
    70     65   		v.visitAttributes(ln.Attrs)
    71     66   		v.b.WriteByte('>')
    72     67   		v.inInteractive = true
    73         -		ast.WalkInlineSlice(v, ln.Inlines)
           68  +		ast.Walk(v, ln.Inlines)
    74     69   		v.inInteractive = false
    75     70   		v.b.WriteString("</a>")
    76     71   	}
    77     72   }
    78     73   
    79         -func (v *visitor) writeAHref(ref *ast.Reference, attrs *ast.Attributes, ins ast.InlineSlice) {
           74  +func (v *visitor) writeAHref(ref *ast.Reference, attrs *ast.Attributes, iln *ast.InlineListNode) {
    80     75   	if v.env.IsInteractive(v.inInteractive) {
    81         -		v.writeSpan(ins, attrs)
           76  +		v.writeSpan(iln, attrs)
    82     77   		return
    83     78   	}
    84     79   	v.b.WriteString("<a href=\"")
    85     80   	v.writeReference(ref)
    86     81   	v.b.WriteByte('"')
    87     82   	v.visitAttributes(attrs)
    88     83   	v.b.WriteByte('>')
    89     84   	v.inInteractive = true
    90         -	ast.WalkInlineSlice(v, ins)
           85  +	ast.Walk(v, iln)
    91     86   	v.inInteractive = false
    92     87   	v.b.WriteString("</a>")
    93     88   }
    94     89   
    95         -func (v *visitor) visitImage(in *ast.ImageNode) {
    96         -	in, n := v.env.AdaptImage(in)
    97         -	if n != nil {
    98         -		ast.Walk(v, n)
    99         -		return
   100         -	}
   101         -	v.lang.push(in.Attrs)
           90  +func (v *visitor) visitEmbed(en *ast.EmbedNode) {
           91  +	v.lang.push(en.Attrs)
   102     92   	defer v.lang.pop()
   103     93   
   104         -	if in.Ref == nil {
           94  +	switch m := en.Material.(type) {
           95  +	case *ast.ReferenceMaterialNode:
           96  +		v.b.WriteString("<img src=\"")
           97  +		v.writeReference(m.Ref)
           98  +	case *ast.BLOBMaterialNode:
   105     99   		v.b.WriteString("<img src=\"data:image/")
   106         -		switch in.Syntax {
          100  +		switch m.Syntax {
   107    101   		case "svg":
   108    102   			v.b.WriteString("svg+xml;utf8,")
   109         -			v.writeQuotedEscaped(string(in.Blob))
          103  +			v.writeQuotedEscaped(string(m.Blob))
   110    104   		default:
   111         -			v.b.WriteStrings(in.Syntax, ";base64,")
   112         -			v.b.WriteBase64(in.Blob)
          105  +			v.b.WriteStrings(m.Syntax, ";base64,")
          106  +			v.b.WriteBase64(m.Blob)
   113    107   		}
   114         -	} else {
   115         -		v.b.WriteString("<img src=\"")
   116         -		v.writeReference(in.Ref)
          108  +	default:
          109  +		panic(fmt.Sprintf("Unknown material type %t for %v", en.Material, en.Material))
   117    110   	}
   118    111   	v.b.WriteString("\" alt=\"")
   119         -	ast.WalkInlineSlice(v, in.Inlines)
          112  +	if en.Inlines != nil {
          113  +		ast.Walk(v, en.Inlines)
          114  +	}
   120    115   	v.b.WriteByte('"')
   121         -	v.visitAttributes(in.Attrs)
          116  +	v.visitAttributes(en.Attrs)
   122    117   	if v.env.IsXHTML() {
   123    118   		v.b.WriteString(" />")
   124    119   	} else {
   125    120   		v.b.WriteByte('>')
   126    121   	}
   127    122   }
   128    123   
   129    124   func (v *visitor) visitCite(cn *ast.CiteNode) {
   130         -	cn, n := v.env.AdaptCite(cn)
   131         -	if n != nil {
   132         -		ast.Walk(v, n)
   133         -		return
   134         -	}
   135         -	if cn == nil {
   136         -		return
   137         -	}
   138    125   	v.lang.push(cn.Attrs)
   139    126   	defer v.lang.pop()
   140    127   	v.b.WriteString(cn.Key)
   141         -	if len(cn.Inlines) > 0 {
          128  +	if cn.Inlines != nil {
   142    129   		v.b.WriteString(", ")
   143         -		ast.WalkInlineSlice(v, cn.Inlines)
          130  +		ast.Walk(v, cn.Inlines)
   144    131   	}
   145    132   }
   146    133   
   147    134   func (v *visitor) visitFootnote(fn *ast.FootnoteNode) {
   148    135   	v.lang.push(fn.Attrs)
   149    136   	defer v.lang.pop()
   150    137   	if v.env.IsInteractive(v.inInteractive) {
................................................................................
   156    143   	// TODO: what to do with Attrs?
   157    144   }
   158    145   
   159    146   func (v *visitor) visitMark(mn *ast.MarkNode) {
   160    147   	if v.env.IsInteractive(v.inInteractive) {
   161    148   		return
   162    149   	}
   163         -	if len(mn.Text) > 0 {
   164         -		v.b.WriteStrings("<a id=\"", mn.Text, "\"></a>")
          150  +	if fragment := mn.Fragment; fragment != "" {
          151  +		v.b.WriteStrings("<a id=\"", fragment, "\"></a>")
   165    152   	}
   166    153   }
   167    154   
   168    155   func (v *visitor) visitFormat(fn *ast.FormatNode) {
   169    156   	v.lang.push(fn.Attrs)
   170    157   	defer v.lang.pop()
   171    158   
   172    159   	var code string
   173         -	attrs := fn.Attrs
          160  +	attrs := fn.Attrs.Clone()
   174    161   	switch fn.Kind {
   175    162   	case ast.FormatItalic:
   176    163   		code = "i"
   177    164   	case ast.FormatEmph:
   178    165   		code = "em"
   179    166   	case ast.FormatBold:
   180    167   		code = "b"
................................................................................
   207    194   		return
   208    195   	default:
   209    196   		panic(fmt.Sprintf("Unknown format kind %v", fn.Kind))
   210    197   	}
   211    198   	v.b.WriteStrings("<", code)
   212    199   	v.visitAttributes(attrs)
   213    200   	v.b.WriteByte('>')
   214         -	ast.WalkInlineSlice(v, fn.Inlines)
          201  +	ast.Walk(v, fn.Inlines)
   215    202   	v.b.WriteStrings("</", code, ">")
   216    203   }
   217    204   
   218         -func (v *visitor) writeSpan(ins ast.InlineSlice, attrs *ast.Attributes) {
          205  +func (v *visitor) writeSpan(iln *ast.InlineListNode, attrs *ast.Attributes) {
   219    206   	v.b.WriteString("<span")
   220    207   	v.visitAttributes(attrs)
   221    208   	v.b.WriteByte('>')
   222         -	ast.WalkInlineSlice(v, ins)
          209  +	ast.Walk(v, iln)
   223    210   	v.b.WriteString("</span>")
   224    211   
   225    212   }
   226    213   
   227    214   var langQuotes = map[string][2]string{
   228    215   	meta.ValueLangEN: {"&ldquo;", "&rdquo;"},
   229    216   	"de":             {"&bdquo;", "&ldquo;"},
................................................................................
   248    235   	if withSpan {
   249    236   		v.b.WriteString("<span")
   250    237   		v.visitAttributes(fn.Attrs)
   251    238   		v.b.WriteByte('>')
   252    239   	}
   253    240   	openingQ, closingQ := getQuotes(v.lang.top())
   254    241   	v.b.WriteString(openingQ)
   255         -	ast.WalkInlineSlice(v, fn.Inlines)
          242  +	ast.Walk(v, fn.Inlines)
   256    243   	v.b.WriteString(closingQ)
   257    244   	if withSpan {
   258    245   		v.b.WriteString("</span>")
   259    246   	}
   260    247   }
   261    248   
   262    249   func (v *visitor) visitLiteral(ln *ast.LiteralNode) {

Changes to encoder/htmlenc/visitor.go.

    13     13   
    14     14   import (
    15     15   	"io"
    16     16   	"sort"
    17     17   	"strconv"
    18     18   	"strings"
    19     19   
           20  +	"zettelstore.de/z/api"
    20     21   	"zettelstore.de/z/ast"
    21     22   	"zettelstore.de/z/domain/meta"
    22     23   	"zettelstore.de/z/encoder"
    23     24   	"zettelstore.de/z/strfun"
    24     25   )
    25     26   
    26     27   // visitor writes the abstract syntax tree to an io.Writer.
    27     28   type visitor struct {
    28     29   	env           *encoder.Environment
    29     30   	b             encoder.BufWriter
    30         -	visibleSpace  bool // Show space character in raw text
           31  +	visibleSpace  bool // Show space character in plain text
    31     32   	inVerse       bool // In verse block
    32     33   	inInteractive bool // Rendered interactive HTML code
    33     34   	lang          langStack
           35  +	textEnc       encoder.Encoder
    34     36   }
    35     37   
    36     38   func newVisitor(he *htmlEncoder, w io.Writer) *visitor {
    37     39   	var lang string
    38     40   	if he.env != nil {
    39     41   		lang = he.env.Lang
    40     42   	}
    41     43   	return &visitor{
    42         -		env:  he.env,
    43         -		b:    encoder.NewBufWriter(w),
    44         -		lang: newLangStack(lang),
           44  +		env:     he.env,
           45  +		b:       encoder.NewBufWriter(w),
           46  +		lang:    newLangStack(lang),
           47  +		textEnc: encoder.Create(api.EncoderText, nil),
    45     48   	}
    46     49   }
    47     50   
    48     51   func (v *visitor) Visit(node ast.Node) ast.Visitor {
    49     52   	switch n := node.(type) {
    50     53   	case *ast.ParaNode:
    51     54   		v.b.WriteString("<p>")
    52         -		ast.WalkInlineSlice(v, n.Inlines)
           55  +		ast.Walk(v, n.Inlines)
    53     56   		v.writeEndPara()
    54     57   	case *ast.VerbatimNode:
    55     58   		v.visitVerbatim(n)
    56     59   	case *ast.RegionNode:
    57     60   		v.visitRegion(n)
    58     61   	case *ast.HeadingNode:
    59     62   		v.visitHeading(n)
................................................................................
    86     89   		} else {
    87     90   			v.b.WriteByte(' ')
    88     91   		}
    89     92   	case *ast.BreakNode:
    90     93   		v.visitBreak(n)
    91     94   	case *ast.LinkNode:
    92     95   		v.visitLink(n)
    93         -	case *ast.ImageNode:
    94         -		v.visitImage(n)
           96  +	case *ast.EmbedNode:
           97  +		v.visitEmbed(n)
    95     98   	case *ast.CiteNode:
    96     99   		v.visitCite(n)
    97    100   	case *ast.FootnoteNode:
    98    101   		v.visitFootnote(n)
    99    102   	case *ast.MarkNode:
   100    103   		v.visitMark(n)
   101    104   	case *ast.FormatNode:
................................................................................
   109    112   }
   110    113   
   111    114   var mapMetaKey = map[string]string{
   112    115   	meta.KeyCopyright: "copyright",
   113    116   	meta.KeyLicense:   "license",
   114    117   }
   115    118   
   116         -func (v *visitor) acceptMeta(m *meta.Meta) {
   117         -	for _, pair := range m.Pairs(true) {
   118         -		if env := v.env; env != nil && env.IgnoreMeta[pair.Key] {
   119         -			continue
   120         -		}
   121         -		if pair.Key == meta.KeyTitle {
          119  +func (v *visitor) acceptMeta(m *meta.Meta, evalMeta encoder.EvalMetaFunc) {
          120  +	ignore := v.setupIgnoreSet()
          121  +	ignore[meta.KeyTitle] = true
          122  +	if tags, ok := m.Get(meta.KeyAllTags); ok {
          123  +		v.writeTags(tags)
          124  +		ignore[meta.KeyAllTags] = true
          125  +		ignore[meta.KeyTags] = true
          126  +	} else if tags, ok := m.Get(meta.KeyTags); ok {
          127  +		v.writeTags(tags)
          128  +		ignore[meta.KeyTags] = true
          129  +	}
          130  +
          131  +	for _, p := range m.Pairs(true) {
          132  +		key := p.Key
          133  +		if ignore[key] {
   122    134   			continue
   123    135   		}
   124         -		if pair.Key == meta.KeyTags {
   125         -			v.writeTags(pair.Value)
   126         -		} else if key, ok := mapMetaKey[pair.Key]; ok {
   127         -			v.writeMeta("", key, pair.Value)
          136  +		value := p.Value
          137  +		if m.Type(key) == meta.TypeZettelmarkup {
          138  +			if v := v.evalValue(value, evalMeta); v != "" {
          139  +				value = v
          140  +			}
          141  +		}
          142  +		if mKey, ok := mapMetaKey[key]; ok {
          143  +			v.writeMeta("", mKey, value)
   128    144   		} else {
   129         -			v.writeMeta("zs-", pair.Key, pair.Value)
          145  +			v.writeMeta("zs-", key, value)
          146  +		}
          147  +	}
          148  +}
          149  +
          150  +func (v *visitor) evalValue(value string, evalMeta encoder.EvalMetaFunc) string {
          151  +	var sb strings.Builder
          152  +	_, err := v.textEnc.WriteInlines(&sb, evalMeta(value))
          153  +	if err == nil {
          154  +		return sb.String()
          155  +	}
          156  +	return ""
          157  +}
          158  +
          159  +func (v *visitor) setupIgnoreSet() map[string]bool {
          160  +	if v.env == nil || v.env.IgnoreMeta == nil {
          161  +		return make(map[string]bool)
          162  +	}
          163  +	result := make(map[string]bool, len(v.env.IgnoreMeta))
          164  +	for k, v := range v.env.IgnoreMeta {
          165  +		if v {
          166  +			result[k] = true
   130    167   		}
   131    168   	}
          169  +	return result
   132    170   }
   133    171   
   134    172   func (v *visitor) writeTags(tags string) {
   135    173   	v.b.WriteString("\n<meta name=\"keywords\" content=\"")
   136    174   	for i, val := range meta.ListFromValue(tags) {
   137    175   		if i > 0 {
   138    176   			v.b.WriteString(", ")
................................................................................
   154    192   		v.b.WriteString("<ol class=\"zs-endnotes\">\n")
   155    193   		for i := 0; i < len(footnotes); i++ {
   156    194   			// Do not use a range loop above, because a footnote may contain
   157    195   			// a footnote. Therefore v.enc.footnote may grow during the loop.
   158    196   			fn := footnotes[i]
   159    197   			n := strconv.Itoa(i + 1)
   160    198   			v.b.WriteStrings("<li id=\"fn:", n, "\" role=\"doc-endnote\">")
   161         -			ast.WalkInlineSlice(v, fn.Inlines)
          199  +			ast.Walk(v, fn.Inlines)
   162    200   			v.b.WriteStrings(
   163    201   				" <a href=\"#fnref:",
   164    202   				n,
   165    203   				"\" class=\"zs-footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;</a></li>\n")
   166    204   		}
   167    205   		v.b.WriteString("</ol>\n")
   168    206   	}
   169    207   }
   170    208   
   171    209   // visitAttributes write HTML attributes
   172    210   func (v *visitor) visitAttributes(a *ast.Attributes) {
   173         -	if a == nil || len(a.Attrs) == 0 {
          211  +	if a.IsEmpty() {
   174    212   		return
   175    213   	}
   176    214   	keys := make([]string, 0, len(a.Attrs))
   177    215   	for k := range a.Attrs {
   178    216   		if k != "-" {
   179    217   			keys = append(keys, k)
   180    218   		}

Deleted encoder/jsonenc/djsonenc.go.

     1         -//-----------------------------------------------------------------------------
     2         -// Copyright (c) 2020-2021 Detlef Stern
     3         -//
     4         -// This file is part of zettelstore.
     5         -//
     6         -// Zettelstore is licensed under the latest version of the EUPL (European Union
     7         -// Public License). Please see file LICENSE.txt for your rights and obligations
     8         -// under this license.
     9         -//-----------------------------------------------------------------------------
    10         -
    11         -// Package jsonenc encodes the abstract syntax tree into JSON.
    12         -package jsonenc
    13         -
    14         -import (
    15         -	"fmt"
    16         -	"io"
    17         -	"sort"
    18         -	"strconv"
    19         -
    20         -	"zettelstore.de/z/api"
    21         -	"zettelstore.de/z/ast"
    22         -	"zettelstore.de/z/domain/meta"
    23         -	"zettelstore.de/z/encoder"
    24         -	"zettelstore.de/z/encoder/encfun"
    25         -)
    26         -
    27         -func init() {
    28         -	encoder.Register(api.EncoderDJSON, encoder.Info{
    29         -		Create: func(env *encoder.Environment) encoder.Encoder { return &jsonDetailEncoder{env: env} },
    30         -	})
    31         -}
    32         -
    33         -type jsonDetailEncoder struct {
    34         -	env *encoder.Environment
    35         -}
    36         -
    37         -// WriteZettel writes the encoded zettel to the writer.
    38         -func (je *jsonDetailEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) {
    39         -	v := newDetailVisitor(w, je)
    40         -	v.b.WriteString("{\"meta\":{\"title\":")
    41         -	v.walkInlineSlice(encfun.MetaAsInlineSlice(zn.InhMeta, meta.KeyTitle))
    42         -	if inhMeta {
    43         -		v.writeMeta(zn.InhMeta)
    44         -	} else {
    45         -		v.writeMeta(zn.Meta)
    46         -	}
    47         -	v.b.WriteByte('}')
    48         -	v.b.WriteString(",\"content\":")
    49         -	v.walkBlockSlice(zn.Ast)
    50         -	v.b.WriteByte('}')
    51         -	length, err := v.b.Flush()
    52         -	return length, err
    53         -}
    54         -
    55         -// WriteMeta encodes meta data as JSON.
    56         -func (je *jsonDetailEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
    57         -	v := newDetailVisitor(w, je)
    58         -	v.b.WriteString("{\"title\":")
    59         -	v.walkInlineSlice(encfun.MetaAsInlineSlice(m, meta.KeyTitle))
    60         -	v.writeMeta(m)
    61         -	v.b.WriteByte('}')
    62         -	length, err := v.b.Flush()
    63         -	return length, err
    64         -}
    65         -
    66         -func (je *jsonDetailEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
    67         -	return je.WriteBlocks(w, zn.Ast)
    68         -}
    69         -
    70         -// WriteBlocks writes a block slice to the writer
    71         -func (je *jsonDetailEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
    72         -	v := newDetailVisitor(w, je)
    73         -	v.walkBlockSlice(bs)
    74         -	length, err := v.b.Flush()
    75         -	return length, err
    76         -}
    77         -
    78         -// WriteInlines writes an inline slice to the writer
    79         -func (je *jsonDetailEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
    80         -	v := newDetailVisitor(w, je)
    81         -	v.walkInlineSlice(is)
    82         -	length, err := v.b.Flush()
    83         -	return length, err
    84         -}
    85         -
    86         -// detailVisitor writes the abstract syntax tree to an io.Writer.
    87         -type detailVisitor struct {
    88         -	b   encoder.BufWriter
    89         -	env *encoder.Environment
    90         -}
    91         -
    92         -func newDetailVisitor(w io.Writer, je *jsonDetailEncoder) *detailVisitor {
    93         -	return &detailVisitor{b: encoder.NewBufWriter(w), env: je.env}
    94         -}
    95         -
    96         -func (v *detailVisitor) Visit(node ast.Node) ast.Visitor {
    97         -	switch n := node.(type) {
    98         -	case *ast.ParaNode:
    99         -		v.writeNodeStart("Para")
   100         -		v.writeContentStart('i')
   101         -		v.walkInlineSlice(n.Inlines)
   102         -	case *ast.VerbatimNode:
   103         -		v.visitVerbatim(n)
   104         -	case *ast.RegionNode:
   105         -		v.visitRegion(n)
   106         -	case *ast.HeadingNode:
   107         -		v.visitHeading(n)
   108         -	case *ast.HRuleNode:
   109         -		v.writeNodeStart("Hrule")
   110         -		v.visitAttributes(n.Attrs)
   111         -	case *ast.NestedListNode:
   112         -		v.visitNestedList(n)
   113         -	case *ast.DescriptionListNode:
   114         -		v.visitDescriptionList(n)
   115         -	case *ast.TableNode:
   116         -		v.visitTable(n)
   117         -	case *ast.BLOBNode:
   118         -		v.writeNodeStart("Blob")
   119         -		v.writeContentStart('q')
   120         -		writeEscaped(&v.b, n.Title)
   121         -		v.writeContentStart('s')
   122         -		writeEscaped(&v.b, n.Syntax)
   123         -		v.writeContentStart('o')
   124         -		v.b.WriteBase64(n.Blob)
   125         -		v.b.WriteByte('"')
   126         -	case *ast.TextNode:
   127         -		v.writeNodeStart("Text")
   128         -		v.writeContentStart('s')
   129         -		writeEscaped(&v.b, n.Text)
   130         -	case *ast.TagNode:
   131         -		v.writeNodeStart("Tag")
   132         -		v.writeContentStart('s')
   133         -		writeEscaped(&v.b, n.Tag)
   134         -	case *ast.SpaceNode:
   135         -		v.writeNodeStart("Space")
   136         -		if l := len(n.Lexeme); l > 1 {
   137         -			v.writeContentStart('n')
   138         -			v.b.WriteString(strconv.Itoa(l))
   139         -		}
   140         -	case *ast.BreakNode:
   141         -		if n.Hard {
   142         -			v.writeNodeStart("Hard")
   143         -		} else {
   144         -			v.writeNodeStart("Soft")
   145         -		}
   146         -	case *ast.LinkNode:
   147         -		n, n2 := v.env.AdaptLink(n)
   148         -		if n2 != nil {
   149         -			ast.Walk(v, n2)
   150         -			return nil
   151         -		}
   152         -		v.writeNodeStart("Link")
   153         -		v.visitAttributes(n.Attrs)
   154         -		v.writeContentStart('q')
   155         -		writeEscaped(&v.b, mapRefState[n.Ref.State])
   156         -		v.writeContentStart('s')
   157         -		writeEscaped(&v.b, n.Ref.String())
   158         -		v.writeContentStart('i')
   159         -		v.walkInlineSlice(n.Inlines)
   160         -	case *ast.ImageNode:
   161         -		v.visitImage(n)
   162         -	case *ast.CiteNode:
   163         -		v.writeNodeStart("Cite")
   164         -		v.visitAttributes(n.Attrs)
   165         -		v.writeContentStart('s')
   166         -		writeEscaped(&v.b, n.Key)
   167         -		if len(n.Inlines) > 0 {
   168         -			v.writeContentStart('i')
   169         -			v.walkInlineSlice(n.Inlines)
   170         -		}
   171         -	case *ast.FootnoteNode:
   172         -		v.writeNodeStart("Footnote")
   173         -		v.visitAttributes(n.Attrs)
   174         -		v.writeContentStart('i')
   175         -		v.walkInlineSlice(n.Inlines)
   176         -	case *ast.MarkNode:
   177         -		v.writeNodeStart("Mark")
   178         -		if len(n.Text) > 0 {
   179         -			v.writeContentStart('s')
   180         -			writeEscaped(&v.b, n.Text)
   181         -		}
   182         -	case *ast.FormatNode:
   183         -		v.writeNodeStart(mapFormatKind[n.Kind])
   184         -		v.visitAttributes(n.Attrs)
   185         -		v.writeContentStart('i')
   186         -		v.walkInlineSlice(n.Inlines)
   187         -	case *ast.LiteralNode:
   188         -		kind, ok := mapLiteralKind[n.Kind]
   189         -		if !ok {
   190         -			panic(fmt.Sprintf("Unknown literal kind %v", n.Kind))
   191         -		}
   192         -		v.writeNodeStart(kind)
   193         -		v.visitAttributes(n.Attrs)
   194         -		v.writeContentStart('s')
   195         -		writeEscaped(&v.b, n.Text)
   196         -	default:
   197         -		return v
   198         -	}
   199         -	v.b.WriteByte('}')
   200         -	return nil
   201         -}
   202         -
   203         -var mapVerbatimKind = map[ast.VerbatimKind]string{
   204         -	ast.VerbatimProg:    "CodeBlock",
   205         -	ast.VerbatimComment: "CommentBlock",
   206         -	ast.VerbatimHTML:    "HTMLBlock",
   207         -}
   208         -
   209         -func (v *detailVisitor) visitVerbatim(vn *ast.VerbatimNode) {
   210         -	kind, ok := mapVerbatimKind[vn.Kind]
   211         -	if !ok {
   212         -		panic(fmt.Sprintf("Unknown verbatim kind %v", vn.Kind))
   213         -	}
   214         -	v.writeNodeStart(kind)
   215         -	v.visitAttributes(vn.Attrs)
   216         -	v.writeContentStart('l')
   217         -	for i, line := range vn.Lines {
   218         -		v.writeComma(i)
   219         -		writeEscaped(&v.b, line)
   220         -	}
   221         -	v.b.WriteByte(']')
   222         -}
   223         -
   224         -var mapRegionKind = map[ast.RegionKind]string{
   225         -	ast.RegionSpan:  "SpanBlock",
   226         -	ast.RegionQuote: "QuoteBlock",
   227         -	ast.RegionVerse: "VerseBlock",
   228         -}
   229         -
   230         -func (v *detailVisitor) visitRegion(rn *ast.RegionNode) {
   231         -	kind, ok := mapRegionKind[rn.Kind]
   232         -	if !ok {
   233         -		panic(fmt.Sprintf("Unknown region kind %v", rn.Kind))
   234         -	}
   235         -	v.writeNodeStart(kind)
   236         -	v.visitAttributes(rn.Attrs)
   237         -	v.writeContentStart('b')
   238         -	v.walkBlockSlice(rn.Blocks)
   239         -	if len(rn.Inlines) > 0 {
   240         -		v.writeContentStart('i')
   241         -		v.walkInlineSlice(rn.Inlines)
   242         -	}
   243         -}
   244         -
   245         -func (v *detailVisitor) visitHeading(hn *ast.HeadingNode) {
   246         -	v.writeNodeStart("Heading")
   247         -	v.visitAttributes(hn.Attrs)
   248         -	v.writeContentStart('n')
   249         -	v.b.WriteString(strconv.Itoa(hn.Level))
   250         -	if slug := hn.Slug; len(slug) > 0 {
   251         -		v.writeContentStart('s')
   252         -		v.b.WriteStrings("\"", slug, "\"")
   253         -	}
   254         -	v.writeContentStart('i')
   255         -	v.walkInlineSlice(hn.Inlines)
   256         -}
   257         -
   258         -var mapNestedListKind = map[ast.NestedListKind]string{
   259         -	ast.NestedListOrdered:   "OrderedList",
   260         -	ast.NestedListUnordered: "BulletList",
   261         -	ast.NestedListQuote:     "QuoteList",
   262         -}
   263         -
   264         -func (v *detailVisitor) visitNestedList(ln *ast.NestedListNode) {
   265         -	v.writeNodeStart(mapNestedListKind[ln.Kind])
   266         -	v.writeContentStart('c')
   267         -	for i, item := range ln.Items {
   268         -		v.writeComma(i)
   269         -		v.b.WriteByte('[')
   270         -		for j, in := range item {
   271         -			v.writeComma(j)
   272         -			ast.Walk(v, in)
   273         -		}
   274         -		v.b.WriteByte(']')
   275         -	}
   276         -	v.b.WriteByte(']')
   277         -}
   278         -
   279         -func (v *detailVisitor) visitDescriptionList(dn *ast.DescriptionListNode) {
   280         -	v.writeNodeStart("DescriptionList")
   281         -	v.writeContentStart('g')
   282         -	for i, def := range dn.Descriptions {
   283         -		v.writeComma(i)
   284         -		v.b.WriteByte('[')
   285         -		v.walkInlineSlice(def.Term)
   286         -
   287         -		if len(def.Descriptions) > 0 {
   288         -			for _, b := range def.Descriptions {
   289         -				v.b.WriteString(",[")
   290         -				for j, dn := range b {
   291         -					v.writeComma(j)
   292         -					ast.Walk(v, dn)
   293         -				}
   294         -				v.b.WriteByte(']')
   295         -			}
   296         -		}
   297         -		v.b.WriteByte(']')
   298         -	}
   299         -	v.b.WriteByte(']')
   300         -}
   301         -
   302         -func (v *detailVisitor) visitTable(tn *ast.TableNode) {
   303         -	v.writeNodeStart("Table")
   304         -	v.writeContentStart('p')
   305         -
   306         -	// Table header
   307         -	v.b.WriteByte('[')
   308         -	for i, cell := range tn.Header {
   309         -		v.writeComma(i)
   310         -		v.writeCell(cell)
   311         -	}
   312         -	v.b.WriteString("],")
   313         -
   314         -	// Table rows
   315         -	v.b.WriteByte('[')
   316         -	for i, row := range tn.Rows {
   317         -		v.writeComma(i)
   318         -		v.b.WriteByte('[')
   319         -		for j, cell := range row {
   320         -			v.writeComma(j)
   321         -			v.writeCell(cell)
   322         -		}
   323         -		v.b.WriteByte(']')
   324         -	}
   325         -	v.b.WriteString("]]")
   326         -}
   327         -
   328         -var alignmentCode = map[ast.Alignment]string{
   329         -	ast.AlignDefault: "[\"\",",
   330         -	ast.AlignLeft:    "[\"<\",",
   331         -	ast.AlignCenter:  "[\":\",",
   332         -	ast.AlignRight:   "[\">\",",
   333         -}
   334         -
   335         -func (v *detailVisitor) writeCell(cell *ast.TableCell) {
   336         -	v.b.WriteString(alignmentCode[cell.Align])
   337         -	v.walkInlineSlice(cell.Inlines)
   338         -	v.b.WriteByte(']')
   339         -}
   340         -
   341         -var mapRefState = map[ast.RefState]string{
   342         -	ast.RefStateInvalid:  "invalid",
   343         -	ast.RefStateZettel:   "zettel",
   344         -	ast.RefStateSelf:     "self",
   345         -	ast.RefStateFound:    "zettel",
   346         -	ast.RefStateBroken:   "broken",
   347         -	ast.RefStateHosted:   "local",
   348         -	ast.RefStateBased:    "based",
   349         -	ast.RefStateExternal: "external",
   350         -}
   351         -
   352         -func (v *detailVisitor) visitImage(in *ast.ImageNode) {
   353         -	in, n := v.env.AdaptImage(in)
   354         -	if n != nil {
   355         -		ast.Walk(v, n)
   356         -		return
   357         -	}
   358         -	v.writeNodeStart("Image")
   359         -	v.visitAttributes(in.Attrs)
   360         -	if in.Ref == nil {
   361         -		v.writeContentStart('j')
   362         -		v.b.WriteString("\"s\":")
   363         -		writeEscaped(&v.b, in.Syntax)
   364         -		switch in.Syntax {
   365         -		case "svg":
   366         -			v.writeContentStart('q')
   367         -			writeEscaped(&v.b, string(in.Blob))
   368         -		default:
   369         -			v.writeContentStart('o')
   370         -			v.b.WriteBase64(in.Blob)
   371         -			v.b.WriteByte('"')
   372         -		}
   373         -		v.b.WriteByte('}')
   374         -	} else {
   375         -		v.writeContentStart('s')
   376         -		writeEscaped(&v.b, in.Ref.String())
   377         -	}
   378         -	if len(in.Inlines) > 0 {
   379         -		v.writeContentStart('i')
   380         -		v.walkInlineSlice(in.Inlines)
   381         -	}
   382         -}
   383         -
   384         -var mapFormatKind = map[ast.FormatKind]string{
   385         -	ast.FormatItalic:    "Italic",
   386         -	ast.FormatEmph:      "Emph",
   387         -	ast.FormatBold:      "Bold",
   388         -	ast.FormatStrong:    "Strong",
   389         -	ast.FormatMonospace: "Mono",
   390         -	ast.FormatStrike:    "Strikethrough",
   391         -	ast.FormatDelete:    "Delete",
   392         -	ast.FormatUnder:     "Underline",
   393         -	ast.FormatInsert:    "Insert",
   394         -	ast.FormatSuper:     "Super",
   395         -	ast.FormatSub:       "Sub",
   396         -	ast.FormatQuote:     "Quote",
   397         -	ast.FormatQuotation: "Quotation",
   398         -	ast.FormatSmall:     "Small",
   399         -	ast.FormatSpan:      "Span",
   400         -}
   401         -
   402         -var mapLiteralKind = map[ast.LiteralKind]string{
   403         -	ast.LiteralProg:    "Code",
   404         -	ast.LiteralKeyb:    "Input",
   405         -	ast.LiteralOutput:  "Output",
   406         -	ast.LiteralComment: "Comment",
   407         -	ast.LiteralHTML:    "HTML",
   408         -}
   409         -
   410         -func (v *detailVisitor) walkBlockSlice(bns ast.BlockSlice) {
   411         -	v.b.WriteByte('[')
   412         -	for i, bn := range bns {
   413         -		v.writeComma(i)
   414         -		ast.Walk(v, bn)
   415         -	}
   416         -	v.b.WriteByte(']')
   417         -}
   418         -
   419         -func (v *detailVisitor) walkInlineSlice(ins ast.InlineSlice) {
   420         -	v.b.WriteByte('[')
   421         -	for i, in := range ins {
   422         -		v.writeComma(i)
   423         -		ast.Walk(v, in)
   424         -	}
   425         -	v.b.WriteByte(']')
   426         -}
   427         -
   428         -// visitAttributes write JSON attributes
   429         -func (v *detailVisitor) visitAttributes(a *ast.Attributes) {
   430         -	if a == nil || len(a.Attrs) == 0 {
   431         -		return
   432         -	}
   433         -	keys := make([]string, 0, len(a.Attrs))
   434         -	for k := range a.Attrs {
   435         -		keys = append(keys, k)
   436         -	}
   437         -	sort.Strings(keys)
   438         -
   439         -	v.b.WriteString(",\"a\":{\"")
   440         -	for i, k := range keys {
   441         -		if i > 0 {
   442         -			v.b.WriteString("\",\"")
   443         -		}
   444         -		v.b.Write(Escape(k))
   445         -		v.b.WriteString("\":\"")
   446         -		v.b.Write(Escape(a.Attrs[k]))
   447         -	}
   448         -	v.b.WriteString("\"}")
   449         -}
   450         -
   451         -func (v *detailVisitor) writeNodeStart(t string) {
   452         -	v.b.WriteStrings("{\"t\":\"", t, "\"")
   453         -}
   454         -
   455         -var contentCode = map[rune][]byte{
   456         -	'b': []byte(",\"b\":"),   // List of blocks
   457         -	'c': []byte(",\"c\":["),  // List of list of blocks
   458         -	'g': []byte(",\"g\":["),  // General list
   459         -	'i': []byte(",\"i\":"),   // List of inlines
   460         -	'j': []byte(",\"j\":{"),  // Embedded JSON object
   461         -	'l': []byte(",\"l\":["),  // List of lines
   462         -	'n': []byte(",\"n\":"),   // Number
   463         -	'o': []byte(",\"o\":\""), // Byte object
   464         -	'p': []byte(",\"p\":["),  // Generic tuple
   465         -	'q': []byte(",\"q\":"),   // String, if 's' is also needed
   466         -	's': []byte(",\"s\":"),   // String
   467         -	't': []byte("Content code 't' is not allowed"),
   468         -	'y': []byte("Content code 'y' is not allowed"), // field after 'j'
   469         -}
   470         -
   471         -func (v *detailVisitor) writeContentStart(code rune) {
   472         -	if b, ok := contentCode[code]; ok {
   473         -		v.b.Write(b)
   474         -		return
   475         -	}
   476         -	panic("Unknown content code " + strconv.Itoa(int(code)))
   477         -}
   478         -
   479         -func (v *detailVisitor) writeMeta(m *meta.Meta) {
   480         -	for _, p := range m.Pairs(true) {
   481         -		if p.Key == meta.KeyTitle {
   482         -			continue
   483         -		}
   484         -		v.b.WriteString(",\"")
   485         -		v.b.Write(Escape(p.Key))
   486         -		v.b.WriteString("\":")
   487         -		if m.Type(p.Key).IsSet {
   488         -			v.writeSetValue(p.Value)
   489         -		} else {
   490         -			v.b.WriteByte('"')
   491         -			v.b.Write(Escape(p.Value))
   492         -			v.b.WriteByte('"')
   493         -		}
   494         -	}
   495         -}
   496         -
   497         -func (v *detailVisitor) writeSetValue(value string) {
   498         -	v.b.WriteByte('[')
   499         -	for i, val := range meta.ListFromValue(value) {
   500         -		v.writeComma(i)
   501         -		v.b.WriteByte('"')
   502         -		v.b.Write(Escape(val))
   503         -		v.b.WriteByte('"')
   504         -	}
   505         -	v.b.WriteByte(']')
   506         -}
   507         -
   508         -func (v *detailVisitor) writeComma(pos int) {
   509         -	if pos > 0 {
   510         -		v.b.WriteByte(',')
   511         -	}
   512         -}

Deleted encoder/jsonenc/jsonenc.go.

     1         -//-----------------------------------------------------------------------------
     2         -// Copyright (c) 2020-2021 Detlef Stern
     3         -//
     4         -// This file is part of zettelstore.
     5         -//
     6         -// Zettelstore is licensed under the latest version of the EUPL (European Union
     7         -// Public License). Please see file LICENSE.txt for your rights and obligations
     8         -// under this license.
     9         -//-----------------------------------------------------------------------------
    10         -
    11         -// Package jsonenc encodes the abstract syntax tree into some JSON formats.
    12         -package jsonenc
    13         -
    14         -import (
    15         -	"bytes"
    16         -	"io"
    17         -
    18         -	"zettelstore.de/z/api"
    19         -	"zettelstore.de/z/ast"
    20         -	"zettelstore.de/z/domain/meta"
    21         -	"zettelstore.de/z/encoder"
    22         -)
    23         -
    24         -func init() {
    25         -	encoder.Register(api.EncoderJSON, encoder.Info{
    26         -		Create:  func(*encoder.Environment) encoder.Encoder { return &jsonEncoder{} },
    27         -		Default: true,
    28         -	})
    29         -}
    30         -
    31         -// jsonEncoder is just a stub. It is not implemented. The real implementation
    32         -// is in file web/adapter/json.go
    33         -type jsonEncoder struct{}
    34         -
    35         -// WriteZettel writes the encoded zettel to the writer.
    36         -func (je *jsonEncoder) WriteZettel(
    37         -	w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) {
    38         -	return 0, encoder.ErrNoWriteZettel
    39         -}
    40         -
    41         -// WriteMeta encodes meta data as HTML5.
    42         -func (je *jsonEncoder) WriteMeta(w io.Writer, meta *meta.Meta) (int, error) {
    43         -	return 0, encoder.ErrNoWriteMeta
    44         -}
    45         -
    46         -func (je *jsonEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
    47         -	return 0, encoder.ErrNoWriteContent
    48         -}
    49         -
    50         -// WriteBlocks writes a block slice to the writer
    51         -func (je *jsonEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
    52         -	return 0, encoder.ErrNoWriteBlocks
    53         -}
    54         -
    55         -// WriteInlines writes an inline slice to the writer
    56         -func (je *jsonEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
    57         -	return 0, encoder.ErrNoWriteInlines
    58         -}
    59         -
    60         -var (
    61         -	jsBackslash   = []byte{'\\', '\\'}
    62         -	jsDoubleQuote = []byte{'\\', '"'}
    63         -	jsNewline     = []byte{'\\', 'n'}
    64         -	jsTab         = []byte{'\\', 't'}
    65         -	jsCr          = []byte{'\\', 'r'}
    66         -	jsUnicode     = []byte{'\\', 'u', '0', '0', '0', '0'}
    67         -	jsHex         = []byte("0123456789ABCDEF")
    68         -)
    69         -
    70         -// Escape returns the given string as a byte slice, where every non-printable
    71         -// rune is made printable.
    72         -func Escape(s string) []byte {
    73         -	var buf bytes.Buffer
    74         -
    75         -	last := 0
    76         -	for i, ch := range s {
    77         -		var b []byte
    78         -		switch ch {
    79         -		case '\t':
    80         -			b = jsTab
    81         -		case '\r':
    82         -			b = jsCr
    83         -		case '\n':
    84         -			b = jsNewline
    85         -		case '"':
    86         -			b = jsDoubleQuote
    87         -		case '\\':
    88         -			b = jsBackslash
    89         -		default:
    90         -			if ch < ' ' {
    91         -				b = jsUnicode
    92         -				b[2] = '0'
    93         -				b[3] = '0'
    94         -				b[4] = jsHex[ch>>4]
    95         -				b[5] = jsHex[ch&0xF]
    96         -			} else {
    97         -				continue
    98         -			}
    99         -		}
   100         -		buf.WriteString(s[last:i])
   101         -		buf.Write(b)
   102         -		last = i + 1
   103         -	}
   104         -	buf.WriteString(s[last:])
   105         -	return buf.Bytes()
   106         -}
   107         -
   108         -func writeEscaped(b *encoder.BufWriter, s string) {
   109         -	b.WriteByte('"')
   110         -	b.Write(Escape(s))
   111         -	b.WriteByte('"')
   112         -}

Changes to encoder/nativeenc/nativeenc.go.

    17     17   	"sort"
    18     18   	"strconv"
    19     19   
    20     20   	"zettelstore.de/z/api"
    21     21   	"zettelstore.de/z/ast"
    22     22   	"zettelstore.de/z/domain/meta"
    23     23   	"zettelstore.de/z/encoder"
    24         -	"zettelstore.de/z/encoder/encfun"
    25         -	"zettelstore.de/z/parser"
    26     24   )
    27     25   
    28     26   func init() {
    29     27   	encoder.Register(api.EncoderNative, encoder.Info{
    30     28   		Create: func(env *encoder.Environment) encoder.Encoder { return &nativeEncoder{env: env} },
    31     29   	})
    32     30   }
    33     31   
    34     32   type nativeEncoder struct {
    35     33   	env *encoder.Environment
    36     34   }
    37     35   
    38     36   // WriteZettel encodes the zettel to the writer.
    39         -func (ne *nativeEncoder) WriteZettel(
    40         -	w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) {
           37  +func (ne *nativeEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) {
    41     38   	v := newVisitor(w, ne)
    42         -	v.b.WriteString("[Title ")
    43         -	v.walkInlineSlice(encfun.MetaAsInlineSlice(zn.InhMeta, meta.KeyTitle))
    44         -	v.b.WriteByte(']')
    45         -	if inhMeta {
    46         -		v.acceptMeta(zn.InhMeta, false)
    47         -	} else {
    48         -		v.acceptMeta(zn.Meta, false)
    49         -	}
           39  +	v.acceptMeta(zn.InhMeta, evalMeta)
    50     40   	v.b.WriteByte('\n')
    51         -	v.walkBlockSlice(zn.Ast)
           41  +	ast.Walk(v, zn.Ast)
    52     42   	length, err := v.b.Flush()
    53     43   	return length, err
    54     44   }
    55     45   
    56     46   // WriteMeta encodes meta data in native format.
    57         -func (ne *nativeEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
           47  +func (ne *nativeEncoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) {
    58     48   	v := newVisitor(w, ne)
    59         -	v.acceptMeta(m, true)
           49  +	v.acceptMeta(m, evalMeta)
    60     50   	length, err := v.b.Flush()
    61     51   	return length, err
    62     52   }
    63     53   
    64     54   func (ne *nativeEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
    65     55   	return ne.WriteBlocks(w, zn.Ast)
    66     56   }
    67     57   
    68     58   // WriteBlocks writes a block slice to the writer
    69         -func (ne *nativeEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
           59  +func (ne *nativeEncoder) WriteBlocks(w io.Writer, bln *ast.BlockListNode) (int, error) {
    70     60   	v := newVisitor(w, ne)
    71         -	v.walkBlockSlice(bs)
           61  +	ast.Walk(v, bln)
    72     62   	length, err := v.b.Flush()
    73     63   	return length, err
    74     64   }
    75     65   
    76     66   // WriteInlines writes an inline slice to the writer
    77         -func (ne *nativeEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
           67  +func (ne *nativeEncoder) WriteInlines(w io.Writer, iln *ast.InlineListNode) (int, error) {
    78     68   	v := newVisitor(w, ne)
    79         -	v.walkInlineSlice(is)
           69  +	ast.Walk(v, iln)
    80     70   	length, err := v.b.Flush()
    81     71   	return length, err
    82     72   }
    83     73   
    84     74   // visitor writes the abstract syntax tree to an io.Writer.
    85     75   type visitor struct {
    86     76   	b     encoder.BufWriter
................................................................................
    90     80   
    91     81   func newVisitor(w io.Writer, enc *nativeEncoder) *visitor {
    92     82   	return &visitor{b: encoder.NewBufWriter(w), env: enc.env}
    93     83   }
    94     84   
    95     85   func (v *visitor) Visit(node ast.Node) ast.Visitor {
    96     86   	switch n := node.(type) {
           87  +	case *ast.BlockListNode:
           88  +		v.visitBlockList(n)
           89  +	case *ast.InlineListNode:
           90  +		v.walkInlineList(n)
    97     91   	case *ast.ParaNode:
    98     92   		v.b.WriteString("[Para ")
    99         -		v.walkInlineSlice(n.Inlines)
           93  +		ast.Walk(v, n.Inlines)
   100     94   		v.b.WriteByte(']')
   101     95   	case *ast.VerbatimNode:
   102     96   		v.visitVerbatim(n)
   103     97   	case *ast.RegionNode:
   104     98   		v.visitRegion(n)
   105     99   	case *ast.HeadingNode:
   106         -		v.b.WriteStrings("[Heading ", strconv.Itoa(n.Level), " \"", n.Slug, "\"")
   107         -		v.visitAttributes(n.Attrs)
   108         -		v.b.WriteByte(' ')
   109         -		v.walkInlineSlice(n.Inlines)
   110         -		v.b.WriteByte(']')
          100  +		v.visitHeading(n)
   111    101   	case *ast.HRuleNode:
   112    102   		v.b.WriteString("[Hrule")
   113    103   		v.visitAttributes(n.Attrs)
   114    104   		v.b.WriteByte(']')
   115    105   	case *ast.NestedListNode:
   116    106   		v.visitNestedList(n)
   117    107   	case *ast.DescriptionListNode:
................................................................................
   144    134   		if n.Hard {
   145    135   			v.b.WriteString("Break")
   146    136   		} else {
   147    137   			v.b.WriteString("Space")
   148    138   		}
   149    139   	case *ast.LinkNode:
   150    140   		v.visitLink(n)
   151         -	case *ast.ImageNode:
   152         -		v.visitImage(n)
          141  +	case *ast.EmbedNode:
          142  +		v.visitEmbed(n)
   153    143   	case *ast.CiteNode:
   154    144   		v.b.WriteString("Cite")
   155    145   		v.visitAttributes(n.Attrs)
   156    146   		v.b.WriteString(" \"")
   157    147   		v.writeEscaped(n.Key)
   158    148   		v.b.WriteByte('"')
   159         -		if len(n.Inlines) > 0 {
          149  +		if n.Inlines != nil {
   160    150   			v.b.WriteString(" [")
   161         -			v.walkInlineSlice(n.Inlines)
          151  +			ast.Walk(v, n.Inlines)
   162    152   			v.b.WriteByte(']')
   163    153   		}
   164    154   	case *ast.FootnoteNode:
   165    155   		v.b.WriteString("Footnote")
   166    156   		v.visitAttributes(n.Attrs)
   167    157   		v.b.WriteString(" [")
   168         -		v.walkInlineSlice(n.Inlines)
          158  +		ast.Walk(v, n.Inlines)
   169    159   		v.b.WriteByte(']')
   170    160   	case *ast.MarkNode:
   171         -		v.b.WriteString("Mark")
   172         -		if len(n.Text) > 0 {
   173         -			v.b.WriteString(" \"")
   174         -			v.writeEscaped(n.Text)
   175         -			v.b.WriteByte('"')
   176         -		}
          161  +		v.visitMark(n)
   177    162   	case *ast.FormatNode:
   178    163   		v.b.Write(mapFormatKind[n.Kind])
   179    164   		v.visitAttributes(n.Attrs)
   180    165   		v.b.WriteString(" [")
   181         -		v.walkInlineSlice(n.Inlines)
          166  +		ast.Walk(v, n.Inlines)
   182    167   		v.b.WriteByte(']')
   183    168   	case *ast.LiteralNode:
   184    169   		kind, ok := mapLiteralKind[n.Kind]
   185    170   		if !ok {
   186    171   			panic(fmt.Sprintf("Unknown literal kind %v", n.Kind))
   187    172   		}
   188    173   		v.b.Write(kind)
................................................................................
   198    183   
   199    184   var (
   200    185   	rawBackslash   = []byte{'\\', '\\'}
   201    186   	rawDoubleQuote = []byte{'\\', '"'}
   202    187   	rawNewline     = []byte{'\\', 'n'}
   203    188   )
   204    189   
   205         -func (v *visitor) acceptMeta(m *meta.Meta, withTitle bool) {
   206         -	if withTitle {
   207         -		v.b.WriteString("[Title ")
   208         -		v.walkInlineSlice(parser.ParseMetadata(m.GetDefault(meta.KeyTitle, "")))
   209         -		v.b.WriteByte(']')
   210         -	}
          190  +func (v *visitor) acceptMeta(m *meta.Meta, evalMeta encoder.EvalMetaFunc) {
          191  +	v.writeZettelmarkup("Title", m.GetDefault(meta.KeyTitle, ""), evalMeta)
   211    192   	v.writeMetaString(m, meta.KeyRole, "Role")
   212    193   	v.writeMetaList(m, meta.KeyTags, "Tags")
   213    194   	v.writeMetaString(m, meta.KeySyntax, "Syntax")
   214    195   	pairs := m.PairsRest(true)
   215    196   	if len(pairs) == 0 {
   216    197   		return
   217    198   	}
   218    199   	v.b.WriteString("\n[Header")
   219    200   	v.level++
   220    201   	for i, p := range pairs {
   221    202   		v.writeComma(i)
   222    203   		v.writeNewLine()
   223         -		v.b.WriteByte('[')
   224         -		v.b.WriteStrings(p.Key, " \"")
   225         -		v.writeEscaped(p.Value)
   226         -		v.b.WriteString("\"]")
          204  +		key, value := p.Key, p.Value
          205  +		if meta.Type(key) == meta.TypeZettelmarkup {
          206  +			v.writeZettelmarkup(key, value, evalMeta)
          207  +		} else {
          208  +			v.b.WriteByte('[')
          209  +			v.b.WriteStrings(key, " \"")
          210  +			v.writeEscaped(value)
          211  +			v.b.WriteString("\"]")
          212  +		}
   227    213   	}
   228    214   	v.level--
   229    215   	v.b.WriteByte(']')
   230    216   }
          217  +
          218  +func (v *visitor) writeZettelmarkup(key, value string, evalMeta encoder.EvalMetaFunc) {
          219  +	v.b.WriteByte('[')
          220  +	v.b.WriteString(key)
          221  +	v.b.WriteByte(' ')
          222  +	ast.Walk(v, evalMeta(value))
          223  +	v.b.WriteByte(']')
          224  +}
   231    225   
   232    226   func (v *visitor) writeMetaString(m *meta.Meta, key, native string) {
   233    227   	if val, ok := m.Get(key); ok && len(val) > 0 {
   234    228   		v.b.WriteStrings("\n[", native, " \"", val, "\"]")
   235    229   	}
   236    230   }
   237    231   
................................................................................
   282    276   	}
   283    277   	v.b.Write(kind)
   284    278   	v.visitAttributes(rn.Attrs)
   285    279   	v.level++
   286    280   	v.writeNewLine()
   287    281   	v.b.WriteByte('[')
   288    282   	v.level++
   289         -	v.walkBlockSlice(rn.Blocks)
          283  +	ast.Walk(v, rn.Blocks)
   290    284   	v.level--
   291    285   	v.b.WriteByte(']')
   292         -	if len(rn.Inlines) > 0 {
          286  +	if rn.Inlines != nil {
   293    287   		v.b.WriteByte(',')
   294    288   		v.writeNewLine()
   295    289   		v.b.WriteString("[Cite ")
   296         -		v.walkInlineSlice(rn.Inlines)
          290  +		ast.Walk(v, rn.Inlines)
   297    291   		v.b.WriteByte(']')
   298    292   	}
   299    293   	v.level--
   300    294   	v.b.WriteByte(']')
   301    295   }
          296  +
          297  +func (v *visitor) visitHeading(hn *ast.HeadingNode) {
          298  +	v.b.WriteStrings("[Heading ", strconv.Itoa(hn.Level))
          299  +	if fragment := hn.Fragment; fragment != "" {
          300  +		v.b.WriteStrings(" #", fragment)
          301  +	}
          302  +	v.visitAttributes(hn.Attrs)
          303  +	v.b.WriteByte(' ')
          304  +	ast.Walk(v, hn.Inlines)
          305  +	v.b.WriteByte(']')
          306  +}
   302    307   
   303    308   var mapNestedListKind = map[ast.NestedListKind][]byte{
   304    309   	ast.NestedListOrdered:   []byte("[OrderedList"),
   305    310   	ast.NestedListUnordered: []byte("[BulletList"),
   306    311   	ast.NestedListQuote:     []byte("[QuoteList"),
   307    312   }
   308    313   
................................................................................
   331    336   func (v *visitor) visitDescriptionList(dn *ast.DescriptionListNode) {
   332    337   	v.b.WriteString("[DescriptionList")
   333    338   	v.level++
   334    339   	for i, descr := range dn.Descriptions {
   335    340   		v.writeComma(i)
   336    341   		v.writeNewLine()
   337    342   		v.b.WriteString("[Term [")
   338         -		v.walkInlineSlice(descr.Term)
          343  +		ast.Walk(v, descr.Term)
   339    344   		v.b.WriteByte(']')
   340    345   
   341    346   		if len(descr.Descriptions) > 0 {
   342    347   			v.level++
   343    348   			for _, b := range descr.Descriptions {
   344    349   				v.b.WriteByte(',')
   345    350   				v.writeNewLine()
................................................................................
   395    400   	ast.AlignLeft:    " Left",
   396    401   	ast.AlignCenter:  " Center",
   397    402   	ast.AlignRight:   " Right",
   398    403   }
   399    404   
   400    405   func (v *visitor) writeCell(cell *ast.TableCell) {
   401    406   	v.b.WriteStrings("[Cell", alignString[cell.Align])
   402         -	if len(cell.Inlines) > 0 {
          407  +	if !cell.Inlines.IsEmpty() {
   403    408   		v.b.WriteByte(' ')
   404         -		v.walkInlineSlice(cell.Inlines)
          409  +		ast.Walk(v, cell.Inlines)
   405    410   	}
   406    411   	v.b.WriteByte(']')
   407    412   }
   408    413   
   409    414   var mapRefState = map[ast.RefState]string{
   410    415   	ast.RefStateInvalid:  "INVALID",
   411    416   	ast.RefStateZettel:   "ZETTEL",
................................................................................
   414    419   	ast.RefStateBroken:   "BROKEN",
   415    420   	ast.RefStateHosted:   "LOCAL",
   416    421   	ast.RefStateBased:    "BASED",
   417    422   	ast.RefStateExternal: "EXTERNAL",
   418    423   }
   419    424   
   420    425   func (v *visitor) visitLink(ln *ast.LinkNode) {
   421         -	ln, n := v.env.AdaptLink(ln)
   422         -	if n != nil {
   423         -		ast.Walk(v, n)
   424         -		return
   425         -	}
   426    426   	v.b.WriteString("Link")
   427    427   	v.visitAttributes(ln.Attrs)
   428    428   	v.b.WriteByte(' ')
   429    429   	v.b.WriteString(mapRefState[ln.Ref.State])
   430    430   	v.b.WriteString(" \"")
   431    431   	v.writeEscaped(ln.Ref.String())
   432    432   	v.b.WriteString("\" [")
   433    433   	if !ln.OnlyRef {
   434         -		v.walkInlineSlice(ln.Inlines)
          434  +		ast.Walk(v, ln.Inlines)
   435    435   	}
   436    436   	v.b.WriteByte(']')
   437    437   }
   438    438   
   439         -func (v *visitor) visitImage(in *ast.ImageNode) {
   440         -	in, n := v.env.AdaptImage(in)
   441         -	if n != nil {
   442         -		ast.Walk(v, n)
   443         -		return
   444         -	}
   445         -	v.b.WriteString("Image")
   446         -	v.visitAttributes(in.Attrs)
   447         -	if in.Ref == nil {
   448         -		v.b.WriteStrings(" {\"", in.Syntax, "\" \"")
   449         -		switch in.Syntax {
          439  +func (v *visitor) visitEmbed(en *ast.EmbedNode) {
          440  +	v.b.WriteString("Embed")
          441  +	v.visitAttributes(en.Attrs)
          442  +	switch m := en.Material.(type) {
          443  +	case *ast.ReferenceMaterialNode:
          444  +		v.b.WriteByte(' ')
          445  +		v.b.WriteString(mapRefState[m.Ref.State])
          446  +		v.b.WriteString(" \"")
          447  +		v.writeEscaped(m.Ref.String())
          448  +		v.b.WriteByte('"')
          449  +	case *ast.BLOBMaterialNode:
          450  +		v.b.WriteStrings(" {\"", m.Syntax, "\" \"")
          451  +		switch m.Syntax {
   450    452   		case "svg":
   451         -			v.writeEscaped(string(in.Blob))
          453  +			v.writeEscaped(string(m.Blob))
   452    454   		default:
   453    455   			v.b.WriteString("\" \"")
   454         -			v.b.WriteBase64(in.Blob)
          456  +			v.b.WriteBase64(m.Blob)
   455    457   		}
   456    458   		v.b.WriteString("\"}")
   457         -	} else {
   458         -		v.b.WriteStrings(" \"", in.Ref.String(), "\"")
          459  +	default:
          460  +		panic(fmt.Sprintf("Unknown material type %t for %v", en.Material, en.Material))
   459    461   	}
   460         -	if len(in.Inlines) > 0 {
          462  +
          463  +	if en.Inlines != nil {
   461    464   		v.b.WriteString(" [")
   462         -		v.walkInlineSlice(in.Inlines)
          465  +		ast.Walk(v, en.Inlines)
   463    466   		v.b.WriteByte(']')
   464    467   	}
   465    468   }
          469  +
          470  +func (v *visitor) visitMark(mn *ast.MarkNode) {
          471  +	v.b.WriteString("Mark")
          472  +	if text := mn.Text; text != "" {
          473  +		v.b.WriteString(" \"")
          474  +		v.writeEscaped(text)
          475  +		v.b.WriteByte('"')
          476  +	}
          477  +	if fragment := mn.Fragment; fragment != "" {
          478  +		v.b.WriteString(" #")
          479  +		v.writeEscaped(fragment)
          480  +	}
          481  +}
   466    482   
   467    483   var mapFormatKind = map[ast.FormatKind][]byte{
   468    484   	ast.FormatItalic:    []byte("Italic"),
   469    485   	ast.FormatEmph:      []byte("Emph"),
   470    486   	ast.FormatBold:      []byte("Bold"),
   471    487   	ast.FormatStrong:    []byte("Strong"),
   472    488   	ast.FormatUnder:     []byte("Underline"),
................................................................................
   486    502   	ast.LiteralProg:    []byte("Code"),
   487    503   	ast.LiteralKeyb:    []byte("Input"),
   488    504   	ast.LiteralOutput:  []byte("Output"),
   489    505   	ast.LiteralComment: []byte("Comment"),
   490    506   	ast.LiteralHTML:    []byte("HTML"),
   491    507   }
   492    508   
   493         -func (v *visitor) walkBlockSlice(bns ast.BlockSlice) {
   494         -	for i, bn := range bns {
          509  +func (v *visitor) visitBlockList(bln *ast.BlockListNode) {
          510  +	for i, bn := range bln.List {
   495    511   		if i > 0 {
   496    512   			v.b.WriteByte(',')
   497    513   			v.writeNewLine()
   498    514   		}
   499    515   		ast.Walk(v, bn)
   500    516   	}
   501    517   }
   502         -func (v *visitor) walkInlineSlice(ins ast.InlineSlice) {
   503         -	for i, in := range ins {
          518  +func (v *visitor) walkInlineList(iln *ast.InlineListNode) {
          519  +	for i, in := range iln.List {
   504    520   		v.writeComma(i)
   505    521   		ast.Walk(v, in)
   506    522   	}
   507    523   }
   508    524   
   509    525   // visitAttributes write native attributes
   510    526   func (v *visitor) visitAttributes(a *ast.Attributes) {
   511         -	if a == nil || len(a.Attrs) == 0 {
          527  +	if a.IsEmpty() {
   512    528   		return
   513    529   	}
   514    530   	keys := make([]string, 0, len(a.Attrs))
   515    531   	for k := range a.Attrs {
   516    532   		keys = append(keys, k)
   517    533   	}
   518    534   	sort.Strings(keys)

Deleted encoder/rawenc/rawenc.go.

     1         -//-----------------------------------------------------------------------------
     2         -// Copyright (c) 2020-2021 Detlef Stern
     3         -//
     4         -// This file is part of zettelstore.
     5         -//
     6         -// Zettelstore is licensed under the latest version of the EUPL (European Union
     7         -// Public License). Please see file LICENSE.txt for your rights and obligations
     8         -// under this license.
     9         -//-----------------------------------------------------------------------------
    10         -
    11         -// Package rawenc encodes the abstract syntax tree as raw content.
    12         -package rawenc
    13         -
    14         -import (
    15         -	"io"
    16         -
    17         -	"zettelstore.de/z/api"
    18         -	"zettelstore.de/z/ast"
    19         -	"zettelstore.de/z/domain/meta"
    20         -	"zettelstore.de/z/encoder"
    21         -)
    22         -
    23         -func init() {
    24         -	encoder.Register(api.EncoderRaw, encoder.Info{
    25         -		Create: func(*encoder.Environment) encoder.Encoder { return &rawEncoder{} },
    26         -	})
    27         -}
    28         -
    29         -type rawEncoder struct{}
    30         -
    31         -// WriteZettel writes the encoded zettel to the writer.
    32         -func (re *rawEncoder) WriteZettel(
    33         -	w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) {
    34         -	b := encoder.NewBufWriter(w)
    35         -	if inhMeta {
    36         -		zn.InhMeta.Write(&b, true)
    37         -	} else {
    38         -		zn.Meta.Write(&b, true)
    39         -	}
    40         -	b.WriteByte('\n')
    41         -	b.WriteString(zn.Content.AsString())
    42         -	length, err := b.Flush()
    43         -	return length, err
    44         -}
    45         -
    46         -// WriteMeta encodes meta data as HTML5.
    47         -func (re *rawEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
    48         -	b := encoder.NewBufWriter(w)
    49         -	m.Write(&b, true)
    50         -	length, err := b.Flush()
    51         -	return length, err
    52         -}
    53         -
    54         -func (re *rawEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
    55         -	b := encoder.NewBufWriter(w)
    56         -	b.WriteString(zn.Content.AsString())
    57         -	length, err := b.Flush()
    58         -	return length, err
    59         -}
    60         -
    61         -// WriteBlocks writes a block slice to the writer
    62         -func (re *rawEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
    63         -	return 0, encoder.ErrNoWriteBlocks
    64         -}
    65         -
    66         -// WriteInlines writes an inline slice to the writer
    67         -func (re *rawEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
    68         -	return 0, encoder.ErrNoWriteInlines
    69         -}

Changes to encoder/textenc/textenc.go.

    14     14   import (
    15     15   	"io"
    16     16   
    17     17   	"zettelstore.de/z/api"
    18     18   	"zettelstore.de/z/ast"
    19     19   	"zettelstore.de/z/domain/meta"
    20     20   	"zettelstore.de/z/encoder"
    21         -	"zettelstore.de/z/parser"
    22     21   )
    23     22   
    24     23   func init() {
    25     24   	encoder.Register(api.EncoderText, encoder.Info{
    26     25   		Create: func(*encoder.Environment) encoder.Encoder { return &textEncoder{} },
    27     26   	})
    28     27   }
    29     28   
    30     29   type textEncoder struct{}
    31     30   
    32     31   // WriteZettel writes metadata and content.
    33         -func (te *textEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) {
           32  +func (te *textEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) {
    34     33   	v := newVisitor(w)
    35         -	if inhMeta {
    36         -		te.WriteMeta(&v.b, zn.InhMeta)
    37         -	} else {
    38         -		te.WriteMeta(&v.b, zn.Meta)
    39         -	}
    40         -	v.acceptBlockSlice(zn.Ast)
           34  +	te.WriteMeta(&v.b, zn.InhMeta, evalMeta)
           35  +	v.visitBlockList(zn.Ast)
    41     36   	length, err := v.b.Flush()
    42     37   	return length, err
    43     38   }
    44     39   
    45     40   // WriteMeta encodes metadata as text.
    46         -func (te *textEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
    47         -	b := encoder.NewBufWriter(w)
           41  +func (te *textEncoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) {
           42  +	buf := encoder.NewBufWriter(w)
    48     43   	for _, pair := range m.Pairs(true) {
    49     44   		switch meta.Type(pair.Key) {
    50     45   		case meta.TypeBool:
    51         -			if meta.BoolValue(pair.Value) {
    52         -				b.WriteString("true")
    53         -			} else {
    54         -				b.WriteString("false")
    55         -			}
           46  +			writeBool(&buf, pair.Value)
    56     47   		case meta.TypeTagSet:
    57         -			for i, tag := range meta.ListFromValue(pair.Value) {
    58         -				if i > 0 {
    59         -					b.WriteByte(' ')
    60         -				}
    61         -				b.WriteString(meta.CleanTag(tag))
    62         -			}
           48  +			writeTagSet(&buf, meta.ListFromValue(pair.Value))
    63     49   		case meta.TypeZettelmarkup:
    64         -			te.WriteInlines(w, parser.ParseMetadata(pair.Value))
           50  +			te.WriteInlines(&buf, evalMeta(pair.Value))
    65     51   		default:
    66         -			b.WriteString(pair.Value)
           52  +			buf.WriteString(pair.Value)
    67     53   		}
    68         -		b.WriteByte('\n')
           54  +		buf.WriteByte('\n')
    69     55   	}
    70         -	length, err := b.Flush()
           56  +	length, err := buf.Flush()
    71     57   	return length, err
           58  +}
           59  +
           60  +func writeBool(buf *encoder.BufWriter, val string) {
           61  +	if meta.BoolValue(val) {
           62  +		buf.WriteString("true")
           63  +	} else {
           64  +		buf.WriteString("false")
           65  +	}
           66  +}
           67  +
           68  +func writeTagSet(buf *encoder.BufWriter, tags []string) {
           69  +	for i, tag := range tags {
           70  +		if i > 0 {
           71  +			buf.WriteByte(' ')
           72  +		}
           73  +		buf.WriteString(meta.CleanTag(tag))
           74  +	}
           75  +
    72     76   }
    73     77   
    74     78   func (te *textEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
    75     79   	return te.WriteBlocks(w, zn.Ast)
    76     80   }
    77     81   
    78     82   // WriteBlocks writes the content of a block slice to the writer.
    79         -func (te *textEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
           83  +func (*textEncoder) WriteBlocks(w io.Writer, bln *ast.BlockListNode) (int, error) {
    80     84   	v := newVisitor(w)
    81         -	v.acceptBlockSlice(bs)
           85  +	v.visitBlockList(bln)
    82     86   	length, err := v.b.Flush()
    83     87   	return length, err
    84     88   }
    85     89   
    86     90   // WriteInlines writes an inline slice to the writer
    87         -func (te *textEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
           91  +func (*textEncoder) WriteInlines(w io.Writer, iln *ast.InlineListNode) (int, error) {
    88     92   	v := newVisitor(w)
    89         -	ast.WalkInlineSlice(v, is)
           93  +	ast.Walk(v, iln)
    90     94   	length, err := v.b.Flush()
    91     95   	return length, err
    92     96   }
    93     97   
    94     98   // visitor writes the abstract syntax tree to an io.Writer.
    95     99   type visitor struct {
    96    100   	b encoder.BufWriter
................................................................................
    98    102   
    99    103   func newVisitor(w io.Writer) *visitor {
   100    104   	return &visitor{b: encoder.NewBufWriter(w)}
   101    105   }
   102    106   
   103    107   func (v *visitor) Visit(node ast.Node) ast.Visitor {
   104    108   	switch n := node.(type) {
          109  +	case *ast.BlockListNode:
          110  +		v.visitBlockList(n)
   105    111   	case *ast.VerbatimNode:
   106         -		if n.Kind == ast.VerbatimComment {
   107         -			return nil
   108         -		}
   109         -		for i, line := range n.Lines {
   110         -			v.writePosChar(i, '\n')
   111         -			v.b.WriteString(line)
   112         -		}
          112  +		v.visitVerbatim(n)
   113    113   		return nil
   114    114   	case *ast.RegionNode:
   115         -		v.acceptBlockSlice(n.Blocks)
   116         -		if len(n.Inlines) > 0 {
          115  +		v.visitBlockList(n.Blocks)
          116  +		if n.Inlines != nil {
   117    117   			v.b.WriteByte('\n')
   118         -			ast.WalkInlineSlice(v, n.Inlines)
          118  +			ast.Walk(v, n.Inlines)
   119    119   		}
   120    120   		return nil
   121    121   	case *ast.NestedListNode:
   122         -		for i, item := range n.Items {
   123         -			v.writePosChar(i, '\n')
   124         -			for j, it := range item {
   125         -				v.writePosChar(j, '\n')
   126         -				ast.Walk(v, it)
   127         -			}
   128         -		}
          122  +		v.visitNestedList(n)
   129    123   		return nil
   130    124   	case *ast.DescriptionListNode:
   131         -		for i, descr := range n.Descriptions {
   132         -			v.writePosChar(i, '\n')
   133         -			ast.WalkInlineSlice(v, descr.Term)
   134         -			for _, b := range descr.Descriptions {
   135         -				v.b.WriteByte('\n')
   136         -				for k, d := range b {
   137         -					v.writePosChar(k, '\n')
   138         -					ast.Walk(v, d)
   139         -				}
   140         -			}
   141         -		}
          125  +		v.visitDescriptionList(n)
   142    126   		return nil
   143    127   	case *ast.TableNode:
   144         -		if len(n.Header) > 0 {
   145         -			v.writeRow(n.Header)
   146         -			v.b.WriteByte('\n')
   147         -		}
   148         -		for i, row := range n.Rows {
   149         -			v.writePosChar(i, '\n')
   150         -			v.writeRow(row)
   151         -		}
          128  +		v.visitTable(n)
   152    129   		return nil
   153    130   	case *ast.TextNode:
   154    131   		v.b.WriteString(n.Text)
   155    132   		return nil
   156    133   	case *ast.TagNode:
   157         -		v.b.WriteStrings("#", n.Tag)
          134  +		v.b.WriteString(n.Tag)
   158    135   		return nil
   159    136   	case *ast.SpaceNode:
   160    137   		v.b.WriteByte(' ')
   161    138   		return nil
   162    139   	case *ast.BreakNode:
   163    140   		if n.Hard {
   164    141   			v.b.WriteByte('\n')
   165    142   		} else {
   166    143   			v.b.WriteByte(' ')
   167    144   		}
   168    145   		return nil
   169    146   	case *ast.LinkNode:
   170    147   		if !n.OnlyRef {
   171         -			ast.WalkInlineSlice(v, n.Inlines)
          148  +			ast.Walk(v, n.Inlines)
   172    149   		}
   173    150   		return nil
   174    151   	case *ast.FootnoteNode:
   175    152   		v.b.WriteByte(' ')
   176    153   		return v // No 'return nil' to write text
   177    154   	case *ast.LiteralNode:
   178    155   		if n.Kind != ast.LiteralComment {
   179    156   			v.b.WriteString(n.Text)
   180    157   		}
   181    158   	}
   182    159   	return v
   183    160   }
          161  +
          162  +func (v *visitor) visitVerbatim(vn *ast.VerbatimNode) {
          163  +	if vn.Kind == ast.VerbatimComment {
          164  +		return
          165  +	}
          166  +	for i, line := range vn.Lines {
          167  +		v.writePosChar(i, '\n')
          168  +		v.b.WriteString(line)
          169  +	}
          170  +}
          171  +
          172  +func (v *visitor) visitNestedList(ln *ast.NestedListNode) {
          173  +	for i, item := range ln.Items {
          174  +		v.writePosChar(i, '\n')
          175  +		for j, it := range item {
          176  +			v.writePosChar(j, '\n')
          177  +			ast.Walk(v, it)
          178  +		}
          179  +	}
          180  +}
          181  +
          182  +func (v *visitor) visitDescriptionList(dl *ast.DescriptionListNode) {
          183  +	for i, descr := range dl.Descriptions {
          184  +		v.writePosChar(i, '\n')
          185  +		ast.Walk(v, descr.Term)
          186  +		for _, b := range descr.Descriptions {
          187  +			v.b.WriteByte('\n')
          188  +			for k, d := range b {
          189  +				v.writePosChar(k, '\n')
          190  +				ast.Walk(v, d)
          191  +			}
          192  +		}
          193  +	}
          194  +}
          195  +
          196  +func (v *visitor) visitTable(tn *ast.TableNode) {
          197  +	if len(tn.Header) > 0 {
          198  +		v.writeRow(tn.Header)
          199  +		v.b.WriteByte('\n')
          200  +	}
          201  +	for i, row := range tn.Rows {
          202  +		v.writePosChar(i, '\n')
          203  +		v.writeRow(row)
          204  +	}
          205  +}
   184    206   
   185    207   func (v *visitor) writeRow(row ast.TableRow) {
   186    208   	for i, cell := range row {
   187    209   		v.writePosChar(i, ' ')
   188         -		ast.WalkInlineSlice(v, cell.Inlines)
          210  +		ast.Walk(v, cell.Inlines)
   189    211   	}
   190    212   }
   191    213   
   192         -func (v *visitor) acceptBlockSlice(bns ast.BlockSlice) {
   193         -	for i, bn := range bns {
          214  +func (v *visitor) visitBlockList(bns *ast.BlockListNode) {
          215  +	for i, bn := range bns.List {
   194    216   		v.writePosChar(i, '\n')
   195    217   		ast.Walk(v, bn)
   196    218   	}
   197    219   }
   198    220   
   199    221   func (v *visitor) writePosChar(pos int, ch byte) {
   200    222   	if pos > 0 {
   201    223   		v.b.WriteByte(ch)
   202    224   	}
   203    225   }

Changes to encoder/zmkenc/zmkenc.go.

    27     27   		Create: func(*encoder.Environment) encoder.Encoder { return &zmkEncoder{} },
    28     28   	})
    29     29   }
    30     30   
    31     31   type zmkEncoder struct{}
    32     32   
    33     33   // WriteZettel writes the encoded zettel to the writer.
    34         -func (ze *zmkEncoder) WriteZettel(
    35         -	w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) {
           34  +func (ze *zmkEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) {
    36     35   	v := newVisitor(w, ze)
    37         -	if inhMeta {
    38         -		zn.InhMeta.WriteAsHeader(&v.b, true)
           36  +	v.acceptMeta(zn.InhMeta, evalMeta)
           37  +	if zn.InhMeta.YamlSep {
           38  +		v.b.WriteString("---\n")
    39     39   	} else {
    40         -		zn.Meta.WriteAsHeader(&v.b, true)
           40  +		v.b.WriteByte('\n')
    41     41   	}
    42         -	ast.WalkBlockSlice(v, zn.Ast)
           42  +	ast.Walk(v, zn.Ast)
    43     43   	length, err := v.b.Flush()
    44     44   	return length, err
    45     45   }
    46     46   
    47     47   // WriteMeta encodes meta data as zmk.
    48         -func (ze *zmkEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
    49         -	return m.Write(w, true)
           48  +func (ze *zmkEncoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) {
           49  +	v := newVisitor(w, ze)
           50  +	v.acceptMeta(m, evalMeta)
           51  +	length, err := v.b.Flush()
           52  +	return length, err
           53  +}
           54  +
           55  +func (v *visitor) acceptMeta(m *meta.Meta, evalMeta encoder.EvalMetaFunc) {
           56  +	for _, p := range m.Pairs(true) {
           57  +		key := p.Key
           58  +		v.b.WriteStrings(key, ": ")
           59  +		if meta.Type(key) == meta.TypeZettelmarkup {
           60  +			ast.Walk(v, evalMeta(p.Value))
           61  +		} else {
           62  +			v.b.WriteString(p.Value)
           63  +		}
           64  +		v.b.WriteByte('\n')
           65  +	}
    50     66   }
    51     67   
    52     68   func (ze *zmkEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
    53     69   	return ze.WriteBlocks(w, zn.Ast)
    54     70   }
    55     71   
    56     72   // WriteBlocks writes the content of a block slice to the writer.
    57         -func (ze *zmkEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
           73  +func (ze *zmkEncoder) WriteBlocks(w io.Writer, bln *ast.BlockListNode) (int, error) {
    58     74   	v := newVisitor(w, ze)
    59         -	ast.WalkBlockSlice(v, bs)
           75  +	ast.Walk(v, bln)
    60     76   	length, err := v.b.Flush()
    61     77   	return length, err
    62     78   }
    63     79   
    64     80   // WriteInlines writes an inline slice to the writer
    65         -func (ze *zmkEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
           81  +func (ze *zmkEncoder) WriteInlines(w io.Writer, iln *ast.InlineListNode) (int, error) {
    66     82   	v := newVisitor(w, ze)
    67         -	ast.WalkInlineSlice(v, is)
           83  +	ast.Walk(v, iln)
    68     84   	length, err := v.b.Flush()
    69     85   	return length, err
    70     86   }
    71     87   
    72     88   // visitor writes the abstract syntax tree to an io.Writer.
    73     89   type visitor struct {
    74     90   	b      encoder.BufWriter
................................................................................
    82     98   		enc: enc,
    83     99   	}
    84    100   }
    85    101   
    86    102   func (v *visitor) Visit(node ast.Node) ast.Visitor {
    87    103   	switch n := node.(type) {
    88    104   	case *ast.ParaNode:
    89         -		ast.WalkInlineSlice(v, n.Inlines)
          105  +		ast.Walk(v, n.Inlines)
    90    106   		v.b.WriteByte('\n')
    91    107   		if len(v.prefix) == 0 {
    92    108   			v.b.WriteByte('\n')
    93    109   		}
    94    110   	case *ast.VerbatimNode:
    95    111   		v.visitVerbatim(n)
    96    112   	case *ast.RegionNode:
................................................................................
   117    133   		v.b.WriteStrings("#", n.Tag)
   118    134   	case *ast.SpaceNode:
   119    135   		v.b.WriteString(n.Lexeme)
   120    136   	case *ast.BreakNode:
   121    137   		v.visitBreak(n)
   122    138   	case *ast.LinkNode:
   123    139   		v.visitLink(n)
   124         -	case *ast.ImageNode:
   125         -		v.visitImage(n)
          140  +	case *ast.EmbedNode:
          141  +		v.visitEmbed(n)
   126    142   	case *ast.CiteNode:
   127    143   		v.visitCite(n)
   128    144   	case *ast.FootnoteNode:
   129    145   		v.b.WriteString("[^")
   130         -		ast.WalkInlineSlice(v, n.Inlines)
          146  +		ast.Walk(v, n.Inlines)
   131    147   		v.b.WriteByte(']')
   132    148   		v.visitAttributes(n.Attrs)
   133    149   	case *ast.MarkNode:
   134    150   		v.b.WriteStrings("[!", n.Text, "]")
   135    151   	case *ast.FormatNode:
   136    152   		v.visitFormat(n)
   137    153   	case *ast.LiteralNode:
................................................................................
   164    180   	kind, ok := mapRegionKind[rn.Kind]
   165    181   	if !ok {
   166    182   		panic(fmt.Sprintf("Unknown region kind %d", rn.Kind))
   167    183   	}
   168    184   	v.b.WriteString(kind)
   169    185   	v.visitAttributes(rn.Attrs)
   170    186   	v.b.WriteByte('\n')
   171         -	ast.WalkBlockSlice(v, rn.Blocks)
          187  +	ast.Walk(v, rn.Blocks)
   172    188   	v.b.WriteString(kind)
   173         -	if len(rn.Inlines) > 0 {
          189  +	if rn.Inlines != nil {
   174    190   		v.b.WriteByte(' ')
   175         -		ast.WalkInlineSlice(v, rn.Inlines)
          191  +		ast.Walk(v, rn.Inlines)
   176    192   	}
   177    193   	v.b.WriteByte('\n')
   178    194   }
   179    195   
   180    196   func (v *visitor) visitHeading(hn *ast.HeadingNode) {
   181    197   	for i := 0; i <= hn.Level; i++ {
   182    198   		v.b.WriteByte('=')
   183    199   	}
   184    200   	v.b.WriteByte(' ')
   185         -	ast.WalkInlineSlice(v, hn.Inlines)
          201  +	ast.Walk(v, hn.Inlines)
   186    202   	v.visitAttributes(hn.Attrs)
   187    203   	v.b.WriteByte('\n')
   188    204   }
   189    205   
   190    206   var mapNestedListKind = map[ast.NestedListKind]byte{
   191    207   	ast.NestedListOrdered:   '#',
   192    208   	ast.NestedListUnordered: '*',
................................................................................
   213    229   	v.prefix = v.prefix[:len(v.prefix)-1]
   214    230   	v.b.WriteByte('\n')
   215    231   }
   216    232   
   217    233   func (v *visitor) visitDescriptionList(dn *ast.DescriptionListNode) {
   218    234   	for _, descr := range dn.Descriptions {
   219    235   		v.b.WriteString("; ")
   220         -		ast.WalkInlineSlice(v, descr.Term)
          236  +		ast.Walk(v, descr.Term)
   221    237   		v.b.WriteByte('\n')
   222    238   
   223    239   		for _, b := range descr.Descriptions {
   224    240   			v.b.WriteString(": ")
   225    241   			ast.WalkDescriptionSlice(v, b)
   226    242   			v.b.WriteByte('\n')
   227    243   		}
................................................................................
   239    255   	if len(tn.Header) > 0 {
   240    256   		for pos, cell := range tn.Header {
   241    257   			v.b.WriteString("|=")
   242    258   			colAlign := tn.Align[pos]
   243    259   			if cell.Align != colAlign {
   244    260   				v.b.WriteString(alignCode[cell.Align])
   245    261   			}
   246         -			ast.WalkInlineSlice(v, cell.Inlines)
          262  +			ast.Walk(v, cell.Inlines)
   247    263   			if colAlign != ast.AlignDefault {
   248    264   				v.b.WriteString(alignCode[colAlign])
   249    265   			}
   250    266   		}
   251    267   		v.b.WriteByte('\n')
   252    268   	}
   253    269   	for _, row := range tn.Rows {
   254    270   		for pos, cell := range row {
   255    271   			v.b.WriteByte('|')
   256    272   			if cell.Align != tn.Align[pos] {
   257    273   				v.b.WriteString(alignCode[cell.Align])
   258    274   			}
   259         -			ast.WalkInlineSlice(v, cell.Inlines)
          275  +			ast.Walk(v, cell.Inlines)
   260    276   		}
   261    277   		v.b.WriteByte('\n')
   262    278   	}
   263    279   	v.b.WriteByte('\n')
   264    280   }
   265    281   
   266    282   var escapeSeqs = map[string]bool{
................................................................................
   318    334   		}
   319    335   	}
   320    336   }
   321    337   
   322    338   func (v *visitor) visitLink(ln *ast.LinkNode) {
   323    339   	v.b.WriteString("[[")
   324    340   	if !ln.OnlyRef {
   325         -		ast.WalkInlineSlice(v, ln.Inlines)
          341  +		ast.Walk(v, ln.Inlines)
   326    342   		v.b.WriteByte('|')
   327    343   	}
   328    344   	v.b.WriteStrings(ln.Ref.String(), "]]")
   329    345   }
   330    346   
   331         -func (v *visitor) visitImage(in *ast.ImageNode) {
   332         -	if in.Ref != nil {
          347  +func (v *visitor) visitEmbed(en *ast.EmbedNode) {
          348  +	switch m := en.Material.(type) {
          349  +	case *ast.ReferenceMaterialNode:
   333    350   		v.b.WriteString("{{")
   334         -		if len(in.Inlines) > 0 {
   335         -			ast.WalkInlineSlice(v, in.Inlines)
          351  +		if en.Inlines != nil {
          352  +			ast.Walk(v, en.Inlines)
   336    353   			v.b.WriteByte('|')
   337    354   		}
   338         -		v.b.WriteStrings(in.Ref.String(), "}}")
          355  +		v.b.WriteStrings(m.Ref.String(), "}}")
          356  +	case *ast.BLOBMaterialNode:
          357  +		panic("TODO")
          358  +	default:
          359  +		panic(fmt.Sprintf("Unknown material type %t for %v", en.Material, en.Material))
   339    360   	}
   340    361   }
   341    362   
   342    363   func (v *visitor) visitCite(cn *ast.CiteNode) {
   343    364   	v.b.WriteStrings("[@", cn.Key)
   344         -	if len(cn.Inlines) > 0 {
          365  +	if cn.Inlines != nil {
   345    366   		v.b.WriteString(", ")
   346         -		ast.WalkInlineSlice(v, cn.Inlines)
          367  +		ast.Walk(v, cn.Inlines)
   347    368   	}
   348    369   	v.b.WriteByte(']')
   349    370   	v.visitAttributes(cn.Attrs)
   350    371   }
   351    372   
   352    373   var mapFormatKind = map[ast.FormatKind][]byte{
   353    374   	ast.FormatItalic:    []byte("//"),
................................................................................
   376    397   	switch fn.Kind {
   377    398   	case ast.FormatEmph, ast.FormatStrong, ast.FormatInsert, ast.FormatDelete:
   378    399   		attrs = attrs.Clone()
   379    400   		attrs.Set("-", "")
   380    401   	}
   381    402   
   382    403   	v.b.Write(kind)
   383         -	ast.WalkInlineSlice(v, fn.Inlines)
          404  +	ast.Walk(v, fn.Inlines)
   384    405   	v.b.Write(kind)
   385    406   	v.visitAttributes(attrs)
   386    407   }
   387    408   
   388    409   func (v *visitor) visitLiteral(ln *ast.LiteralNode) {
   389    410   	switch ln.Kind {
   390    411   	case ast.LiteralProg:
................................................................................
   409    430   	v.writeEscaped(text, code)
   410    431   	v.b.WriteBytes(code, code)
   411    432   	v.visitAttributes(attrs)
   412    433   }
   413    434   
   414    435   // visitAttributes write HTML attributes
   415    436   func (v *visitor) visitAttributes(a *ast.Attributes) {
   416         -	if a == nil || len(a.Attrs) == 0 {
          437  +	if a.IsEmpty() {
   417    438   		return
   418    439   	}
   419    440   	keys := make([]string, 0, len(a.Attrs))
   420    441   	for k := range a.Attrs {
   421    442   		keys = append(keys, k)
   422    443   	}
   423    444   	sort.Strings(keys)

Added evaluator/evaluator.go.

            1  +//-----------------------------------------------------------------------------
            2  +// Copyright (c) 2021 Detlef Stern
            3  +//
            4  +// This file is part of zettelstore.
            5  +//
            6  +// Zettelstore is licensed under the latest version of the EUPL (European Union
            7  +// Public License). Please see file LICENSE.txt for your rights and obligations
            8  +// under this license.
            9  +//-----------------------------------------------------------------------------
           10  +
           11  +// Package evaluator interprets and evaluates the AST.
           12  +package evaluator
           13  +
           14  +import (
           15  +	"context"
           16  +	"errors"
           17  +	"fmt"
           18  +	"strconv"
           19  +
           20  +	"zettelstore.de/z/ast"
           21  +	"zettelstore.de/z/box"
           22  +	"zettelstore.de/z/config"
           23  +	"zettelstore.de/z/domain"
           24  +	"zettelstore.de/z/domain/id"
           25  +	"zettelstore.de/z/domain/meta"
           26  +	"zettelstore.de/z/parser"
           27  +	"zettelstore.de/z/parser/cleaner"
           28  +)
           29  +
           30  +// Environment contains values to control the evaluation.
           31  +type Environment struct {
           32  +	EmbedImage   bool
           33  +	GetTagRef    func(string) *ast.Reference
           34  +	GetHostedRef func(string) *ast.Reference
           35  +	GetFoundRef  func(zid id.Zid, fragment string) *ast.Reference
           36  +}
           37  +
           38  +// Port contains all methods to retrieve zettel (or part of it) to evaluate a zettel.
           39  +type Port interface {
           40  +	GetMeta(context.Context, id.Zid) (*meta.Meta, error)
           41  +	GetZettel(context.Context, id.Zid) (domain.Zettel, error)
           42  +}
           43  +
           44  +var emptyEnv Environment
           45  +
           46  +// EvaluateZettel evaluates the given zettel in the given context, with the
           47  +// given ports, and the given environment.
           48  +func EvaluateZettel(ctx context.Context, port Port, env *Environment, rtConfig config.Config, zn *ast.ZettelNode) {
           49  +	evaluateNode(ctx, port, env, rtConfig, zn.Ast)
           50  +	cleaner.CleanBlockList(zn.Ast)
           51  +}
           52  +
           53  +// EvaluateInline evaluates the given inline list in the given context, with
           54  +// the given ports, and the given environment.
           55  +func EvaluateInline(ctx context.Context, port Port, env *Environment, rtConfig config.Config, iln *ast.InlineListNode) {
           56  +	evaluateNode(ctx, port, env, rtConfig, iln)
           57  +	cleaner.CleanInlineList(iln)
           58  +}
           59  +
           60  +func evaluateNode(ctx context.Context, port Port, env *Environment, rtConfig config.Config, n ast.Node) {
           61  +	if env == nil {
           62  +		env = &emptyEnv
           63  +	}
           64  +	e := evaluator{
           65  +		ctx:        ctx,
           66  +		port:       port,
           67  +		env:        env,
           68  +		rtConfig:   rtConfig,
           69  +		astMap:     map[id.Zid]*ast.ZettelNode{},
           70  +		embedMap:   map[string]*ast.InlineListNode{},
           71  +		embedCount: 0,
           72  +		marker:     &ast.ZettelNode{},
           73  +	}
           74  +	ast.Walk(&e, n)
           75  +}
           76  +
           77  +type evaluator struct {
           78  +	ctx        context.Context
           79  +	port       Port
           80  +	env        *Environment
           81  +	rtConfig   config.Config
           82  +	astMap     map[id.Zid]*ast.ZettelNode
           83  +	marker     *ast.ZettelNode
           84  +	embedMap   map[string]*ast.InlineListNode
           85  +	embedCount int
           86  +}
           87  +
           88  +func (e *evaluator) Visit(node ast.Node) ast.Visitor {
           89  +	switch n := node.(type) {
           90  +	case *ast.InlineListNode:
           91  +		e.visitInlineList(n)
           92  +	default:
           93  +		return e
           94  +	}
           95  +	return nil
           96  +}
           97  +
           98  +func (e *evaluator) visitInlineList(iln *ast.InlineListNode) {
           99  +	for i := 0; i < len(iln.List); i++ {
          100  +		in := iln.List[i]
          101  +		ast.Walk(e, in)
          102  +		switch n := in.(type) {
          103  +		case *ast.TagNode:
          104  +			iln.List[i] = e.visitTag(n)
          105  +		case *ast.LinkNode:
          106  +			iln.List[i] = e.evalLinkNode(n)
          107  +		case *ast.EmbedNode:
          108  +			in := e.evalEmbedNode(n)
          109  +			if ln, ok := in.(*ast.InlineListNode); ok {
          110  +				iln.List = replaceWithInlineNodes(iln.List, i, ln.List)
          111  +				i += len(ln.List) - 1
          112  +			} else {
          113  +				iln.List[i] = in
          114  +			}
          115  +		}
          116  +	}
          117  +}
          118  +
          119  +func replaceWithInlineNodes(ins []ast.InlineNode, i int, replaceIns []ast.InlineNode) []ast.InlineNode {
          120  +	if len(replaceIns) == 1 {
          121  +		ins[i] = replaceIns[0]
          122  +		return ins
          123  +	}
          124  +	newIns := make([]ast.InlineNode, 0, len(ins)+len(replaceIns)-1)
          125  +	if i > 0 {
          126  +		newIns = append(newIns, ins[:i]...)
          127  +	}
          128  +	if len(replaceIns) > 0 {
          129  +		newIns = append(newIns, replaceIns...)
          130  +	}
          131  +	if i+1 < len(ins) {
          132  +		newIns = append(newIns, ins[i+1:]...)
          133  +	}
          134  +	return newIns
          135  +}
          136  +
          137  +func (e *evaluator) visitTag(tn *ast.TagNode) ast.InlineNode {
          138  +	if gtr := e.env.GetTagRef; gtr != nil {
          139  +		fullTag := "#" + tn.Tag
          140  +		return &ast.LinkNode{
          141  +			Ref:     e.env.GetTagRef(fullTag),
          142  +			Inlines: ast.CreateInlineListNodeFromWords(fullTag),
          143  +		}
          144  +	}
          145  +	return tn
          146  +}
          147  +
          148  +func (e *evaluator) evalLinkNode(ln *ast.LinkNode) ast.InlineNode {
          149  +	ref := ln.Ref
          150  +	if ref == nil {
          151  +		return ln
          152  +	}
          153  +	if ref.State == ast.RefStateBased {
          154  +		if ghr := e.env.GetHostedRef; ghr != nil {
          155  +			ln.Ref = ghr(ref.Value[1:])
          156  +		}
          157  +		return ln
          158  +	}
          159  +	if ref.State != ast.RefStateZettel {
          160  +		return ln
          161  +	}
          162  +	zid, err := id.Parse(ref.URL.Path)
          163  +	if err != nil {
          164  +		panic(err)
          165  +	}
          166  +	_, err = e.port.GetMeta(box.NoEnrichContext(e.ctx), zid)
          167  +	if errors.Is(err, &box.ErrNotAllowed{}) {
          168  +		return &ast.FormatNode{
          169  +			Kind:    ast.FormatSpan,
          170  +			Attrs:   ln.Attrs,
          171  +			Inlines: ln.Inlines,
          172  +		}
          173  +	} else if err != nil {
          174  +		ln.Ref.State = ast.RefStateBroken
          175  +		return ln
          176  +	}
          177  +
          178  +	if gfr := e.env.GetFoundRef; gfr != nil {
          179  +		ln.Ref = gfr(zid, ref.URL.EscapedFragment())
          180  +	}
          181  +	return ln
          182  +}
          183  +
          184  +func (e *evaluator) evalEmbedNode(en *ast.EmbedNode) ast.InlineNode {
          185  +	switch en.Material.(type) {
          186  +	case *ast.ReferenceMaterialNode:
          187  +	case *ast.BLOBMaterialNode:
          188  +		return en
          189  +	default:
          190  +		panic(fmt.Sprintf("Unknown material type %t for %v", en.Material, en.Material))
          191  +	}
          192  +
          193  +	ref := en.Material.(*ast.ReferenceMaterialNode)
          194  +	switch ref.Ref.State {
          195  +	case ast.RefStateInvalid:
          196  +		return e.createErrorImage(en)
          197  +	case ast.RefStateZettel, ast.RefStateFound:
          198  +	case ast.RefStateSelf:
          199  +		return createErrorText(en, "Self", "embed", "reference:")
          200  +	case ast.RefStateBroken:
          201  +		return e.createErrorImage(en)
          202  +	case ast.RefStateHosted, ast.RefStateBased, ast.RefStateExternal:
          203  +		return en
          204  +	default:
          205  +		panic(fmt.Sprintf("Unknown state %v for reference %v", ref.Ref.State, ref.Ref))
          206  +	}
          207  +
          208  +	zid, err := id.Parse(ref.Ref.URL.Path)
          209  +	if err != nil {
          210  +		panic(err)
          211  +	}
          212  +	zettel, err := e.port.GetZettel(box.NoEnrichContext(e.ctx), zid)
          213  +	if err != nil {
          214  +		return e.createErrorImage(en)
          215  +	}
          216  +
          217  +	syntax := e.getSyntax(zettel.Meta)
          218  +	if parser.IsImageFormat(syntax) {
          219  +		return e.embedImage(en, zettel, syntax)
          220  +	}
          221  +	if !parser.IsTextParser(syntax) {
          222  +		// Not embeddable.
          223  +		return createErrorText(en, "Not", "embeddable (syntax="+syntax+"):")
          224  +	}
          225  +
          226  +	zn, ok := e.astMap[zid]
          227  +	if zn == e.marker {
          228  +		return createErrorText(en, "Recursive", "transclusion:")
          229  +	}
          230  +	if !ok {
          231  +		e.astMap[zid] = e.marker
          232  +		zn = e.evaluateEmbeddedZettel(zettel, syntax)
          233  +		e.astMap[zid] = zn
          234  +	}
          235  +
          236  +	result, ok := e.embedMap[ref.Ref.Value]
          237  +	if !ok {
          238  +		// Search for text to be embedded.
          239  +		result = findInlineList(zn.Ast, ref.Ref.URL.Fragment)
          240  +		e.embedMap[ref.Ref.Value] = result
          241  +		if result.IsEmpty() {
          242  +			return createErrorText(en, "Nothing", "to", "transclude:")
          243  +		}
          244  +	}
          245  +
          246  +	e.embedCount++
          247  +	if maxTrans := e.rtConfig.GetMaxTransclusions(); e.embedCount > maxTrans {
          248  +		return createErrorText(en, "Too", "many", "transclusions ("+strconv.Itoa(maxTrans)+"):")
          249  +	}
          250  +	return result
          251  +}
          252  +
          253  +func (e *evaluator) getSyntax(m *meta.Meta) string {
          254  +	if cfg := e.rtConfig; cfg != nil {
          255  +		return config.GetSyntax(m, cfg)
          256  +	}
          257  +	return m.GetDefault(meta.KeySyntax, "")
          258  +}
          259  +
          260  +func (e *evaluator) createErrorImage(en *ast.EmbedNode) *ast.EmbedNode {
          261  +	zid := id.EmojiZid
          262  +	if !e.env.EmbedImage {
          263  +		en.Material = &ast.ReferenceMaterialNode{Ref: ast.ParseReference(zid.String())}
          264  +		return en
          265  +	}
          266  +	zettel, err := e.port.GetZettel(box.NoEnrichContext(e.ctx), zid)
          267  +	if err == nil {
          268  +		return doEmbedImage(en, zettel, e.getSyntax(zettel.Meta))
          269  +	}
          270  +	panic(err)
          271  +}
          272  +
          273  +func (e *evaluator) embedImage(en *ast.EmbedNode, zettel domain.Zettel, syntax string) *ast.EmbedNode {
          274  +	if e.env.EmbedImage {
          275  +		return doEmbedImage(en, zettel, syntax)
          276  +	}
          277  +	return en
          278  +}
          279  +
          280  +func doEmbedImage(en *ast.EmbedNode, zettel domain.Zettel, syntax string) *ast.EmbedNode {
          281  +	en.Material = &ast.BLOBMaterialNode{
          282  +		Blob:   zettel.Content.AsBytes(),
          283  +		Syntax: syntax,
          284  +	}
          285  +	return en
          286  +}
          287  +
          288  +func createErrorText(en *ast.EmbedNode, msgWords ...string) ast.InlineNode {
          289  +	ref := en.Material.(*ast.ReferenceMaterialNode)
          290  +	ln := &ast.LinkNode{
          291  +		Ref:     ref.Ref,
          292  +		Inlines: ast.CreateInlineListNodeFromWords(ref.Ref.String()),
          293  +		OnlyRef: true,
          294  +	}
          295  +	text := ast.CreateInlineListNodeFromWords(msgWords...)
          296  +	text.Append(&ast.SpaceNode{Lexeme: " "}, ln)
          297  +	fn := &ast.FormatNode{
          298  +		Kind:    ast.FormatMonospace,
          299  +		Inlines: text,
          300  +	}
          301  +	fn = &ast.FormatNode{
          302  +		Kind:    ast.FormatBold,
          303  +		Inlines: ast.CreateInlineListNode(fn),
          304  +	}
          305  +	fn.Attrs = fn.Attrs.AddClass("error")
          306  +	return fn
          307  +}
          308  +
          309  +func (e *evaluator) evaluateEmbeddedZettel(zettel domain.Zettel, syntax string) *ast.ZettelNode {
          310  +	zn := parser.ParseZettel(zettel, syntax, e.rtConfig)
          311  +	ast.Walk(e, zn.Ast)
          312  +	return zn
          313  +}
          314  +
          315  +func findInlineList(bnl *ast.BlockListNode, fragment string) *ast.InlineListNode {
          316  +	if fragment == "" {
          317  +		return firstFirstTopLevelParagraph(bnl.List)
          318  +	}
          319  +	fs := fragmentSearcher{
          320  +		fragment: fragment,
          321  +		result:   nil,
          322  +	}
          323  +	ast.Walk(&fs, bnl)
          324  +	return fs.result
          325  +}
          326  +
          327  +func firstFirstTopLevelParagraph(bns []ast.BlockNode) *ast.InlineListNode {
          328  +	for _, bn := range bns {
          329  +		pn, ok := bn.(*ast.ParaNode)
          330  +		if !ok {
          331  +			continue
          332  +		}
          333  +		inl := pn.Inlines
          334  +		if inl != nil && len(inl.List) > 0 {
          335  +			return inl
          336  +		}
          337  +	}
          338  +	return nil
          339  +}
          340  +
          341  +type fragmentSearcher struct {
          342  +	fragment string
          343  +	result   *ast.InlineListNode
          344  +}
          345  +
          346  +func (fs *fragmentSearcher) Visit(node ast.Node) ast.Visitor {
          347  +	if fs.result != nil {
          348  +		return nil
          349  +	}
          350  +	switch n := node.(type) {
          351  +	case *ast.BlockListNode:
          352  +		for i, bn := range n.List {
          353  +			if hn, ok := bn.(*ast.HeadingNode); ok && hn.Fragment == fs.fragment {
          354  +				fs.result = firstFirstTopLevelParagraph(n.List[i+1:])
          355  +				return nil
          356  +			}
          357  +			ast.Walk(fs, bn)
          358  +		}
          359  +	case *ast.InlineListNode:
          360  +		for i, in := range n.List {
          361  +			if mn, ok := in.(*ast.MarkNode); ok && mn.Fragment == fs.fragment {
          362  +				fs.result = ast.CreateInlineListNode(skipSpaceNodes(n.List[i+1:])...)
          363  +				return nil
          364  +			}
          365  +			ast.Walk(fs, in)
          366  +		}
          367  +	default:
          368  +		return fs
          369  +	}
          370  +	return nil
          371  +}
          372  +
          373  +func skipSpaceNodes(ins []ast.InlineNode) []ast.InlineNode {
          374  +	for i, in := range ins {
          375  +		switch in.(type) {
          376  +		case *ast.SpaceNode:
          377  +		case *ast.BreakNode:
          378  +		default:
          379  +			return ins[i:]
          380  +		}
          381  +	}
          382  +	return nil
          383  +}

Changes to go.mod.

     1      1   module zettelstore.de/z
     2      2   
     3         -go 1.16
            3  +go 1.17
     4      4   
     5      5   require (
     6         -	github.com/fsnotify/fsnotify v1.4.9
            6  +	github.com/fsnotify/fsnotify v1.5.1
     7      7   	github.com/pascaldekloe/jwt v1.10.0
     8         -	github.com/yuin/goldmark v1.4.0
            8  +	github.com/yuin/goldmark v1.4.1
     9      9   	golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
    10     10   	golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
    11         -	golang.org/x/text v0.3.6
           11  +	golang.org/x/text v0.3.7
    12     12   )
           13  +
           14  +require golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect

Changes to go.sum.

     1         -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
     2         -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
            1  +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
            2  +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
     3      3   github.com/pascaldekloe/jwt v1.10.0 h1:ktcIUV4TPvh404R5dIBEnPCsSwj0sqi3/0+XafE5gJs=
     4      4   github.com/pascaldekloe/jwt v1.10.0/go.mod h1:TKhllgThT7TOP5rGr2zMLKEDZRAgJfBbtKyVeRsNB9A=
     5         -github.com/yuin/goldmark v1.4.0 h1:OtISOGfH6sOWa1/qXqqAiOIAO6Z5J3AEAE18WAq6BiQ=
     6         -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
            5  +github.com/yuin/goldmark v1.4.1 h1:/vn0k+RBvwlxEmP5E7SZMqNxPhfMVFEJiykr15/0XKM=
            6  +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
     7      7   golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
     8      8   golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
     9      9   golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
    10         -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
    11     10   golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
    12         -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
    13     11   golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
           12  +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
           13  +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
    14     14   golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
    15     15   golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
    16     16   golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
    17     17   golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
    18         -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
    19         -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
           18  +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
           19  +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
    20     20   golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

Changes to kernel/impl/cfg.go.

    43     43   				if vis == meta.VisibilityUnknown {
    44     44   					return nil
    45     45   				}
    46     46   				return vis
    47     47   			},
    48     48   			true,
    49     49   		},
    50         -		meta.KeyExpertMode:     {"Expert mode", parseBool, true},
    51         -		meta.KeyFooterHTML:     {"Footer HTML", parseString, true},
    52         -		meta.KeyHomeZettel:     {"Home zettel", parseZid, true},
    53         -		meta.KeyMarkerExternal: {"Marker external URL", parseString, true},
    54         -		meta.KeySiteName:       {"Site name", parseString, true},
    55         -		meta.KeyYAMLHeader:     {"YAML header", parseBool, true},
           50  +		meta.KeyExpertMode:       {"Expert mode", parseBool, true},
           51  +		meta.KeyFooterHTML:       {"Footer HTML", parseString, true},
           52  +		meta.KeyHomeZettel:       {"Home zettel", parseZid, true},
           53  +		meta.KeyMarkerExternal:   {"Marker external URL", parseString, true},
           54  +		meta.KeyMaxTransclusions: {"Maximum transclusions", parseInt, true},
           55  +		meta.KeySiteName:         {"Site name", parseString, true},
           56  +		meta.KeyYAMLHeader:       {"YAML header", parseBool, true},
    56     57   		meta.KeyZettelFileSyntax: {
    57     58   			"Zettel file syntax",
    58     59   			func(val string) interface{} { return strings.Fields(val) },
    59     60   			true,
    60     61   		},
    61     62   	}
    62     63   	cs.next = interfaceMap{
................................................................................
    66     67   		meta.KeyDefaultSyntax:     meta.ValueSyntaxZmk,
    67     68   		meta.KeyDefaultTitle:      "Untitled",
    68     69   		meta.KeyDefaultVisibility: meta.VisibilityLogin,
    69     70   		meta.KeyExpertMode:        false,
    70     71   		meta.KeyFooterHTML:        "",
    71     72   		meta.KeyHomeZettel:        id.DefaultHomeZid,
    72     73   		meta.KeyMarkerExternal:    "&#10138;",
           74  +		meta.KeyMaxTransclusions:  1024,
    73     75   		meta.KeySiteName:          "Zettelstore",
    74     76   		meta.KeyYAMLHeader:        false,
    75     77   		meta.KeyZettelFileSyntax:  nil,
    76     78   	}
    77     79   }
    78     80   
    79     81   func (cs *configService) Start(kern *myKernel) error {
................................................................................
   230    232   	}
   231    233   	cfg.mx.RLock()
   232    234   	val, _ = cfg.orig.Get(meta.KeyDefaultVisibility)
   233    235   	vis := meta.GetVisibility(val)
   234    236   	cfg.mx.RUnlock()
   235    237   	return vis
   236    238   }
          239  +
          240  +// GetMaxTransclusions return the maximum number of indirect transclusions.
          241  +func (cfg *myConfig) GetMaxTransclusions() int {
          242  +	cfg.mx.RLock()
          243  +	val, ok := cfg.data.GetNumber(meta.KeyMaxTransclusions)
          244  +	cfg.mx.RUnlock()
          245  +	if ok && val > 0 {
          246  +		return val
          247  +	}
          248  +	return 1024
          249  +}
   237    250   
   238    251   // GetYAMLHeader returns the current value of the "yaml-header" key.
   239    252   func (cfg *myConfig) GetYAMLHeader() bool { return cfg.getBool(meta.KeyYAMLHeader) }
   240    253   
   241    254   // GetMarkerExternal returns the current value of the "marker-external" key.
   242    255   func (cfg *myConfig) GetMarkerExternal() string {
   243    256   	return cfg.getString(meta.KeyMarkerExternal)

Changes to kernel/impl/cmd.go.

   188    188   		func(sess *cmdSession, cmd string, args []string) bool { sess.kern.Shutdown(false); return false },
   189    189   	},
   190    190   	"start": {"start service", cmdStart},
   191    191   	"stat":  {"show service statistics", cmdStat},
   192    192   	"stop":  {"stop service", cmdStop},
   193    193   }
   194    194   
   195         -func cmdHelp(sess *cmdSession, cmd string, args []string) bool {
          195  +func cmdHelp(sess *cmdSession, _ string, _ []string) bool {
   196    196   	cmds := make([]string, 0, len(commands))
   197    197   	for key := range commands {
   198    198   		if key == "" {
   199    199   			continue
   200    200   		}
   201    201   		cmds = append(cmds, key)
   202    202   	}
................................................................................
   218    218   	table := [][]string{{"Key", "Description"}}
   219    219   	for _, kd := range srv.ConfigDescriptions() {
   220    220   		table = append(table, []string{kd.Key, kd.Descr})
   221    221   	}
   222    222   	sess.printTable(table)
   223    223   	return true
   224    224   }
   225         -func cmdGetConfig(sess *cmdSession, cmd string, args []string) bool {
          225  +func cmdGetConfig(sess *cmdSession, _ string, args []string) bool {
   226    226   	showConfig(sess, args,
   227    227   		listCurConfig, func(srv service, key string) interface{} { return srv.GetConfig(key) })
   228    228   	return true
   229    229   }
   230         -func cmdNextConfig(sess *cmdSession, cmd string, args []string) bool {
          230  +func cmdNextConfig(sess *cmdSession, _ string, args []string) bool {
   231    231   	showConfig(sess, args,
   232    232   		listNextConfig, func(srv service, key string) interface{} { return srv.GetNextConfig(key) })
   233    233   	return true
   234    234   }
   235    235   func showConfig(sess *cmdSession, args []string,
   236    236   	listConfig func(*cmdSession, service), getConfig func(service, string) interface{}) {
   237    237   
................................................................................
   296    296   	newValue := strings.Join(args[2:], " ")
   297    297   	if !srvD.srv.SetConfig(args[1], newValue) {
   298    298   		sess.println("Unable to set key", args[1], "to value", newValue)
   299    299   	}
   300    300   	return true
   301    301   }
   302    302   
   303         -func cmdServices(sess *cmdSession, cmd string, args []string) bool {
          303  +func cmdServices(sess *cmdSession, _ string, _ []string) bool {
   304    304   	names := make([]string, 0, len(sess.kern.srvNames))
   305    305   	for name := range sess.kern.srvNames {
   306    306   		names = append(names, name)
   307    307   	}
   308    308   	sort.Strings(names)
   309    309   
   310    310   	table := [][]string{{"Service", "Status"}}
................................................................................
   386    386   	if !ok {
   387    387   		sess.println("Unknown service", args[0])
   388    388   		return 0, false
   389    389   	}
   390    390   	return srvD.srvnum, true
   391    391   }
   392    392   
   393         -func cmdMetrics(sess *cmdSession, cmd string, args []string) bool {
          393  +func cmdMetrics(sess *cmdSession, _ string, _ []string) bool {
   394    394   	var samples []metrics.Sample
   395    395   	all := metrics.All()
   396    396   	for _, d := range all {
   397    397   		if d.Kind == metrics.KindFloat64Histogram {
   398    398   			continue
   399    399   		}
   400    400   		samples = append(samples, metrics.Sample{Name: d.Name})
................................................................................
   428    428   		}
   429    429   		table = append(table, []string{sVal, descr})
   430    430   	}
   431    431   	sess.printTable(table)
   432    432   	return true
   433    433   }
   434    434   
   435         -func cmdDumpIndex(sess *cmdSession, cmd string, args []string) bool {
          435  +func cmdDumpIndex(sess *cmdSession, _ string, _ []string) bool {
   436    436   	sess.kern.DumpIndex(sess.w)
   437    437   	return true
   438    438   }
   439    439   func cmdDumpRecover(sess *cmdSession, cmd string, args []string) bool {
   440    440   	if len(args) == 0 {
   441    441   		sess.usage(cmd, "RECOVER")
   442    442   		sess.println("-- A valid value for RECOVER can be obtained via 'stat core'.")
................................................................................
   448    448   	}
   449    449   	for _, line := range lines {
   450    450   		sess.println(line)
   451    451   	}
   452    452   	return true
   453    453   }
   454    454   
   455         -func cmdEnvironment(sess *cmdSession, cmd string, args []string) bool {
          455  +func cmdEnvironment(sess *cmdSession, _ string, _ []string) bool {
   456    456   	workDir, err := os.Getwd()
   457    457   	if err != nil {
   458    458   		workDir = err.Error()
   459    459   	}
   460    460   	execName, err := os.Executable()
   461    461   	if err != nil {
   462    462   		execName = err.Error()

Changes to kernel/impl/config.go.

   221    221   	}
   222    222   	switch val[0] {
   223    223   	case '0', 'f', 'F', 'n', 'N':
   224    224   		return false
   225    225   	}
   226    226   	return true
   227    227   }
          228  +
          229  +func parseInt(val string) interface{} {
          230  +	i, err := strconv.Atoi(val)
          231  +	if err == nil {
          232  +		return i
          233  +	}
          234  +	return 0
          235  +}
   228    236   
   229    237   func parseZid(val string) interface{} {
   230    238   	if zid, err := id.Parse(val); err == nil {
   231    239   		return zid
   232    240   	}
   233    241   	return id.Invalid
   234    242   }

Changes to kernel/impl/impl.go.

    93     93   		for _, dep := range deps {
    94     94   			kern.depStop[dep] = append(kern.depStop[dep], srv)
    95     95   		}
    96     96   	}
    97     97   	return kern
    98     98   }
    99     99   
   100         -func (kern *myKernel) Start(headline bool, lineServer bool) {
          100  +func (kern *myKernel) Start(headline, lineServer bool) {
   101    101   	for _, srvD := range kern.srvs {
   102    102   		srvD.srv.Freeze()
   103    103   	}
   104    104   	kern.wg.Add(1)
   105    105   	signal.Notify(kern.interrupt, os.Interrupt, syscall.SIGTERM)
   106    106   	go func() {
   107    107   		// Wait for interrupt.

Changes to parser/blob/blob.go.

    16     16   	"zettelstore.de/z/domain/meta"
    17     17   	"zettelstore.de/z/input"
    18     18   	"zettelstore.de/z/parser"
    19     19   )
    20     20   
    21     21   func init() {
    22     22   	parser.Register(&parser.Info{
    23         -		Name:         "gif",
    24         -		AltNames:     nil,
    25         -		ParseBlocks:  parseBlocks,
    26         -		ParseInlines: parseInlines,
           23  +		Name:          "gif",
           24  +		AltNames:      nil,
           25  +		IsTextParser:  false,
           26  +		IsImageFormat: true,
           27  +		ParseBlocks:   parseBlocks,
           28  +		ParseInlines:  parseInlines,
    27     29   	})
    28     30   	parser.Register(&parser.Info{
    29         -		Name:         "jpeg",
    30         -		AltNames:     []string{"jpg"},
    31         -		ParseBlocks:  parseBlocks,
    32         -		ParseInlines: parseInlines,
           31  +		Name:          "jpeg",
           32  +		AltNames:      []string{"jpg"},
           33  +		IsTextParser:  false,
           34  +		IsImageFormat: true,
           35  +		ParseBlocks:   parseBlocks,
           36  +		ParseInlines:  parseInlines,
    33     37   	})
    34     38   	parser.Register(&parser.Info{
    35         -		Name:         "png",
    36         -		AltNames:     nil,
    37         -		ParseBlocks:  parseBlocks,
    38         -		ParseInlines: parseInlines,
           39  +		Name:          "png",
           40  +		AltNames:      nil,
           41  +		IsTextParser:  false,
           42  +		IsImageFormat: true,
           43  +		ParseBlocks:   parseBlocks,
           44  +		ParseInlines:  parseInlines,
    39     45   	})
    40     46   }
    41     47   
    42         -func parseBlocks(inp *input.Input, m *meta.Meta, syntax string) ast.BlockSlice {
           48  +func parseBlocks(inp *input.Input, m *meta.Meta, syntax string) *ast.BlockListNode {
    43     49   	if p := parser.Get(syntax); p != nil {
    44     50   		syntax = p.Name
    45     51   	}
    46     52   	title, _ := m.Get(meta.KeyTitle)
    47         -	return ast.BlockSlice{
           53  +	return &ast.BlockListNode{List: []ast.BlockNode{
    48     54   		&ast.BLOBNode{
    49     55   			Title:  title,
    50     56   			Syntax: syntax,
    51     57   			Blob:   []byte(inp.Src),
    52     58   		},
    53         -	}
           59  +	}}
    54     60   }
    55     61   
    56         -func parseInlines(inp *input.Input, syntax string) ast.InlineSlice {
    57         -	return ast.InlineSlice{}
           62  +func parseInlines(*input.Input, string) *ast.InlineListNode {
           63  +	return nil
    58     64   }

Changes to parser/cleaner/cleaner.go.

    17     17   
    18     18   	"zettelstore.de/z/api"
    19     19   	"zettelstore.de/z/ast"
    20     20   	"zettelstore.de/z/encoder"
    21     21   	"zettelstore.de/z/strfun"
    22     22   )
    23     23   
    24         -// CleanupBlockSlice cleans the given block slice.
    25         -func CleanupBlockSlice(bs ast.BlockSlice) {
    26         -	cv := cleanupVisitor{
           24  +// CleanBlockList cleans the given block list.
           25  +func CleanBlockList(bln *ast.BlockListNode) { cleanNode(bln) }
           26  +
           27  +// CleanInlineList cleans the given inline list.
           28  +func CleanInlineList(iln *ast.InlineListNode) { cleanNode(iln) }
           29  +
           30  +func cleanNode(n ast.Node) {
           31  +	cv := cleanVisitor{
    27     32   		textEnc: encoder.Create(api.EncoderText, nil),
    28     33   		hasMark: false,
    29     34   		doMark:  false,
    30     35   	}
    31         -	ast.WalkBlockSlice(&cv, bs)
           36  +	ast.Walk(&cv, n)
    32     37   	if cv.hasMark {
    33     38   		cv.doMark = true
    34         -		ast.WalkBlockSlice(&cv, bs)
           39  +		ast.Walk(&cv, n)
    35     40   	}
    36     41   }
    37     42   
    38         -type cleanupVisitor struct {
           43  +type cleanVisitor struct {
    39     44   	textEnc encoder.Encoder
    40     45   	ids     map[string]ast.Node
    41     46   	hasMark bool
    42     47   	doMark  bool
    43     48   }
    44     49   
    45         -func (cv *cleanupVisitor) Visit(node ast.Node) ast.Visitor {
           50  +func (cv *cleanVisitor) Visit(node ast.Node) ast.Visitor {
    46     51   	switch n := node.(type) {
    47     52   	case *ast.HeadingNode:
    48         -		if cv.doMark || n == nil || n.Inlines == nil {
    49         -			return nil
    50         -		}
    51         -		var sb strings.Builder
    52         -		_, err := cv.textEnc.WriteInlines(&sb, n.Inlines)
    53         -		if err != nil {
    54         -			return nil
    55         -		}
    56         -		s := strfun.Slugify(sb.String())
    57         -		if len(s) > 0 {
    58         -			n.Slug = cv.addIdentifier(s, n)
    59         -		}
           53  +		cv.visitHeading(n)
    60     54   		return nil
    61     55   	case *ast.MarkNode:
    62         -		if !cv.doMark {
    63         -			cv.hasMark = true
    64         -			return nil
    65         -		}
    66         -		if n.Text == "" {
    67         -			n.Text = cv.addIdentifier("*", n)
    68         -			return nil
    69         -		}
    70         -		n.Text = cv.addIdentifier(n.Text, n)
           56  +		cv.visitMark(n)
    71     57   		return nil
    72     58   	}
    73     59   	return cv
    74     60   }
    75     61   
    76         -func (cv *cleanupVisitor) addIdentifier(id string, node ast.Node) string {
           62  +func (cv *cleanVisitor) visitHeading(hn *ast.HeadingNode) {
           63  +	if cv.doMark || hn == nil || hn.Inlines.IsEmpty() {
           64  +		return
           65  +	}
           66  +	if hn.Slug == "" {
           67  +		var sb strings.Builder
           68  +		_, err := cv.textEnc.WriteInlines(&sb, hn.Inlines)
           69  +		if err != nil {
           70  +			return
           71  +		}
           72  +		hn.Slug = strfun.Slugify(sb.String())
           73  +	}
           74  +	if hn.Slug != "" {
           75  +		hn.Fragment = cv.addIdentifier(hn.Slug, hn)
           76  +	}
           77  +}
           78  +
           79  +func (cv *cleanVisitor) visitMark(mn *ast.MarkNode) {
           80  +	if !cv.doMark {
           81  +		cv.hasMark = true
           82  +		return
           83  +	}
           84  +	if mn.Text == "" {
           85  +		mn.Slug = ""
           86  +		mn.Fragment = cv.addIdentifier("*", mn)
           87  +		return
           88  +	}
           89  +	if mn.Slug == "" {
           90  +		mn.Slug = strfun.Slugify(mn.Text)
           91  +	}
           92  +	mn.Fragment = cv.addIdentifier(mn.Slug, mn)
           93  +}
           94  +
           95  +func (cv *cleanVisitor) addIdentifier(id string, node ast.Node) string {
    77     96   	if cv.ids == nil {
    78     97   		cv.ids = map[string]ast.Node{id: node}
    79     98   		return id
    80     99   	}
    81    100   	if n, ok := cv.ids[id]; ok && n != node {
    82    101   		prefix := id + "-"
    83    102   		for count := 1; ; count++ {

Changes to parser/markdown/markdown.go.

    26     26   	"zettelstore.de/z/encoder"
    27     27   	"zettelstore.de/z/input"
    28     28   	"zettelstore.de/z/parser"
    29     29   )
    30     30   
    31     31   func init() {
    32     32   	parser.Register(&parser.Info{
    33         -		Name:         "markdown",
    34         -		AltNames:     []string{"md"},
    35         -		ParseBlocks:  parseBlocks,
    36         -		ParseInlines: parseInlines,
           33  +		Name:          "markdown",
           34  +		AltNames:      []string{"md"},
           35  +		IsTextParser:  true,
           36  +		IsImageFormat: false,
           37  +		ParseBlocks:   parseBlocks,
           38  +		ParseInlines:  parseInlines,
    37     39   	})
    38     40   }
    39     41   
    40         -func parseBlocks(inp *input.Input, m *meta.Meta, syntax string) ast.BlockSlice {
           42  +func parseBlocks(inp *input.Input, _ *meta.Meta, _ string) *ast.BlockListNode {
    41     43   	p := parseMarkdown(inp)
    42         -	return p.acceptBlockSlice(p.docNode)
           44  +	return p.acceptBlockChildren(p.docNode)
    43     45   }
    44     46   
    45         -func parseInlines(inp *input.Input, syntax string) ast.InlineSlice {
           47  +func parseInlines(*input.Input, string) *ast.InlineListNode {
    46     48   	panic("markdown.parseInline not yet implemented")
    47     49   }
    48     50   
    49     51   func parseMarkdown(inp *input.Input) *mdP {
    50     52   	source := []byte(inp.Src[inp.Pos:])
    51     53   	parser := gm.DefaultParser()
    52     54   	node := parser.Parse(gmText.NewReader(source))
................................................................................
    56     58   
    57     59   type mdP struct {
    58     60   	source  []byte
    59     61   	docNode gmAst.Node
    60     62   	textEnc encoder.Encoder
    61     63   }
    62     64   
    63         -func (p *mdP) acceptBlockSlice(docNode gmAst.Node) ast.BlockSlice {
           65  +func (p *mdP) acceptBlockChildren(docNode gmAst.Node) *ast.BlockListNode {
    64     66   	if docNode.Type() != gmAst.TypeDocument {
    65     67   		panic(fmt.Sprintf("Expected document, but got node type %v", docNode.Type()))
    66     68   	}
    67         -	result := make(ast.BlockSlice, 0, docNode.ChildCount())
           69  +	result := make([]ast.BlockNode, 0, docNode.ChildCount())
    68     70   	for child := docNode.FirstChild(); child != nil; child = child.NextSibling() {
    69     71   		if block := p.acceptBlock(child); block != nil {
    70     72   			result = append(result, block)
    71     73   		}
    72     74   	}
    73         -	return result
           75  +	return &ast.BlockListNode{List: result}
    74     76   }
    75     77   
    76     78   func (p *mdP) acceptBlock(node gmAst.Node) ast.ItemNode {
    77     79   	if node.Type() != gmAst.TypeBlock {
    78     80   		panic(fmt.Sprintf("Expected block node, but got node type %v", node.Type()))
    79     81   	}
    80     82   	switch n := node.(type) {
................................................................................
    81     83   	case *gmAst.Paragraph:
    82     84   		return p.acceptParagraph(n)
    83     85   	case *gmAst.TextBlock:
    84     86   		return p.acceptTextBlock(n)
    85     87   	case *gmAst.Heading:
    86     88   		return p.acceptHeading(n)
    87     89   	case *gmAst.ThematicBreak:
    88         -		return p.acceptThematicBreak(n)
           90  +		return p.acceptThematicBreak()
    89     91   	case *gmAst.CodeBlock:
    90     92   		return p.acceptCodeBlock(n)
    91     93   	case *gmAst.FencedCodeBlock:
    92     94   		return p.acceptFencedCodeBlock(n)
    93     95   	case *gmAst.Blockquote:
    94     96   		return p.acceptBlockquote(n)
    95     97   	case *gmAst.List:
................................................................................
    97     99   	case *gmAst.HTMLBlock:
    98    100   		return p.acceptHTMLBlock(n)
    99    101   	}
   100    102   	panic(fmt.Sprintf("Unhandled block node of kind %v", node.Kind()))
   101    103   }
   102    104   
   103    105   func (p *mdP) acceptParagraph(node *gmAst.Paragraph) ast.ItemNode {
   104         -	if ins := p.acceptInlineSlice(node); len(ins) > 0 {
   105         -		return &ast.ParaNode{
   106         -			Inlines: ins,
   107         -		}
          106  +	if iln := p.acceptInlineChildren(node); iln != nil && len(iln.List) > 0 {
          107  +		return &ast.ParaNode{Inlines: iln}
   108    108   	}
   109    109   	return nil
   110    110   }
   111    111   
   112    112   func (p *mdP) acceptHeading(node *gmAst.Heading) *ast.HeadingNode {
   113    113   	return &ast.HeadingNode{
   114    114   		Level:   node.Level,
   115         -		Inlines: p.acceptInlineSlice(node),
          115  +		Inlines: p.acceptInlineChildren(node),
   116    116   		Attrs:   nil,
   117    117   	}
   118    118   }
   119    119   
   120         -func (p *mdP) acceptThematicBreak(node *gmAst.ThematicBreak) *ast.HRuleNode {
          120  +func (*mdP) acceptThematicBreak() *ast.HRuleNode {
   121    121   	return &ast.HRuleNode{
   122    122   		Attrs: nil, //TODO
   123    123   	}
   124    124   }
   125    125   
   126    126   func (p *mdP) acceptCodeBlock(node *gmAst.CodeBlock) *ast.VerbatimNode {
   127    127   	return &ast.VerbatimNode{
................................................................................
   201    201   			result = append(result, item)
   202    202   		}
   203    203   	}
   204    204   	return result
   205    205   }
   206    206   
   207    207   func (p *mdP) acceptTextBlock(node *gmAst.TextBlock) ast.ItemNode {
   208         -	if ins := p.acceptInlineSlice(node); len(ins) > 0 {
   209         -		return &ast.ParaNode{
   210         -			Inlines: ins,
   211         -		}
          208  +	if iln := p.acceptInlineChildren(node); iln != nil && len(iln.List) > 0 {
          209  +		return &ast.ParaNode{Inlines: iln}
   212    210   	}
   213    211   	return nil
   214    212   }
   215    213   
   216    214   func (p *mdP) acceptHTMLBlock(node *gmAst.HTMLBlock) *ast.VerbatimNode {
   217    215   	lines := p.acceptRawText(node)
   218    216   	if node.HasClosure() {
................................................................................
   224    222   	}
   225    223   	return &ast.VerbatimNode{
   226    224   		Kind:  ast.VerbatimHTML,
   227    225   		Lines: lines,
   228    226   	}
   229    227   }
   230    228   
   231         -func (p *mdP) acceptInlineSlice(node gmAst.Node) ast.InlineSlice {
   232         -	result := make(ast.InlineSlice, 0, node.ChildCount())
          229  +func (p *mdP) acceptInlineChildren(node gmAst.Node) *ast.InlineListNode {
          230  +	result := make([]ast.InlineNode, 0, node.ChildCount())
   233    231   	for child := node.FirstChild(); child != nil; child = child.NextSibling() {
   234    232   		if inlines := p.acceptInline(child); inlines != nil {
   235    233   			result = append(result, inlines...)
   236    234   		}
   237    235   	}
   238         -	return result
          236  +	return ast.CreateInlineListNode(result...)
   239    237   }
   240    238   
   241         -func (p *mdP) acceptInline(node gmAst.Node) ast.InlineSlice {
          239  +func (p *mdP) acceptInline(node gmAst.Node) []ast.InlineNode {
   242    240   	if node.Type() != gmAst.TypeInline {
   243    241   		panic(fmt.Sprintf("Expected inline node, but got %v", node.Type()))
   244    242   	}
   245    243   	switch n := node.(type) {
   246    244   	case *gmAst.Text:
   247    245   		return p.acceptText(n)
   248    246   	case *gmAst.CodeSpan:
................................................................................
   257    255   		return p.acceptAutoLink(n)
   258    256   	case *gmAst.RawHTML:
   259    257   		return p.acceptRawHTML(n)
   260    258   	}
   261    259   	panic(fmt.Sprintf("Unhandled inline node %v", node.Kind()))
   262    260   }
   263    261   
   264         -func (p *mdP) acceptText(node *gmAst.Text) ast.InlineSlice {
          262  +func (p *mdP) acceptText(node *gmAst.Text) []ast.InlineNode {
   265    263   	segment := node.Segment
   266    264   	if node.IsRaw() {
   267    265   		return splitText(string(segment.Value(p.source)))
   268    266   	}
   269    267   	ins := splitText(string(segment.Value(p.source)))
   270         -	result := make(ast.InlineSlice, 0, len(ins)+1)
          268  +	result := make([]ast.InlineNode, 0, len(ins)+1)
   271    269   	for _, in := range ins {
   272    270   		if tn, ok := in.(*ast.TextNode); ok {
   273    271   			tn.Text = cleanText(tn.Text, true)
   274    272   		}
   275    273   		result = append(result, in)
   276    274   	}
   277    275   	if node.HardLineBreak() {
................................................................................
   279    277   	} else if node.SoftLineBreak() {
   280    278   		result = append(result, &ast.BreakNode{Hard: false})
   281    279   	}
   282    280   	return result
   283    281   }
   284    282   
   285    283   // splitText transform the text into a sequence of TextNode and SpaceNode
   286         -func splitText(text string) ast.InlineSlice {
          284  +func splitText(text string) []ast.InlineNode {
   287    285   	if text == "" {
   288         -		return ast.InlineSlice{}
          286  +		return nil
   289    287   	}
   290         -	result := make(ast.InlineSlice, 0, 1)
          288  +	result := make([]ast.InlineNode, 0, 1)
   291    289   
   292    290   	state := 0 // 0=unknown,1=non-spaces,2=spaces
   293    291   	lastPos := 0
   294    292   	for pos, ch := range text {
   295    293   		if input.IsSpace(ch) {
   296    294   			if state == 1 {
   297    295   				result = append(result, &ast.TextNode{Text: text[lastPos:pos]})
................................................................................
   354    352   	}
   355    353   	if lastPos < len(text) {
   356    354   		sb.WriteString(text[lastPos:])
   357    355   	}
   358    356   	return sb.String()
   359    357   }
   360    358   
   361         -func (p *mdP) acceptCodeSpan(node *gmAst.CodeSpan) ast.InlineSlice {
   362         -	return ast.InlineSlice{
          359  +func (p *mdP) acceptCodeSpan(node *gmAst.CodeSpan) []ast.InlineNode {
          360  +	return []ast.InlineNode{
   363    361   		&ast.LiteralNode{
   364    362   			Kind:  ast.LiteralProg,
   365    363   			Attrs: nil, //TODO
   366    364   			Text:  cleanCodeSpan(string(node.Text(p.source))),
   367    365   		},
   368    366   	}
   369    367   }
................................................................................
   386    384   	if lastPos == 0 {
   387    385   		return text
   388    386   	}
   389    387   	sb.WriteString(text[lastPos:])
   390    388   	return sb.String()
   391    389   }
   392    390   
   393         -func (p *mdP) acceptEmphasis(node *gmAst.Emphasis) ast.InlineSlice {
          391  +func (p *mdP) acceptEmphasis(node *gmAst.Emphasis) []ast.InlineNode {
   394    392   	kind := ast.FormatEmph
   395    393   	if node.Level == 2 {
   396    394   		kind = ast.FormatStrong
   397    395   	}
   398         -	return ast.InlineSlice{
          396  +	return []ast.InlineNode{
   399    397   		&ast.FormatNode{
   400    398   			Kind:    kind,
   401    399   			Attrs:   nil, //TODO
   402         -			Inlines: p.acceptInlineSlice(node),
          400  +			Inlines: p.acceptInlineChildren(node),
   403    401   		},
   404    402   	}
   405    403   }
   406    404   
   407         -func (p *mdP) acceptLink(node *gmAst.Link) ast.InlineSlice {
          405  +func (p *mdP) acceptLink(node *gmAst.Link) []ast.InlineNode {
   408    406   	ref := ast.ParseReference(cleanText(string(node.Destination), true))
   409    407   	var attrs *ast.Attributes
   410    408   	if title := string(node.Title); len(title) > 0 {
   411    409   		attrs = attrs.Set("title", cleanText(title, true))
   412    410   	}
   413         -	return ast.InlineSlice{
          411  +	return []ast.InlineNode{
   414    412   		&ast.LinkNode{
   415    413   			Ref:     ref,
   416         -			Inlines: p.acceptInlineSlice(node),
          414  +			Inlines: p.acceptInlineChildren(node),
   417    415   			OnlyRef: false,
   418    416   			Attrs:   attrs,
   419    417   		},
   420    418   	}
   421    419   }
   422    420   
   423         -func (p *mdP) acceptImage(node *gmAst.Image) ast.InlineSlice {
          421  +func (p *mdP) acceptImage(node *gmAst.Image) []ast.InlineNode {
   424    422   	ref := ast.ParseReference(cleanText(string(node.Destination), true))
   425    423   	var attrs *ast.Attributes
   426    424   	if title := string(node.Title); len(title) > 0 {
   427    425   		attrs = attrs.Set("title", cleanText(title, true))
   428    426   	}
   429         -	return ast.InlineSlice{
   430         -		&ast.ImageNode{
   431         -			Ref:     ref,
   432         -			Inlines: p.flattenInlineSlice(node),
   433         -			Attrs:   attrs,
          427  +	return []ast.InlineNode{
          428  +		&ast.EmbedNode{
          429  +			Material: &ast.ReferenceMaterialNode{Ref: ref},
          430  +			Inlines:  p.flattenInlineList(node),
          431  +			Attrs:    attrs,
   434    432   		},
   435    433   	}
   436    434   }
   437    435   
   438         -func (p *mdP) flattenInlineSlice(node gmAst.Node) ast.InlineSlice {
   439         -	ins := p.acceptInlineSlice(node)
          436  +func (p *mdP) flattenInlineList(node gmAst.Node) *ast.InlineListNode {
          437  +	iln := p.acceptInlineChildren(node)
   440    438   	var sb strings.Builder
   441         -	_, err := p.textEnc.WriteInlines(&sb, ins)
          439  +	_, err := p.textEnc.WriteInlines(&sb, iln)
   442    440   	if err != nil {
   443    441   		panic(err)
   444    442   	}
   445    443   	text := sb.String()
   446    444   	if text == "" {
   447    445   		return nil
   448    446   	}
   449         -	return ast.InlineSlice{
   450         -		&ast.TextNode{
   451         -			Text: text,
   452         -		},
   453         -	}
          447  +	return ast.CreateInlineListNode(&ast.TextNode{Text: text})
   454    448   }
   455    449   
   456         -func (p *mdP) acceptAutoLink(node *gmAst.AutoLink) ast.InlineSlice {
          450  +func (p *mdP) acceptAutoLink(node *gmAst.AutoLink) []ast.InlineNode {
   457    451   	url := node.URL(p.source)
   458    452   	if node.AutoLinkType == gmAst.AutoLinkEmail &&
   459    453   		!bytes.HasPrefix(bytes.ToLower(url), []byte("mailto:")) {
   460    454   		url = append([]byte("mailto:"), url...)
   461    455   	}
   462    456   	ref := ast.ParseReference(cleanText(string(url), false))
   463    457   	label := node.Label(p.source)
   464    458   	if len(label) == 0 {
   465    459   		label = url
   466    460   	}
   467         -	return ast.InlineSlice{
          461  +	return []ast.InlineNode{
   468    462   		&ast.LinkNode{
   469    463   			Ref:     ref,
   470         -			Inlines: ast.InlineSlice{&ast.TextNode{Text: string(label)}},
          464  +			Inlines: ast.CreateInlineListNode(&ast.TextNode{Text: string(label)}),
   471    465   			OnlyRef: true,
   472    466   			Attrs:   nil, //TODO
   473    467   		},
   474    468   	}
   475    469   }
   476    470   
   477         -func (p *mdP) acceptRawHTML(node *gmAst.RawHTML) ast.InlineSlice {
          471  +func (p *mdP) acceptRawHTML(node *gmAst.RawHTML) []ast.InlineNode {
   478    472   	segs := make([]string, 0, node.Segments.Len())
   479    473   	for i := 0; i < node.Segments.Len(); i++ {
   480    474   		segment := node.Segments.At(i)
   481    475   		segs = append(segs, string(segment.Value(p.source)))
   482    476   	}
   483         -	return ast.InlineSlice{
          477  +	return []ast.InlineNode{
   484    478   		&ast.LiteralNode{
   485    479   			Kind:  ast.LiteralHTML,
   486    480   			Attrs: nil, // TODO: add HTML as language
   487    481   			Text:  strings.Join(segs, ""),
   488    482   		},
   489    483   	}
   490    484   }

Changes to parser/none/none.go.

    16     16   	"zettelstore.de/z/domain/meta"
    17     17   	"zettelstore.de/z/input"
    18     18   	"zettelstore.de/z/parser"
    19     19   )
    20     20   
    21     21   func init() {
    22     22   	parser.Register(&parser.Info{
    23         -		Name:         meta.ValueSyntaxNone,
    24         -		AltNames:     []string{},
    25         -		ParseBlocks:  parseBlocks,
    26         -		ParseInlines: parseInlines,
           23  +		Name:          meta.ValueSyntaxNone,
           24  +		AltNames:      []string{},
           25  +		IsTextParser:  false,
           26  +		IsImageFormat: false,
           27  +		ParseBlocks:   parseBlocks,
           28  +		ParseInlines:  parseInlines,
    27     29   	})
    28     30   }
    29     31   
    30         -func parseBlocks(inp *input.Input, m *meta.Meta, syntax string) ast.BlockSlice {
           32  +func parseBlocks(_ *input.Input, m *meta.Meta, _ string) *ast.BlockListNode {
    31     33   	descrlist := &ast.DescriptionListNode{}
    32     34   	for _, p := range m.Pairs(true) {
    33     35   		descrlist.Descriptions = append(
    34     36   			descrlist.Descriptions, getDescription(p.Key, p.Value))
    35     37   	}
    36         -	return ast.BlockSlice{descrlist}
           38  +	return &ast.BlockListNode{List: []ast.BlockNode{descrlist}}
    37     39   }
    38     40   
    39     41   func getDescription(key, value string) ast.Description {
    40     42   	return ast.Description{
    41         -		Term: ast.InlineSlice{&ast.TextNode{Text: key}},
           43  +		Term: ast.CreateInlineListNode(&ast.TextNode{Text: key}),
    42     44   		Descriptions: []ast.DescriptionSlice{{
    43     45   			&ast.ParaNode{
    44         -				Inlines: convertToInlineSlice(value, meta.Type(key)),
           46  +				Inlines: convertToInlineList(value, meta.Type(key)),
    45     47   			}},
    46     48   		},
    47     49   	}
    48     50   }
    49     51   
    50         -func convertToInlineSlice(value string, dt *meta.DescriptionType) ast.InlineSlice {
           52  +func convertToInlineList(value string, dt *meta.DescriptionType) *ast.InlineListNode {
    51     53   	var sliceData []string
    52     54   	if dt.IsSet {
    53     55   		sliceData = meta.ListFromValue(value)
    54     56   		if len(sliceData) == 0 {
    55         -			return ast.InlineSlice{}
           57  +			return &ast.InlineListNode{}
    56     58   		}
    57     59   	} else {
    58     60   		sliceData = []string{value}
    59     61   	}
    60     62   	var makeLink bool
    61     63   	switch dt {
    62     64   	case meta.TypeID, meta.TypeIDSet:
    63     65   		makeLink = true
    64     66   	}
    65     67   
    66         -	result := make(ast.InlineSlice, 0, 2*len(sliceData)-1)
           68  +	result := make([]ast.InlineNode, 0, 2*len(sliceData)-1)
    67     69   	for i, val := range sliceData {
    68     70   		if i > 0 {
    69     71   			result = append(result, &ast.SpaceNode{Lexeme: " "})
    70     72   		}
    71     73   		tn := &ast.TextNode{Text: val}
    72     74   		if makeLink {
    73     75   			result = append(result, &ast.LinkNode{
    74     76   				Ref:     ast.ParseReference(val),
    75         -				Inlines: ast.InlineSlice{tn},
           77  +				Inlines: ast.CreateInlineListNode(tn),
    76     78   			})
    77     79   		} else {
    78     80   			result = append(result, tn)
    79     81   		}
    80     82   	}
    81         -	return result
           83  +	return ast.CreateInlineListNode(result...)
    82     84   }
    83     85   
    84         -func parseInlines(inp *input.Input, syntax string) ast.InlineSlice {
           86  +func parseInlines(inp *input.Input, _ string) *ast.InlineListNode {
    85     87   	inp.SkipToEOL()
    86         -	return ast.InlineSlice{
           88  +	return ast.CreateInlineListNode(
    87     89   		&ast.FormatNode{
    88     90   			Kind:  ast.FormatSpan,
    89     91   			Attrs: &ast.Attributes{Attrs: map[string]string{"class": "warning"}},
    90         -			Inlines: ast.InlineSlice{
    91         -				&ast.TextNode{Text: "parser.meta.ParseInlines:"},
    92         -				&ast.SpaceNode{Lexeme: " "},
    93         -				&ast.TextNode{Text: "not"},
    94         -				&ast.SpaceNode{Lexeme: " "},
    95         -				&ast.TextNode{Text: