Index: VERSION ================================================================== --- VERSION +++ VERSION @@ -1,1 +1,1 @@ -0.3 +0.4 Index: ast/ast.go ================================================================== --- ast/ast.go +++ ast/ast.go @@ -24,11 +24,11 @@ type ZettelNode struct { Meta *meta.Meta // Original metadata Content domain.Content // Original content Zid id.Zid // Zettel identification. InhMeta *meta.Meta // Metadata of the zettel, with inherited values. - Ast *BlockListNode // Zettel abstract syntax tree is a sequence of block nodes. + Ast BlockSlice // Zettel abstract syntax tree is a sequence of block nodes. Syntax string // Syntax / parser that produced the Ast } // Node is the interface, all nodes must implement. type Node interface { @@ -39,52 +39,19 @@ type BlockNode interface { Node blockNode() } -// BlockSlice is a slice of BlockNodes. -type BlockSlice []BlockNode - -// FirstParagraphInlines returns the inline list of the first paragraph that -// contains a inline list. -func (bns BlockSlice) FirstParagraphInlines() *InlineListNode { - if len(bns) > 0 { - for _, bn := range bns { - pn, ok := bn.(*ParaNode) - if !ok { - continue - } - inl := pn.Inlines - if inl != nil && len(inl.List) > 0 { - return inl - } - } - } - return nil -} - // ItemNode is a node that can occur as a list item. type ItemNode interface { BlockNode itemNode() } // ItemSlice is a slice of ItemNodes. type ItemSlice []ItemNode -// ItemListNode is a list of BlockNodes. -type ItemListNode struct { - List []ItemNode -} - -// WalkChildren walks down to the descriptions. -func (iln *ItemListNode) WalkChildren(v Visitor) { - for _, bn := range iln.List { - Walk(v, bn) - } -} - // DescriptionNode is a node that contains just textual description. type DescriptionNode interface { ItemNode descriptionNode() } DELETED ast/attr.go Index: ast/attr.go ================================================================== --- ast/attr.go +++ ast/attr.go @@ -1,107 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2022 Detlef Stern -// -// This file is part of Zettelstore. -// -// Zettelstore is licensed under the latest version of the EUPL (European Union -// Public License). Please see file LICENSE.txt for your rights and obligations -// under this license. -//----------------------------------------------------------------------------- - -package ast - -import "strings" - -// Attributes store additional information about some node types. -type Attributes struct { - Attrs map[string]string -} - -// IsEmpty returns true if there are no attributes. -func (a *Attributes) IsEmpty() bool { return a == nil || len(a.Attrs) == 0 } - -// HasDefault returns true, if the default attribute "-" has been set. -func (a *Attributes) HasDefault() bool { - if a != nil { - _, ok := a.Attrs["-"] - return ok - } - return false -} - -// RemoveDefault removes the default attribute -func (a *Attributes) RemoveDefault() { - a.Remove("-") -} - -// Get returns the attribute value of the given key and a succes value. -func (a *Attributes) Get(key string) (string, bool) { - if a != nil { - value, ok := a.Attrs[key] - return value, ok - } - return "", false -} - -// Clone returns a duplicate of the attribute. -func (a *Attributes) Clone() *Attributes { - if a == nil { - return nil - } - attrs := make(map[string]string, len(a.Attrs)) - for k, v := range a.Attrs { - attrs[k] = v - } - return &Attributes{attrs} -} - -// Set changes the attribute that a given key has now a given value. -func (a *Attributes) Set(key, value string) *Attributes { - if a == nil { - return &Attributes{map[string]string{key: value}} - } - if a.Attrs == nil { - a.Attrs = make(map[string]string) - } - a.Attrs[key] = value - return a -} - -// Remove the key from the attributes. -func (a *Attributes) Remove(key string) *Attributes { - if a != nil { - delete(a.Attrs, key) - } - return a -} - -// AddClass adds a value to the class attribute. -func (a *Attributes) AddClass(class string) *Attributes { - if a == nil { - return &Attributes{map[string]string{"class": class}} - } - if a.Attrs == nil { - a.Attrs = make(map[string]string) - } - classes := a.GetClasses() - for _, cls := range classes { - if cls == class { - return a - } - } - classes = append(classes, class) - a.Attrs["class"] = strings.Join(classes, " ") - return a -} - -// GetClasses returns the class values as a string slice -func (a *Attributes) GetClasses() []string { - if a == nil { - return nil - } - classes, ok := a.Attrs["class"] - if !ok { - return nil - } - return strings.Fields(classes) -} DELETED ast/attr_test.go Index: ast/attr_test.go ================================================================== --- ast/attr_test.go +++ ast/attr_test.go @@ -1,49 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-2021 Detlef Stern -// -// This file is part of zettelstore. -// -// Zettelstore is licensed under the latest version of the EUPL (European Union -// Public License). Please see file LICENSE.txt for your rights and obligations -// under this license. -//----------------------------------------------------------------------------- - -package ast_test - -import ( - "testing" - - "zettelstore.de/z/ast" -) - -func TestHasDefault(t *testing.T) { - t.Parallel() - attr := &ast.Attributes{} - if attr.HasDefault() { - t.Error("Should not have default attr") - } - attr = &ast.Attributes{Attrs: map[string]string{"-": "value"}} - if !attr.HasDefault() { - t.Error("Should have default attr") - } -} - -func TestAttrClone(t *testing.T) { - t.Parallel() - orig := &ast.Attributes{} - clone := orig.Clone() - if len(clone.Attrs) > 0 { - t.Error("Attrs must be empty") - } - - orig = &ast.Attributes{Attrs: map[string]string{"": "0", "-": "1", "a": "b"}} - clone = orig.Clone() - m := clone.Attrs - if m[""] != "0" || m["-"] != "1" || m["a"] != "b" || len(m) != len(orig.Attrs) { - t.Error("Wrong cloned map") - } - m["a"] = "c" - if orig.Attrs["a"] != "b" { - t.Error("Aliased map") - } -} Index: ast/block.go ================================================================== --- ast/block.go +++ ast/block.go @@ -8,66 +8,74 @@ // under this license. //----------------------------------------------------------------------------- package ast +import "zettelstore.de/c/zjson" + // Definition of Block nodes. -// BlockListNode is a list of BlockNodes. -type BlockListNode struct { - List BlockSlice -} - -func (*BlockListNode) blockNode() { /* Just a marker */ } - -// CreateBlockListNode make a new block list node from nodes -func CreateBlockListNode(nodes ...BlockNode) *BlockListNode { - return &BlockListNode{List: nodes} -} +// BlockSlice is a slice of BlockNodes. +type BlockSlice []BlockNode + +func (*BlockSlice) blockNode() { /* Just a marker */ } // WalkChildren walks down to the descriptions. -func (bln *BlockListNode) WalkChildren(v Visitor) { - if bns := bln.List; bns != nil { - for _, bn := range bns { +func (bs *BlockSlice) WalkChildren(v Visitor) { + if bs != nil { + for _, bn := range *bs { Walk(v, bn) } } } + +// FirstParagraphInlines returns the inline list of the first paragraph that +// contains a inline list. +func (bs BlockSlice) FirstParagraphInlines() InlineSlice { + for _, bn := range bs { + pn, ok := bn.(*ParaNode) + if !ok { + continue + } + if inl := pn.Inlines; len(inl) > 0 { + return inl + } + } + return nil +} //-------------------------------------------------------------------------- // ParaNode contains just a sequence of inline elements. // Another name is "paragraph". type ParaNode struct { - Inlines *InlineListNode + Inlines InlineSlice } func (*ParaNode) blockNode() { /* Just a marker */ } func (*ParaNode) itemNode() { /* Just a marker */ } func (*ParaNode) descriptionNode() { /* Just a marker */ } // NewParaNode creates an empty ParaNode. -func NewParaNode() *ParaNode { return &ParaNode{Inlines: &InlineListNode{}} } +func NewParaNode() *ParaNode { return &ParaNode{} } // CreateParaNode creates a parameter block from inline nodes. func CreateParaNode(nodes ...InlineNode) *ParaNode { - return &ParaNode{Inlines: CreateInlineListNode(nodes...)} + return &ParaNode{Inlines: nodes} } // WalkChildren walks down the inline elements. func (pn *ParaNode) WalkChildren(v Visitor) { - if iln := pn.Inlines; iln != nil { - Walk(v, iln) - } + Walk(v, &pn.Inlines) } //-------------------------------------------------------------------------- // VerbatimNode contains uninterpreted text type VerbatimNode struct { Kind VerbatimKind - Attrs *Attributes + Attrs zjson.Attributes Content []byte } // VerbatimKind specifies the format that is applied to code inline nodes. type VerbatimKind uint8 @@ -90,13 +98,13 @@ //-------------------------------------------------------------------------- // RegionNode encapsulates a region of block nodes. type RegionNode struct { Kind RegionKind - Attrs *Attributes - Blocks *BlockListNode - Inlines *InlineListNode // Optional text at the end of the region + Attrs zjson.Attributes + Blocks BlockSlice + Inlines InlineSlice // Optional text at the end of the region } // RegionKind specifies the actual region type. type RegionKind uint8 @@ -111,42 +119,38 @@ func (*RegionNode) blockNode() { /* Just a marker */ } func (*RegionNode) itemNode() { /* Just a marker */ } // WalkChildren walks down the blocks and the text. func (rn *RegionNode) WalkChildren(v Visitor) { - Walk(v, rn.Blocks) - if iln := rn.Inlines; iln != nil { - Walk(v, iln) - } + Walk(v, &rn.Blocks) + Walk(v, &rn.Inlines) } //-------------------------------------------------------------------------- // HeadingNode stores the heading text and level. type HeadingNode struct { Level int - Inlines *InlineListNode // Heading text, possibly formatted - Slug string // Heading text, normalized - Fragment string // Heading text, suitable to be used as an unique URL fragment - Attrs *Attributes + Inlines InlineSlice // Heading text, possibly formatted + Slug string // Heading text, normalized + Fragment string // Heading text, suitable to be used as an unique URL fragment + Attrs zjson.Attributes } func (*HeadingNode) blockNode() { /* Just a marker */ } func (*HeadingNode) itemNode() { /* Just a marker */ } // WalkChildren walks the heading text. func (hn *HeadingNode) WalkChildren(v Visitor) { - if iln := hn.Inlines; iln != nil { - Walk(v, iln) - } + Walk(v, &hn.Inlines) } //-------------------------------------------------------------------------- // HRuleNode specifies a horizontal rule. type HRuleNode struct { - Attrs *Attributes + Attrs zjson.Attributes } func (*HRuleNode) blockNode() { /* Just a marker */ } func (*HRuleNode) itemNode() { /* Just a marker */ } @@ -157,11 +161,11 @@ // NestedListNode specifies a nestable list, either ordered or unordered. type NestedListNode struct { Kind NestedListKind Items []ItemSlice - Attrs *Attributes + Attrs zjson.Attributes } // NestedListKind specifies the actual list type. type NestedListKind uint8 @@ -192,22 +196,22 @@ Descriptions []Description } // Description is one element of a description list. type Description struct { - Term *InlineListNode + Term InlineSlice Descriptions []DescriptionSlice } func (*DescriptionListNode) blockNode() { /* Just a marker */ } // WalkChildren walks down to the descriptions. func (dn *DescriptionListNode) WalkChildren(v Visitor) { if descrs := dn.Descriptions; descrs != nil { - for _, desc := range descrs { - if term := desc.Term; term != nil { - Walk(v, term) + for i, desc := range descrs { + if len(desc.Term) > 0 { + Walk(v, &descrs[i].Term) // Otherwise, changes in desc.Term will not go back into AST } if dss := desc.Descriptions; dss != nil { for _, dns := range dss { WalkDescriptionSlice(v, dns) } @@ -225,12 +229,12 @@ Rows []TableRow // The slice of cell rows } // TableCell contains the data for one table cell type TableCell struct { - Align Alignment // Cell alignment - Inlines *InlineListNode // Cell content + Align Alignment // Cell alignment + Inlines InlineSlice // Cell content } // TableRow is a slice of cells. type TableRow []*TableCell @@ -250,22 +254,18 @@ func (*TableNode) blockNode() { /* Just a marker */ } // WalkChildren walks down to the cells. func (tn *TableNode) WalkChildren(v Visitor) { if header := tn.Header; header != nil { - for _, cell := range header { - if iln := cell.Inlines; iln != nil { - Walk(v, iln) - } + for i := range header { + Walk(v, &header[i].Inlines) // Otherwise changes will not go back } } if rows := tn.Rows; rows != nil { for _, row := range rows { - for _, cell := range row { - if iln := cell.Inlines; iln != nil { - Walk(v, iln) - } + for i := range row { + Walk(v, &row[i].Inlines) // Otherwise changes will not go back } } } } Index: ast/inline.go ================================================================== --- ast/inline.go +++ ast/inline.go @@ -7,53 +7,42 @@ // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package ast + +import ( + "unicode/utf8" + + "zettelstore.de/c/zjson" +) // Definitions of inline nodes. -// InlineListNode is a list of BlockNodes. -type InlineListNode struct { - List []InlineNode -} - -func (*InlineListNode) inlineNode() { /* Just a marker */ } - -// CreateInlineListNode make a new inline list node from nodes -func CreateInlineListNode(nodes ...InlineNode) *InlineListNode { - return &InlineListNode{List: nodes} -} - -// CreateInlineListNodeFromWords makes a new inline list from words, +// InlineSlice is a list of BlockNodes. +type InlineSlice []InlineNode + +func (*InlineSlice) inlineNode() { /* Just a marker */ } + +// CreateInlineSliceFromWords makes a new inline list from words, // that will be space-separated. -func CreateInlineListNodeFromWords(words ...string) *InlineListNode { - inl := make([]InlineNode, 0, 2*len(words)-1) +func CreateInlineSliceFromWords(words ...string) InlineSlice { + inl := make(InlineSlice, 0, 2*len(words)-1) for i, word := range words { if i > 0 { inl = append(inl, &SpaceNode{Lexeme: " "}) } inl = append(inl, &TextNode{Text: word}) } - return &InlineListNode{List: inl} + return inl } // WalkChildren walks down to the list. -func (iln *InlineListNode) WalkChildren(v Visitor) { - if ins := iln.List; ins != nil { - for _, bn := range ins { - Walk(v, bn) - } - } -} - -// IsEmpty returns true if the list has no elements. -func (iln *InlineListNode) IsEmpty() bool { return iln == nil || len(iln.List) == 0 } - -// Append inline node(s) to the list. -func (iln *InlineListNode) Append(in ...InlineNode) { - iln.List = append(iln.List, in...) +func (is *InlineSlice) WalkChildren(v Visitor) { + for _, in := range *is { + Walk(v, in) + } } // -------------------------------------------------------------------------- // TextNode just contains some text. @@ -87,10 +76,15 @@ func (*SpaceNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. func (*SpaceNode) WalkChildren(Visitor) { /* No children*/ } + +// Count returns the number of space runes. +func (sn *SpaceNode) Count() int { + return utf8.RuneCountInString(sn.Lexeme) +} // -------------------------------------------------------------------------- // BreakNode signals a new line that must / should be interpreted as a new line break. type BreakNode struct { @@ -105,156 +99,149 @@ // -------------------------------------------------------------------------- // LinkNode contains the specified link. type LinkNode struct { Ref *Reference - Inlines *InlineListNode // The text associated with the link. - OnlyRef bool // True if no text was specified. - Attrs *Attributes // Optional attributes + Inlines InlineSlice // The text associated with the link. + Attrs zjson.Attributes // Optional attributes } func (*LinkNode) inlineNode() { /* Just a marker */ } // WalkChildren walks to the link text. func (ln *LinkNode) WalkChildren(v Visitor) { - if iln := ln.Inlines; iln != nil { - Walk(v, iln) + if len(ln.Inlines) > 0 { + Walk(v, &ln.Inlines) } } // -------------------------------------------------------------------------- // EmbedRefNode contains the specified embedded reference material. type EmbedRefNode struct { - Ref *Reference // The reference to be embedded. - Inlines *InlineListNode // Optional text associated with the image. - Attrs *Attributes // Optional attributes + Ref *Reference // The reference to be embedded. + Inlines InlineSlice // Optional text associated with the image. + Attrs zjson.Attributes // Optional attributes + Syntax string // Syntax of referenced material, if known } func (*EmbedRefNode) inlineNode() { /* Just a marker */ } func (*EmbedRefNode) inlineEmbedNode() { /* Just a marker */ } // WalkChildren walks to the text that describes the embedded material. func (en *EmbedRefNode) WalkChildren(v Visitor) { - if iln := en.Inlines; iln != nil { - Walk(v, iln) - } + Walk(v, &en.Inlines) } // -------------------------------------------------------------------------- // EmbedBLOBNode contains the specified embedded BLOB material. type EmbedBLOBNode struct { - Blob []byte // BLOB data itself. - Syntax string // Syntax of Blob - Inlines *InlineListNode // Optional text associated with the image. - Attrs *Attributes // Optional attributes + Blob []byte // BLOB data itself. + Syntax string // Syntax of Blob + Inlines InlineSlice // Optional text associated with the image. + Attrs zjson.Attributes // Optional attributes } func (*EmbedBLOBNode) inlineNode() { /* Just a marker */ } func (*EmbedBLOBNode) inlineEmbedNode() { /* Just a marker */ } // WalkChildren walks to the text that describes the embedded material. func (en *EmbedBLOBNode) WalkChildren(v Visitor) { - if iln := en.Inlines; iln != nil { - Walk(v, iln) - } + Walk(v, &en.Inlines) } // -------------------------------------------------------------------------- // CiteNode contains the specified citation. type CiteNode struct { - Key string // The citation key - Inlines *InlineListNode // Optional text associated with the citation. - Attrs *Attributes // Optional attributes + Key string // The citation key + Inlines InlineSlice // Optional text associated with the citation. + Attrs zjson.Attributes // Optional attributes } func (*CiteNode) inlineNode() { /* Just a marker */ } // WalkChildren walks to the cite text. func (cn *CiteNode) WalkChildren(v Visitor) { - if iln := cn.Inlines; iln != nil { - Walk(v, iln) - } + Walk(v, &cn.Inlines) } // -------------------------------------------------------------------------- // MarkNode contains the specified merked position. // It is a BlockNode too, because although it is typically parsed during inline // mode, it is moved into block mode afterwards. type MarkNode struct { - Text string - Slug string // Slugified form of Text - Fragment string // Unique form of Slug + Mark string // The mark text itself + Slug string // Slugified form of Mark + Fragment string // Unique form of Slug + Inlines InlineSlice // Marked inline content } func (*MarkNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. -func (*MarkNode) WalkChildren(Visitor) { /* No children*/ } +func (mn *MarkNode) WalkChildren(v Visitor) { + if len(mn.Inlines) > 0 { + Walk(v, &mn.Inlines) + } +} // -------------------------------------------------------------------------- // FootnoteNode contains the specified footnote. type FootnoteNode struct { - Inlines *InlineListNode // The footnote text. - Attrs *Attributes // Optional attributes + Inlines InlineSlice // The footnote text. + Attrs zjson.Attributes // Optional attributes } func (*FootnoteNode) inlineNode() { /* Just a marker */ } // WalkChildren walks to the footnote text. func (fn *FootnoteNode) WalkChildren(v Visitor) { - if iln := fn.Inlines; iln != nil { - Walk(v, iln) - } + Walk(v, &fn.Inlines) } // -------------------------------------------------------------------------- // FormatNode specifies some inline formatting. type FormatNode struct { Kind FormatKind - Attrs *Attributes // Optional attributes. - Inlines *InlineListNode + Attrs zjson.Attributes // Optional attributes. + Inlines InlineSlice } // FormatKind specifies the format that is applied to the inline nodes. type FormatKind uint8 // Constants for FormatCode const ( - _ FormatKind = iota - FormatEmph // Emphasized text. - FormatStrong // Strongly emphasized text. - FormatInsert // Inserted text. - FormatDelete // Deleted text. - FormatSuper // Superscripted text. - FormatSub // SubscriptedText. - FormatQuote // Quoted text. - FormatQuotation // Quotation text. - FormatSpan // Generic inline container. - FormatMonospace // Monospaced text. + _ FormatKind = iota + FormatEmph // Emphasized text. + FormatStrong // Strongly emphasized text. + FormatInsert // Inserted text. + FormatDelete // Deleted text. + FormatSuper // Superscripted text. + FormatSub // SubscriptedText. + FormatQuote // Quoted text. + FormatSpan // Generic inline container. ) func (*FormatNode) inlineNode() { /* Just a marker */ } // WalkChildren walks to the formatted text. func (fn *FormatNode) WalkChildren(v Visitor) { - if iln := fn.Inlines; iln != nil { - Walk(v, iln) - } + Walk(v, &fn.Inlines) } // -------------------------------------------------------------------------- // LiteralNode specifies some uninterpreted text. type LiteralNode struct { Kind LiteralKind - Attrs *Attributes // Optional attributes. + Attrs zjson.Attributes // Optional attributes. Content []byte } // LiteralKind specifies the format that is applied to code inline nodes. type LiteralKind uint8 @@ -262,15 +249,15 @@ // Constants for LiteralCode const ( _ LiteralKind = iota LiteralZettel // Zettel content LiteralProg // Inline program code - LiteralKeyb // Keyboard strokes - LiteralOutput // Sample output. + LiteralInput // Computer input, e.g. Keyboard strokes + LiteralOutput // Computer output LiteralComment // Inline comment LiteralHTML // Inline HTML, e.g. for Markdown ) func (*LiteralNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. func (*LiteralNode) WalkChildren(Visitor) { /* No children*/ } Index: ast/walk_test.go ================================================================== --- ast/walk_test.go +++ ast/walk_test.go @@ -11,65 +11,61 @@ package ast_test import ( "testing" + "zettelstore.de/c/zjson" "zettelstore.de/z/ast" ) func BenchmarkWalk(b *testing.B) { - root := ast.CreateBlockListNode( + root := ast.BlockSlice{ &ast.HeadingNode{ - Inlines: ast.CreateInlineListNodeFromWords("A", "Simple", "Heading"), + Inlines: ast.CreateInlineSliceFromWords("A", "Simple", "Heading"), }, &ast.ParaNode{ - Inlines: ast.CreateInlineListNodeFromWords("This", "is", "the", "introduction."), + Inlines: ast.CreateInlineSliceFromWords("This", "is", "the", "introduction."), }, &ast.NestedListNode{ Kind: ast.NestedListUnordered, Items: []ast.ItemSlice{ []ast.ItemNode{ &ast.ParaNode{ - Inlines: ast.CreateInlineListNodeFromWords("Item", "1"), - }, - }, - []ast.ItemNode{ - &ast.ParaNode{ - Inlines: ast.CreateInlineListNodeFromWords("Item", "2"), - }, - }, - }, - }, - &ast.ParaNode{ - Inlines: ast.CreateInlineListNodeFromWords("This", "is", "some", "intermediate", "text."), - }, - ast.CreateParaNode( - &ast.FormatNode{ - Kind: ast.FormatEmph, - Attrs: &ast.Attributes{ - Attrs: map[string]string{ - "": "class", - "color": "green", - }, - }, - Inlines: ast.CreateInlineListNodeFromWords("This", "is", "some", "emphasized", "text."), - }, - &ast.SpaceNode{Lexeme: " "}, - &ast.LinkNode{ - Ref: &ast.Reference{ - Value: "http://zettelstore.de", - }, - Inlines: ast.CreateInlineListNodeFromWords("URL", "text."), - OnlyRef: false, - }, - ), - ) - v := benchVisitor{} - b.ResetTimer() - for n := 0; n < b.N; n++ { - ast.Walk(&v, root) + Inlines: ast.CreateInlineSliceFromWords("Item", "1"), + }, + }, + []ast.ItemNode{ + &ast.ParaNode{ + Inlines: ast.CreateInlineSliceFromWords("Item", "2"), + }, + }, + }, + }, + &ast.ParaNode{ + Inlines: ast.CreateInlineSliceFromWords("This", "is", "some", "intermediate", "text."), + }, + ast.CreateParaNode( + &ast.FormatNode{ + Kind: ast.FormatEmph, + Attrs: zjson.Attributes(map[string]string{ + "": "class", + "color": "green", + }), + Inlines: ast.CreateInlineSliceFromWords("This", "is", "some", "emphasized", "text."), + }, + &ast.SpaceNode{Lexeme: " "}, + &ast.LinkNode{ + Ref: &ast.Reference{Value: "http://zettelstore.de"}, + Inlines: ast.CreateInlineSliceFromWords("URL", "text."), + }, + ), + } + v := benchVisitor{} + b.ResetTimer() + for n := 0; n < b.N; n++ { + ast.Walk(&v, &root) } } type benchVisitor struct{} func (bv *benchVisitor) Visit(ast.Node) ast.Visitor { return bv } Index: box/box.go ================================================================== --- box/box.go +++ box/box.go @@ -1,9 +1,9 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020-2021 Detlef Stern +// Copyright (c) 2020-2022 Detlef Stern // -// This file is part of zettelstore. +// This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- @@ -14,10 +14,12 @@ import ( "context" "errors" "fmt" "io" + "net/url" + "strconv" "time" "zettelstore.de/c/api" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" @@ -284,10 +286,38 @@ var ErrNotFound = errors.New("zettel not found") // ErrConflict is returned if a box operation detected a conflict.. // One example: if calculating a new zettel identifier takes too long. var ErrConflict = errors.New("conflict") + +// ErrCapacity is returned if a box has reached its capacity. +var ErrCapacity = errors.New("capacity exceeded") // ErrInvalidID is returned if the zettel id is not appropriate for the box operation. type ErrInvalidID struct{ Zid id.Zid } func (err *ErrInvalidID) Error() string { return "invalid Zettel id: " + err.Zid.String() } + +// GetQueryBool is a helper function to extract bool values from a box URI. +func GetQueryBool(u *url.URL, key string) bool { + _, ok := u.Query()[key] + return ok +} + +// GetQueryInt is a helper function to extract int values of a specified range from a box URI. +func GetQueryInt(u *url.URL, key string, min, def, max int) int { + sVal := u.Query().Get(key) + if sVal == "" { + return def + } + iVal, err := strconv.Atoi(sVal) + if err != nil { + return def + } + if iVal < min { + return min + } + if iVal > max { + return max + } + return iVal +} Index: box/compbox/compbox.go ================================================================== --- box/compbox/compbox.go +++ box/compbox/compbox.go @@ -1,9 +1,9 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020-2021 Detlef Stern +// Copyright (c) 2020-2022 Detlef Stern // -// This file is part of zettelstore. +// This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- @@ -179,11 +179,10 @@ st.Zettel = len(myZettel) cb.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats") } func updateMeta(m *meta.Meta) { - m.Set(api.KeyNoIndex, api.ValueTrue) if _, ok := m.Get(api.KeySyntax); !ok { m.Set(api.KeySyntax, api.ValueSyntaxZmk) } m.Set(api.KeyRole, api.ValueRoleConfiguration) m.Set(api.KeyLang, api.ValueLangEN) Index: box/constbox/base.css ================================================================== --- box/constbox/base.css +++ box/constbox/base.css @@ -27,16 +27,12 @@ text-align: center; padding:.41rem .5rem; text-decoration: none; color:black; } - nav.zs-menu > a:hover, .zs-dropdown:hover button { - background-color: hsl(210, 28%, 80%); - } - nav.zs-menu form { - float: right; - } + nav.zs-menu > a:hover, .zs-dropdown:hover button { background-color: hsl(210, 28%, 80%) } + nav.zs-menu form { float: right } nav.zs-menu form input[type=text] { padding: .12rem; border: none; margin-top: .25rem; margin-right: .5rem; @@ -69,22 +65,14 @@ padding:.41rem .5rem; text-decoration: none; display: block; text-align: left; } - .zs-dropdown-content > a:hover { - background-color: hsl(210, 28%, 75%); - } - .zs-dropdown:hover > .zs-dropdown-content { - display: block; - } - main { - padding: 0 1rem; - } - article > * + * { - margin-top: .5rem; - } + .zs-dropdown-content > a:hover { background-color: hsl(210, 28%, 75%) } + .zs-dropdown:hover > .zs-dropdown-content { display: block } + main { padding: 0 1rem } + article > * + * { margin-top: .5rem } article header { padding: 0; margin: 0; } h1,h2,h3,h4,h5,h6 { font-family:sans-serif; font-weight:normal } @@ -92,56 +80,38 @@ h2 { font-size:1.25rem; margin:.70rem 0 } h3 { font-size:1.15rem; margin:.75rem 0 } h4 { font-size:1.05rem; margin:.8rem 0; font-weight: bold } h5 { font-size:1.05rem; margin:.8rem 0 } h6 { font-size:1.05rem; margin:.8rem 0; font-weight: lighter } - p { - margin: .5rem 0 0 0; - } - ol,ul { - padding-left: 1.1rem; - } - li,figure,figcaption,dl { - margin: 0; - } - dt { - margin: .5rem 0 0 0; - } - dt+dd { - margin-top: 0; - } - dd { - margin: .5rem 0 0 2rem; - } - dd > p:first-child { - margin: 0 0 0 0; - } + p { margin: .5rem 0 0 0 } + ol,ul { padding-left: 1.1rem } + li,figure,figcaption,dl { margin: 0 } + dt { margin: .5rem 0 0 0 } + dt+dd { margin-top: 0 } + dd { margin: .5rem 0 0 2rem } + dd > p:first-child { margin: 0 0 0 0 } blockquote { border-left: 0.5rem solid lightgray; padding-left: 1rem; margin-left: 1rem; margin-right: 2rem; font-style: italic; } - blockquote p { - margin-bottom: .5rem; - } - blockquote cite { - font-style: normal; - } + blockquote p { margin-bottom: .5rem } + blockquote cite { font-style: normal } table { border-collapse: collapse; border-spacing: 0; max-width: 100%; } th,td { text-align: left; padding: .25rem .5rem; } - td { border-bottom: 1px solid hsl(0, 0%, 85%); } - thead th { border-bottom: 2px solid hsl(0, 0%, 70%); } - tfoot th { border-top: 2px solid hsl(0, 0%, 70%); } + td { border-bottom: 1px solid hsl(0, 0%, 85%) } + thead th { border-bottom: 2px solid hsl(0, 0%, 70%) } + tfoot th { border-top: 2px solid hsl(0, 0%, 70%) } main form { padding: 0 .5em; margin: .5em 0 0 0; } main form:after { @@ -149,19 +119,13 @@ display: block; height: 0; clear: both; visibility: hidden; } - main form div { - margin: .5em 0 0 0 - } - input { - font-family: monospace; - } - input[type="submit"],button,select { - font: inherit; - } + main form div { margin: .5em 0 0 0 } + input { font-family: monospace } + input[type="submit"],button,select { font: inherit } label { font-family: sans-serif; font-size:.9rem } label::after { content:":" } textarea { font-family: monospace; resize: vertical; @@ -176,24 +140,20 @@ } .zs-button { float:right; margin: .5em 0 .5em 1em; } - a:not([class]) { - text-decoration-skip-ink: auto; - } - .zs-broken { - text-decoration: line-through; - } - img { - max-width: 100%; - } - .zs-endnotes { + a:not([class]) { text-decoration-skip-ink: auto } + a.broken { text-decoration: line-through } + img { max-width: 100% } + img.right { float: right } + ol.endnotes { padding-top: .5rem; border-top: 1px solid; } - code,pre,kbd { + kbd { font-family:monospace } + code,pre { font-family: monospace; font-size: 85%; } code { padding: .1rem .2rem; @@ -219,13 +179,11 @@ padding: .5rem .7rem; max-width: 100%; border-radius: .5rem; border: 1px solid black; } - div.zs-indication p:first-child { - margin-top: 0; - } + div.zs-indication p:first-child { margin-top: 0 } span.zs-indication { border: 1px solid black; border-radius: .25rem; padding: .1rem .2rem; font-size: 95%; @@ -242,42 +200,28 @@ .zs-error { background-color: lightpink; border-style: none !important; font-weight: bold; } - .zs-ta-left { text-align:left } - .zs-ta-center { text-align:center } - .zs-ta-right { text-align:right } - .zs-monospace { font-family:monospace } + td.left,th.left { text-align:left } + td.center,th.center { text-align:center } + td.right,th.right { text-align:right } .zs-font-size-0 { font-size:75% } .zs-font-size-1 { font-size:83% } .zs-font-size-2 { font-size:100% } .zs-font-size-3 { font-size:117% } .zs-font-size-4 { font-size:150% } .zs-font-size-5 { font-size:200% } .zs-deprecated { border-style: dashed; padding: .2rem } - kbd { - background: hsl(210, 5%, 100%); - border: 1px solid hsl(210, 5%, 70%); - border-radius: .25rem; - padding: .1rem .2rem; - font-size: 75%; - } .zs-meta { font-size:.75rem; color:#444; margin-bottom:1rem; } - .zs-meta a { - color:#444; - } - h1+.zs-meta { - margin-top:-1rem; - } - nav > details { - margin-top:1rem; - } + .zs-meta a { color:#444 } + h1+.zs-meta { margin-top:-1rem } + nav > details { margin-top:1rem } details > summary { width: 100%; background-color: #eee; font-family:sans-serif; } @@ -284,16 +228,14 @@ details > ul { margin-top:0; padding-left:2rem; background-color: #eee; } - footer { - padding: 0 1rem; - } + footer { padding: 0 1rem } @media (prefers-reduced-motion: reduce) { * { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } Index: box/constbox/constbox.go ================================================================== --- box/constbox/constbox.go +++ box/constbox/constbox.go @@ -1,9 +1,9 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020-2021 Detlef Stern +// Copyright (c) 2020-2022 Detlef Stern // -// This file is part of zettelstore. +// This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- @@ -148,11 +148,10 @@ id.ConfigurationZid: { constHeader{ api.KeyTitle: "Zettelstore Runtime Configuration", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: api.ValueSyntaxNone, - api.KeyNoIndex: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityOwner, }, domain.NewContent(nil)}, id.MustParse(api.ZidLicense): { constHeader{ @@ -187,119 +186,106 @@ id.BaseTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Base HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, - api.KeyNoIndex: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentBaseMustache)}, id.LoginTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Login Form HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, - api.KeyNoIndex: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentLoginMustache)}, id.ZettelTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, - api.KeyNoIndex: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentZettelMustache)}, id.InfoTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Info HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, - api.KeyNoIndex: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentInfoMustache)}, id.ContextTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Context HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, - api.KeyNoIndex: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentContextMustache)}, id.FormTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Form HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, - api.KeyNoIndex: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentFormMustache)}, id.RenameTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Rename Form HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, - api.KeyNoIndex: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentRenameMustache)}, id.DeleteTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Delete HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, - api.KeyNoIndex: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentDeleteMustache)}, id.ListTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore List Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, - api.KeyNoIndex: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentListZettelMustache)}, id.RolesTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore List Roles HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, - api.KeyNoIndex: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentListRolesMustache)}, id.TagsTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore List Tags HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, - api.KeyNoIndex: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentListTagsMustache)}, id.ErrorTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Error HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, - api.KeyNoIndex: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentErrorMustache)}, id.MustParse(api.ZidBaseCSS): { constHeader{ api.KeyTitle: "Zettelstore Base CSS", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: "css", - api.KeyNoIndex: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityPublic, }, domain.NewContent(contentBaseCSS)}, id.MustParse(api.ZidUserCSS): { constHeader{ Index: box/dirbox/dirbox.go ================================================================== --- box/dirbox/dirbox.go +++ box/dirbox/dirbox.go @@ -1,9 +1,9 @@ //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // -// This file is part of zettelstore. +// This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- @@ -15,11 +15,10 @@ "context" "errors" "net/url" "os" "path/filepath" - "strconv" "sync" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" @@ -44,15 +43,15 @@ } dp := dirBox{ log: log, number: cdata.Number, location: u.String(), - readonly: getQueryBool(u, "readonly"), + readonly: box.GetQueryBool(u, "readonly"), cdata: *cdata, dir: path, notifySpec: getDirSrvInfo(log, u.Query().Get("type")), - fSrvs: makePrime(uint32(getQueryInt(u, "worker", 1, 7, 1499))), + fSrvs: makePrime(uint32(box.GetQueryInt(u, "worker", 1, 7, 1499))), } return &dp, nil }) } @@ -110,33 +109,10 @@ return filepath.Clean(u.Opaque) } return filepath.Clean(u.Path) } -func getQueryBool(u *url.URL, key string) bool { - _, ok := u.Query()[key] - return ok -} - -func getQueryInt(u *url.URL, key string, min, def, max int) int { - sVal := u.Query().Get(key) - if sVal == "" { - return def - } - iVal, err := strconv.Atoi(sVal) - if err != nil { - return def - } - if iVal < min { - return min - } - if iVal > max { - return max - } - return iVal -} - // dirBox uses a directory to store zettel as files. type dirBox struct { log *logger.Logger number int location string Index: box/manager/collect.go ================================================================== --- box/manager/collect.go +++ box/manager/collect.go @@ -32,15 +32,15 @@ data.urls = store.NewWordSet() data.itags = store.NewWordSet() } func collectZettelIndexData(zn *ast.ZettelNode, data *collectData) { - ast.Walk(data, zn.Ast) + ast.Walk(data, &zn.Ast) } -func collectInlineIndexData(iln *ast.InlineListNode, data *collectData) { - ast.Walk(data, iln) +func collectInlineIndexData(is *ast.InlineSlice, data *collectData) { + ast.Walk(data, is) } func (data *collectData) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.VerbatimNode: Index: box/manager/indexer.go ================================================================== --- box/manager/indexer.go +++ box/manager/indexer.go @@ -1,9 +1,9 @@ //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // -// This file is part of zettelstore. +// This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- @@ -14,11 +14,10 @@ "context" "fmt" "net/url" "time" - "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" @@ -165,21 +164,15 @@ } return true } func (mgr *Manager) idxUpdateZettel(ctx context.Context, zettel domain.Zettel) { - m := zettel.Meta - if m.GetBool(api.KeyNoIndex) { - // Zettel maybe in index - toCheck := mgr.idxStore.DeleteZettel(ctx, m.Zid) - mgr.idxCheckZettel(toCheck) - return - } - var cData collectData cData.initialize() collectZettelIndexData(parser.ParseZettel(zettel, "", mgr.rtConfig), &cData) + + m := zettel.Meta zi := store.NewZettelIndex(m.Zid) mgr.idxCollectFromMeta(ctx, m, zi, &cData) mgr.idxProcessData(ctx, zi, &cData) toCheck := mgr.idxStore.UpdateReferences(ctx, zi) mgr.idxCheckZettel(toCheck) @@ -197,11 +190,12 @@ case meta.TypeIDSet: for _, val := range meta.ListFromValue(pair.Value) { mgr.idxUpdateValue(ctx, descr.Inverse, val, zi) } case meta.TypeZettelmarkup: - collectInlineIndexData(parser.ParseMetadata(pair.Value), cData) + is := parser.ParseMetadata(pair.Value) + collectInlineIndexData(&is, cData) case meta.TypeURL: if _, err := url.Parse(pair.Value); err == nil { cData.urls.Add(pair.Value) } default: Index: box/membox/membox.go ================================================================== --- box/membox/membox.go +++ box/membox/membox.go @@ -1,9 +1,9 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020-2021 Detlef Stern +// Copyright (c) 2020-2022 Detlef Stern // -// This file is part of zettelstore. +// This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- @@ -31,22 +31,27 @@ "mem", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { return &memBox{ log: kernel.Main.GetLogger(kernel.BoxService).Clone(). Str("box", "mem").Int("boxnum", int64(cdata.Number)).Child(), - u: u, - cdata: *cdata, + u: u, + cdata: *cdata, + maxZettel: box.GetQueryInt(u, "max-zettel", 0, 127, 65535), + maxBytes: box.GetQueryInt(u, "max-bytes", 0, 65535, (1024*1024*1024)-1), }, nil }) } type memBox struct { - log *logger.Logger - u *url.URL - cdata manager.ConnectData - zettel map[id.Zid]domain.Zettel - mx sync.RWMutex + log *logger.Logger + u *url.URL + cdata manager.ConnectData + maxZettel int + maxBytes int + mx sync.RWMutex // Protects the following fields + zettel map[id.Zid]domain.Zettel + curBytes int } func (mb *memBox) notifyChanged(reason box.UpdateReason, zid id.Zid) { if chci := mb.cdata.Notify; chci != nil { chci <- box.UpdateInfo{Reason: reason, Zid: zid} @@ -58,24 +63,35 @@ } func (mb *memBox) Start(context.Context) error { mb.mx.Lock() mb.zettel = make(map[id.Zid]domain.Zettel) + mb.curBytes = 0 mb.mx.Unlock() + mb.log.Trace().Int("max-zettel", int64(mb.maxZettel)).Int("max-bytes", int64(mb.maxBytes)).Msg("Start Box") return nil } func (mb *memBox) Stop(context.Context) { mb.mx.Lock() mb.zettel = nil mb.mx.Unlock() } -func (*memBox) CanCreateZettel(context.Context) bool { return true } +func (mb *memBox) CanCreateZettel(context.Context) bool { + mb.mx.RLock() + defer mb.mx.RUnlock() + return len(mb.zettel) < mb.maxZettel +} func (mb *memBox) CreateZettel(_ context.Context, zettel domain.Zettel) (id.Zid, error) { mb.mx.Lock() + newBytes := mb.curBytes + zettel.Length() + if mb.maxZettel < len(mb.zettel) || mb.maxBytes < newBytes { + mb.mx.Unlock() + return id.Invalid, box.ErrCapacity + } zid, err := box.GetNewZid(func(zid id.Zid) (bool, error) { _, ok := mb.zettel[zid] return !ok, nil }) if err != nil { @@ -84,10 +100,11 @@ } meta := zettel.Meta.Clone() meta.Zid = zid zettel.Meta = meta mb.zettel[zid] = zettel + mb.curBytes = newBytes mb.mx.Unlock() mb.notifyChanged(box.OnUpdate, zid) mb.log.Trace().Zid(zid).Msg("CreateZettel") return zid, nil } @@ -139,22 +156,46 @@ } } return nil } -func (*memBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return true } +func (mb *memBox) CanUpdateZettel(_ context.Context, zettel domain.Zettel) bool { + mb.mx.RLock() + defer mb.mx.RUnlock() + zid := zettel.Meta.Zid + if !zid.IsValid() { + return false + } + + newBytes := mb.curBytes + zettel.Length() + if prevZettel, found := mb.zettel[zid]; found { + newBytes -= prevZettel.Length() + } + return newBytes < mb.maxBytes +} func (mb *memBox) UpdateZettel(_ context.Context, zettel domain.Zettel) error { + m := zettel.Meta.Clone() + if !m.Zid.IsValid() { + return &box.ErrInvalidID{Zid: m.Zid} + } + mb.mx.Lock() - meta := zettel.Meta.Clone() - if !meta.Zid.IsValid() { - return &box.ErrInvalidID{Zid: meta.Zid} - } - zettel.Meta = meta - mb.zettel[meta.Zid] = zettel - mb.mx.Unlock() - mb.notifyChanged(box.OnUpdate, meta.Zid) + newBytes := mb.curBytes + zettel.Length() + if prevZettel, found := mb.zettel[m.Zid]; found { + newBytes -= prevZettel.Length() + } + if mb.maxBytes < newBytes { + mb.mx.Unlock() + return box.ErrCapacity + } + + zettel.Meta = m + mb.zettel[m.Zid] = zettel + mb.curBytes = newBytes + mb.mx.Unlock() + mb.notifyChanged(box.OnUpdate, m.Zid) mb.log.Trace().Msg("UpdateZettel") return nil } func (*memBox) AllowRenameZettel(context.Context, id.Zid) bool { return true } @@ -192,15 +233,17 @@ return ok } func (mb *memBox) DeleteZettel(_ context.Context, zid id.Zid) error { mb.mx.Lock() - if _, ok := mb.zettel[zid]; !ok { + oldZettel, found := mb.zettel[zid] + if !found { mb.mx.Unlock() return box.ErrNotFound } delete(mb.zettel, zid) + mb.curBytes -= oldZettel.Length() mb.mx.Unlock() mb.notifyChanged(box.OnDelete, zid) mb.log.Trace().Msg("DeleteZettel") return nil } Index: cmd/cmd_file.go ================================================================== --- cmd/cmd_file.go +++ cmd/cmd_file.go @@ -1,9 +1,9 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020-2021 Detlef Stern +// Copyright (c) 2020-2022 Detlef Stern // -// This file is part of zettelstore. +// This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- Index: cmd/cmd_run.go ================================================================== --- cmd/cmd_run.go +++ cmd/cmd_run.go @@ -73,32 +73,27 @@ ucDelete := usecase.NewDeleteZettel(ucLog, protectedBoxManager) ucUpdate := usecase.NewUpdateZettel(ucLog, protectedBoxManager) ucRename := usecase.NewRenameZettel(ucLog, protectedBoxManager) ucUnlinkedRefs := usecase.NewUnlinkedReferences(protectedBoxManager, rtConfig) ucRefresh := usecase.NewRefresh(ucLog, protectedBoxManager) + ucVersion := usecase.NewVersion(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) webSrv.Handle("/", wui.MakeGetRootHandler(protectedBoxManager)) // Web user interface if !authManager.IsReadonly() { webSrv.AddZettelRoute('b', server.MethodGet, wui.MakeGetRenameZettelHandler( ucGetMeta, &ucEvaluate)) webSrv.AddZettelRoute('b', server.MethodPost, wui.MakePostRenameZettelHandler(&ucRename)) - webSrv.AddZettelRoute('c', server.MethodGet, wui.MakeGetCopyZettelHandler( - ucGetZettel, usecase.NewCopyZettel())) + webSrv.AddZettelRoute('c', server.MethodGet, wui.MakeGetCreateZettelHandler( + ucGetZettel, &ucCreateZettel)) webSrv.AddZettelRoute('c', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel)) webSrv.AddZettelRoute('d', server.MethodGet, wui.MakeGetDeleteZettelHandler( ucGetMeta, ucGetAllMeta, &ucEvaluate)) webSrv.AddZettelRoute('d', server.MethodPost, wui.MakePostDeleteZettelHandler(&ucDelete)) webSrv.AddZettelRoute('e', server.MethodGet, wui.MakeEditGetZettelHandler(ucGetZettel)) webSrv.AddZettelRoute('e', server.MethodPost, wui.MakeEditSetZettelHandler(&ucUpdate)) - webSrv.AddZettelRoute('f', server.MethodGet, wui.MakeGetFolgeZettelHandler( - ucGetZettel, usecase.NewFolgeZettel(rtConfig))) - webSrv.AddZettelRoute('f', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel)) - webSrv.AddZettelRoute('g', server.MethodGet, wui.MakeGetNewZettelHandler( - ucGetZettel, usecase.NewNewZettel())) - webSrv.AddZettelRoute('g', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel)) } webSrv.AddListRoute('g', server.MethodGet, wui.MakeGetGoActionHandler(&ucRefresh)) webSrv.AddListRoute('h', server.MethodGet, wui.MakeListHTMLMetaHandler( ucListMeta, ucListRoles, ucListTags, &ucEvaluate)) webSrv.AddZettelRoute('h', server.MethodGet, wui.MakeGetHTMLZettelHandler( @@ -113,21 +108,20 @@ // API webSrv.AddListRoute('a', server.MethodPost, a.MakePostLoginHandler(&ucAuthenticate)) webSrv.AddListRoute('a', server.MethodPut, a.MakeRenewAuthHandler()) webSrv.AddListRoute('j', server.MethodGet, a.MakeListMetaHandler(ucListMeta)) webSrv.AddZettelRoute('j', server.MethodGet, a.MakeGetZettelHandler(ucGetZettel)) - webSrv.AddZettelRoute('l', server.MethodGet, a.MakeGetLinksHandler(ucEvaluate)) webSrv.AddZettelRoute('m', server.MethodGet, a.MakeGetMetaHandler(ucGetMeta)) webSrv.AddZettelRoute('o', server.MethodGet, a.MakeGetOrderHandler( usecase.NewZettelOrder(protectedBoxManager, ucEvaluate))) webSrv.AddZettelRoute('p', server.MethodGet, a.MakeGetParsedZettelHandler(ucParseZettel)) webSrv.AddListRoute('r', server.MethodGet, a.MakeListRoleHandler(ucListRoles)) webSrv.AddListRoute('t', server.MethodGet, a.MakeListTagsHandler(ucListTags)) webSrv.AddZettelRoute('u', server.MethodGet, a.MakeListUnlinkedMetaHandler( ucGetMeta, ucUnlinkedRefs, &ucEvaluate)) - webSrv.AddListRoute('v', server.MethodPost, a.MakePostEncodeInlinesHandler(ucEvaluate)) webSrv.AddZettelRoute('v', server.MethodGet, a.MakeGetEvalZettelHandler(ucEvaluate)) + webSrv.AddListRoute('x', server.MethodGet, a.MakeGetDataHandler(ucVersion)) webSrv.AddListRoute('x', server.MethodPost, a.MakePostCommandHandler(&ucIsAuth, &ucRefresh)) webSrv.AddZettelRoute('x', server.MethodGet, a.MakeZettelContextHandler(ucZettelContext)) webSrv.AddListRoute('z', server.MethodGet, a.MakeListPlainHandler(ucListMeta)) webSrv.AddZettelRoute('z', server.MethodGet, a.MakeGetPlainZettelHandler(ucGetZettel)) if !authManager.IsReadonly() { Index: cmd/register.go ================================================================== --- cmd/register.go +++ cmd/register.go @@ -16,18 +16,18 @@ _ "zettelstore.de/z/box/compbox" // Allow to use computed box. _ "zettelstore.de/z/box/constbox" // Allow to use global internal box. _ "zettelstore.de/z/box/dirbox" // Allow to use directory box. _ "zettelstore.de/z/box/filebox" // Allow to use file box. _ "zettelstore.de/z/box/membox" // Allow to use in-memory box. - _ "zettelstore.de/z/encoder/djsonenc" // Allow to use DJSON encoder. _ "zettelstore.de/z/encoder/htmlenc" // Allow to use HTML encoder. _ "zettelstore.de/z/encoder/nativeenc" // Allow to use native encoder. _ "zettelstore.de/z/encoder/textenc" // Allow to use text encoder. + _ "zettelstore.de/z/encoder/zjsonenc" // Allow to use ZJSON encoder. _ "zettelstore.de/z/encoder/zmkenc" // Allow to use zmk encoder. _ "zettelstore.de/z/kernel/impl" // Allow kernel implementation to create itself _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. _ "zettelstore.de/z/parser/draw" // Allow to use draw parser. _ "zettelstore.de/z/parser/markdown" // Allow to use markdown parser. _ "zettelstore.de/z/parser/none" // Allow to use none parser. _ "zettelstore.de/z/parser/plain" // Allow to use plain parser. _ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser. ) Index: collect/collect.go ================================================================== --- collect/collect.go +++ collect/collect.go @@ -21,13 +21,11 @@ } // References returns all references mentioned in the given zettel. This also // includes references to images. func References(zn *ast.ZettelNode) (s Summary) { - if zn.Ast != nil { - ast.Walk(&s, zn.Ast) - } + ast.Walk(&s, &zn.Ast) return s } // Visit all node to collect data for the summary. func (s *Summary) Visit(node ast.Node) ast.Visitor { Index: collect/collect_test.go ================================================================== --- collect/collect_test.go +++ collect/collect_test.go @@ -34,30 +34,28 @@ t.Error("No links/images expected, but got:", summary.Links, "and", summary.Embeds) } intNode := &ast.LinkNode{Ref: parseRef("01234567890123")} para := ast.CreateParaNode(intNode, &ast.LinkNode{Ref: parseRef("https://zettelstore.de/z")}) - zn.Ast = ast.CreateBlockListNode(para) + zn.Ast = ast.BlockSlice{para} summary = collect.References(zn) if summary.Links == nil || summary.Embeds != nil { t.Error("Links expected, and no images, but got:", summary.Links, "and", summary.Embeds) } - para.Inlines.Append(intNode) + para.Inlines = append(para.Inlines, intNode) summary = collect.References(zn) if cnt := len(summary.Links); cnt != 3 { t.Error("Link count does not work. Expected: 3, got", summary.Links) } } func TestEmbed(t *testing.T) { t.Parallel() zn := &ast.ZettelNode{ - Ast: ast.CreateBlockListNode( - ast.CreateParaNode(&ast.EmbedRefNode{Ref: parseRef("12345678901234")}), - ), + Ast: ast.BlockSlice{ast.CreateParaNode(&ast.EmbedRefNode{Ref: parseRef("12345678901234")})}, } summary := collect.References(zn) if summary.Embeds == nil { t.Error("Only image expected, but got: ", summary.Embeds) } } Index: collect/order.go ================================================================== --- collect/order.go +++ collect/order.go @@ -13,14 +13,11 @@ import "zettelstore.de/z/ast" // Order of internal reference within the given zettel. func Order(zn *ast.ZettelNode) (result []*ast.Reference) { - if zn.Ast == nil { - return nil - } - for _, bn := range zn.Ast.List { + for _, bn := range zn.Ast { ln, ok := bn.(*ast.NestedListNode) if !ok { continue } switch ln.Kind { @@ -44,15 +41,12 @@ } } return nil } -func firstInlineZettelReference(iln *ast.InlineListNode) (result *ast.Reference) { - if iln == nil { - return nil - } - for _, inl := range iln.List { +func firstInlineZettelReference(is ast.InlineSlice) (result *ast.Reference) { + for _, inl := range is { switch in := inl.(type) { case *ast.LinkNode: if ref := in.Ref; ref.IsZettel() { return ref } Index: docs/development/20210916194900.zettel ================================================================== --- docs/development/20210916194900.zettel +++ docs/development/20210916194900.zettel @@ -1,13 +1,15 @@ id: 20210916194900 title: Checklist for Release role: zettel syntax: zmk -modified: 20211214181017 +modified: 20220309105459 # Sync with the official repository #* ``fossil sync -u`` +# Make sure that all dependencies are up-to-date. +#* ``cat go.mod`` # Clean up your Go workspace: #* ``go run tools/build.go clean`` (alternatively: ``make clean``). # All internal tests must succeed: #* ``go run tools/build.go relcheck`` (alternatively: ``make relcheck``). # The API tests must succeed on every development platform: Index: docs/manual/00000000000100.zettel ================================================================== --- docs/manual/00000000000100.zettel +++ docs/manual/00000000000100.zettel @@ -5,9 +5,9 @@ default-copyright: (c) 2020-2022 by Detlef Stern default-license: EUPL-1.2-or-later default-visibility: public footer-html:

Imprint / Privacy

home-zettel: 00001000000000 -no-index: true +modified: 20220215171041 site-name: Zettelstore Manual visibility: owner Index: docs/manual/00001003305000.zettel ================================================================== --- docs/manual/00001003305000.zettel +++ docs/manual/00001003305000.zettel @@ -1,11 +1,11 @@ id: 00001003305000 title: Enable Zettelstore to start automatically on Windows role: manual tags: #installation #manual #zettelstore syntax: zmk -modified: 20211125201602 +modified: 20220218125541 Windows is a complicated beast. There are several ways to automatically start Zettelstore. === Startup folder @@ -13,14 +13,14 @@ Open the folder where you have placed in the Explorer. Create a shortcut file for the Zettelstore executable. There are some ways to do this: * Execute a right-click on the executable, and choose the menu entry ""Create shortcut"", * Execute a right-click on the executable, and then click Send To > Desktop (Create shortcut). -* Drag the executable to your Desktop with pressing the ++Alt++-Key. +* Drag the executable to your Desktop with pressing the ''Alt''-Key. If you have created the shortcut file, you must move it into the Startup folder. -Press the Windows logo key and the key ++R++, type ''shell:startup''. +Press the Windows logo key and the key ''R'', type ''shell:startup''. Select the OK button. This will open the Startup folder. Move the shortcut file into this folder. The next time you log into your computer, Zettelstore will be started automatically. @@ -40,11 +40,11 @@ On the negative side, you will not be notified when you enter the wrong data in the Task scheduler and Zettelstore fails to start. This can be mitigated by first using the command line prompt to start Zettelstore with the appropriate options. Once everything works, you can register Zettelstore to be automatically started by the task scheduler. There you should make sure that you have followed the first steps as described on the [[parent page|00001003300000]]. -To sart the Task scheduler management console, press the Windows logo key and the key ++R++, type ''taskschd.msc''. +To sart the Task scheduler management console, press the Windows logo key and the key ''R'', type ''taskschd.msc''. Select the OK button. {{00001003305102}} This will start the ""Task Scheduler"". Index: docs/manual/00001003315000.zettel ================================================================== --- docs/manual/00001003315000.zettel +++ docs/manual/00001003315000.zettel @@ -1,17 +1,42 @@ id: 00001003315000 title: Enable Zettelstore to start automatically on Linux role: manual tags: #installation #manual #zettelstore syntax: zmk -modified: 20220131162209 +modified: 20220307104944 Since there is no such thing as the one Linux, there are too many different ways to automatically start Zettelstore. * One way is to interpret your Linux desktop system as a server and use the [[recipe to install Zettelstore on a server|00001003600000]]. +** See below for a lighter alternative. * If you are using the [[Gnome Desktop|https://www.gnome.org/]], you could use the tool [[Tweak|https://wiki.gnome.org/action/show/Apps/Tweaks]] (formerly known as ""GNOME Tweak Tool"" or just ""Tweak Tool""). It allows to specify application that should run on startup / login. * [[KDE|https://kde.org/]] provides a system setting to [[autostart|https://docs.kde.org/stable5/en/plasma-workspace/kcontrol/autostart/]] applications. * [[Xfce|https://xfce.org/]] allows to specify [[autostart applications|https://docs.xfce.org/xfce/xfce4-session/preferences#application_autostart]]. * [[LXDE|https://www.lxde.org/]] uses [[LXSession Edit|https://wiki.lxde.org/en/LXSession_Edit]] to allow users to specify autostart applications. If you use a different desktop environment, it often helps to to provide its name and the string ""autostart"" to google for it with the search engine of your choice. + +Yet another way is to make use of the middleware that is provided. +Many Linux distributions make use of [[systemd|https://systemd.io/]], which allows to start processes on behalf of an user. +On the command line, adapt the following script to your own needs and execute it: +``` +# mkdir -p "$HOME/.config/systemd/user" +# cd "$HOME/.config/systemd/user" +# cat <<__EOF__ > zettelstore.service +[Unit] +Description=Zettelstore +After=network.target home.mount + +[Service] +ExecStart=/usr/local/bin/zettelstore run -d zettel + +[Install] +WantedBy=default.target +__EOF__ +# systemctl --user daemon-reload +# systemctl --user enable zettelstore.service +# systemctl --user start zettelstore.service +# systemctl --user status zettelstore.service +``` +The last command should output some lines to indicate success. Index: docs/manual/00001004010000.zettel ================================================================== --- docs/manual/00001004010000.zettel +++ docs/manual/00001004010000.zettel @@ -1,11 +1,11 @@ id: 00001004010000 title: Zettelstore startup configuration role: manual tags: #configuration #manual #zettelstore syntax: zmk -modified: 20211212143318 +modified: 20220304115353 The configuration file, as specified by the ''-c CONFIGFILE'' [[command line option|00001004051000]], allows you to specify some startup options. These options cannot be stored in a [[configuration zettel|00001004020000]] because either they are needed before Zettelstore can start or because of security reasons. For example, Zettelstore need to know in advance, on which network address is must listen or where zettel are stored. An attacker that is able to change the owner can do anything. @@ -14,87 +14,90 @@ The file for startup configuration must be created via a text editor in advance. The syntax of the configuration file is the same as for any zettel metadata. The following keys are supported: -; [!admin-port]''admin-port'' +; [!admin-port|''admin-port''] : Specifies the TCP port through which you can reach the [[administrator console|00001004100000]]. - A value of ''0'' (the default) disables the administrator console. + A value of ""0"" (the default) disables the administrator console. The administrator console will only be enabled if Zettelstore is started with the [[''run'' sub-command|00001004051000]]. - On most operating systems, the value must be greater than ''1024'' unless you start Zettelstore with the full privileges of a system administrator (which is not recommended). + On most operating systems, the value must be greater than ""1024"" unless you start Zettelstore with the full privileges of a system administrator (which is not recommended). - Default: ''0'' -; [!box-uri-x]''box-uri-__X__'', where __X__ is a number greater or equal to one + Default: ""0"" +; [!box-uri-x|''box-uri-X''], where __X__ is a number greater or equal to one : Specifies a [[box|00001004011200]] where zettel are stored. During startup __X__ is counted up, starting with one, until no key is found. This allows to configure more than one box. - If no ''box-uri-1'' key is given, the overall effect will be the same as if only ''box-uri-1'' was specified with the value ''dir://.zettel''. + If no ''box-uri-1'' key is given, the overall effect will be the same as if only ''box-uri-1'' was specified with the value ""dir://.zettel"". In this case, even a key ''box-uri-2'' will be ignored. -; [!debug-mode]''debug-mode'' -: Allows to debug the Zettelstore software (mostly used by the developers). +; [!debug-mode|''debug-mode''] +: Allows to debug the Zettelstore software (mostly used by the developers) if set to [[true|00001006030500]] Disables any timeout values of the internal web server and does not send some security-related data. Sets [[''log-level''|#log-level]] to ""debug"". Do not enable it for a production server. - Default: ''false'' -; [!default-dir-box-type]''default-dir-box-type'' + Default: ""false"" +; [!default-dir-box-type|''default-dir-box-type''] : Specifies the default value for the (sub-) type of [[directory boxes|00001004011400#type]]. Zettel are typically stored in such boxes. - Default: ''notify'' -; [!insecure-cookie]''insecure-cookie'' -: Must be set to ''true'', if authentication is enabled and Zettelstore is not accessible not via HTTPS (but via HTTP). + Default: ""notify"" +; [!insecure-cookie|''insecure-cookie''] +: Must be set to [[true|00001006030500]], if authentication is enabled and Zettelstore is not accessible not via HTTPS (but via HTTP). Otherwise web browser are free to ignore the authentication cookie. - Default: ''false'' -; [!listen-addr]''listen-addr'' + Default: ""false"" +; [!listen-addr|''listen-addr''] : Configures the network address, where the Zettelstore service is listening for requests. - Syntax is: ''[NETWORKIP]:PORT'', where ''NETWORKIP'' is the IP-address of the networking interface (or something like ''0.0.0.0'' if you want to listen on all network interfaces, and ''PORT'' is the TCP port. + Syntax is: ''[NETWORKIP]:PORT'', where ''NETWORKIP'' is the IP-address of the networking interface (or something like ""0.0.0.0"" if you want to listen on all network interfaces, and ''PORT'' is the TCP port. - Default value: ''"127.0.0.1:23123"'' -; [!log-level]''log-level'' + Default value: ""127.0.0.1:23123"" +; [!log-level|''log-level''] : Specify the global [[logging level|00001004059700]] for the whole application, overwriting the level ""debug"" set by configuration [[''debug-mode''|#debug-mode]]. Can be changed at runtime, even for specific internal services, with the ''log-level'' command of the [[administrator console|00001004101000#log-level]]. - Default: ''info''. + Default: ""info"". - When you are familiar to operate the Zettelstore, you might set the level to ''warn'' or ''error'' to receive less noisy messages from the Zettelstore. -; [!owner]''owner'' + When you are familiar to operate the Zettelstore, you might set the level to ""warn"" or ""error"" to receive less noisy messages from the Zettelstore. +; [!owner|''owner''] : [[Identifier|00001006050000]] of a zettel that contains data about the owner of the Zettelstore. The owner has full authorization for the Zettelstore. Only if owner is set to some value, user [[authentication|00001010000000]] is enabled. -; [!persistent-cookie]''persistent-cookie'' -: A boolean value to make the access cookie persistent. +; [!persistent-cookie|''persistent-cookie''] +: A [[boolean value|00001006030500]] to make the access cookie persistent. This is helpful if you access the Zettelstore via a mobile device. On these devices, the operating system is free to stop the web browser and to remove temporary cookies. Therefore, an authenticated user will be logged off. - If ''true'', a persistent cookie is used. + If ""true"", a persistent cookie is used. Its lifetime exceeds the lifetime of the authentication token (see option ''token-lifetime-html'') by 30 seconds. - Default: ''false'' -; [!read-only-mode]''read-only-mode'' -: Puts the Zettelstore service into a read-only mode. + Default: ""false"" +; [!read-only-mode|''read-only-mode''] +: Puts the Zettelstore service into a read-only mode, if set to a [[true value|00001006030500]]. No changes are possible. - Default: false. -; [!token-lifetime-api]''token-lifetime-api'', [!token-lifetime-html]''token-lifetime-html'' + + Default: ""false"". +; [!token-lifetime-api|''token-lifetime-api''], [!token-lifetime-html|''token-lifetime-html''] : Define lifetime of access tokens in minutes. Values are only valid if authentication is enabled, i.e. key ''owner'' is set. ''token-lifetime-api'' is for accessing Zettelstore via its [[API|00001012000000]]. - Default: 10. + Default: ""10"". ''token-lifetime-html'' specifies the lifetime for the HTML views. - Default: 60. It is automatically extended, when a new HTML view is rendered. -; [!url-prefix]''url-prefix'' + Default: ""60"". +; [!url-prefix|''url-prefix''] : Add the given string as a prefix to the local part of a Zettelstore local URL/URI when rendering zettel representations. - Must begin and end with a slash character (""''/''"", ''U+002F''). - Default: ''"/"''. + Must begin and end with a slash character (""''/''"", U+002F). + + Default: ""/"". This allows to use a forwarding proxy [[server|00001010090100]] in front of the Zettelstore. -; [!verbose-mode]''verbose-mode'' -: Be more verbose when logging data. - Default: false +; [!verbose-mode|''verbose-mode''] +: Be more verbose when logging data, if set to a [[true value|00001006030500]]. + + Default: ""false"" Index: docs/manual/00001004011200.zettel ================================================================== --- docs/manual/00001004011200.zettel +++ docs/manual/00001004011200.zettel @@ -1,11 +1,11 @@ id: 00001004011200 title: Zettelstore boxes role: manual tags: #configuration #manual #zettelstore syntax: zmk -modified: 20211103163225 +modified: 20220307121547 A Zettelstore must store its zettel somehow and somewhere. In most cases you want to store your zettel as files in a directory. Under certain circumstances you may want to store your zettel elsewhere. @@ -17,30 +17,31 @@ This is done via the ''box-uri-X'' keys of the [[startup configuration|00001004010000#box-uri-X]] (X is a number). Boxes are specified using special [[URIs|https://en.wikipedia.org/wiki/Uniform_Resource_Identifier]], somehow similar to web addresses. The following box URIs are supported: -; [!dir]''dir:\//DIR'' +; [!dir|''dir://DIR''] : Specifies a directory where zettel files are stored. ''DIR'' is the file path. - Although it is possible to use relative file paths, such as ''./zettel'' (→ URI is ''dir:\//.zettel''), it is preferable to use absolute file paths, e.g. ''/home/user/zettel''. + Although it is possible to use relative file paths, such as ''./zettel'' (→ URI is ''dir://.zettel''), it is preferable to use absolute file paths, e.g. ''/home/user/zettel''. The directory must exist before starting the Zettelstore[^There is one exception: when Zettelstore is [[started without any parameter|00001004050000]], e.g. via double-clicking its icon, an directory called ''./zettel'' will be created.]. It is possible to [[configure|00001004011400]] a directory box. -; [!file]''file:FILE.zip'' oder ''file:/\//path/to/file.zip'' +; [!file|''file:FILE.zip'' or ''file:///path/to/file.zip''] : Specifies a ZIP file which contains files that store zettel. You can create such a ZIP file, if you zip a directory full of zettel files. This box is always read-only. -; [!mem]''mem:'' +; [!mem|''mem:''] : Stores all its zettel in volatile memory. If you stop the Zettelstore, all changes are lost. + To limit usage of volatile memory, you should [[configure|00001004011600]] this type of box, although the default values might be valid for your use case. All boxes that you configure via the ''box-uri-X'' keys form a chain of boxes. If a zettel should be retrieved, a search starts in the box specified with the ''box-uri-2'' key, then ''box-uri-3'' and so on. If a zettel is created or changed, it is always stored in the box specified with the ''box-uri-1'' key. This allows to overwrite zettel from other boxes, e.g. the predefined zettel. If you use the ''mem:'' box, where zettel are stored in volatile memory, it makes only sense if you configure it as ''box-uri-1''. Such a box will be empty when Zettelstore starts and only the first box will receive updates. You must make sure that your computer has enough RAM to store all zettel. Index: docs/manual/00001004011400.zettel ================================================================== --- docs/manual/00001004011400.zettel +++ docs/manual/00001004011400.zettel @@ -1,19 +1,19 @@ id: 00001004011400 title: Configure file directory boxes role: manual tags: #configuration #manual #zettelstore syntax: zmk -modified: 20211216152540 +modified: 20220307121244 Under certain circumstances, it is preferable to further configure a file directory box. This is done by appending query parameters after the base box URI ''dir:\//DIR''. The following parameters are supported: |= Parameter:|Description|Default value:| -|type|(Sub-) Type of the directory service|(value of ''[[default-dir-box-type|00001004010000#default-dir-box-type]]'') +|type|(Sub-) Type of the directory service|(value of ""[[default-dir-box-type|00001004010000#default-dir-box-type]]"") |worker|Number of worker that can access the directory in parallel|7 |readonly|Allow only operations that do not create or change zettel|n/a === Type On some operating systems, Zettelstore tries to detect changes to zettel files outside of Zettelstore's control[^This includes Linux, Windows, and macOS.]. ADDED docs/manual/00001004011600.zettel Index: docs/manual/00001004011600.zettel ================================================================== --- docs/manual/00001004011600.zettel +++ docs/manual/00001004011600.zettel @@ -0,0 +1,25 @@ +id: 00001004011600 +title: Configure memory boxes +role: manual +tags: #configuration #manual #zettelstore +syntax: zmk +modified: 20220307122554 + +Under most circumstances, it is preferable to further configure a memory box. +This is done by appending query parameters after the base box URI ''mem:''. + +The following parameters are supported: + +|= Parameter:|Description|Default value:|Maximum value: +|max-bytes|Maximum number of bytes the box will store|65535|1073741824 (1 GiB) +|max-zettel|Maximum number of zettel|127|65535 + +The default values are somehow arbitrarily, but applicable for many use cases. + +While the number of zettel should be easily calculable by an user, the number of bytes might be a little more difficult. + +Metadata consumes 6 bytes for the zettel identifier and for each metadata value one byte for the separator, plus the length of key and data. +Then size of the content is its size in bytes. +For text content, its the number of bytes for its UTF-8 encoding. + +If one of the limits are exceeded, Zettelstore will give an error indication, based on the HTTP status code 507. Index: docs/manual/00001004020000.zettel ================================================================== --- docs/manual/00001004020000.zettel +++ docs/manual/00001004020000.zettel @@ -1,78 +1,78 @@ id: 00001004020000 title: Configure the running Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk -modified: 20220111103757 +modified: 20220304114412 You can configure a running Zettelstore by modifying the special zettel with the ID [[00000000000100]]. This zettel is called __configuration zettel__. The following metadata keys change the appearance / behavior of Zettelstore: -; [!default-copyright]''default-copyright'' +; [!default-copyright|''default-copyright''] : Copyright value to be used when rendering content. Can be overwritten in a zettel with [[meta key|00001006020000]] ''copyright''. Default: (the empty string). -; [!default-lang]''default-lang'' +; [!default-lang|''default-lang''] : Default language to be used when displaying content. Can be overwritten in a zettel with [[meta key|00001006020000]] ''lang''. Default: ""en"". This value is also used to specify the language for all non-zettel content, e.g. lists or search results. Use values according to the language definition of [[RFC-5646|https://tools.ietf.org/html/rfc5646]]. -; [!default-license]''default-license'' +; [!default-license|''default-license''] : License value to be used when rendering content. Can be overwritten in a zettel with [[meta key|00001006020000]] ''license''. Default: (the empty string). -; [!default-role]''default-role'' +; [!default-role|''default-role''] : Role to be used, if a zettel specifies no ''role'' [[meta key|00001006020000]]. Default: ""zettel"". -; [!default-syntax]''default-syntax'' +; [!default-syntax|''default-syntax''] : Syntax to be used, if a zettel specifies no ''syntax'' [[meta key|00001006020000]]. Default: ""zmk"" (""[[Zettelmarkup|00001007000000]]""). -; [!default-title]''default-title'' +; [!default-title|''default-title''] : Title to be used, if a zettel specifies no ''title'' [[meta key|00001006020000]]. Default: ""Untitled"". You can use all [[inline-structured elements|00001007040000]] of Zettelmarkup. -; [!default-visibility]''default-visibility'' +; [!default-visibility|''default-visibility''] : Visibility to be used, if zettel does not specify a value for the [[''visibility''|00001006020000#visibility]] metadata key. Default: ""login"". -; [!expert-mode]''expert-mode'' -: If set to a boolean true value, all zettel with [[visibility ""expert""|00001010070200]] will be shown (to the owner, if [[authentication is enabled|00001010040100]]; to all, otherwise). +; [!expert-mode|''expert-mode''] +: If set to a [[boolean true value|00001006030500]], all zettel with [[visibility ""expert""|00001010070200]] will be shown (to the owner, if [[authentication is enabled|00001010040100]]; to all, otherwise). This affects most computed zettel. Default: ""False"". -; [!footer-html]''footer-html'' +; [!footer-html|''footer-html''] : Contains some HTML code that will be included into the footer of each Zettelstore web page. It only affects the [[web user interface|00001014000000]]. Zettel content, delivered via the [[API|00001012000000]] as JSON, etc. is not affected. Default: (the empty string). -; [!home-zettel]''home-zettel'' +; [!home-zettel|''home-zettel''] : Specifies the identifier of the zettel, that should be presented for the default view / home view. If not given or if the identifier does not identify a zettel, the zettel with the identifier ''00010000000000'' is shown. -; [!marker-external]''marker-external'' +; [!marker-external|''marker-external''] : Some HTML code that is displayed after a [[reference to external material|00001007040310]]. Default: ""&\#10138;"", to display a ""➚"" sign. -; [!max-transclusions]''max-transclusions'' +; [!max-transclusions|''max-transclusions''] : Maximum number of indirect transclusion. This is used to avoid an exploding ""transclusion bomb"", a form of a [[billion laughs attack|https://en.wikipedia.org/wiki/Billion_laughs_attack]]. Default: ""1024"". -; [!site-name]''site-name'' +; [!site-name|''site-name''] : Name of the Zettelstore instance. Will be used when displaying some lists. Default: ""Zettelstore"". -; [!yaml-header]''yaml-header'' -: If true, metadata and content will be separated by ''-\--\\n'' instead of an empty line (''\\n\\n''). +; [!yaml-header|''yaml-header''] +: If [[true|00001006030500]], metadata and content will be separated by ''---\\n'' instead of an empty line (''\\n\\n''). Default: ""False"". You will probably use this key, if you are working with another software processing [[Markdown|https://daringfireball.net/projects/markdown/]] that uses a subset of [[YAML|https://yaml.org/]] to specify metadata. -; [!zettel-file-syntax]''zettel-file-syntax'' +; [!zettel-file-syntax|''zettel-file-syntax''] : If you create a new zettel with a syntax different to ""zmk"", Zettelstore will store the zettel as two files: one for the metadata (file without a filename extension) and another for the content (file extension based on the syntax value). If you want to specify alternative syntax values, for which you want new zettel to be stored in one file (file extension ''.zettel''), you can use this key. All values are case-insensitive, duplicate values are removed. For example, you could use this key if you're working with Markdown syntax and you want to store metadata and content in one ''.zettel'' file. If ''yaml-header'' evaluates to true, a zettel is always stored in one ''.zettel'' file. Index: docs/manual/00001004051000.zettel ================================================================== --- docs/manual/00001004051000.zettel +++ docs/manual/00001004051000.zettel @@ -1,48 +1,48 @@ id: 00001004051000 title: The ''run'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk -modified: 20211124140711 +modified: 20220214175947 === ``zettelstore run`` This starts the web service. ``` zettelstore run [-a PORT] [-c CONFIGFILE] [-d DIR] [-debug] [-p PORT] [-r] [-v] ``` -; [!a]''-a PORT'' +; [!a|''-a PORT''] : Specifies the TCP port through which you can reach the [[administrator console|00001004100000]]. See the explanation of [[''admin-port''|00001004010000#admin-port]] for more details. -; [!c]''-c CONFIGFILE'' +; [!c|''-c CONFIGFILE''] : Specifies ''CONFIGFILE'' as a file, where [[startup configuration data|00001004010000]] is read. It is ignored, when the given file is not available, nor readable. Default: ''./.zscfg''. (''.\\.zscfg'' on Windows)), where ''.'' denotes the ""current directory"". -; [!d]''-d DIR'' +; [!d|''-d DIR''] : Specifies ''DIR'' as the directory that contains all zettel. Default is ''./zettel'' (''.\\zettel'' on Windows), where ''.'' denotes the ""current directory"". -; [!debug]''-debug'' +; [!debug|''-debug''] : Allows better debugging of the internal web server by disabling any timeout values. You should specify this only as a developer. Especially do not enable it for a production server. [[https://blog.cloudflare.com/exposing-go-on-the-internet/#timeouts]] contains a good explanation for the usefulness of sensitive timeout values. -; [!p]''-p PORT'' +; [!p|''-p PORT''] : Specifies the integer value ''PORT'' as the TCP port, where the Zettelstore web server listens for requests. Default: 23123. Zettelstore listens only on ''127.0.0.1'', e.g. only requests from the current computer will be processed. If you want to listen on network card to process requests from other computer, please use [[''listen-addr''|00001004010000#listen-addr]] of the configuration file as described below. -; [!r]''-r'' +; [!r|''-r''] : Puts the Zettelstore in read-only mode. No changes are possible via the [[web user interface|00001014000000]] / via the [[API|00001012000000]]. This allows to publish your content without any risks of unauthorized changes. -; [!v]''-v'' +; [!v|''-v''] : Be more verbose when writing logs. Command line options take precedence over [[configuration file|00001004010000]] options. Index: docs/manual/00001004051100.zettel ================================================================== --- docs/manual/00001004051100.zettel +++ docs/manual/00001004051100.zettel @@ -1,11 +1,11 @@ id: 00001004051100 title: The ''run-simple'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk -modified: 20210712234203 +modified: 20220214180253 === ``zettelstore run-simple`` This sub-command is implicitly called, when an user starts Zettelstore by double-clicking on its GUI icon. It is s simplified variant of the [[''run'' sub-command|00001004051000]]. @@ -16,9 +16,9 @@ ``` zettelstore run-simple [-d DIR] ``` -; [!d]''-d DIR'' +; [!d|''-d DIR''] : Specifies ''DIR'' as the directory that contains all zettel. Default is ''./zettel'' (''.\\zettel'' on Windows), where ''.'' denotes the ""current directory"". Index: docs/manual/00001004051200.zettel ================================================================== --- docs/manual/00001004051200.zettel +++ docs/manual/00001004051200.zettel @@ -1,11 +1,11 @@ id: 00001004051200 title: The ''file'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk -modified: 20210727120507 +modified: 20220209114650 Reads zettel data from a file (or from standard input / stdin) and renders it to standard output / stdout. This allows Zettelstore to render files manually. ``` zettelstore file [-t FORMAT] [file-1 [file-2]] @@ -13,16 +13,16 @@ ; ''-t FORMAT'' : Specifies the output format. Supported values are: [[''html''|00001012920510]] (default), - [[''djson''|00001012920503]], [[''native''|00001012920513]], [[''text''|00001012920519]], + [[''zjson''|00001012920503]], and [[''zmk''|00001012920522]]. ; ''file-1'' : Specifies the file name, where at least metadata is read. If ''file-2'' is not given, the zettel content is also read from here. ; ''file-2'' : File name where the zettel content is stored. If neither ''file-1'' nor ''file-2'' are given, metadata and zettel content are read from standard input / stdin. Index: docs/manual/00001004101000.zettel ================================================================== --- docs/manual/00001004101000.zettel +++ docs/manual/00001004101000.zettel @@ -1,69 +1,69 @@ id: 00001004101000 title: List of supported commands of the administrator console role: manual tags: #configuration #manual #zettelstore syntax: zmk -modified: 20211210184654 +modified: 20220218133526 -; ''bye'' +; [!bye|''bye''] : Closes the connection to the administrator console. -; ''config SERVICE'' +; [!config|''config SERVICE''] : Displays all valid configuration keys for the given service. - If a key ends with the hyphen-minus character (""''-''"", ''U+002D''), the key denotes a list value. + If a key ends with the hyphen-minus character (""''-''"", U+002D), the key denotes a list value. Keys of list elements are specified by appending a number greater than zero to the key. -; ''crlf'' +; [!crlf|''crlf''] : Toggles CRLF mode for console output. Changes end of line sequences between Windows mode (==\\r\\n==) and non-Windows mode (==\\n==, initial value). Often used on Windows telnet clients that otherwise scramble the output of commands. -; ''dump-index'' +; [!dump-index|''dump-index''] : Displays the content of the internal search index. -; ''dump-recover RECOVER'' +; [!dump-recover|''dump-recover RECOVER''] : Displays data about the last given recovered internal activity. The value for ''RECOVER'' can be obtained via the command ``stat core``, which lists all overview data about all recoveries. -; ''echo'' +; [!echo|''echo''] : Toggles the echo mode, where each command is printed before execution. -; ''end-profile'' +; [!end-profile|''end-profile''] : Stops profiling the application. -; ''env'' +; [!env|''env''] : Display environment values. -; ''help'' +; [!help|''help''] : Displays a list of all available commands. -; ''get-config'' +; [!get-config|''get-config''] : Displays current configuration data. ``get-config`` shows all current configuration data. ``get-config SERVICE`` shows only the current configuration data of the given service. ``get-config SERVICE KEY`` shows the current configuration data for the given service and key. -; ''header'' +; [!header|''header''] : Toggles the header mode, where each table is show with a header nor not. -; [!log-level]''log-level'' +; [!log-level|''log-level''] : Displays or sets the [[logging level|00001004059700]] for the kernel or a service. ``log-level`` shows all known log level. ``log-level NAME`` shows log level for the given service or for the kernel. ``log-level NAME VALUE`` sets the log level for the given service or for the kernel. ''VALUE'' is either the name of the log level or its numerical value. -; ''metrics'' +; [!metrics|''metrics''] : Displays some values that reflect the inner workings of Zettelstore. See [[here|https://golang.org/pkg/runtime/metrics/]] for a technical description of these values. -; ''next-config'' +; [!next-config|''next-config''] : Displays next configuration data. It will be the current configuration, if the corresponding services is restarted. ``next-config`` shows all next configuration data. ``next-config SERVICE`` shows only the next configuration data of the given service. ``next-config SERVICE KEY`` shows the next configuration data for the given service and key. -; ''profile [PROFILE] [FILE]'' +; [!profile|''profile [PROFILE] [FILE]''] : Starts to profile the software with the profile PROFILE and writes profiling data to file FILE. If PROFILE is not given, a value ''CPU'' is assumed, which specifies to profile CPU usage. If FILE is not given, a value ''PROFILE.prof'' will be used. Other values for ''PROFILE'' are: ''goroutine'', ''heap'', ''allocs'', ''threadcreate'', ''block'', and ''mutex''. @@ -71,24 +71,24 @@ See the [[Go documentation|https://pkg.go.dev/runtime/pprof#Profile]] for details. This feature is dependent on the internal implementation language of Zettelstore, Go. It may be removed without any further notice at any time. In most cases, it is a tool for software developers to optimize Zettelstore's internal workings. -; ''restart SERVICE'' +; [!restart|''restart SERVICE''] : Restart the given service and all other that depend on this. -; ''services'' +; [!services|''services''] : Displays s list of all available services and their current status. -; ''set-config SERVICE KEY VALUE'' +; [!set-config|''set-config SERVICE KEY VALUE''] : Sets a single configuration value for the next configuration of a given service. It will become effective if the service is restarted. If the key specifies a list value, all other list values with a number greater than the given key are deleted. You can use the special number ""0"" to delete all values. E.g. ``set-config box box-uri-0 any_text`` will remove all values of the list __box-uri-__. -; ''shutdown'' +; [!shutdown|''shutdown''] : Terminate the Zettelstore itself (and closes the connection to the administrator console). -; ''start SERVICE'' -: Start the given bservice and all dependent services. -; ''stat SERVICE'' +; [!start|''start SERVICE''] +: Start the given service and all dependent services. +; [!stat|''stat SERVICE''] : Display some statistical values for the given service. -; ''stop SERVICE'' +; [!stop|''stop SERVICE''] : Stop the given service and all other that depend on this. Index: docs/manual/00001006010000.zettel ================================================================== --- docs/manual/00001006010000.zettel +++ docs/manual/00001006010000.zettel @@ -1,16 +1,16 @@ id: 00001006010000 title: Syntax of Metadata role: manual tags: #manual #syntax #zettelstore syntax: zmk -modified: 20211103164152 +modified: 20220218131923 The metadata of a zettel is a collection of key-value pairs. The syntax roughly resembles the internal header of an email ([[RFC5322|https://tools.ietf.org/html/rfc5322]]). -The key is a sequence of alphanumeric characters, a hyphen-minus character (""''-''"") is also allowed. +The key is a sequence of alphanumeric characters, a hyphen-minus character (""''-''"", U+002D) is also allowed. It begins at the first position of a new line. A key is separated from its value either by * a colon character (""'':''""), * a non-empty sequence of space characters, Index: docs/manual/00001006020000.zettel ================================================================== --- docs/manual/00001006020000.zettel +++ docs/manual/00001006020000.zettel @@ -1,112 +1,110 @@ id: 00001006020000 title: Supported Metadata Keys role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk -modified: 20220111103609 +modified: 20220218130146 Although you are free to define your own metadata, by using any key (according to the [[syntax|00001006010000]]), some keys have a special meaning that is enforced by Zettelstore. See the [[computed list of supported metadata keys|00000000000090]] for details. Most keys conform to a [[type|00001006030000]]. -; [!all-tags]''all-tags'' +; [!all-tags|''all-tags''] : 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. -; [!back]''back'' +; [!back|''back''] : Is a property that contains the identifier of all zettel that reference the zettel of this metadata, that are not referenced by this zettel. Basically, it is the value of [[''backward''|#bachward]], but without any zettel identifier that is contained in [[''forward''|#forward]]. -; [!backward]''backward'' +; [!backward|''backward''] : Is a property that contains the identifier of all zettel that reference the zettel of this metadata. References within invertible values are not included here, e.g. [[''precursor''|#precursor]]. -; [!box-number]''box-number'' +; [!box-number|''box-number''] : Is a computed value and contains the number of the box where the zettel was found. For all but the [[predefined zettel|00001005090000]], this number is equal to the number __X__ specified in startup configuration key [[''box-uri-__X__''|00001004010000#box-uri-x]]. -; [!copyright]''copyright'' +; [!copyright|''copyright''] : Defines a copyright string that will be encoded. If not given, the value ''default-copyright'' from the [[configuration zettel|00001004020000#default-copyright]] will be used. -; [!credential]''credential'' +; [!credential|''credential''] : Contains the hashed password, as it was emitted by [[``zettelstore password``|00001004051400]]. It is internally created by hashing the password, the [[zettel identifier|00001006050000]], and the value of the ''ident'' key. It is only used for zettel with a ''role'' value of ""user"". -; [!dead]''dead'' +; [!dead|''dead''] : Property that contains all references that does __not__ identify a zettel. -; [!folge]''folge'' +; [!folge|''folge''] : Is a property that contains identifier of all zettel that reference this zettel through the [[''precursor''|#precursor]] value. -; [!forward]''forward'' +; [!forward|''forward''] : Property that contains all references that identify another zettel within the content of the zettel. -; [!id]''id'' +; [!id|''id''] : Contains the [[zettel identifier|00001006050000]], as given by the Zettelstore. It cannot be set manually, because it is a computed value. -; [!lang]''lang'' +; [!lang|''lang''] : Language for the zettel. Mostly used for HTML rendering of the zettel. If not given, the value ''default-lang'' from the [[configuration zettel|00001004020000#default-lang]] will be used. Use values according to the language definition of [[RFC-5646|https://tools.ietf.org/html/rfc5646]]. -; [!license]''license'' +; [!license|''license''] : Defines a license string that will be rendered. If not given, the value ''default-license'' from the [[configuration zettel|00001004020000#default-license]] will be used. -; [!modified]''modified'' +; [!modified|''modified''] : Date and time when a zettel was modified through Zettelstore. If you edit a zettel with an editor software outside Zettelstore, you should set it manually to an appropriate value. This is a computed value. There is no need to set it via Zettelstore. -; [!no-index]''no-index'' -: If set to a ""true"" value, the zettel will not be indexed and therefore not be found in full-text searches. -; [!precursor]''precursor'' +; [!precursor|''precursor''] : References zettel for which this zettel is a ""Folgezettel"" / follow-up zettel. Basically the inverse of key [[''folge''|#folge]]. -; [!published]''published'' +; [!published|''published''] : This property contains the timestamp of the mast modification / creation of the zettel. If [[''modified''|#modified]] is set, it contains the same value. Otherwise, if the zettel identifier contains a valid timestamp, the identifier is used. In all other cases, this property is not set. It can be used for [[sorting|00001012052000]] zettel based on their publication date. It is a computed value. There is no need to set it via Zettelstore. -; [!read-only]''read-only'' +; [!read-only|''read-only''] : Marks a zettel as read-only. The interpretation of [[supported values|00001006020400]] for this key depends, whether authentication is [[enabled|00001010040100]] or not. -; [!role]''role'' +; [!role|''role''] : Defines the role of the zettel. Can be used for selecting zettel. See [[supported zettel roles|00001006020100]]. If not given, the value ''default-role'' from the [[configuration zettel|00001004020000#default-role]] will be used. -; [!syntax]''syntax'' +; [!syntax|''syntax''] : Specifies the syntax that should be used for interpreting the zettel. The zettel about [[other markup languages|00001008000000]] defines supported values. If not given, the value ''default-syntax'' from the [[configuration zettel|00001004020000#default-syntax]] will be used. -; [!tags]''tags'' +; [!tags|''tags''] : Contains a space separated list of tags to describe the zettel further. - Each Tag must begin with the number sign character (""''#''"", ''U+0023''). -; [!title]''title'' + Each Tag must begin with the number sign character (""''#''"", U+0023). +; [!title|''title''] : Specifies the title of the zettel. If not given, the value ''default-title'' from the [[configuration zettel|00001004020000#default-title]] will be used. You can use all [[inline-structured elements|00001007040000]] of Zettelmarkup. -; [!url]''url'' +; [!url|''url''] : Defines an URL / URI for this zettel that possibly references external material. One use case is to specify the document that the current zettel comments on. The URL will be rendered special in the [[web user interface|00001014000000]] if you use the default template. -; [!useless-files]''useless-files'' +; [!useless-files|''useless-files''] : Contains the file names that are rejected to serve the content of a zettel. Is used for [[directory boxes|00001004011400]] and [[file boxes|00001004011200#file]]. If a zettel is renamed or deleted, these files will be deleted. -; [!user-id]''user-id'' +; [!user-id|''user-id''] : Provides some unique user identification for an [[user zettel|00001010040200]]. It is used as a user name for authentication. It is only used for zettel with a ''role'' value of ""user"". -; [!user-role]''user-role'' +; [!user-role|''user-role''] : Defines the basic privileges of an authenticated user, e.g. reading / changing zettel. Is only valid in a user zettel. See [[User roles|00001010070300]] for more details. -; [!visibility]''visibility'' +; [!visibility|''visibility''] : When you work with authentication, you can give every zettel a value to decide, who can see the zettel. Its default value can be set with [[''default-visibility''|00001004020000#default-visibility]] of the configuration zettel. See [[visibility rules for zettel|00001010070200]] for more details. Index: docs/manual/00001006020100.zettel ================================================================== --- docs/manual/00001006020100.zettel +++ docs/manual/00001006020100.zettel @@ -1,34 +1,34 @@ id: 00001006020100 title: Supported Zettel Roles role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk -modified: 20211127174441 +modified: 20220214174553 The [[''role'' key|00001006020000#role]] defines what kind of zettel you are writing. You are free to define your own roles. The role ''zettel'' is predefined as the default role, but you can [[change this|00001004020000#default-role]]. Some roles are defined for technical reasons: -; [!configuration]''configuration'' +; [!configuration|''configuration''] : A zettel that contains some configuration data for the Zettelstore. Most prominent is [[00000000000100]], as described in [[00001004020000]]. -; [!manual]''manual'' +; [!manual|''manual''] : All zettel that document the inner workings of the Zettelstore software. This role is only used in this specific Zettelstore. If you adhere to the process outlined by Niklas Luhmann, a zettel could have one of the following three roles: -; [!note]''note'' +; [!note|''note''] : A small note, to remember something. Notes are not real zettel, they just help to create a real zettel. Think of them as Post-it notes. -; [!literature]''literature'' +; [!literature|''literature''] : Contains some remarks about a book, a paper, a web page, etc. You should add a citation key for citing it. -; [!zettel]''zettel'' +; [!zettel|''zettel''] : A real zettel that contains your own thoughts. 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, ... Index: docs/manual/00001006030000.zettel ================================================================== --- docs/manual/00001006030000.zettel +++ docs/manual/00001006030000.zettel @@ -1,31 +1,33 @@ id: 00001006030000 title: Supported Key Types role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk -modified: 20220110161544 +modified: 20220304114106 All [[supported metadata keys|00001006020000]] conform to a type. User-defined metadata keys conform also to a type, based on the suffix of the key. |=Suffix|Type | ''-number'' | [[Number|00001006033000]] | ''-role'' | [[Word|00001006035500]] +| ''-set'' | [[WordSet|00001006036000]] +| ''-title'' | [[Zettelmarkup|00001006036500]] | ''-url'' | [[URL|00001006035000]] | ''-zettel'' | [[Identifier|00001006032000]] | ''-zid'' | [[Identifier|00001006032000]] +| ''-zids'' | [[IdentifierSet|00001006032500]] | any other suffix | [[EString|00001006031500]] The name of the metadata key is bound to the key type Every key type has an associated validation rule to check values of the given type. There is also a rule how values are matched, e.g. against a search term when selecting some zettel. And there is a rule how values compare for sorting. -* [[Boolean|00001006030500]] * [[Credential|00001006031000]] * [[EString|00001006031500]] * [[Identifier|00001006032000]] * [[IdentifierSet|00001006032500]] * [[Number|00001006033000]] Index: docs/manual/00001006030500.zettel ================================================================== --- docs/manual/00001006030500.zettel +++ docs/manual/00001006030500.zettel @@ -1,21 +1,14 @@ id: 00001006030500 -title: Boolean Key Type -role: manual -tags: #manual #meta #reference #zettel #zettelstore -syntax: zmk - -Values of this type denote a truth value. - -=== Allowed values -Every character sequence that begins with a ""''0''"", ""''F''"", ""''N''"", ""''f''"", or a ""''n''"" is interpreted as the ""false"" boolean value. -All other metadata value is interpreted as the ""true"" boolean value. - -=== Match operator -The match operator is the equals operator, i.e. -* ``(true == true) == true`` -* ``(false == false) == true`` -* ``(true == false) == false`` -* ``(false == true) == false`` - -=== Sorting -The ""false"" value is less than the ""true"" value: ``false < true`` +title: Boolean Value +role: manual +tags: #manual #reference #zettel #zettelstore +syntax: zmk +modified: 20220304114040 + +On some places, metadata values are interpreted as a truth value. + +Every character sequence that begins with a ""0"", ""F"", ""N"", ""f"", or a ""n"" is interpreted as the boolean ""false"" value. +All values are interpreted as the boolean ""true"" value. + +However, there is no full support for a boolean type. +Such values are interpreted as a [[string value|00001006033500]], e.g. with respect to comparing and sorting. Index: docs/manual/00001006034000.zettel ================================================================== --- docs/manual/00001006034000.zettel +++ docs/manual/00001006034000.zettel @@ -1,18 +1,18 @@ id: 00001006034000 title: TagSet Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk -modified: 20220111103723 +modified: 20220218130413 Values of this type denote a (sorted) set of tags. A set is different to a list, as no duplicate values are allowed. === Allowed values -Every tag must must begin with the number sign character (""''#''"", ''U+0023''), followed by at least one printable character. +Every tag must must begin with the number sign character (""''#''"", U+0023), followed by at least one printable character. Tags are separated by space characters. All characters are mapped to their lower case values. === Match operator Index: docs/manual/00001007020000.zettel ================================================================== --- docs/manual/00001007020000.zettel +++ docs/manual/00001007020000.zettel @@ -1,21 +1,21 @@ id: 00001007020000 title: Zettelmarkup: Basic Definitions role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk -modified: 20211124175237 +modified: 20220218130713 -Every zettelmark content consists of a sequence of Unicode codepoints. -Unicode codepoints are called in the following as **character**s. +Every Zettelmarkup content consists of a sequence of Unicode code-points. +Unicode code-points are called in the following as **character**s. Characters are encoded with UTF-8. ; Line -: A __line__ is a sequence of characters, except newline (''U+000A'') and carraige return (''U+000D''), followed by a line ending sequence or the end of content. +: A __line__ is a sequence of characters, except newline (U+000A) and carriage return (U+000D), followed by a line ending sequence or the end of content. ; Line ending : A __line ending__ is either a newline not followed by a carriage return, a newline followed by a carriage return, or a carriage return. Different line can be finalized by different line endings. ; Empty line : An __empty line__ is an empty sequence of characters, followed by a line ending or the end of content. ; Space character -: The __space character__ is the Unicode codepoint ''U+0020''. +: The __space character__ is the Unicode code-point U+0020. Index: docs/manual/00001007030100.zettel ================================================================== --- docs/manual/00001007030100.zettel +++ docs/manual/00001007030100.zettel @@ -1,19 +1,19 @@ id: 00001007030100 title: Zettelmarkup: Description Lists role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk -modified: 20211124172247 +modified: 20220218131155 A description list is a sequence of terms to be described together with the descriptions of each term. Every term can described in multiple ways. -A description term (short: __term__) is specified with one semicolon (""'';''"", ''U+003B'') at the first position, followed by a space character and the described term, specified as a sequence of line elements. +A description term (short: __term__) is specified with one semicolon (""'';''"", U+003B) at the first position, followed by a space character and the described term, specified as a sequence of line elements. If the following lines should also be part of the term, exactly two spaces must be given at the beginning of each following line. -The description of a term is given with one colon (""'':''"", ''U+003A'') at the first position, followed by a space character and the description itself, specified as a sequence of [[inline elements|00001007040000]]. +The description of a term is given with one colon (""'':''"", U+003A) at the first position, followed by a space character and the description itself, specified as a sequence of [[inline elements|00001007040000]]. Similar to terms, following lines can also be part of the actual description, if they begin at each line with exactly two space characters. In contrast to terms, the actual descriptions are merged into a paragraph. This is because, an actual description can contain more than one paragraph. As usual, paragraphs are separated by an empty line. Index: docs/manual/00001007030200.zettel ================================================================== --- docs/manual/00001007030200.zettel +++ docs/manual/00001007030200.zettel @@ -1,15 +1,15 @@ id: 00001007030200 title: Zettelmarkup: Nested Lists role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk -modified: 20211124172337 +modified: 20220218133902 There are thee kinds of lists that can be nested: ordered lists, unordered lists, and quotation lists. -Ordered lists are specified with the number sign (""''#''"", ''U+0023''), unordered lists use the asterisk (""''*''"", ''U+002A''), and quotation lists are specified with the greater-than sing (""''>''"", ''U+003E''). +Ordered lists are specified with the number sign (""''#''"", U+0023), unordered lists use the asterisk (""''*''"", U+002A), and quotation lists are specified with the greater-than sing (""''>''"", U+003E). Let's call these three characters __list characters__. Any nested list item is specified by a non-empty sequence of list characters, followed by a space character and a sequence of [[inline elements|00001007040000]]. In case of a quotation list as the last list character, the space character followed by a sequence of inline elements is optional. The number / count of list characters gives the nesting of the lists. Index: docs/manual/00001007030300.zettel ================================================================== --- docs/manual/00001007030300.zettel +++ docs/manual/00001007030300.zettel @@ -1,14 +1,14 @@ id: 00001007030300 title: Zettelmarkup: Headings role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk -modified: 20211124172401 +modified: 20220218133755 To specify a (sub-) section of a zettel, you should use the headings syntax: at -the beginning of a new line type at least three equal signs (""''=''"", ''U+003D''), plus at least one +the beginning of a new line type at least three equal signs (""''=''"", U+003D), plus at least one space and enter the text of the heading as [[inline elements|00001007040000]]. ```zmk === Level 1 Heading ==== Level 2 Heading Index: docs/manual/00001007030400.zettel ================================================================== --- docs/manual/00001007030400.zettel +++ docs/manual/00001007030400.zettel @@ -1,31 +1,31 @@ id: 00001007030400 title: Zettelmarkup: Horizontal Rules / Thematic Break role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk -modified: 20211124174251 +modified: 20220218133651 -To signal a thematic break, you can specify a horizonal rule. -This is done by entering at least three hyphen-minus characters (""''-''"", ''U+002D'') at the first position of a line. +To signal a thematic break, you can specify a horizontal rule. +This is done by entering at least three hyphen-minus characters (""''-''"", U+002D) at the first position of a line. You can add some [[attributes|00001007050000]], although the horizontal rule does not support the default attribute. Any other character in this line will be ignored -If you do not enter the three hyphen-minus charachter at the very first position of a line, the are interpreted as [[inline elements|00001007040000]], typically as an ""en-dash" followed by a hyphen-minus. +If you do not enter the three hyphen-minus character at the very first position of a line, the are interpreted as [[inline elements|00001007040000]], typically as an ""en-dash" followed by a hyphen-minus. Example: ```zmk --- -----{color=green} +----{.zs-deprecated} ----- --- inline --- ignored ``` is rendered in HTML as :::example --- -----{color=green} +----{.zs-deprecated} ----- --- inline --- ignored ::: Index: docs/manual/00001007030500.zettel ================================================================== --- docs/manual/00001007030500.zettel +++ docs/manual/00001007030500.zettel @@ -1,18 +1,18 @@ id: 00001007030500 title: Zettelmarkup: Verbatim Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk -modified: 20211124170510 +modified: 20220218131500 Verbatim blocks are used to enter text that should not be interpreted. -They begin with at least three grave accent characters (""''`''"", ''U+0060'') at the first position of a line. -Alternatively, a modifier letter grave accent (""''ˋ''"", ''U+02CB'') is also allowed[^On some devices, such as an iPhone / iPad, a grave accent character is harder to enter and is often confused with a modifier letter grave accent.]. +They begin with at least three grave accent characters (""''`''"", U+0060) at the first position of a line. +Alternatively, a modifier letter grave accent (""''ˋ''"", U+02CB) is also allowed[^On some devices, such as an iPhone / iPad, a grave accent character is harder to enter and is often confused with a modifier letter grave accent.]. You can add some [[attributes|00001007050000]] on the beginning line of a verbatim block, following the initiating characters. -The verbatim block supports the default attribute: when given, all spaces in the text are rendered in HTML as open box characters (""''␣''"", ''U+2423''). +The verbatim block supports the default attribute: when given, all spaces in the text are rendered in HTML as open box characters (U+2423). If you want to give only one attribute and this attribute is the generic attribute, you can omit the most of the attribute syntax and just specify the value. It will be interpreted as a ([[programming|00001007050200]]) language to support colorizing the text when rendered in HTML. Any other character in this line will be ignored Index: docs/manual/00001007030600.zettel ================================================================== --- docs/manual/00001007030600.zettel +++ docs/manual/00001007030600.zettel @@ -1,17 +1,17 @@ id: 00001007030600 title: Zettelmarkup: Quotation Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk -modified: 20211124174017 +modified: 20220218131806 A simple way to enter a quotation is to use the [[quotation list|00001007030200]]. A quotation list loosely follows the convention of quoting text within emails. However, if you want to attribute the quotation to seomeone, a quotation block is more appropriately. -This kind of line-range block begins with at least three less-than characters (""''<''"", ''U+003C'') at the first position of a line. +This kind of line-range block begins with at least three less-than characters (""''<''"", U+003C) at the first position of a line. You can add some [[attributes|00001007050000]] on the beginning line of a quotation block, following the initiating characters. The quotation does not support the default attribute, nor the generic attribute. Attributes are interpreted on HTML rendering. Any other character in this line will be ignored Index: docs/manual/00001007030700.zettel ================================================================== --- docs/manual/00001007030700.zettel +++ docs/manual/00001007030700.zettel @@ -1,17 +1,18 @@ id: 00001007030700 title: Zettelmarkup: Verse Blocks +role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk -role: manual +modified: 20220218132432 Sometimes, you want to enter text with significant space characters at the beginning of each line and with significant line endings. Poetry is one typical example. Of course, you could help yourself with hard space characters and hard line breaks, by entering a backslash character before a space character and at the end of each line. Using a verse block might be easier. -This kind of line-range block begins with at least three quotation mark characters (""''"''"", ''U+0022'') at the first position of a line. +This kind of line-range block begins with at least three quotation mark characters (""''"''"", U+0022) at the first position of a line. You can add some [[attributes|00001007050000]] on the beginning line of a verse block, following the initiating characters. The verse block does not support the default attribute, nor the generic attribute. Attributes are interpreted on HTML rendering. Any other character in this line will be ignored. @@ -26,11 +27,11 @@ """" A verse block with an embedded verse block -"""{style=color:green} +"""{.zs-deprecated} Embedded verse block """ Embedded Author """" Verse Author @@ -40,12 +41,12 @@ """" A verse block with an embedded verse block -"""{style=color:green} +"""{.zs-deprecated} Embedded verse block """ Embedded Author """" Verse Author ::: Index: docs/manual/00001007030800.zettel ================================================================== --- docs/manual/00001007030800.zettel +++ docs/manual/00001007030800.zettel @@ -1,18 +1,18 @@ id: 00001007030800 title: Zettelmarkup: Region Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk -modified: 20211124173940 +modified: 20220218131131 Region blocks does not directly have a visual representation. They just group a range of lines. You can use region blocks to enter [[attributes|00001007050000]] that apply only to this range of lines. One example is to enter a multi-line warning that should be visible. -This kind of line-range block begins with at least three colon characters (""'':''"", ''U+003A'') at the first position of a line[^Since a [[description text|00001007030100]] only use exactly one colon character at the first position of a line, there is no possible ambiguity between these elements.]. +This kind of line-range block begins with at least three colon characters (""'':''"", U+003A) at the first position of a line[^Since a [[description text|00001007030100]] only use exactly one colon character at the first position of a line, there is no possible ambiguity between these elements.]. You can add some [[attributes|00001007050000]] on the beginning line of a region block, following the initiating characters. The region block does not support the default attribute, but it supports the generic attribute. Some generic attributes, like ``=note``, ``=warning`` will be rendered special. Attributes are interpreted on HTML rendering. Any other character in this line will be ignored. Index: docs/manual/00001007030900.zettel ================================================================== --- docs/manual/00001007030900.zettel +++ docs/manual/00001007030900.zettel @@ -1,16 +1,17 @@ id: 00001007030900 title: Zettelmarkup: Comment Blocks +role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk -role: manual +modified: 20220218130330 Comment blocks are quite similar to [[verbatim blocks|00001007030500]]: both are used to enter text that should not be interpreted. While the text entered inside a verbatim block will be processed somehow, text inside a comment block will be ignored[^Well, not completely ignored: text is read, but it will typically not rendered visible.]. Comment blocks are typically used to give some internal comments, e.g. the license of a text or some internal remarks. -Comment blocks begin with at least three percent sign characters (""''%''"", ''U+0025'') at the first position of a line. +Comment blocks begin with at least three percent sign characters (""''%''"", U+0025) at the first position of a line. You can add some [[attributes|00001007050000]] on the beginning line of a comment block, following the initiating characters. The comment block supports the default attribute: when given, the text will be rendered, e.g. as an HTML comment. When rendered to JSON, the comment block will not be ignored but it will output some JSON text. Same for other renderers. Index: docs/manual/00001007031000.zettel ================================================================== --- docs/manual/00001007031000.zettel +++ docs/manual/00001007031000.zettel @@ -1,19 +1,19 @@ id: 00001007031000 title: Zettelmarkup: Tables role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk -modified: 20211124174218 +modified: 20220218131107 Tables are used to show some data in a two-dimenensional fashion. In zettelmarkup, table are not specified explicitly, but by entering __table rows__. Therefore, a table can be seen as a sequence of table rows. A table row is nothing as a sequence of __table cells__. The length of a table is the number of table rows, the width of a table is the maximum length of its rows. -The first cell of a row must begin with the vertical bar character (""''|''"", ''U+007C'') at the first position of a line. +The first cell of a row must begin with the vertical bar character (""''|''"", U+007C) at the first position of a line. The other cells of a row begin with the same vertical bar character at later positions in that line. A cell is delimited by the vertical bar character of the next cell or by the end of the current line. A vertical bar character as the last character of a line will not result in a table cell. It will be ignored. Inside a cell, you can specify any [[inline elements|00001007040000]]. @@ -30,11 +30,11 @@ | b1 | b2 | b3 | c1 | c2 ::: === Header row -If any cell in the first row of a table contains an equal sing character (""''=''"", ''U+003D'') as the very first character, then this first row will be interpreted as a __table header__ row. +If any cell in the first row of a table contains an equal sing character (""''=''"", U+003D) as the very first character, then this first row will be interpreted as a __table header__ row. For example: ```zmk | a1 | a2 |= a3| | b1 | b2 | b3 @@ -50,13 +50,13 @@ === Column alignment Inside a header row, you can specify the alignment of each header cell by a given character as the last character of a cell. The alignment of a header cell determines the alignment of every cell in the same column. The following characters specify the alignment: -* the colon character (""'':''"", ''U+003A'') forces a centered aligment, -* the less-than sign character (""''<''"", ''U+0060'') specifies an alignment to the left, -* the greater-than sign character (""''>''"", ''U+0062'') will produce right aligned cells. +* the colon character (""'':''"", U+003A) forces a centered alignment, +* the less-than sign character (""''<''"", U+0060) specifies an alignment to the left, +* the greater-than sign character (""''>''"", U+0062) will produce right aligned cells. If no alignment character is given, a default alignment is used. For example: ```zmk @@ -88,11 +88,11 @@ |123456|123456|123456|123456| |123|123|123|123 ::: === Rows to be ignored -A line that begins with the sequence ''|%'' (vertical bar character (""''|''"", ''U+007C''), followed by a percent sign character (“%”, U+0025)) will be ignored. +A line that begins with the sequence ''|%'' (vertical bar character (""''|''"", U+007C), followed by a percent sign character (“%”, U+0025)) will be ignored. For example, this allows to specify a horizontal rule that is not rendered. Such tables are emitted by some commands of the [[administrator console|00001004100000]]. For example, the command ``get-config box`` will emit: ``` |=Key | Value | Description Index: docs/manual/00001007031100.zettel ================================================================== --- docs/manual/00001007031100.zettel +++ docs/manual/00001007031100.zettel @@ -1,15 +1,15 @@ id: 00001007031100 title: Zettelmarkup: Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk -modified: 20220201133837 +modified: 20220218133058 A transclusion allows to include the content of another zettel into the current zettel just by referencing the other zettel. -The transclusion specification begins with three consecutive left curly bracket characters (""''{''"", ''U+007B'') at the first position of a line and ends with three consecutive right curly bracket characters (""''}''"", ''U+007D''). +The transclusion specification begins with three consecutive left curly bracket characters (""''{''"", U+007B) at the first position of a line and ends with three consecutive right curly bracket characters (""''}''"", U+007D). The curly brackets delimit the [[zettel identifier|00001006050000]] to be included. First, the referenced zettel is read. If it contains some transclusions itself, these will be expanded, recursively. When a recursion is detected, expansion does not take place. Index: docs/manual/00001007031200.zettel ================================================================== --- docs/manual/00001007031200.zettel +++ docs/manual/00001007031200.zettel @@ -1,19 +1,19 @@ id: 00001007031200 title: Zettelmarkup: Inline-Zettel Block role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk -modified: 20220201151458 +modified: 20220218172121 An inline-zettel block allows to specify some content with another syntax without creating a new zettel. This is useful, for example, if you want to specify a [[simple drawing|00001008050000]] within your zettel and you are sure that you do not need the drawing in another context. Another example is to embed some [[Markdown|00001008010500]] content, because you are too lazy to translate Markdown into Zettelmarkup.[^However, translating into Zettelmarkup is quite easy with the [[zmk encoder|00001012920522]].] A last example is to specify HTML code to use it for some kind of web frontend framework. As all other [[line-range blocks|00001007030000#line-range-blocks]], an inline-zettel block begins with at least three identical characters, starting at the first position of a line. -For inline-zettel blocks, the at-sign character (""''@''"", ''U+0040'') is used. +For inline-zettel blocks, the at-sign character (""''@''"", U+0040) is used. You can add some [[attributes|00001007050000]] on the beginning line of a verbatim block, following the initiating characters. The inline-zettel block uses the attribute key ""syntax"" to specify the [[syntax|00001008000000]] of the inline-zettel. Alternatively, you can use the generic attribute to specify the syntax value. If no value is provided, ""draw"" is assumed. @@ -55,16 +55,18 @@ Using HTML: ```zmk @@@html

H1 Heading

+Alea iacta est @@@ ``` will a section heading of level 1, which is not allowed within Zettelmarkup: :::example @@@html

H1 Heading

+Alea iacta est @@@ ::: :::note Please note: some HTML code will not be fully rendered because of possible security implications. This include HTML lines that contain a ''