Index: VERSION ================================================================== --- VERSION +++ VERSION @@ -1,1 +1,1 @@ -0.0.14 +0.0.15 Index: api/api.go ================================================================== --- api/api.go +++ api/api.go @@ -18,45 +18,41 @@ Expires int `json:"expires_in"` } // ZidJSON contains the identifier data of a zettel. type ZidJSON struct { - ID string `json:"id"` - URL string `json:"url"` + ID string `json:"id"` } // ZidMetaJSON contains the identifier and the metadata of a zettel. type ZidMetaJSON struct { ID string `json:"id"` - URL string `json:"url"` Meta map[string]string `json:"meta"` } // ZidMetaRelatedList contains identifier/metadata of a zettel and the same for related zettel type ZidMetaRelatedList struct { ID string `json:"id"` - URL string `json:"url"` Meta map[string]string `json:"meta"` List []ZidMetaJSON `json:"list"` } // ZettelLinksJSON store all links / connections from one zettel to other. type ZettelLinksJSON struct { - ID string `json:"id"` - URL string `json:"url"` - Links struct { - Incoming []ZidJSON `json:"incoming,omitempty"` - Outgoing []ZidJSON `json:"outgoing,omitempty"` - Local []string `json:"local,omitempty"` - External []string `json:"external,omitempty"` - Meta []string `json:"meta,omitempty"` - } `json:"links"` - Images struct { - Outgoing []ZidJSON `json:"outgoing,omitempty"` - Local []string `json:"local,omitempty"` - External []string `json:"external,omitempty"` - } `json:"images,omitempty"` + ID string `json:"id"` + Linked struct { + Incoming []string `json:"incoming,omitempty"` + Outgoing []string `json:"outgoing,omitempty"` + Local []string `json:"local,omitempty"` + External []string `json:"external,omitempty"` + Meta []string `json:"meta,omitempty"` + } `json:"linked"` + Embedded struct { + Outgoing []string `json:"outgoing,omitempty"` + Local []string `json:"local,omitempty"` + External []string `json:"external,omitempty"` + } `json:"embedded,omitempty"` Cites []string `json:"cites,omitempty"` } // ZettelDataJSON contains all data for a zettel. type ZettelDataJSON struct { @@ -66,19 +62,18 @@ } // ZettelJSON contains all data for a zettel, the identifier, the metadata, and the content. type ZettelJSON struct { ID string `json:"id"` - URL string `json:"url"` Meta map[string]string `json:"meta"` Encoding string `json:"encoding"` Content string `json:"content"` } // ZettelListJSON contains data for a zettel list. type ZettelListJSON struct { - List []ZettelJSON `json:"list"` + List []ZidMetaJSON `json:"list"` } // TagListJSON specifies the list/map of tags type TagListJSON struct { Tags map[string][]string `json:"tags"` Index: api/const.go ================================================================== --- api/const.go +++ api/const.go @@ -9,13 +9,11 @@ //----------------------------------------------------------------------------- // Package api contains common definition used for client and server. package api -import ( - "fmt" -) +import "fmt" // Additional HTTP constants used. const ( MethodMove = "MOVE" // HTTP method for renaming a zettel @@ -25,54 +23,55 @@ HeaderLocation = "Location" ) // Values for HTTP query parameter. const ( - QueryKeyDepth = "depth" - QueryKeyDir = "dir" - QueryKeyFormat = "_format" - QueryKeyLimit = "limit" - QueryKeyPart = "_part" + QueryKeyDepth = "depth" + QueryKeyDir = "dir" + QueryKeyEncoding = "_enc" + QueryKeyLimit = "_limit" + QueryKeyNegate = "_negate" + QueryKeyOffset = "_offset" + QueryKeyOrder = "_order" + QueryKeyPart = "_part" + QueryKeySearch = "_s" + QueryKeySort = "_sort" ) // Supported dir values. const ( DirBackward = "backward" DirForward = "forward" ) -// Supported format values. -const ( - FormatDJSON = "djson" - FormatHTML = "html" - FormatJSON = "json" - FormatNative = "native" - FormatRaw = "raw" - FormatText = "text" - FormatZMK = "zmk" -) - -var formatEncoder = map[string]EncodingEnum{ - FormatDJSON: EncoderDJSON, - FormatHTML: EncoderHTML, - FormatJSON: EncoderJSON, - FormatNative: EncoderNative, - FormatRaw: EncoderRaw, - FormatText: EncoderText, - FormatZMK: EncoderZmk, -} -var encoderFormat = map[EncodingEnum]string{} - -func init() { - for k, v := range formatEncoder { - encoderFormat[v] = k - } -} - -// Encoder returns the internal encoder code for the given format string. -func Encoder(format string) EncodingEnum { - if e, ok := formatEncoder[format]; ok { +// Supported encoding values. +const ( + EncodingDJSON = "djson" + EncodingHTML = "html" + EncodingNative = "native" + EncodingText = "text" + EncodingZMK = "zmk" +) + +var mapEncodingEnum = map[string]EncodingEnum{ + EncodingDJSON: EncoderDJSON, + EncodingHTML: EncoderHTML, + EncodingNative: EncoderNative, + EncodingText: EncoderText, + EncodingZMK: EncoderZmk, +} +var mapEnumEncoding = map[EncodingEnum]string{} + +func init() { + for k, v := range mapEncodingEnum { + mapEnumEncoding[v] = k + } +} + +// Encoder returns the internal encoder code for the given encoding string. +func Encoder(encoding string) EncodingEnum { + if e, ok := mapEncodingEnum[encoding]; ok { return e } return EncoderUnknown } @@ -82,27 +81,24 @@ // Values for EncoderEnum const ( EncoderUnknown EncodingEnum = iota EncoderDJSON EncoderHTML - EncoderJSON EncoderNative - EncoderRaw EncoderText EncoderZmk ) // String representation of an encoder key. func (e EncodingEnum) String() string { - if f, ok := encoderFormat[e]; ok { + if f, ok := mapEnumEncoding[e]; ok { return f } return fmt.Sprintf("*Unknown*(%d)", e) } // Supported part values. const ( - PartID = "id" PartMeta = "meta" PartContent = "content" PartZettel = "zettel" ) Index: ast/ast.go ================================================================== --- ast/ast.go +++ ast/ast.go @@ -20,16 +20,15 @@ ) // ZettelNode is the root node of the abstract syntax tree. // It is *not* part of the visitor pattern. type ZettelNode struct { - // Zettel domain.Zettel 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 BlockSlice // Zettel abstract syntax tree is a sequence of block nodes. + Ast *BlockListNode // Zettel abstract syntax tree is a sequence of block nodes. } // Node is the interface, all nodes must implement. type Node interface { WalkChildren(v Visitor) @@ -39,21 +38,30 @@ type BlockNode interface { Node blockNode() } -// BlockSlice is a slice of BlockNodes. -type BlockSlice []BlockNode - // 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() @@ -66,13 +74,10 @@ type InlineNode interface { Node inlineNode() } -// InlineSlice is a slice of InlineNodes. -type InlineSlice []InlineNode - // Reference is a reference to external or internal material. type Reference struct { URL *url.URL Value string State RefState Index: ast/attr.go ================================================================== --- ast/attr.go +++ ast/attr.go @@ -18,10 +18,13 @@ // 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 @@ -77,10 +80,13 @@ // 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 } Index: ast/block.go ================================================================== --- ast/block.go +++ ast/block.go @@ -11,23 +11,40 @@ // Package ast provides the abstract syntax tree. package ast // Definition of Block nodes. +// BlockListNode is a list of BlockNodes. +type BlockListNode struct { + List []BlockNode +} + +// WalkChildren walks down to the descriptions. +func (bln *BlockListNode) WalkChildren(v Visitor) { + for _, bn := range bln.List { + Walk(v, bn) + } +} + +//-------------------------------------------------------------------------- + // ParaNode contains just a sequence of inline elements. // Another name is "paragraph". type ParaNode struct { - Inlines InlineSlice + Inlines *InlineListNode } -func (pn *ParaNode) blockNode() { /* Just a marker */ } -func (pn *ParaNode) itemNode() { /* Just a marker */ } -func (pn *ParaNode) descriptionNode() { /* Just a marker */ } +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{}} } // WalkChildren walks down the inline elements. func (pn *ParaNode) WalkChildren(v Visitor) { - WalkInlineSlice(v, pn.Inlines) + Walk(v, pn.Inlines) } //-------------------------------------------------------------------------- // VerbatimNode contains lines of uninterpreted text @@ -46,24 +63,24 @@ VerbatimProg // Program code. VerbatimComment // Block comment VerbatimHTML // Block HTML, e.g. for Markdown ) -func (vn *VerbatimNode) blockNode() { /* Just a marker */ } -func (vn *VerbatimNode) itemNode() { /* Just a marker */ } +func (*VerbatimNode) blockNode() { /* Just a marker */ } +func (*VerbatimNode) itemNode() { /* Just a marker */ } // WalkChildren does nothing. -func (vn *VerbatimNode) WalkChildren(v Visitor) { /* No children*/ } +func (*VerbatimNode) WalkChildren(Visitor) { /* No children*/ } //-------------------------------------------------------------------------- // RegionNode encapsulates a region of block nodes. type RegionNode struct { Kind RegionKind Attrs *Attributes - Blocks BlockSlice - Inlines InlineSlice // Additional text at the end of the region + Blocks *BlockListNode + Inlines *InlineListNode // Optional text at the end of the region } // RegionKind specifies the actual region type. type RegionKind uint8 @@ -73,49 +90,52 @@ RegionSpan // Just a span of blocks RegionQuote // A longer quotation RegionVerse // Line breaks matter ) -func (rn *RegionNode) blockNode() { /* Just a marker */ } -func (rn *RegionNode) itemNode() { /* Just a marker */ } +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) { - WalkBlockSlice(v, rn.Blocks) - WalkInlineSlice(v, rn.Inlines) + Walk(v, rn.Blocks) + if iln := rn.Inlines; iln != nil { + Walk(v, iln) + } } //-------------------------------------------------------------------------- // HeadingNode stores the heading text and level. type HeadingNode struct { - Level int - Inlines InlineSlice // Heading text, possibly formatted - Slug string // Heading text, suitable to be used as an URL fragment - Attrs *Attributes + 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 } -func (hn *HeadingNode) blockNode() { /* Just a marker */ } -func (hn *HeadingNode) itemNode() { /* Just a marker */ } +func (*HeadingNode) blockNode() { /* Just a marker */ } +func (*HeadingNode) itemNode() { /* Just a marker */ } // WalkChildren walks the heading text. func (hn *HeadingNode) WalkChildren(v Visitor) { - WalkInlineSlice(v, hn.Inlines) + Walk(v, hn.Inlines) } //-------------------------------------------------------------------------- // HRuleNode specifies a horizontal rule. type HRuleNode struct { Attrs *Attributes } -func (hn *HRuleNode) blockNode() { /* Just a marker */ } -func (hn *HRuleNode) itemNode() { /* Just a marker */ } +func (*HRuleNode) blockNode() { /* Just a marker */ } +func (*HRuleNode) itemNode() { /* Just a marker */ } // WalkChildren does nothing. -func (hn *HRuleNode) WalkChildren(v Visitor) { /* No children*/ } +func (*HRuleNode) WalkChildren(Visitor) { /* No children*/ } //-------------------------------------------------------------------------- // NestedListNode specifies a nestable list, either ordered or unordered. type NestedListNode struct { @@ -133,12 +153,12 @@ NestedListOrdered // Ordered list. NestedListUnordered // Unordered list. NestedListQuote // Quote list. ) -func (ln *NestedListNode) blockNode() { /* Just a marker */ } -func (ln *NestedListNode) itemNode() { /* Just a marker */ } +func (*NestedListNode) blockNode() { /* Just a marker */ } +func (*NestedListNode) itemNode() { /* Just a marker */ } // WalkChildren walks down the items. func (ln *NestedListNode) WalkChildren(v Visitor) { for _, item := range ln.Items { WalkItemSlice(v, item) @@ -152,20 +172,20 @@ Descriptions []Description } // Description is one element of a description list. type Description struct { - Term InlineSlice + Term *InlineListNode Descriptions []DescriptionSlice } -func (dn *DescriptionListNode) blockNode() {} +func (*DescriptionListNode) blockNode() { /* Just a marker */ } // WalkChildren walks down to the descriptions. func (dn *DescriptionListNode) WalkChildren(v Visitor) { for _, desc := range dn.Descriptions { - WalkInlineSlice(v, desc.Term) + Walk(v, desc.Term) for _, dns := range desc.Descriptions { WalkDescriptionSlice(v, dns) } } } @@ -179,12 +199,12 @@ Rows []TableRow // The slice of cell rows } // TableCell contains the data for one table cell type TableCell struct { - Align Alignment // Cell alignment - Inlines InlineSlice // Cell content + Align Alignment // Cell alignment + Inlines *InlineListNode // Cell content } // TableRow is a slice of cells. type TableRow []*TableCell @@ -199,20 +219,20 @@ AlignLeft // Left alignment AlignCenter // Center the content AlignRight // Right alignment ) -func (tn *TableNode) blockNode() { /* Just a marker */ } +func (*TableNode) blockNode() { /* Just a marker */ } // WalkChildren walks down to the cells. func (tn *TableNode) WalkChildren(v Visitor) { for _, cell := range tn.Header { - WalkInlineSlice(v, cell.Inlines) + Walk(v, cell.Inlines) } for _, row := range tn.Rows { for _, cell := range row { - WalkInlineSlice(v, cell.Inlines) + Walk(v, cell.Inlines) } } } //-------------------------------------------------------------------------- @@ -223,9 +243,9 @@ Title string Syntax string Blob []byte } -func (bn *BLOBNode) blockNode() { /* Just a marker */ } +func (*BLOBNode) blockNode() { /* Just a marker */ } // WalkChildren does nothing. -func (bn *BLOBNode) WalkChildren(v Visitor) { /* No children*/ } +func (*BLOBNode) WalkChildren(Visitor) { /* No children*/ } Index: ast/inline.go ================================================================== --- ast/inline.go +++ ast/inline.go @@ -11,143 +11,191 @@ // Package ast provides the abstract syntax tree. package ast // 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, +// that will be space-separated. +func CreateInlineListNodeFromWords(words ...string) *InlineListNode { + inl := make([]InlineNode, 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} +} + +// WalkChildren walks down to the descriptions. +func (iln *InlineListNode) WalkChildren(v Visitor) { + for _, bn := range iln.List { + 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...) +} + +// -------------------------------------------------------------------------- + // TextNode just contains some text. type TextNode struct { Text string // The text itself. } -func (tn *TextNode) inlineNode() { /* Just a marker */ } +func (*TextNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. -func (tn *TextNode) WalkChildren(v Visitor) { /* No children*/ } +func (*TextNode) WalkChildren(Visitor) { /* No children*/ } // -------------------------------------------------------------------------- // TagNode contains a tag. type TagNode struct { Tag string // The text itself. } -func (tn *TagNode) inlineNode() { /* Just a marker */ } +func (*TagNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. -func (tn *TagNode) WalkChildren(v Visitor) { /* No children*/ } +func (*TagNode) WalkChildren(Visitor) { /* No children*/ } // -------------------------------------------------------------------------- // SpaceNode tracks inter-word space characters. type SpaceNode struct { Lexeme string } -func (sn *SpaceNode) inlineNode() { /* Just a marker */ } +func (*SpaceNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. -func (sn *SpaceNode) WalkChildren(v Visitor) { /* No children*/ } +func (*SpaceNode) WalkChildren(Visitor) { /* No children*/ } // -------------------------------------------------------------------------- // BreakNode signals a new line that must / should be interpreted as a new line break. type BreakNode struct { Hard bool // Hard line break? } -func (bn *BreakNode) inlineNode() { /* Just a marker */ } +func (*BreakNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. -func (bn *BreakNode) WalkChildren(v Visitor) { /* No children*/ } +func (*BreakNode) WalkChildren(Visitor) { /* No children*/ } // -------------------------------------------------------------------------- // LinkNode contains the specified link. type LinkNode struct { Ref *Reference - Inlines InlineSlice // The text associated with the link. - OnlyRef bool // True if no text was specified. - Attrs *Attributes // Optional attributes + Inlines *InlineListNode // The text associated with the link. + OnlyRef bool // True if no text was specified. + Attrs *Attributes // Optional attributes } -func (ln *LinkNode) inlineNode() { /* Just a marker */ } +func (*LinkNode) inlineNode() { /* Just a marker */ } // WalkChildren walks to the link text. func (ln *LinkNode) WalkChildren(v Visitor) { - WalkInlineSlice(v, ln.Inlines) + if iln := ln.Inlines; iln != nil { + Walk(v, iln) + } } // -------------------------------------------------------------------------- -// ImageNode contains the specified image reference. -type ImageNode struct { - Ref *Reference // Reference to image - Blob []byte // BLOB data of the image, as an alternative to Ref. - Syntax string // Syntax of Blob - Inlines InlineSlice // The text associated with the image. - Attrs *Attributes // Optional attributes +// EmbedNode contains the specified embedded material. +type EmbedNode struct { + Material MaterialNode // The material to be embedded + Inlines *InlineListNode // Optional text associated with the image. + Attrs *Attributes // Optional attributes } -func (in *ImageNode) inlineNode() { /* Just a marker */ } +func (*EmbedNode) inlineNode() { /* Just a marker */ } -// WalkChildren walks to the image text. -func (in *ImageNode) WalkChildren(v Visitor) { - WalkInlineSlice(v, in.Inlines) +// WalkChildren walks to the text that describes the embedded material. +func (en *EmbedNode) WalkChildren(v Visitor) { + if iln := en.Inlines; iln != nil { + Walk(v, iln) + } } // -------------------------------------------------------------------------- // CiteNode contains the specified citation. type CiteNode struct { - Key string // The citation key - Inlines InlineSlice // The text associated with the citation. - Attrs *Attributes // Optional attributes + Key string // The citation key + Inlines *InlineListNode // Optional text associated with the citation. + Attrs *Attributes // Optional attributes } -func (cn *CiteNode) inlineNode() { /* Just a marker */ } +func (*CiteNode) inlineNode() { /* Just a marker */ } // WalkChildren walks to the cite text. func (cn *CiteNode) WalkChildren(v Visitor) { - WalkInlineSlice(v, cn.Inlines) + if iln := cn.Inlines; iln != nil { + Walk(v, iln) + } } // -------------------------------------------------------------------------- // 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 + Text string + Slug string // Slugified form of Text + Fragment string // Unique form of Slug } -func (mn *MarkNode) inlineNode() { /* Just a marker */ } +func (*MarkNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. -func (mn *MarkNode) WalkChildren(v Visitor) { /* No children*/ } +func (*MarkNode) WalkChildren(Visitor) { /* No children*/ } // -------------------------------------------------------------------------- // FootnoteNode contains the specified footnote. type FootnoteNode struct { - Inlines InlineSlice // The footnote text. - Attrs *Attributes // Optional attributes + Inlines *InlineListNode // The footnote text. + Attrs *Attributes // Optional attributes } -func (fn *FootnoteNode) inlineNode() { /* Just a marker */ } +func (*FootnoteNode) inlineNode() { /* Just a marker */ } // WalkChildren walks to the footnote text. func (fn *FootnoteNode) WalkChildren(v Visitor) { - WalkInlineSlice(v, fn.Inlines) + Walk(v, fn.Inlines) } // -------------------------------------------------------------------------- // FormatNode specifies some inline formatting. type FormatNode struct { Kind FormatKind Attrs *Attributes // Optional attributes. - Inlines InlineSlice + Inlines *InlineListNode } // FormatKind specifies the format that is applied to the inline nodes. type FormatKind uint8 @@ -169,15 +217,15 @@ FormatSmall // Smaller text. FormatSpan // Generic inline container. FormatMonospace // Monospaced text. ) -func (fn *FormatNode) inlineNode() { /* Just a marker */ } +func (*FormatNode) inlineNode() { /* Just a marker */ } // WalkChildren walks to the formatted text. func (fn *FormatNode) WalkChildren(v Visitor) { - WalkInlineSlice(v, fn.Inlines) + Walk(v, fn.Inlines) } // -------------------------------------------------------------------------- // LiteralNode specifies some uninterpreted text. @@ -198,9 +246,9 @@ LiteralOutput // Sample output. LiteralComment // Inline comment LiteralHTML // Inline HTML, e.g. for Markdown ) -func (ln *LiteralNode) inlineNode() { /* Just a marker */ } +func (*LiteralNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. -func (ln *LiteralNode) WalkChildren(v Visitor) { /* No children*/ } +func (*LiteralNode) WalkChildren(Visitor) { /* No children*/ } ADDED ast/material.go Index: ast/material.go ================================================================== --- ast/material.go +++ ast/material.go @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2021 Detlef Stern +// +// This file is part of zettelstore. +// +// Zettelstore is licensed under the latest version of the EUPL (European Union +// Public License). Please see file LICENSE.txt for your rights and obligations +// under this license. +//----------------------------------------------------------------------------- + +// Package ast provides the abstract syntax tree. +package ast + +// MaterialNode references the various types of zettel material. +type MaterialNode interface { + Node + materialNode() +} + +// -------------------------------------------------------------------------- + +// ReferenceMaterialNode is material that can be retrieved by using a reference. +type ReferenceMaterialNode struct { + Ref *Reference +} + +func (*ReferenceMaterialNode) materialNode() { /* Just a marker */ } + +// WalkChildren does nothing. +func (*ReferenceMaterialNode) WalkChildren(Visitor) { /* No children*/ } + +// -------------------------------------------------------------------------- + +// BLOBMaterialNode represents itself. +type BLOBMaterialNode struct { + Blob []byte // BLOB data itself. + Syntax string // Syntax of Blob +} + +func (*BLOBMaterialNode) materialNode() { /* Just a marker */ } + +// WalkChildren does nothing. +func (*BLOBMaterialNode) WalkChildren(Visitor) { /* No children*/ } Index: ast/walk.go ================================================================== --- ast/walk.go +++ ast/walk.go @@ -23,24 +23,10 @@ } node.WalkChildren(v) v.Visit(nil) } -// WalkBlockSlice traverse a block slice. -func WalkBlockSlice(v Visitor, bns BlockSlice) { - for _, bn := range bns { - Walk(v, bn) - } -} - -// WalkInlineSlice traverses an inline slice. -func WalkInlineSlice(v Visitor, ins InlineSlice) { - for _, in := range ins { - Walk(v, in) - } -} - // WalkItemSlice traverses an item slice. func WalkItemSlice(v Visitor, ins ItemSlice) { for _, in := range ins { Walk(v, in) } Index: auth/policy/policy_test.go ================================================================== --- auth/policy/policy_test.go +++ auth/policy/policy_test.go @@ -43,12 +43,12 @@ } pol := newPolicy(authzManager, &authConfig{ts.expert}) name := fmt.Sprintf("readonly=%v/withauth=%v/expert=%v", ts.readonly, ts.withAuth, ts.expert) t.Run(name, func(tt *testing.T) { - testCreate(tt, pol, ts.withAuth, ts.readonly, ts.expert) - testRead(tt, pol, ts.withAuth, ts.readonly, ts.expert) + testCreate(tt, pol, ts.withAuth, ts.readonly) + testRead(tt, pol, ts.withAuth, ts.expert) testWrite(tt, pol, ts.withAuth, ts.readonly, ts.expert) testRename(tt, pol, ts.withAuth, ts.readonly, ts.expert) testDelete(tt, pol, ts.withAuth, ts.readonly, ts.expert) }) } @@ -92,11 +92,11 @@ return meta.GetVisibility(vis) } return meta.VisibilityLogin } -func testCreate(t *testing.T, pol auth.Policy, withAuth, readonly, isExpert bool) { +func testCreate(t *testing.T, pol auth.Policy, withAuth, readonly bool) { t.Helper() anonUser := newAnon() creator := newCreator() reader := newReader() writer := newWriter() @@ -139,11 +139,11 @@ } }) } } -func testRead(t *testing.T, pol auth.Policy, withAuth, readonly, expert bool) { +func testRead(t *testing.T, pol auth.Policy, withAuth, expert bool) { t.Helper() anonUser := newAnon() creator := newCreator() reader := newReader() writer := newWriter() Index: box/box.go ================================================================== --- box/box.go +++ box/box.go @@ -41,13 +41,10 @@ GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) // GetMeta retrieves just the meta data of a specific zettel. GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) - // FetchZids returns the set of all zettel identifer managed by the box. - FetchZids(ctx context.Context) (id.Set, error) - // CanUpdateZettel returns true, if box could possibly update the given zettel. CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool // UpdateZettel updates an existing zettel. UpdateZettel(ctx context.Context, zettel domain.Zettel) error @@ -62,17 +59,26 @@ CanDeleteZettel(ctx context.Context, zid id.Zid) bool // DeleteZettel removes the zettel from the box. DeleteZettel(ctx context.Context, zid id.Zid) error } + +// ZidFunc is a function that processes identifier of a zettel. +type ZidFunc func(id.Zid) + +// MetaFunc is a function that processes metadata of a zettel. +type MetaFunc func(*meta.Meta) // ManagedBox is the interface of managed boxes. type ManagedBox interface { BaseBox - // SelectMeta returns all zettel meta data that match the selection criteria. - SelectMeta(ctx context.Context, match search.MetaMatchFunc) ([]*meta.Meta, error) + // Apply identifier of every zettel to the given function. + ApplyZid(context.Context, ZidFunc) error + + // Apply metadata of every zettel to the given function. + ApplyMeta(context.Context, MetaFunc) error // ReadStats populates st with box statistics ReadStats(st *ManagedBoxStats) } @@ -96,10 +102,13 @@ } // Box is to be used outside the box package and its descendants. type Box interface { BaseBox + + // FetchZids returns the set of all zettel identifer managed by the box. + FetchZids(ctx context.Context) (id.Set, error) // SelectMeta returns a list of metadata that comply to the given selection criteria. SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) // GetAllZettel retrieves a specific zettel from all managed boxes. Index: box/compbox/compbox.go ================================================================== --- box/compbox/compbox.go +++ box/compbox/compbox.go @@ -18,11 +18,10 @@ "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" - "zettelstore.de/z/search" ) func init() { manager.Register( " comp", @@ -55,20 +54,19 @@ } // Setup remembers important values. func Setup(cfg *meta.Meta) { myConfig = cfg.Clone() } -func (pp *compBox) Location() string { return "" } +func (*compBox) Location() string { return "" } -func (pp *compBox) CanCreateZettel(ctx context.Context) bool { return false } +func (*compBox) CanCreateZettel(context.Context) bool { return false } -func (pp *compBox) CreateZettel( - ctx context.Context, zettel domain.Zettel) (id.Zid, error) { +func (*compBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) { return id.Invalid, box.ErrReadOnly } -func (pp *compBox) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) { +func (*compBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) { if gen, ok := myZettel[zid]; ok && gen.meta != nil { if m := gen.meta(zid); m != nil { updateMeta(m) if genContent := gen.content; genContent != nil { return domain.Zettel{ @@ -80,11 +78,11 @@ } } return domain.Zettel{}, box.ErrNotFound } -func (pp *compBox) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { +func (*compBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) { if gen, ok := myZettel[zid]; ok { if genMeta := gen.meta; genMeta != nil { if m := genMeta(zid); m != nil { updateMeta(m) return m, nil @@ -92,67 +90,60 @@ } } return nil, box.ErrNotFound } -func (pp *compBox) FetchZids(ctx context.Context) (id.Set, error) { - result := id.NewSetCap(len(myZettel)) +func (*compBox) ApplyZid(_ context.Context, handle box.ZidFunc) error { for zid, gen := range myZettel { if genMeta := gen.meta; genMeta != nil { if genMeta(zid) != nil { - result[zid] = true + handle(zid) } } } - return result, nil + return nil } -func (pp *compBox) SelectMeta(ctx context.Context, match search.MetaMatchFunc) (res []*meta.Meta, err error) { +func (pp *compBox) ApplyMeta(ctx context.Context, handle box.MetaFunc) error { for zid, gen := range myZettel { if genMeta := gen.meta; genMeta != nil { if m := genMeta(zid); m != nil { updateMeta(m) pp.enricher.Enrich(ctx, m, pp.number) - if match(m) { - res = append(res, m) - } + handle(m) } } } - return res, nil + return nil } -func (pp *compBox) CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool { - return false -} - -func (pp *compBox) UpdateZettel(ctx context.Context, zettel domain.Zettel) error { - return box.ErrReadOnly -} - -func (pp *compBox) AllowRenameZettel(ctx context.Context, zid id.Zid) bool { +func (*compBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return false } + +func (*compBox) UpdateZettel(context.Context, domain.Zettel) error { return box.ErrReadOnly } + +func (*compBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool { _, ok := myZettel[zid] return !ok } -func (pp *compBox) RenameZettel(ctx context.Context, curZid, newZid id.Zid) error { +func (*compBox) RenameZettel(_ context.Context, curZid, _ id.Zid) error { if _, ok := myZettel[curZid]; ok { return box.ErrReadOnly } return box.ErrNotFound } -func (pp *compBox) CanDeleteZettel(ctx context.Context, zid id.Zid) bool { return false } +func (*compBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } -func (pp *compBox) DeleteZettel(ctx context.Context, zid id.Zid) error { +func (*compBox) DeleteZettel(_ context.Context, zid id.Zid) error { if _, ok := myZettel[zid]; ok { return box.ErrReadOnly } return box.ErrNotFound } -func (pp *compBox) ReadStats(st *box.ManagedBoxStats) { +func (*compBox) ReadStats(st *box.ManagedBoxStats) { st.ReadOnly = true st.Zettel = len(myZettel) } func updateMeta(m *meta.Meta) { Index: box/constbox/base.css ================================================================== --- box/constbox/base.css +++ box/constbox/base.css @@ -254,10 +254,13 @@ color:#444; } h1+.zs-meta { margin-top:-1rem; } + nav > details { + margin-top:1rem; + } details > summary { width: 100%; background-color: #eee; font-family:sans-serif; } Index: box/constbox/base.mustache ================================================================== --- box/constbox/base.mustache +++ box/constbox/base.mustache @@ -24,11 +24,11 @@ {{/UserIsValid}} {{^UserIsValid}} Login {{/UserIsValid}} {{#UserIsValid}} -Logout +Logout {{/UserIsValid}} {{/WithAuth}} {{/WithUser}} @@ -49,11 +49,11 @@ {{/NewZettelLinks}} {{/HasNewZettelLinks}}
- +
{{{Content}}}
Index: box/constbox/constbox.go ================================================================== --- box/constbox/constbox.go +++ box/constbox/constbox.go @@ -19,11 +19,10 @@ "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" - "zettelstore.de/z/search" ) func init() { manager.Register( " const", @@ -36,18 +35,10 @@ }) } type constHeader map[string]string -func makeMeta(zid id.Zid, h constHeader) *meta.Meta { - m := meta.New(zid) - for k, v := range h { - m.Set(k, v) - } - return m -} - type constZettel struct { header constHeader content domain.Content } @@ -55,75 +46,66 @@ number int zettel map[id.Zid]constZettel enricher box.Enricher } -func (cp *constBox) Location() string { - return "const:" -} +func (*constBox) Location() string { return "const:" } + +func (*constBox) CanCreateZettel(context.Context) bool { return false } -func (cp *constBox) CanCreateZettel(ctx context.Context) bool { return false } - -func (cp *constBox) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) { +func (*constBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) { return id.Invalid, box.ErrReadOnly } -func (cp *constBox) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) { +func (cp *constBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) { if z, ok := cp.zettel[zid]; ok { - return domain.Zettel{Meta: makeMeta(zid, z.header), Content: z.content}, nil + return domain.Zettel{Meta: meta.NewWithData(zid, z.header), Content: z.content}, nil } return domain.Zettel{}, box.ErrNotFound } -func (cp *constBox) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { - if z, ok := cp.zettel[zid]; ok { - return makeMeta(zid, z.header), nil - } - return nil, box.ErrNotFound -} - -func (cp *constBox) FetchZids(ctx context.Context) (id.Set, error) { - result := id.NewSetCap(len(cp.zettel)) - for zid := range cp.zettel { - result[zid] = true - } - return result, nil -} - -func (cp *constBox) SelectMeta(ctx context.Context, match search.MetaMatchFunc) (res []*meta.Meta, err error) { - for zid, zettel := range cp.zettel { - m := makeMeta(zid, zettel.header) - cp.enricher.Enrich(ctx, m, cp.number) - if match(m) { - res = append(res, m) - } - } - return res, nil -} - -func (cp *constBox) CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool { - return false -} - -func (cp *constBox) UpdateZettel(ctx context.Context, zettel domain.Zettel) error { - return box.ErrReadOnly -} - -func (cp *constBox) AllowRenameZettel(ctx context.Context, zid id.Zid) bool { +func (cp *constBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) { + if z, ok := cp.zettel[zid]; ok { + return meta.NewWithData(zid, z.header), nil + } + return nil, box.ErrNotFound +} + +func (cp *constBox) ApplyZid(_ context.Context, handle box.ZidFunc) error { + for zid := range cp.zettel { + handle(zid) + } + return nil +} + +func (cp *constBox) ApplyMeta(ctx context.Context, handle box.MetaFunc) error { + for zid, zettel := range cp.zettel { + m := meta.NewWithData(zid, zettel.header) + cp.enricher.Enrich(ctx, m, cp.number) + handle(m) + } + return nil +} + +func (*constBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return false } + +func (*constBox) UpdateZettel(context.Context, domain.Zettel) error { return box.ErrReadOnly } + +func (cp *constBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool { _, ok := cp.zettel[zid] return !ok } -func (cp *constBox) RenameZettel(ctx context.Context, curZid, newZid id.Zid) error { +func (cp *constBox) RenameZettel(_ context.Context, curZid, _ id.Zid) error { if _, ok := cp.zettel[curZid]; ok { return box.ErrReadOnly } return box.ErrNotFound } -func (cp *constBox) CanDeleteZettel(ctx context.Context, zid id.Zid) bool { return false } +func (*constBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } -func (cp *constBox) DeleteZettel(ctx context.Context, zid id.Zid) error { +func (cp *constBox) DeleteZettel(_ context.Context, zid id.Zid) error { if _, ok := cp.zettel[zid]; ok { return box.ErrReadOnly } return box.ErrNotFound } Index: box/constbox/home.zettel ================================================================== --- box/constbox/home.zettel +++ box/constbox/home.zettel @@ -3,18 +3,17 @@ You will find the lastest information about Zettelstore at [[https://zettelstore.de]]. Check that website regulary for [[upgrades|https://zettelstore.de/home/doc/trunk/www/download.wiki]] to the latest version. You should consult the [[change log|https://zettelstore.de/home/doc/trunk/www/changes.wiki]] before upgrading. Sometimes, you have to edit some of your Zettelstore-related zettel before upgrading. Since Zettelstore is currently in a development state, every upgrade might fix some of your problems. -To check for versions, there is a zettel with the [[current version|00000000000001]] of your Zettelstore. If you have problems concerning Zettelstore, do not hesitate to get in [[contact with the main developer|mailto:ds@zettelstore.de]]. === Reporting errors If you have encountered an error, please include the content of the following zettel in your mail (if possible): -* [[Zettelstore Version|00000000000001]] +* [[Zettelstore Version|00000000000001]]: {{00000000000001}} * [[Zettelstore Operating System|00000000000003]] * [[Zettelstore Startup Configuration|00000000000096]] * [[Zettelstore Runtime Configuration|00000000000100]] Additionally, you have to describe, what you have done before that error occurs Index: box/constbox/info.mustache ================================================================== --- box/constbox/info.mustache +++ box/constbox/info.mustache @@ -29,20 +29,31 @@
  • {{.}}
  • {{/ExtLinks}} {{/HasExtLinks}} {{/HasLinks}} -

    Parts and format

    +

    Parts and encodings

    + +{{#EvalMatrix}} + + +{{#Elements}} +{{/Elements}} + +{{/EvalMatrix}} +
    {{Header}}{{Text}}
    +

    Parsed (not evaluated)

    -{{#Matrix}} +{{#ParseMatrix}} -{{#Elements}}{{#HasURL}}{{/HasURL}}{{^HasURL}}{{/HasURL}} + +{{#Elements}} {{/Elements}} -{{/Matrix}} +{{/ParseMatrix}}
    {{Text}}{{Text}}{{Header}}{{Text}}
    {{#HasShadowLinks}}

    Shadowed Boxes

    {{/HasShadowLinks}} {{#Endnotes}}{{{Endnotes}}}{{/Endnotes}} Index: box/constbox/login.mustache ================================================================== --- box/constbox/login.mustache +++ box/constbox/login.mustache @@ -3,11 +3,11 @@

    {{Title}}

    {{#Retry}}
    Wrong user name / password. Try again.
    {{/Retry}} -
    +
    Index: box/constbox/zettel.mustache ================================================================== --- box/constbox/zettel.mustache +++ box/constbox/zettel.mustache @@ -7,22 +7,35 @@ Info · ({{RoleText}}) {{#HasTags}}· {{#Tags}} {{Text}}{{/Tags}}{{/HasTags}} {{#CanCopy}}· Copy{{/CanCopy}} {{#CanFolge}}· Folge{{/CanFolge}} -{{#FolgeRefs}}
    Folge: {{{FolgeRefs}}}{{/FolgeRefs}} {{#PrecursorRefs}}
    Precursor: {{{PrecursorRefs}}}{{/PrecursorRefs}} {{#HasExtURL}}
    URL: {{ExtURL}}{{/HasExtURL}}
    {{{Content}}} + +{{#HasFolgeLinks}} + +{{/HasFolgeLinks}} {{#HasBackLinks}} + {{/HasBackLinks}} - Index: box/dirbox/dirbox.go ================================================================== --- box/dirbox/dirbox.go +++ box/dirbox/dirbox.go @@ -27,11 +27,10 @@ "zettelstore.de/z/box/filebox" "zettelstore.de/z/box/manager" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" - "zettelstore.de/z/search" ) func init() { manager.Register("dir", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { path := getDirPath(u) @@ -110,16 +109,16 @@ func (dp *dirBox) Location() string { return dp.location } -func (dp *dirBox) Start(ctx context.Context) error { +func (dp *dirBox) Start(context.Context) error { dp.mxCmds.Lock() dp.fCmds = make([]chan fileCmd, 0, dp.fSrvs) for i := uint32(0); i < dp.fSrvs; i++ { cc := make(chan fileCmd) - go fileService(i, cc) + go fileService(cc) dp.fCmds = append(dp.fCmds, cc) } dp.setupDirService() dp.mxCmds.Unlock() if dp.dirSrv == nil { @@ -126,11 +125,11 @@ panic("No directory service") } return dp.dirSrv.Start() } -func (dp *dirBox) Stop(ctx context.Context) error { +func (dp *dirBox) Stop(_ context.Context) error { dirSrv := dp.dirSrv dp.dirSrv = nil err := dirSrv.Stop() for _, c := range dp.fCmds { close(c) @@ -156,15 +155,15 @@ dp.mxCmds.RLock() defer dp.mxCmds.RUnlock() return dp.fCmds[sum%dp.fSrvs] } -func (dp *dirBox) CanCreateZettel(ctx context.Context) bool { +func (dp *dirBox) CanCreateZettel(_ context.Context) bool { return !dp.readonly } -func (dp *dirBox) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) { +func (dp *dirBox) CreateZettel(_ context.Context, zettel domain.Zettel) (id.Zid, error) { if dp.readonly { return id.Invalid, box.ErrReadOnly } entry, err := dp.dirSrv.GetNew() @@ -181,80 +180,71 @@ } dp.notifyChanged(box.OnUpdate, meta.Zid) return meta.Zid, err } -func (dp *dirBox) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) { +func (dp *dirBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) { entry, err := dp.dirSrv.GetEntry(zid) if err != nil || !entry.IsValid() { return domain.Zettel{}, box.ErrNotFound } m, c, err := getMetaContent(dp, entry, zid) if err != nil { return domain.Zettel{}, err } - dp.cleanupMeta(ctx, m) + dp.cleanupMeta(m) zettel := domain.Zettel{Meta: m, Content: domain.NewContent(c)} return zettel, nil } -func (dp *dirBox) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { +func (dp *dirBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) { entry, err := dp.dirSrv.GetEntry(zid) if err != nil || !entry.IsValid() { return nil, box.ErrNotFound } m, err := getMeta(dp, entry, zid) if err != nil { return nil, err } - dp.cleanupMeta(ctx, m) + dp.cleanupMeta(m) return m, nil } -func (dp *dirBox) FetchZids(ctx context.Context) (id.Set, error) { - entries, err := dp.dirSrv.GetEntries() - if err != nil { - return nil, err - } - result := id.NewSetCap(len(entries)) - for _, entry := range entries { - result[entry.Zid] = true - } - return result, nil -} - -func (dp *dirBox) SelectMeta(ctx context.Context, match search.MetaMatchFunc) (res []*meta.Meta, err error) { - entries, err := dp.dirSrv.GetEntries() - if err != nil { - return nil, err - } - res = make([]*meta.Meta, 0, len(entries)) +func (dp *dirBox) ApplyZid(_ context.Context, handle box.ZidFunc) error { + entries, err := dp.dirSrv.GetEntries() + if err != nil { + return err + } + for _, entry := range entries { + handle(entry.Zid) + } + return nil +} + +func (dp *dirBox) ApplyMeta(ctx context.Context, handle box.MetaFunc) error { + entries, err := dp.dirSrv.GetEntries() + if err != nil { + return err + } // The following loop could be parallelized if needed for performance. for _, entry := range entries { m, err1 := getMeta(dp, entry, entry.Zid) - err = err1 - if err != nil { - continue + if err1 != nil { + return err1 } - dp.cleanupMeta(ctx, m) + dp.cleanupMeta(m) dp.cdata.Enricher.Enrich(ctx, m, dp.number) - - if match(m) { - res = append(res, m) - } - } - if err != nil { - return nil, err - } - return res, nil + handle(m) + } + return nil } -func (dp *dirBox) CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool { +func (dp *dirBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return !dp.readonly } -func (dp *dirBox) UpdateZettel(ctx context.Context, zettel domain.Zettel) error { +func (dp *dirBox) UpdateZettel(_ context.Context, zettel domain.Zettel) error { if dp.readonly { return box.ErrReadOnly } meta := zettel.Meta @@ -317,11 +307,11 @@ } } return directory.MetaSpecFile, syntax } -func (dp *dirBox) AllowRenameZettel(ctx context.Context, zid id.Zid) bool { +func (dp *dirBox) AllowRenameZettel(context.Context, id.Zid) bool { return !dp.readonly } func (dp *dirBox) RenameZettel(ctx context.Context, curZid, newZid id.Zid) error { if curZid == newZid { @@ -369,19 +359,19 @@ dp.notifyChanged(box.OnUpdate, newZid) } return err } -func (dp *dirBox) CanDeleteZettel(ctx context.Context, zid id.Zid) bool { +func (dp *dirBox) CanDeleteZettel(_ context.Context, zid id.Zid) bool { if dp.readonly { return false } entry, err := dp.dirSrv.GetEntry(zid) return err == nil && entry.IsValid() } -func (dp *dirBox) DeleteZettel(ctx context.Context, zid id.Zid) error { +func (dp *dirBox) DeleteZettel(_ context.Context, zid id.Zid) error { if dp.readonly { return box.ErrReadOnly } entry, err := dp.dirSrv.GetEntry(zid) @@ -399,11 +389,11 @@ func (dp *dirBox) ReadStats(st *box.ManagedBoxStats) { st.ReadOnly = dp.readonly st.Zettel, _ = dp.dirSrv.NumEntries() } -func (dp *dirBox) cleanupMeta(ctx context.Context, m *meta.Meta) { +func (dp *dirBox) cleanupMeta(m *meta.Meta) { if role, ok := m.Get(meta.KeyRole); !ok || role == "" { m.Set(meta.KeyRole, dp.cdata.Config.GetDefaultRole()) } if syntax, ok := m.Get(meta.KeySyntax); !ok || syntax == "" { m.Set(meta.KeySyntax, dp.cdata.Config.GetDefaultSyntax()) Index: box/dirbox/service.go ================================================================== --- box/dirbox/service.go +++ box/dirbox/service.go @@ -20,11 +20,11 @@ "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" ) -func fileService(num uint32, cmds <-chan fileCmd) { +func fileService(cmds <-chan fileCmd) { for cmd := range cmds { cmd.run() } } Index: box/filebox/zipbox.go ================================================================== --- box/filebox/zipbox.go +++ box/filebox/zipbox.go @@ -21,11 +21,10 @@ "zettelstore.de/z/box" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" - "zettelstore.de/z/search" ) var validFileName = regexp.MustCompile(`^(\d{14}).*(\.(.+))$`) func matchValidFileName(name string) []string { @@ -51,11 +50,11 @@ return "file://" + zp.name } return "file:" + zp.name } -func (zp *zipBox) Start(ctx context.Context) error { +func (zp *zipBox) Start(context.Context) error { reader, err := zip.OpenReader(zp.name) if err != nil { return err } defer reader.Close() @@ -96,22 +95,22 @@ entry.contentName = name } } } -func (zp *zipBox) Stop(ctx context.Context) error { +func (zp *zipBox) Stop(context.Context) error { zp.zettel = nil return nil } -func (zp *zipBox) CanCreateZettel(ctx context.Context) bool { return false } +func (*zipBox) CanCreateZettel(context.Context) bool { return false } -func (zp *zipBox) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) { +func (*zipBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) { return id.Invalid, box.ErrReadOnly } -func (zp *zipBox) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) { +func (zp *zipBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) { entry, ok := zp.zettel[zid] if !ok { return domain.Zettel{}, box.ErrNotFound } reader, err := zip.OpenReader(zp.name) @@ -146,11 +145,11 @@ } CleanupMeta(m, zid, entry.contentExt, inMeta, false) return domain.Zettel{Meta: m, Content: domain.NewContent(src)}, nil } -func (zp *zipBox) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { +func (zp *zipBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) { entry, ok := zp.zettel[zid] if !ok { return nil, box.ErrNotFound } reader, err := zip.OpenReader(zp.name) @@ -159,60 +158,53 @@ } defer reader.Close() return readZipMeta(reader, zid, entry) } -func (zp *zipBox) FetchZids(ctx context.Context) (id.Set, error) { - result := id.NewSetCap(len(zp.zettel)) +func (zp *zipBox) ApplyZid(_ context.Context, handle box.ZidFunc) error { for zid := range zp.zettel { - result[zid] = true + handle(zid) } - return result, nil + return nil } -func (zp *zipBox) SelectMeta(ctx context.Context, match search.MetaMatchFunc) (res []*meta.Meta, err error) { +func (zp *zipBox) ApplyMeta(ctx context.Context, handle box.MetaFunc) error { reader, err := zip.OpenReader(zp.name) if err != nil { - return nil, err + return err } defer reader.Close() for zid, entry := range zp.zettel { m, err := readZipMeta(reader, zid, entry) if err != nil { continue } zp.enricher.Enrich(ctx, m, zp.number) - if match(m) { - res = append(res, m) - } - } - return res, nil -} - -func (zp *zipBox) CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool { - return false -} - -func (zp *zipBox) UpdateZettel(ctx context.Context, zettel domain.Zettel) error { - return box.ErrReadOnly -} - -func (zp *zipBox) AllowRenameZettel(ctx context.Context, zid id.Zid) bool { + handle(m) + } + return nil +} + +func (*zipBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return false } + +func (*zipBox) UpdateZettel(context.Context, domain.Zettel) error { return box.ErrReadOnly } + +func (zp *zipBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool { _, ok := zp.zettel[zid] return !ok } -func (zp *zipBox) RenameZettel(ctx context.Context, curZid, newZid id.Zid) error { +func (zp *zipBox) RenameZettel(_ context.Context, curZid, _ id.Zid) error { if _, ok := zp.zettel[curZid]; ok { return box.ErrReadOnly } return box.ErrNotFound } -func (zp *zipBox) CanDeleteZettel(ctx context.Context, zid id.Zid) bool { return false } +func (*zipBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } -func (zp *zipBox) DeleteZettel(ctx context.Context, zid id.Zid) error { +func (zp *zipBox) DeleteZettel(_ context.Context, zid id.Zid) error { if _, ok := zp.zettel[zid]; ok { return box.ErrReadOnly } return box.ErrNotFound } Index: box/manager/box.go ================================================================== --- box/manager/box.go +++ box/manager/box.go @@ -12,11 +12,10 @@ package manager import ( "context" "errors" - "sort" "strings" "zettelstore.de/z/box" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" @@ -127,33 +126,22 @@ } return result, nil } // FetchZids returns the set of all zettel identifer managed by the box. -func (mgr *Manager) FetchZids(ctx context.Context) (result id.Set, err error) { +func (mgr *Manager) FetchZids(ctx context.Context) (id.Set, error) { mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if !mgr.started { return nil, box.ErrStopped } + result := id.Set{} for _, p := range mgr.boxes { - zids, err := p.FetchZids(ctx) + err := p.ApplyZid(ctx, func(zid id.Zid) { result[zid] = true }) if err != nil { return nil, err } - if result == nil { - result = zids - } else if len(result) <= len(zids) { - for zid := range result { - zids[zid] = true - } - result = zids - } else { - for zid := range zids { - result[zid] = true - } - } } return result, nil } // SelectMeta returns all zettel meta data that match the selection @@ -162,26 +150,34 @@ mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if !mgr.started { return nil, box.ErrStopped } - var result []*meta.Meta + selected, rejected := map[id.Zid]*meta.Meta{}, id.Set{} match := s.CompileMatch(mgr) + handleMeta := func(m *meta.Meta) { + zid := m.Zid + if rejected[zid] { + return + } + if _, ok := selected[zid]; ok { + return + } + if match(m) { + selected[zid] = m + } else { + rejected[zid] = true + } + } for _, p := range mgr.boxes { - selected, err := p.SelectMeta(ctx, match) - if err != nil { + if err := p.ApplyMeta(ctx, handleMeta); err != nil { return nil, err } - sort.Slice(selected, func(i, j int) bool { return selected[i].Zid > selected[j].Zid }) - if len(result) == 0 { - result = selected - } else { - result = box.MergeSorted(result, selected) - } - } - if s == nil { - return result, nil + } + result := make([]*meta.Meta, 0, len(selected)) + for _, m := range selected { + result = append(result, m) } return s.Sort(result), nil } // CanUpdateZettel returns true, if box could possibly update the given zettel. Index: box/manager/collect.go ================================================================== --- box/manager/collect.go +++ box/manager/collect.go @@ -22,24 +22,26 @@ type collectData struct { refs id.Set words store.WordSet urls store.WordSet + itags store.WordSet } func (data *collectData) initialize() { data.refs = id.NewSet() data.words = store.NewWordSet() data.urls = store.NewWordSet() + data.itags = store.NewWordSet() } func collectZettelIndexData(zn *ast.ZettelNode, data *collectData) { - ast.WalkBlockSlice(data, zn.Ast) + ast.Walk(data, zn.Ast) } -func collectInlineIndexData(ins ast.InlineSlice, data *collectData) { - ast.WalkInlineSlice(data, ins) +func collectInlineIndexData(iln *ast.InlineListNode, data *collectData) { + ast.Walk(data, iln) } func (data *collectData) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.VerbatimNode: @@ -48,14 +50,17 @@ } case *ast.TextNode: data.addText(n.Text) case *ast.TagNode: data.addText(n.Tag) + data.itags.Add("#" + strings.ToLower(n.Tag)) case *ast.LinkNode: data.addRef(n.Ref) - case *ast.ImageNode: - data.addRef(n.Ref) + case *ast.EmbedNode: + if m, ok := n.Material.(*ast.ReferenceMaterialNode); ok { + data.addRef(m.Ref) + } case *ast.LiteralNode: data.addText(n.Text) } return data } Index: box/manager/indexer.go ================================================================== --- box/manager/indexer.go +++ box/manager/indexer.go @@ -195,10 +195,11 @@ zi.AddDeadRef(ref) } } zi.SetWords(cData.words) zi.SetUrls(cData.urls) + zi.SetITags(cData.itags) } func (mgr *Manager) idxUpdateValue(ctx context.Context, inverseKey, value string, zi *store.ZettelIndex) { zid, err := id.Parse(value) if err != nil { Index: box/manager/memstore/memstore.go ================================================================== --- box/manager/memstore/memstore.go +++ box/manager/memstore/memstore.go @@ -34,17 +34,18 @@ forward id.Slice backward id.Slice meta map[string]metaRefs words []string urls []string + itags string // Inline tags } func (zi *zettelIndex) isEmpty() bool { if len(zi.forward) > 0 || len(zi.backward) > 0 || len(zi.dead) > 0 || len(zi.words) > 0 { return false } - return zi.meta == nil || len(zi.meta) == 0 + return len(zi.meta) == 0 } type stringRefs map[string]id.Slice type memStore struct { @@ -66,19 +67,19 @@ words: make(stringRefs), urls: make(stringRefs), } } -func (ms *memStore) Enrich(ctx context.Context, m *meta.Meta) { - if ms.doEnrich(ctx, m) { +func (ms *memStore) Enrich(_ context.Context, m *meta.Meta) { + if ms.doEnrich(m) { ms.mx.Lock() ms.updates++ ms.mx.Unlock() } } -func (ms *memStore) doEnrich(ctx context.Context, m *meta.Meta) bool { +func (ms *memStore) doEnrich(m *meta.Meta) bool { ms.mx.RLock() defer ms.mx.RUnlock() zi, ok := ms.idx[m.Zid] if !ok { return false @@ -108,10 +109,21 @@ } } if len(back) > 0 { m.Set(meta.KeyBack, back.String()) updated = true + } + if zi.itags != "" { + if tags, ok := m.Get(meta.KeyTags); ok { + m.Set(meta.KeyAllTags, tags+" "+zi.itags) + } else { + m.Set(meta.KeyAllTags, zi.itags) + } + updated = true + } else if tags, ok := m.Get(meta.KeyTags); ok { + m.Set(meta.KeyAllTags, tags) + updated = true } return updated } // SearchEqual returns all zettel that contains the given exact word. @@ -254,11 +266,11 @@ } } return back } -func (ms *memStore) UpdateReferences(ctx context.Context, zidx *store.ZettelIndex) id.Set { +func (ms *memStore) UpdateReferences(_ context.Context, zidx *store.ZettelIndex) id.Set { ms.mx.Lock() defer ms.mx.Unlock() zi, ziExist := ms.idx[zidx.Zid] if !ziExist || zi == nil { zi = &zettelIndex{} @@ -276,10 +288,11 @@ ms.updateDeadReferences(zidx, zi) ms.updateForwardBackwardReferences(zidx, zi) ms.updateMetadataReferences(zidx, zi) zi.words = updateWordSet(zidx.Zid, ms.words, zi.words, zidx.GetWords()) zi.urls = updateWordSet(zidx.Zid, ms.urls, zi.urls, zidx.GetUrls()) + zi.itags = setITags(zidx.GetITags()) // Check if zi must be inserted into ms.idx if !ziExist && !zi.isEmpty() { ms.idx[zidx.Zid] = zi } @@ -368,10 +381,19 @@ } srefs[word] = refs2 } return next.Words() } + +func setITags(next store.WordSet) string { + itags := next.Words() + if len(itags) == 0 { + return "" + } + sort.Strings(itags) + return strings.Join(itags, " ") +} func (ms *memStore) getEntry(zid id.Zid) *zettelIndex { // Must only be called if ms.mx is write-locked! if zi, ok := ms.idx[zid]; ok { return zi @@ -379,11 +401,11 @@ zi := &zettelIndex{} ms.idx[zid] = zi return zi } -func (ms *memStore) DeleteZettel(ctx context.Context, zid id.Zid) id.Set { +func (ms *memStore) DeleteZettel(_ context.Context, zid id.Zid) id.Set { ms.mx.Lock() defer ms.mx.Unlock() zi, ok := ms.idx[zid] if !ok { Index: box/manager/store/zettel.go ================================================================== --- box/manager/store/zettel.go +++ box/manager/store/zettel.go @@ -19,10 +19,11 @@ backrefs id.Set // set of back references metarefs map[string]id.Set // references to inverse keys deadrefs id.Set // set of dead references words WordSet urls WordSet + itags WordSet } // NewZettelIndex creates a new zettel index. func NewZettelIndex(zid id.Zid) *ZettelIndex { return &ZettelIndex{ @@ -58,10 +59,13 @@ func (zi *ZettelIndex) SetWords(words WordSet) { zi.words = words } // SetUrls sets the words to the given value. func (zi *ZettelIndex) SetUrls(urls WordSet) { zi.urls = urls } +// SetITags sets the words to the given value. +func (zi *ZettelIndex) SetITags(itags WordSet) { zi.itags = itags } + // GetDeadRefs returns all dead references as a sorted list. func (zi *ZettelIndex) GetDeadRefs() id.Slice { return zi.deadrefs.Sorted() } @@ -85,5 +89,8 @@ // GetWords returns a reference to the set of words. It must not be modified. func (zi *ZettelIndex) GetWords() WordSet { return zi.words } // GetUrls returns a reference to the set of URLs. It must not be modified. func (zi *ZettelIndex) GetUrls() WordSet { return zi.urls } + +// GetITags returns a reference to the set of internal tags. It must not be modified. +func (zi *ZettelIndex) GetITags() WordSet { return zi.itags } Index: box/membox/membox.go ================================================================== --- box/membox/membox.go +++ box/membox/membox.go @@ -19,11 +19,10 @@ "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" - "zettelstore.de/z/search" ) func init() { manager.Register( "mem", @@ -47,27 +46,27 @@ func (mp *memBox) Location() string { return mp.u.String() } -func (mp *memBox) Start(ctx context.Context) error { +func (mp *memBox) Start(context.Context) error { mp.mx.Lock() mp.zettel = make(map[id.Zid]domain.Zettel) mp.mx.Unlock() return nil } -func (mp *memBox) Stop(ctx context.Context) error { +func (mp *memBox) Stop(context.Context) error { mp.mx.Lock() mp.zettel = nil mp.mx.Unlock() return nil } -func (mp *memBox) CanCreateZettel(ctx context.Context) bool { return true } +func (*memBox) CanCreateZettel(context.Context) bool { return true } -func (mp *memBox) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) { +func (mp *memBox) CreateZettel(_ context.Context, zettel domain.Zettel) (id.Zid, error) { mp.mx.Lock() zid, err := box.GetNewZid(func(zid id.Zid) (bool, error) { _, ok := mp.zettel[zid] return !ok, nil }) @@ -82,11 +81,11 @@ mp.mx.Unlock() mp.notifyChanged(box.OnUpdate, zid) return zid, nil } -func (mp *memBox) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) { +func (mp *memBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) { mp.mx.RLock() zettel, ok := mp.zettel[zid] mp.mx.RUnlock() if !ok { return domain.Zettel{}, box.ErrNotFound @@ -93,49 +92,43 @@ } zettel.Meta = zettel.Meta.Clone() return zettel, nil } -func (mp *memBox) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { +func (mp *memBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) { mp.mx.RLock() zettel, ok := mp.zettel[zid] mp.mx.RUnlock() if !ok { return nil, box.ErrNotFound } return zettel.Meta.Clone(), nil } -func (mp *memBox) FetchZids(ctx context.Context) (id.Set, error) { - mp.mx.RLock() - result := id.NewSetCap(len(mp.zettel)) - for zid := range mp.zettel { - result[zid] = true - } - mp.mx.RUnlock() - return result, nil -} - -func (mp *memBox) SelectMeta(ctx context.Context, match search.MetaMatchFunc) ([]*meta.Meta, error) { - result := make([]*meta.Meta, 0, len(mp.zettel)) - mp.mx.RLock() +func (mp *memBox) ApplyZid(_ context.Context, handle box.ZidFunc) error { + mp.mx.RLock() + defer mp.mx.RUnlock() + for zid := range mp.zettel { + handle(zid) + } + return nil +} + +func (mp *memBox) ApplyMeta(ctx context.Context, handle box.MetaFunc) error { + mp.mx.RLock() + defer mp.mx.RUnlock() for _, zettel := range mp.zettel { m := zettel.Meta.Clone() mp.cdata.Enricher.Enrich(ctx, m, mp.cdata.Number) - if match(m) { - result = append(result, m) - } - } - mp.mx.RUnlock() - return result, nil -} - -func (mp *memBox) CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool { - return true -} - -func (mp *memBox) UpdateZettel(ctx context.Context, zettel domain.Zettel) error { + handle(m) + } + return nil +} + +func (*memBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return true } + +func (mp *memBox) UpdateZettel(_ context.Context, zettel domain.Zettel) error { mp.mx.Lock() meta := zettel.Meta.Clone() if !meta.Zid.IsValid() { return &box.ErrInvalidID{Zid: meta.Zid} } @@ -144,13 +137,13 @@ mp.mx.Unlock() mp.notifyChanged(box.OnUpdate, meta.Zid) return nil } -func (mp *memBox) AllowRenameZettel(ctx context.Context, zid id.Zid) bool { return true } +func (*memBox) AllowRenameZettel(context.Context, id.Zid) bool { return true } -func (mp *memBox) RenameZettel(ctx context.Context, curZid, newZid id.Zid) error { +func (mp *memBox) RenameZettel(_ context.Context, curZid, newZid id.Zid) error { mp.mx.Lock() zettel, ok := mp.zettel[curZid] if !ok { mp.mx.Unlock() return box.ErrNotFound @@ -171,18 +164,18 @@ mp.notifyChanged(box.OnDelete, curZid) mp.notifyChanged(box.OnUpdate, newZid) return nil } -func (mp *memBox) CanDeleteZettel(ctx context.Context, zid id.Zid) bool { +func (mp *memBox) CanDeleteZettel(_ context.Context, zid id.Zid) bool { mp.mx.RLock() _, ok := mp.zettel[zid] mp.mx.RUnlock() return ok } -func (mp *memBox) DeleteZettel(ctx context.Context, zid id.Zid) error { +func (mp *memBox) DeleteZettel(_ context.Context, zid id.Zid) error { mp.mx.Lock() if _, ok := mp.zettel[zid]; !ok { mp.mx.Unlock() return box.ErrNotFound } DELETED box/merge.go Index: box/merge.go ================================================================== --- box/merge.go +++ box/merge.go @@ -1,46 +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 box provides a generic interface to zettel boxes. -package box - -import "zettelstore.de/z/domain/meta" - -// MergeSorted returns a merged sequence of metadata, sorted by Zid. -// The lists first and second must be sorted descending by Zid. -func MergeSorted(first, second []*meta.Meta) []*meta.Meta { - lenFirst := len(first) - lenSecond := len(second) - result := make([]*meta.Meta, 0, lenFirst+lenSecond) - iFirst := 0 - iSecond := 0 - for iFirst < lenFirst && iSecond < lenSecond { - zidFirst := first[iFirst].Zid - zidSecond := second[iSecond].Zid - if zidFirst > zidSecond { - result = append(result, first[iFirst]) - iFirst++ - } else if zidFirst < zidSecond { - result = append(result, second[iSecond]) - iSecond++ - } else { // zidFirst == zidSecond - result = append(result, first[iFirst]) - iFirst++ - iSecond++ - } - } - if iFirst < lenFirst { - result = append(result, first[iFirst:]...) - } else { - result = append(result, second[iSecond:]...) - } - - return result -} Index: client/client.go ================================================================== --- client/client.go +++ client/client.go @@ -15,10 +15,11 @@ "bytes" "context" "encoding/json" "errors" "io" + "net" "net/http" "net/url" "strconv" "strings" "time" @@ -33,34 +34,45 @@ username string password string token string tokenType string expires time.Time + client http.Client } // NewClient create a new client. func NewClient(baseURL string) *Client { if !strings.HasSuffix(baseURL, "/") { baseURL += "/" } - c := Client{baseURL: baseURL} + c := Client{ + baseURL: baseURL, + client: http.Client{ + Timeout: 10 * time.Second, + Transport: &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: 5 * time.Second, // TCP connect timeout + }).DialContext, + TLSHandshakeTimeout: 5 * time.Second, + }, + }, + } return &c } func (c *Client) newURLBuilder(key byte) *api.URLBuilder { return api.NewURLBuilder(c.baseURL, key) } -func (c *Client) newRequest(ctx context.Context, method string, ub *api.URLBuilder, body io.Reader) (*http.Request, error) { +func (*Client) newRequest(ctx context.Context, method string, ub *api.URLBuilder, body io.Reader) (*http.Request, error) { return http.NewRequestWithContext(ctx, method, ub.String(), body) } func (c *Client) executeRequest(req *http.Request) (*http.Response, error) { if c.token != "" { req.Header.Add("Authorization", c.tokenType+" "+c.token) } - client := http.Client{} - resp, err := client.Do(req) + resp, err := c.client.Do(req) if err != nil { if resp != nil && resp.Body != nil { resp.Body.Close() } return nil, err @@ -125,34 +137,56 @@ } // Authenticate sets a new token by sending user name and password. func (c *Client) Authenticate(ctx context.Context) error { authData := url.Values{"username": {c.username}, "password": {c.password}} - req, err := c.newRequest(ctx, http.MethodPost, c.newURLBuilder('v'), strings.NewReader(authData.Encode())) + req, err := c.newRequest(ctx, http.MethodPost, c.newURLBuilder('a'), strings.NewReader(authData.Encode())) if err != nil { return err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") return c.executeAuthRequest(req) } // RefreshToken updates the access token func (c *Client) RefreshToken(ctx context.Context) error { - req, err := c.newRequest(ctx, http.MethodPut, c.newURLBuilder('v'), nil) + req, err := c.newRequest(ctx, http.MethodPut, c.newURLBuilder('a'), nil) if err != nil { return err } return c.executeAuthRequest(req) } // CreateZettel creates a new zettel and returns its URL. -func (c *Client) CreateZettel(ctx context.Context, data *api.ZettelDataJSON) (id.Zid, error) { +func (c *Client) CreateZettel(ctx context.Context, data string) (id.Zid, error) { + ub := c.jsonZettelURLBuilder('z', nil) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodPost, ub, strings.NewReader(data), nil) + if err != nil { + return id.Invalid, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusCreated { + return id.Invalid, errors.New(resp.Status) + } + b, err := io.ReadAll(resp.Body) + if err != nil { + return id.Invalid, err + } + zid, err := id.Parse(string(b)) + if err != nil { + return id.Invalid, err + } + return zid, nil +} + +// CreateZettelJSON creates a new zettel and returns its URL. +func (c *Client) CreateZettelJSON(ctx context.Context, data *api.ZettelDataJSON) (id.Zid, error) { var buf bytes.Buffer if err := encodeZettelData(&buf, data); err != nil { return id.Invalid, err } - ub := c.jsonZettelURLBuilder(nil) + ub := c.jsonZettelURLBuilder('j', nil) resp, err := c.buildAndExecuteRequest(ctx, http.MethodPost, ub, &buf, nil) if err != nil { return id.Invalid, err } defer resp.Body.Close() @@ -177,12 +211,30 @@ enc.SetEscapeHTML(false) return enc.Encode(&data) } // ListZettel returns a list of all Zettel. -func (c *Client) ListZettel(ctx context.Context, query url.Values) ([]api.ZettelJSON, error) { - ub := c.jsonZettelURLBuilder(query) +func (c *Client) ListZettel(ctx context.Context, query url.Values) (string, error) { + ub := c.jsonZettelURLBuilder('z', query) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", errors.New(resp.Status) + } + data, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + return string(data), nil +} + +// ListZettelJSON returns a list of all Zettel. +func (c *Client) ListZettelJSON(ctx context.Context, query url.Values) ([]api.ZidMetaJSON, error) { + ub := c.jsonZettelURLBuilder('j', query) resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) if err != nil { return nil, err } defer resp.Body.Close() @@ -195,14 +247,35 @@ if err != nil { return nil, err } return zl.List, nil } + +// GetZettel returns a zettel as a string. +func (c *Client) GetZettel(ctx context.Context, zid id.Zid, part string) (string, error) { + ub := c.jsonZettelURLBuilder('z', nil).SetZid(zid) + if part != "" && part != api.PartContent { + ub.AppendQuery(api.QueryKeyPart, part) + } + resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", errors.New(resp.Status) + } + data, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + return string(data), nil +} // GetZettelJSON returns a zettel as a JSON struct. -func (c *Client) GetZettelJSON(ctx context.Context, zid id.Zid, query url.Values) (*api.ZettelDataJSON, error) { - ub := c.jsonZettelURLBuilder(query).SetZid(zid) +func (c *Client) GetZettelJSON(ctx context.Context, zid id.Zid) (*api.ZettelDataJSON, error) { + ub := c.jsonZettelURLBuilder('j', nil).SetZid(zid) resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) if err != nil { return nil, err } defer resp.Body.Close() @@ -216,14 +289,23 @@ return nil, err } return &out, nil } -// GetEvaluatedZettel return a zettel in a defined encoding. +// GetParsedZettel return a parsed zettel in a defined encoding. +func (c *Client) GetParsedZettel(ctx context.Context, zid id.Zid, enc api.EncodingEnum) (string, error) { + return c.getZettelString(ctx, 'p', zid, enc) +} + +// GetEvaluatedZettel return an evaluated zettel in a defined encoding. func (c *Client) GetEvaluatedZettel(ctx context.Context, zid id.Zid, enc api.EncodingEnum) (string, error) { - ub := c.jsonZettelURLBuilder(nil).SetZid(zid) - ub.AppendQuery(api.QueryKeyFormat, enc.String()) + return c.getZettelString(ctx, 'v', zid, enc) +} + +func (c *Client) getZettelString(ctx context.Context, key byte, zid id.Zid, enc api.EncodingEnum) (string, error) { + ub := c.jsonZettelURLBuilder(key, nil).SetZid(zid) + ub.AppendQuery(api.QueryKeyEncoding, enc.String()) ub.AppendQuery(api.QueryKeyPart, api.PartContent) resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) if err != nil { return "", err } @@ -304,11 +386,11 @@ return nil, err } return &out, nil } -// GetZettelLinks returns connections to ohter zettel, images, externals URLs. +// GetZettelLinks returns connections to other zettel, embedded material, externals URLs. func (c *Client) GetZettelLinks(ctx context.Context, zid id.Zid) (*api.ZettelLinksJSON, error) { ub := c.newURLBuilder('l').SetZid(zid) resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil, nil) if err != nil { return nil, err @@ -325,16 +407,30 @@ } return &out, nil } // UpdateZettel updates an existing zettel. -func (c *Client) UpdateZettel(ctx context.Context, zid id.Zid, data *api.ZettelDataJSON) error { +func (c *Client) UpdateZettel(ctx context.Context, zid id.Zid, data string) error { + ub := c.jsonZettelURLBuilder('z', nil).SetZid(zid) + resp, err := c.buildAndExecuteRequest(ctx, http.MethodPut, ub, strings.NewReader(data), nil) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusNoContent { + return errors.New(resp.Status) + } + return nil +} + +// UpdateZettelJSON updates an existing zettel. +func (c *Client) UpdateZettelJSON(ctx context.Context, zid id.Zid, data *api.ZettelDataJSON) error { var buf bytes.Buffer if err := encodeZettelData(&buf, data); err != nil { return err } - ub := c.jsonZettelURLBuilder(nil).SetZid(zid) + ub := c.jsonZettelURLBuilder('j', nil).SetZid(zid) resp, err := c.buildAndExecuteRequest(ctx, http.MethodPut, ub, &buf, nil) if err != nil { return err } defer resp.Body.Close() @@ -344,13 +440,13 @@ return nil } // RenameZettel renames a zettel. func (c *Client) RenameZettel(ctx context.Context, oldZid, newZid id.Zid) error { - ub := c.jsonZettelURLBuilder(nil).SetZid(oldZid) + ub := c.jsonZettelURLBuilder('z', nil).SetZid(oldZid) h := http.Header{ - api.HeaderDestination: {c.jsonZettelURLBuilder(nil).SetZid(newZid).String()}, + api.HeaderDestination: {c.jsonZettelURLBuilder('z', nil).SetZid(newZid).String()}, } resp, err := c.buildAndExecuteRequest(ctx, api.MethodMove, ub, nil, h) if err != nil { return err } @@ -361,11 +457,11 @@ return nil } // DeleteZettel deletes a zettel with the given identifier. func (c *Client) DeleteZettel(ctx context.Context, zid id.Zid) error { - ub := c.jsonZettelURLBuilder(nil).SetZid(zid) + ub := c.jsonZettelURLBuilder('z', nil).SetZid(zid) resp, err := c.buildAndExecuteRequest(ctx, http.MethodDelete, ub, nil, nil) if err != nil { return err } defer resp.Body.Close() @@ -373,14 +469,14 @@ return errors.New(resp.Status) } return nil } -func (c *Client) jsonZettelURLBuilder(query url.Values) *api.URLBuilder { - ub := c.newURLBuilder('z') +func (c *Client) jsonZettelURLBuilder(key byte, query url.Values) *api.URLBuilder { + ub := c.newURLBuilder(key) for key, values := range query { - if key == api.QueryKeyFormat { + if key == api.QueryKeyEncoding { continue } for _, val := range values { ub.AppendQuery(key, val) } Index: client/client_test.go ================================================================== --- client/client_test.go +++ client/client_test.go @@ -14,23 +14,66 @@ import ( "context" "flag" "fmt" "net/url" + "strings" "testing" "zettelstore.de/z/api" "zettelstore.de/z/client" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) -func TestCreateRenameDeleteZettel(t *testing.T) { +func TestCreateGetRenameDeleteZettel(t *testing.T) { + // Is not to be allowed to run in parallel with other tests. + zettel := `title: A Test + +Example content.` + c := getClient() + c.SetAuth("owner", "owner") + zid, err := c.CreateZettel(context.Background(), zettel) + if err != nil { + t.Error("Cannot create zettel:", err) + return + } + if !zid.IsValid() { + t.Error("Invalid zettel ID", zid) + return + } + data, err := c.GetZettel(context.Background(), zid, api.PartZettel) + if err != nil { + t.Error("Cannot read zettel", zid, err) + return + } + exp := `title: A Test +role: zettel +syntax: zmk + +Example content.` + if data != exp { + t.Errorf("Expected zettel data: %q, but got %q", exp, data) + } + newZid := zid + 1 + err = c.RenameZettel(context.Background(), zid, newZid) + if err != nil { + t.Error("Cannot rename", zid, ":", err) + newZid = zid + } + err = c.DeleteZettel(context.Background(), newZid) + if err != nil { + t.Error("Cannot delete", zid, ":", err) + return + } +} + +func TestCreateRenameDeleteZettelJSON(t *testing.T) { // Is not to be allowed to run in parallel with other tests. c := getClient() c.SetAuth("creator", "creator") - zid, err := c.CreateZettel(context.Background(), &api.ZettelDataJSON{ + zid, err := c.CreateZettelJSON(context.Background(), &api.ZettelDataJSON{ Meta: nil, Encoding: "", Content: "Example", }) if err != nil { @@ -55,13 +98,52 @@ } } func TestUpdateZettel(t *testing.T) { t.Parallel() + c := getClient() + c.SetAuth("owner", "owner") + z, err := c.GetZettel(context.Background(), id.DefaultHomeZid, api.PartZettel) + if err != nil { + t.Error(err) + return + } + if !strings.HasPrefix(z, "title: Home\n") { + t.Error("Got unexpected zettel", z) + return + } + newZettel := `title: New Home +role: zettel +syntax: zmk + +Empty` + err = c.UpdateZettel(context.Background(), id.DefaultHomeZid, newZettel) + if err != nil { + t.Error(err) + return + } + zt, err := c.GetZettel(context.Background(), id.DefaultHomeZid, api.PartZettel) + if err != nil { + t.Error(err) + return + } + if zt != newZettel { + t.Errorf("Expected zettel %q, got %q", newZettel, zt) + } + // Must delete to clean up for next tests + err = c.DeleteZettel(context.Background(), id.DefaultHomeZid) + if err != nil { + t.Error("Cannot delete", id.DefaultHomeZid, ":", err) + return + } +} + +func TestUpdateZettelJSON(t *testing.T) { + t.Parallel() c := getClient() c.SetAuth("writer", "writer") - z, err := c.GetZettelJSON(context.Background(), id.DefaultHomeZid, nil) + z, err := c.GetZettelJSON(context.Background(), id.DefaultHomeZid) if err != nil { t.Error(err) return } if got := z.Meta[meta.KeyTitle]; got != "Home" { @@ -68,26 +150,27 @@ t.Errorf("Title of zettel is not \"Home\", but %q", got) return } newTitle := "New Home" z.Meta[meta.KeyTitle] = newTitle - err = c.UpdateZettel(context.Background(), id.DefaultHomeZid, z) + err = c.UpdateZettelJSON(context.Background(), id.DefaultHomeZid, z) if err != nil { t.Error(err) return } - zt, err := c.GetZettelJSON(context.Background(), id.DefaultHomeZid, nil) + zt, err := c.GetZettelJSON(context.Background(), id.DefaultHomeZid) if err != nil { t.Error(err) return } if got := zt.Meta[meta.KeyTitle]; got != newTitle { t.Errorf("Title of zettel is not %q, but %q", newTitle, got) } + // No need to clean up, because we just changed the title. } -func TestList(t *testing.T) { +func TestListZettel(t *testing.T) { testdata := []struct { user string exp int }{ {"", 7}, @@ -97,15 +180,15 @@ {"owner", 34}, } t.Parallel() c := getClient() - query := url.Values{api.QueryKeyFormat: {"html"}} // Client must remove "html" + query := url.Values{api.QueryKeyEncoding: {api.EncodingHTML}} // Client must remove "html" for i, tc := range testdata { t.Run(fmt.Sprintf("User %d/%q", i, tc.user), func(tt *testing.T) { c.SetAuth(tc.user, tc.user) - l, err := c.ListZettel(context.Background(), query) + l, err := c.ListZettelJSON(context.Background(), query) if err != nil { tt.Error(err) return } got := len(l) @@ -112,38 +195,58 @@ if got != tc.exp { tt.Errorf("List of length %d expected, but got %d\n%v", tc.exp, got, l) } }) } - l, err := c.ListZettel(context.Background(), url.Values{meta.KeyRole: {meta.ValueRoleConfiguration}}) + l, err := c.ListZettelJSON(context.Background(), url.Values{meta.KeyRole: {meta.ValueRoleConfiguration}}) if err != nil { t.Error(err) return } got := len(l) if got != 27 { t.Errorf("List of length %d expected, but got %d\n%v", 27, got, l) } + + pl, err := c.ListZettel(context.Background(), url.Values{meta.KeyRole: {meta.ValueRoleConfiguration}}) + if err != nil { + t.Error(err) + return + } + lines := strings.Split(pl, "\n") + if lines[len(lines)-1] == "" { + lines = lines[:len(lines)-1] + } + if len(lines) != len(l) { + t.Errorf("Different list lenght: Plain=%d, JSON=%d", len(lines), len(l)) + } else { + for i, line := range lines { + if got := line[:14]; got != l[i].ID { + t.Errorf("%d: JSON=%q, got=%q", i, l[i].ID, got) + } + } + } } -func TestGetZettel(t *testing.T) { + +func TestGetZettelJSON(t *testing.T) { t.Parallel() c := getClient() c.SetAuth("owner", "owner") - z, err := c.GetZettelJSON(context.Background(), id.DefaultHomeZid, url.Values{api.QueryKeyPart: {api.PartContent}}) + z, err := c.GetZettelJSON(context.Background(), id.DefaultHomeZid) if err != nil { t.Error(err) return } - if m := z.Meta; len(m) > 0 { - t.Errorf("Exptected empty meta, but got %v", z.Meta) + if m := z.Meta; len(m) == 0 { + t.Errorf("Exptected non-empty meta, but got %v", z.Meta) } if z.Content == "" || z.Encoding != "" { t.Errorf("Expect non-empty content, but empty encoding (got %q)", z.Encoding) } } -func TestGetEvaluatedZettel(t *testing.T) { +func TestGetParsedEvaluatedZettel(t *testing.T) { t.Parallel() c := getClient() c.SetAuth("owner", "owner") encodings := []api.EncodingEnum{ api.EncoderDJSON, @@ -150,20 +253,45 @@ api.EncoderHTML, api.EncoderNative, api.EncoderText, } for _, enc := range encodings { - content, err := c.GetEvaluatedZettel(context.Background(), id.DefaultHomeZid, enc) + content, err := c.GetParsedZettel(context.Background(), id.DefaultHomeZid, enc) + if err != nil { + t.Error(err) + continue + } + if len(content) == 0 { + t.Errorf("Empty content for parsed encoding %v", enc) + } + content, err = c.GetEvaluatedZettel(context.Background(), id.DefaultHomeZid, enc) if err != nil { t.Error(err) continue } if len(content) == 0 { - t.Errorf("Empty content for encoding %v", enc) + t.Errorf("Empty content for evaluated encoding %v", enc) } } } + +func checkZid(t *testing.T, expected id.Zid, got string) bool { + t.Helper() + if exp := expected.String(); exp != got { + t.Errorf("Expected a Zid %q, but got %q", exp, got) + return false + } + return true +} + +func checkListZid(t *testing.T, l []api.ZidMetaJSON, pos int, expected id.Zid) { + t.Helper() + exp := expected.String() + if got := l[pos].ID; got != exp { + t.Errorf("Expected result[%d]=%v, but got %v", pos, exp, got) + } +} func TestGetZettelOrder(t *testing.T) { t.Parallel() c := getClient() c.SetAuth("owner", "owner") @@ -170,25 +298,20 @@ rl, err := c.GetZettelOrder(context.Background(), id.TOCNewTemplateZid) if err != nil { t.Error(err) return } - if rl.ID != id.TOCNewTemplateZid.String() { - t.Errorf("Expected an Zid %v, but got %v", id.TOCNewTemplateZid, rl.ID) + if !checkZid(t, id.TOCNewTemplateZid, rl.ID) { return } l := rl.List if got := len(l); got != 2 { t.Errorf("Expected list fo length 2, got %d", got) return } - if got := l[0].ID; got != id.TemplateNewZettelZid.String() { - t.Errorf("Expected result[0]=%v, but got %v", id.TemplateNewZettelZid, got) - } - if got := l[1].ID; got != id.TemplateNewUserZid.String() { - t.Errorf("Expected result[1]=%v, but got %v", id.TemplateNewUserZid, got) - } + checkListZid(t, l, 0, id.TemplateNewZettelZid) + checkListZid(t, l, 1, id.TemplateNewUserZid) } func TestGetZettelContext(t *testing.T) { t.Parallel() c := getClient() @@ -196,46 +319,36 @@ rl, err := c.GetZettelContext(context.Background(), id.VersionZid, client.DirBoth, 0, 3) if err != nil { t.Error(err) return } - if rl.ID != id.VersionZid.String() { - t.Errorf("Expected an Zid %v, but got %v", id.VersionZid, rl.ID) + if !checkZid(t, id.VersionZid, rl.ID) { return } l := rl.List if got := len(l); got != 3 { t.Errorf("Expected list fo length 3, got %d", got) return } - if got := l[0].ID; got != id.DefaultHomeZid.String() { - t.Errorf("Expected result[0]=%v, but got %v", id.DefaultHomeZid, got) - } - if got := l[1].ID; got != id.OperatingSystemZid.String() { - t.Errorf("Expected result[1]=%v, but got %v", id.OperatingSystemZid, got) - } - if got := l[2].ID; got != id.StartupConfigurationZid.String() { - t.Errorf("Expected result[2]=%v, but got %v", id.StartupConfigurationZid, got) - } + checkListZid(t, l, 0, id.DefaultHomeZid) + checkListZid(t, l, 1, id.OperatingSystemZid) + checkListZid(t, l, 2, id.StartupConfigurationZid) rl, err = c.GetZettelContext(context.Background(), id.VersionZid, client.DirBackward, 0, 0) if err != nil { t.Error(err) return } - if rl.ID != id.VersionZid.String() { - t.Errorf("Expected an Zid %v, but got %v", id.VersionZid, rl.ID) + if !checkZid(t, id.VersionZid, rl.ID) { return } l = rl.List if got := len(l); got != 1 { t.Errorf("Expected list fo length 1, got %d", got) return } - if got := l[0].ID; got != id.DefaultHomeZid.String() { - t.Errorf("Expected result[0]=%v, but got %v", id.DefaultHomeZid, got) - } + checkListZid(t, l, 0, id.DefaultHomeZid) } func TestGetZettelLinks(t *testing.T) { t.Parallel() c := getClient() @@ -243,24 +356,23 @@ zl, err := c.GetZettelLinks(context.Background(), id.DefaultHomeZid) if err != nil { t.Error(err) return } - if zl.ID != id.DefaultHomeZid.String() { - t.Errorf("Expected an Zid %v, but got %v", id.DefaultHomeZid, zl.ID) + if !checkZid(t, id.DefaultHomeZid, zl.ID) { return } - if len(zl.Links.Incoming) != 0 { - t.Error("No incomings expected", zl.Links.Incoming) + if len(zl.Linked.Incoming) != 0 { + t.Error("No incomings expected", zl.Linked.Incoming) } - if got := len(zl.Links.Outgoing); got != 4 { + if got := len(zl.Linked.Outgoing); got != 4 { t.Errorf("Expected 4 outgoing links, got %d", got) } - if got := len(zl.Links.Local); got != 1 { + if got := len(zl.Linked.Local); got != 1 { t.Errorf("Expected 1 local link, got %d", got) } - if got := len(zl.Links.External); got != 4 { + if got := len(zl.Linked.External); got != 4 { t.Errorf("Expected 4 external link, got %d", got) } } func TestListTags(t *testing.T) { Index: cmd/cmd_file.go ================================================================== --- cmd/cmd_file.go +++ cmd/cmd_file.go @@ -25,12 +25,12 @@ "zettelstore.de/z/parser" ) // ---------- Subcommand: file ----------------------------------------------- -func cmdFile(fs *flag.FlagSet, cfg *meta.Meta) (int, error) { - format := fs.Lookup("t").Value.String() +func cmdFile(fs *flag.FlagSet, _ *meta.Meta) (int, error) { + enc := fs.Lookup("t").Value.String() m, inp, err := getInput(fs.Args()) if m == nil { return 2, err } z := parser.ParseZettel( @@ -39,16 +39,18 @@ Content: domain.NewContent(inp.Src[inp.Pos:]), }, m.GetDefault(meta.KeySyntax, meta.ValueSyntaxZmk), nil, ) - enc := encoder.Create(api.Encoder(format), &encoder.Environment{Lang: m.GetDefault(meta.KeyLang, meta.ValueLangEN)}) - if enc == nil { - fmt.Fprintf(os.Stderr, "Unknown format %q\n", format) + encdr := encoder.Create(api.Encoder(enc), &encoder.Environment{ + Lang: m.GetDefault(meta.KeyLang, meta.ValueLangEN), + }) + if encdr == nil { + fmt.Fprintf(os.Stderr, "Unknown format %q\n", enc) return 2, nil } - _, err = enc.WriteZettel(os.Stdout, z, format != "raw") + _, err = encdr.WriteZettel(os.Stdout, z, parser.ParseMetadata) if err != nil { return 2, err } fmt.Println() Index: cmd/cmd_run.go ================================================================== --- cmd/cmd_run.go +++ cmd/cmd_run.go @@ -10,13 +10,11 @@ package cmd import ( "flag" - "net/http" - zsapi "zettelstore.de/z/api" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" @@ -41,11 +39,11 @@ func withDebug(fs *flag.FlagSet) bool { dbg := fs.Lookup("debug") return dbg != nil && dbg.Value.String() == "true" } -func runFunc(fs *flag.FlagSet, cfg *meta.Meta) (int, error) { +func runFunc(fs *flag.FlagSet, _ *meta.Meta) (int, error) { exitCode, err := doRun(withDebug(fs)) kernel.Main.WaitForShutdown() return exitCode, err } @@ -58,19 +56,20 @@ return 0, nil } func setupRouting(webSrv server.Server, boxManager box.Manager, authManager auth.Manager, rtConfig config.Config) { protectedBoxManager, authPolicy := authManager.BoxWithPolicy(webSrv, boxManager, rtConfig) - api := api.New(webSrv, authManager, authManager, webSrv, rtConfig) + a := api.New(webSrv, authManager, authManager, webSrv, rtConfig) wui := webui.New(webSrv, authManager, rtConfig, authManager, boxManager, authPolicy) ucAuthenticate := usecase.NewAuthenticate(authManager, authManager, boxManager) ucCreateZettel := usecase.NewCreateZettel(rtConfig, protectedBoxManager) ucGetMeta := usecase.NewGetMeta(protectedBoxManager) ucGetAllMeta := usecase.NewGetAllMeta(protectedBoxManager) ucGetZettel := usecase.NewGetZettel(protectedBoxManager) ucParseZettel := usecase.NewParseZettel(rtConfig, ucGetZettel) + ucEvaluate := usecase.NewEvaluate(rtConfig, ucGetZettel, ucGetMeta) ucListMeta := usecase.NewListMeta(protectedBoxManager) ucListRoles := usecase.NewListRole(protectedBoxManager) ucListTags := usecase.NewListTags(protectedBoxManager) ucZettelContext := usecase.NewZettelContext(protectedBoxManager) ucDelete := usecase.NewDeleteZettel(protectedBoxManager) @@ -78,59 +77,66 @@ ucRename := usecase.NewRenameZettel(protectedBoxManager) webSrv.Handle("/", wui.MakeGetRootHandler(protectedBoxManager)) // Web user interface - webSrv.AddListRoute('a', http.MethodGet, wui.MakeGetLoginHandler()) - webSrv.AddListRoute('a', http.MethodPost, wui.MakePostLoginHandler(ucAuthenticate)) - webSrv.AddZettelRoute('a', http.MethodGet, wui.MakeGetLogoutHandler()) if !authManager.IsReadonly() { - webSrv.AddZettelRoute('b', http.MethodGet, wui.MakeGetRenameZettelHandler(ucGetMeta)) - webSrv.AddZettelRoute('b', http.MethodPost, wui.MakePostRenameZettelHandler(ucRename)) - webSrv.AddZettelRoute('c', http.MethodGet, wui.MakeGetCopyZettelHandler( + webSrv.AddZettelRoute('b', server.MethodGet, wui.MakeGetRenameZettelHandler(ucGetMeta)) + webSrv.AddZettelRoute('b', server.MethodPost, wui.MakePostRenameZettelHandler(ucRename)) + webSrv.AddZettelRoute('c', server.MethodGet, wui.MakeGetCopyZettelHandler( ucGetZettel, usecase.NewCopyZettel())) - webSrv.AddZettelRoute('c', http.MethodPost, wui.MakePostCreateZettelHandler(ucCreateZettel)) - webSrv.AddZettelRoute('d', http.MethodGet, wui.MakeGetDeleteZettelHandler(ucGetZettel)) - webSrv.AddZettelRoute('d', http.MethodPost, wui.MakePostDeleteZettelHandler(ucDelete)) - webSrv.AddZettelRoute('e', http.MethodGet, wui.MakeEditGetZettelHandler(ucGetZettel)) - webSrv.AddZettelRoute('e', http.MethodPost, wui.MakeEditSetZettelHandler(ucUpdate)) - webSrv.AddZettelRoute('f', http.MethodGet, wui.MakeGetFolgeZettelHandler( + webSrv.AddZettelRoute('c', server.MethodPost, wui.MakePostCreateZettelHandler(ucCreateZettel)) + webSrv.AddZettelRoute('d', server.MethodGet, wui.MakeGetDeleteZettelHandler(ucGetZettel)) + 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', http.MethodPost, wui.MakePostCreateZettelHandler(ucCreateZettel)) - webSrv.AddZettelRoute('g', http.MethodGet, wui.MakeGetNewZettelHandler( - ucGetZettel, usecase.NewNewZettel())) - webSrv.AddZettelRoute('g', http.MethodPost, wui.MakePostCreateZettelHandler(ucCreateZettel)) - } - webSrv.AddListRoute('f', http.MethodGet, wui.MakeSearchHandler( - usecase.NewSearch(protectedBoxManager), ucGetMeta, ucGetZettel)) - webSrv.AddListRoute('h', http.MethodGet, wui.MakeListHTMLMetaHandler( - ucListMeta, ucListRoles, ucListTags)) - webSrv.AddZettelRoute('h', http.MethodGet, wui.MakeGetHTMLZettelHandler( - ucParseZettel, ucGetMeta)) - webSrv.AddZettelRoute('i', http.MethodGet, wui.MakeGetInfoHandler( - ucParseZettel, ucGetMeta, ucGetAllMeta)) - webSrv.AddZettelRoute('j', http.MethodGet, wui.MakeZettelContextHandler(ucZettelContext)) + 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('f', server.MethodGet, wui.MakeSearchHandler( + usecase.NewSearch(protectedBoxManager), &ucEvaluate)) + webSrv.AddListRoute('h', server.MethodGet, wui.MakeListHTMLMetaHandler( + ucListMeta, ucListRoles, ucListTags, &ucEvaluate)) + webSrv.AddZettelRoute('h', server.MethodGet, wui.MakeGetHTMLZettelHandler( + &ucEvaluate, ucGetMeta)) + webSrv.AddListRoute('i', server.MethodGet, wui.MakeGetLoginOutHandler()) + webSrv.AddListRoute('i', server.MethodPost, wui.MakePostLoginHandler(ucAuthenticate)) + webSrv.AddZettelRoute('i', server.MethodGet, wui.MakeGetInfoHandler( + ucParseZettel, &ucEvaluate, ucGetMeta, ucGetAllMeta)) + webSrv.AddListRoute('i', server.MethodGet, wui.MakeGetLoginOutHandler()) + webSrv.AddZettelRoute('k', server.MethodGet, wui.MakeZettelContextHandler( + ucZettelContext, &ucEvaluate)) // API - webSrv.AddZettelRoute('l', http.MethodGet, api.MakeGetLinksHandler(ucParseZettel)) - webSrv.AddZettelRoute('o', http.MethodGet, api.MakeGetOrderHandler( - usecase.NewZettelOrder(protectedBoxManager, ucParseZettel))) - webSrv.AddListRoute('r', http.MethodGet, api.MakeListRoleHandler(ucListRoles)) - webSrv.AddListRoute('t', http.MethodGet, api.MakeListTagsHandler(ucListTags)) - webSrv.AddListRoute('v', http.MethodPost, api.MakePostLoginHandler(ucAuthenticate)) - webSrv.AddListRoute('v', http.MethodPut, api.MakeRenewAuthHandler()) - webSrv.AddZettelRoute('x', http.MethodGet, api.MakeZettelContextHandler(ucZettelContext)) - webSrv.AddListRoute('z', http.MethodGet, api.MakeListMetaHandler( - usecase.NewListMeta(protectedBoxManager), ucGetMeta, ucParseZettel)) - webSrv.AddZettelRoute('z', http.MethodGet, api.MakeGetZettelHandler( - ucParseZettel, ucGetMeta)) + webSrv.AddListRoute('a', server.MethodPost, a.MakePostLoginHandler(ucAuthenticate)) + webSrv.AddListRoute('a', server.MethodPut, a.MakeRenewAuthHandler()) + webSrv.AddListRoute('j', server.MethodGet, api.MakeListMetaHandler(ucListMeta)) + webSrv.AddZettelRoute('j', server.MethodGet, api.MakeGetZettelHandler(ucGetZettel)) + webSrv.AddZettelRoute('l', server.MethodGet, api.MakeGetLinksHandler(ucEvaluate)) + webSrv.AddZettelRoute('o', server.MethodGet, api.MakeGetOrderHandler( + usecase.NewZettelOrder(protectedBoxManager, ucEvaluate))) + webSrv.AddZettelRoute('p', server.MethodGet, a.MakeGetParsedZettelHandler(ucParseZettel)) + webSrv.AddListRoute('r', server.MethodGet, api.MakeListRoleHandler(ucListRoles)) + webSrv.AddListRoute('t', server.MethodGet, api.MakeListTagsHandler(ucListTags)) + webSrv.AddZettelRoute('v', server.MethodGet, a.MakeGetEvalZettelHandler(ucEvaluate)) + webSrv.AddZettelRoute('x', server.MethodGet, api.MakeZettelContextHandler(ucZettelContext)) + webSrv.AddListRoute('z', server.MethodGet, a.MakeListPlainHandler(ucListMeta)) + webSrv.AddZettelRoute('z', server.MethodGet, a.MakeGetPlainZettelHandler(ucGetZettel)) if !authManager.IsReadonly() { - webSrv.AddListRoute('z', http.MethodPost, api.MakePostCreateZettelHandler(ucCreateZettel)) - webSrv.AddZettelRoute('z', http.MethodDelete, api.MakeDeleteZettelHandler(ucDelete)) - webSrv.AddZettelRoute('z', http.MethodPut, api.MakeUpdateZettelHandler(ucUpdate)) - webSrv.AddZettelRoute('z', zsapi.MethodMove, api.MakeRenameZettelHandler(ucRename)) + webSrv.AddListRoute('j', server.MethodPost, a.MakePostCreateZettelHandler(ucCreateZettel)) + webSrv.AddZettelRoute('j', server.MethodPut, api.MakeUpdateZettelHandler(ucUpdate)) + webSrv.AddZettelRoute('j', server.MethodDelete, api.MakeDeleteZettelHandler(ucDelete)) + webSrv.AddZettelRoute('j', server.MethodMove, api.MakeRenameZettelHandler(ucRename)) + webSrv.AddListRoute('z', server.MethodPost, a.MakePostCreatePlainZettelHandler(ucCreateZettel)) + webSrv.AddZettelRoute('z', server.MethodPut, api.MakeUpdatePlainZettelHandler(ucUpdate)) + webSrv.AddZettelRoute('z', server.MethodDelete, api.MakeDeleteZettelHandler(ucDelete)) + webSrv.AddZettelRoute('z', server.MethodMove, api.MakeRenameZettelHandler(ucRename)) } if authManager.WithAuth() { webSrv.SetUserRetriever(usecase.NewGetUserByZid(boxManager)) } } Index: cmd/fd_limit.go ================================================================== --- cmd/fd_limit.go +++ cmd/fd_limit.go @@ -6,10 +6,11 @@ // 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. //----------------------------------------------------------------------------- +//go:build !darwin // +build !darwin package cmd func raiseFdLimit() error { return nil } Index: cmd/fd_limit_raise.go ================================================================== --- cmd/fd_limit_raise.go +++ cmd/fd_limit_raise.go @@ -6,10 +6,11 @@ // 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. //----------------------------------------------------------------------------- +//go:build darwin // +build darwin package cmd import ( Index: cmd/main.go ================================================================== --- cmd/main.go +++ cmd/main.go @@ -18,10 +18,11 @@ "net/url" "os" "strconv" "strings" + "zettelstore.de/z/api" "zettelstore.de/z/auth" "zettelstore.de/z/auth/impl" "zettelstore.de/z/box" "zettelstore.de/z/box/compbox" "zettelstore.de/z/box/manager" @@ -70,11 +71,11 @@ }) RegisterCommand(Command{ Name: "file", Func: cmdFile, Flags: func(fs *flag.FlagSet) { - fs.String("t", "html", "target output format") + fs.String("t", api.EncoderHTML.String(), "target output encoding") }, }) RegisterCommand(Command{ Name: "password", Func: cmdPassword, Index: cmd/register.go ================================================================== --- cmd/register.go +++ cmd/register.go @@ -16,18 +16,17 @@ _ "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/jsonenc" // Allow to use JSON encoder. _ "zettelstore.de/z/encoder/nativeenc" // Allow to use native encoder. - _ "zettelstore.de/z/encoder/rawenc" // Allow to use raw encoder. _ "zettelstore.de/z/encoder/textenc" // Allow to use text 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/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 @@ -13,31 +13,33 @@ import "zettelstore.de/z/ast" // Summary stores the relevant parts of the syntax tree type Summary struct { - Links []*ast.Reference // list of all referenced links - Images []*ast.Reference // list of all referenced images + Links []*ast.Reference // list of all linked material + Embeds []*ast.Reference // list of all embedded material Cites []*ast.CiteNode // list of all referenced citations } // References returns all references mentioned in the given zettel. This also // includes references to images. func References(zn *ast.ZettelNode) (s Summary) { - ast.WalkBlockSlice(&s, zn.Ast) + if zn.Ast != nil { + 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 { switch n := node.(type) { case *ast.LinkNode: s.Links = append(s.Links, n.Ref) - case *ast.ImageNode: - if n.Ref != nil { - s.Images = append(s.Images, n.Ref) + case *ast.EmbedNode: + if m, ok := n.Material.(*ast.ReferenceMaterialNode); ok { + s.Embeds = append(s.Embeds, m.Ref) } case *ast.CiteNode: s.Cites = append(s.Cites, n) } return s } Index: collect/collect_test.go ================================================================== --- collect/collect_test.go +++ collect/collect_test.go @@ -28,45 +28,45 @@ func TestLinks(t *testing.T) { t.Parallel() zn := &ast.ZettelNode{} summary := collect.References(zn) - if summary.Links != nil || summary.Images != nil { - t.Error("No links/images expected, but got:", summary.Links, "and", summary.Images) + if summary.Links != nil || summary.Embeds != nil { + t.Error("No links/images expected, but got:", summary.Links, "and", summary.Embeds) } intNode := &ast.LinkNode{Ref: parseRef("01234567890123")} para := &ast.ParaNode{ - Inlines: ast.InlineSlice{ + Inlines: ast.CreateInlineListNode( intNode, &ast.LinkNode{Ref: parseRef("https://zettelstore.de/z")}, - }, + ), } - zn.Ast = ast.BlockSlice{para} + zn.Ast = &ast.BlockListNode{List: []ast.BlockNode{para}} summary = collect.References(zn) - if summary.Links == nil || summary.Images != nil { - t.Error("Links expected, and no images, but got:", summary.Links, "and", summary.Images) + if summary.Links == nil || summary.Embeds != nil { + t.Error("Links expected, and no images, but got:", summary.Links, "and", summary.Embeds) } - para.Inlines = append(para.Inlines, intNode) + para.Inlines.Append(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 TestImage(t *testing.T) { +func TestEmbed(t *testing.T) { t.Parallel() zn := &ast.ZettelNode{ - Ast: ast.BlockSlice{ + Ast: &ast.BlockListNode{List: []ast.BlockNode{ &ast.ParaNode{ - Inlines: ast.InlineSlice{ - &ast.ImageNode{Ref: parseRef("12345678901234")}, - }, + Inlines: ast.CreateInlineListNode( + &ast.EmbedNode{Material: &ast.ReferenceMaterialNode{Ref: parseRef("12345678901234")}}, + ), }, - }, + }}, } summary := collect.References(zn) - if summary.Images == nil { - t.Error("Only image expected, but got: ", summary.Images) + if summary.Embeds == nil { + t.Error("Only image expected, but got: ", summary.Embeds) } } Index: collect/order.go ================================================================== --- collect/order.go +++ collect/order.go @@ -13,18 +13,23 @@ import "zettelstore.de/z/ast" // Order of internal reference within the given zettel. func Order(zn *ast.ZettelNode) (result []*ast.Reference) { - for _, bn := range zn.Ast { - if ln, ok := bn.(*ast.NestedListNode); ok { - switch ln.Kind { - case ast.NestedListOrdered, ast.NestedListUnordered: - for _, is := range ln.Items { - if ref := firstItemZettelReference(is); ref != nil { - result = append(result, ref) - } + if zn.Ast == nil { + return nil + } + for _, bn := range zn.Ast.List { + ln, ok := bn.(*ast.NestedListNode) + if !ok { + continue + } + switch ln.Kind { + case ast.NestedListOrdered, ast.NestedListUnordered: + for _, is := range ln.Items { + if ref := firstItemZettelReference(is); ref != nil { + result = append(result, ref) } } } } return result @@ -39,19 +44,22 @@ } } return nil } -func firstInlineZettelReference(ins ast.InlineSlice) (result *ast.Reference) { - for _, inl := range ins { +func firstInlineZettelReference(iln *ast.InlineListNode) (result *ast.Reference) { + if iln == nil { + return nil + } + for _, inl := range iln.List { switch in := inl.(type) { case *ast.LinkNode: if ref := in.Ref; ref.IsZettel() { return ref } result = firstInlineZettelReference(in.Inlines) - case *ast.ImageNode: + case *ast.EmbedNode: result = firstInlineZettelReference(in.Inlines) case *ast.CiteNode: result = firstInlineZettelReference(in.Inlines) case *ast.FootnoteNode: // Ignore references in footnotes Index: config/config.go ================================================================== --- config/config.go +++ config/config.go @@ -41,10 +41,13 @@ // GetHomeZettel returns the value of the "home-zettel" key. GetHomeZettel() id.Zid // GetDefaultVisibility returns the default value for zettel visibility. GetDefaultVisibility() meta.Visibility + + // GetMaxTransclusions return the maximum number of indirect transclusions. + GetMaxTransclusions() int // GetYAMLHeader returns the current value of the "yaml-header" key. GetYAMLHeader() bool // GetZettelFileSyntax returns the current value of the "zettel-file-syntax" key. @@ -71,11 +74,14 @@ // is no such value, GetDefaultTitle is returned. func GetTitle(m *meta.Meta, cfg Config) string { if val, ok := m.Get(meta.KeyTitle); ok { return val } - return cfg.GetDefaultTitle() + if cfg != nil { + return cfg.GetDefaultTitle() + } + return "Untitled" } // GetRole returns the value of the "role" key of the given meta. If there // is no such value, GetDefaultRole is returned. func GetRole(m *meta.Meta, cfg Config) string { ADDED docs/development/00010000000000.zettel Index: docs/development/00010000000000.zettel ================================================================== --- docs/development/00010000000000.zettel +++ docs/development/00010000000000.zettel @@ -0,0 +1,8 @@ +id: 00010000000000 +title: Developments Notes +role: zettel +syntax: zmk +modified: 20210916194954 + +* [[Required Software|20210916193200]] +* [[Checklist for Release|20210916194900]] ADDED docs/development/20210916193200.zettel Index: docs/development/20210916193200.zettel ================================================================== --- docs/development/20210916193200.zettel +++ docs/development/20210916193200.zettel @@ -0,0 +1,20 @@ +id: 20210916193200 +title: Required Software +role: zettel +syntax: zmk +modified: 20210916194748 + +The following software must be installed: + +* A current, supported [[release of Go|https://golang.org/doc/devel/release.html]], +* [[golint|https://github.com/golang/lint]], +* [[shadow|https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow]] via ``go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest``, +* [[staticcheck|https://staticcheck.io/]] via ``go get honnef.co/go/tools/cmd/staticcheck``, +* [[unparam|mvdan.cc/unparam]][^[[GitHub|https://github.com/mvdan/unparam]]] via ``go install mvdan.cc/unparam@latest`` + +Make sure that the software is in your path, e.g. via: + +```sh +export PATH=$PATH:/usr/local/go/bin +export PATH=$PATH:$(go env GOPATH)/bin +``` ADDED docs/development/20210916194900.zettel Index: docs/development/20210916194900.zettel ================================================================== --- docs/development/20210916194900.zettel +++ docs/development/20210916194900.zettel @@ -0,0 +1,52 @@ +id: 20210916194900 +title: Checklist for Release +role: zettel +syntax: zmk +modified: 20210917165448 + +# Check for unused parameters: +#* ``unparam ./...`` +#* ``unparam -exported -tests ./...`` +# Clean up your Go workspace: +#* ``go run tools/build.go clean`` (alternatively: ``make clean``). +# All internal tests must succeed: +#* ``go run tools/build.go check`` (alternatively: ``make check``). +# The API tests must succeed on every development platform: +#* ``go run tools/build.go testapi`` (alternatively: ``make api``). +# Run [[linkchecker|https://linkchecker.github.io/linkchecker/]] with the manual: +#* ``go run -race cmd/zettelstore/main.go run -d docs/manual`` +#* ``linkchecker http://127.0.0.1:23123 2>&1 | tee lc.txt`` +#* Check all ""Error: 404 Not Found"" +#* Check all ""Error: 403 Forbidden"": allowed for endpoint ''/p'' with encoding ''html'' for those zettel that are accessible only in ''expert-mode''. +#* Try to resolve other error messages and warnings +#* Warnings about empty content can be ignored +# On every development platform, the box with 10.000 zettel must run, with ''-race'' enabled: +#* ``go run -race cmd/zettelstore/main.go run -d DIR``. +# Create a development release: +#* ``go run tools/build.go release`` (alternatively: ``make release``). +# On every platform (esp. macOS), the box with 10.000 zettel must run properly: +#* ``./zettelstore -d DIR`` +# Update files in directory ''www'' +#* index.wiki +#* download.wiki +#* changes.wiki +#* plan.wiki +# Set file ''VERSION'' to the new release version +# Disable Fossil autosync mode: +#* ``fossil setting autosync off`` +# Commit the new release version: +#* ``fossil commit --tag release --tag version-VERSION -m "Version VERSION"`` +# Clean up your Go workspace: +#* ``go run tools/build.go clean`` (alternatively: ``make clean``). +# Create the release: +#* ``go run tools/build.go release`` (alternatively: ``make release``). +# Remove previous executables: +#* ``fossil uv remove --glob '*-PREVVERSION*'`` +# Add executables for release: +#* ``cd release`` +#* ``fossil uv add *.zip`` +#* ``cd ..`` +#* Synchronize with main repository: +#* ``fossil sync -u`` +# Enable autosync: +#* ``fossil setting autosync on`` Index: docs/manual/00001004020000.zettel ================================================================== --- docs/manual/00001004020000.zettel +++ docs/manual/00001004020000.zettel @@ -1,11 +1,11 @@ id: 00001004020000 title: Configure the running Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk -modified: 20210611213730 +modified: 20210810103936 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: @@ -53,10 +53,14 @@ : Specifies the identifier of the zettel, that should be presented for the default view / home view. If not given or if the identifier does not identify a zettel, the zettel with the identifier ''00010000000000'' is shown. ; [!marker-external]''marker-external'' : Some HTML code that is displayed after a reference to external material. Default: ''&\#10138;'', to display a ""➚"" sign. +; [!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'' : Name of the Zettelstore instance. Will be used when displaying some lists. Default: ''Zettelstore''. ; [!yaml-header]''yaml-header'' 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: 20210712234222 +modified: 20210727120507 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]] @@ -14,17 +14,15 @@ ; ''-t FORMAT'' : Specifies the output format. Supported values are: [[''html''|00001012920510]] (default), [[''djson''|00001012920503]], - [[''json''|00001012920501]], [[''native''|00001012920513]], - [[''raw''|00001012920516]], [[''text''|00001012920519]], 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/00001006000000.zettel ================================================================== --- docs/manual/00001006000000.zettel +++ docs/manual/00001006000000.zettel @@ -1,12 +1,13 @@ id: 00001006000000 title: Layout of a Zettel +role: manual tags: #design #manual #zettelstore syntax: zmk -role: manual +modified: 20210903210655 -A zettel consists of two part: the metadata and the zettel content. +A zettel consists of two parts: the metadata and the zettel content. Metadata gives some information mostly about the zettel content, how it should be interpreted, how it is sorted within Zettelstore. The zettel content is, well, the actual content. In many cases, the content is in plain text form. Plain text is long-lasting. However, content in binary format is also possible. @@ -23,6 +24,6 @@ This includes markup languages, like [[Zettelmarkup|00001007000000]] and [[CommonMark|https://commonmark.org/]]. Other text formats are also supported, like CSS and HTML templates. Plain text content is always Unicode, encoded as UTF-8. Other character encodings are not supported and will never be[^This is not a real problem, since every modern software should support UTF-8 as an encoding.]. There is support for a graphical format with a text represenation: SVG. -And the is support for some binary image formats, like GIF, PNG, and JPEG. +And there is support for some binary image formats, like GIF, PNG, and JPEG. Index: docs/manual/00001006020000.zettel ================================================================== --- docs/manual/00001006020000.zettel +++ docs/manual/00001006020000.zettel @@ -1,17 +1,19 @@ id: 00001006020000 title: Supported Metadata Keys role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk -modified: 20210709162756 +modified: 20210822234119 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'' +: 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'' : 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'' : Is a property that contains the identifier of all zettel that reference the zettel of this metadata. Index: docs/manual/00001006020100.zettel ================================================================== --- docs/manual/00001006020100.zettel +++ docs/manual/00001006020100.zettel @@ -1,10 +1,11 @@ id: 00001006020100 title: Supported Zettel Roles role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk +modified: 20210727120817 The [[''role'' key|00001006020000#role]] defines what kind of zettel you are writing. The following values are used internally by Zettelstore and must exist: ; [!user]''user'' @@ -33,6 +34,6 @@ : Contains some remarks about a book, a paper, a web page, etc. You should add a citation key for citing it. ; [!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 raw text of a scientific paper, ''project'' to define a project, ... +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,17 +1,18 @@ id: 00001006030000 title: Supported Key Types role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk -modified: 20210627170437 +modified: 20210830132855 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]] | ''-url'' | [[URL|00001006035000]] | ''-zid'' | [[Identifier|00001006032000]] | any other suffix | [[EString|00001006031500]] The name of the metadata key is bound to the key type Index: docs/manual/00001006034000.zettel ================================================================== --- docs/manual/00001006034000.zettel +++ docs/manual/00001006034000.zettel @@ -1,26 +1,28 @@ id: 00001006034000 title: TagSet Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk +modified: 20210817201410 Values of this type denote a (sorted) set of tags. A set is different to a list, as no duplicates are allowed. === Allowed values 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 It depends of the first character of a search string how it is matched against a tag set value: * If the first character of the search string is a number sign character, it must exactly match one of the values of a tag. * In other cases, the search string must be the prefix of at least one tag. -Conpectually, all number sign characters are removed at the beginning of the search string -and of all tags. +Conceptually, all number sign characters are removed at the beginning of the search string and of all tags. === Sorting Sorting is done by comparing the [[String|00001006033500]] values. Index: docs/manual/00001006035500.zettel ================================================================== --- docs/manual/00001006035500.zettel +++ docs/manual/00001006035500.zettel @@ -1,16 +1,19 @@ id: 00001006035500 title: Word Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk +modified: 20210817201304 Values of this type denote a single word. === Allowed values Must be a non-empty sequence of characters, but without the space character. +All characters are mapped to their lower case values. + === Match operator -A value matches a word value, if both value are character-wise equal. +A value matches a word value, if both value are character-wise equal, ignoring upper / lower case. === Sorting Sorting is done by comparing the [[String|00001006033500]] values. Index: docs/manual/00001006055000.zettel ================================================================== --- docs/manual/00001006055000.zettel +++ docs/manual/00001006055000.zettel @@ -1,11 +1,11 @@ id: 00001006055000 title: Reserved zettel identifier role: manual tags: #design #manual #zettelstore syntax: zmk -modified: 20210721125518 +modified: 20210917154229 [[Zettel identifier|00001006050000]] are typically created by examine the current date and time. By renaming a zettel, you are able to provide any sequence of 14 digits. If no other zettel has the same identifier, you are allowed to rename a zettel. @@ -38,6 +38,6 @@ This list may change in the future. ==== External Applications |= From | To | Description -| 00009000001000 | 00009000001000 | ZS Slides, an application to display zettel as a HTML-based slideshow +| 00009000001000 | 00009000001999 | ZS Slides, an application to display zettel as a HTML-based slideshow Index: docs/manual/00001007040000.zettel ================================================================== --- docs/manual/00001007040000.zettel +++ docs/manual/00001007040000.zettel @@ -1,32 +1,33 @@ id: 00001007040000 title: Zettelmarkup: Inline-Structured Elements +role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk -role: manual +modified: 20210822234205 Most characters you type is concerned with inline-structured elements. The content of a zettel contains is many cases just ordinary text, lightly formatted. Inline-structured elements allow to format your text and add some helpful links or images. Sometimes, you want to enter characters that have no representation on your keyboard. -=== Text formatting -Every [[text formatting|00001007040100]] element begins with two same characters at the beginning. -It lasts until the same two characters occurred the second time. -Some of these elements explicitly support [[attributes|00001007050000]]. - -=== Literal-like formatting -Sometime you want to render the text as it is. -This is the core motivation of [[literal-like formatting|00001007040200]]. - -=== Reference-like text -You can reference other zettel and (external) material within one zettel. -This kind of reference may be a link, or an images that is display inline when the zettel is rendered. -Footnotes sometimes factor out some useful text that hinders the flow of reading text. -Internal marks allow to reference something within a zettel. -An important aspect of all knowledge work is to reference others work, e.g. with citation keys. -All these elements can be subsumed under [[reference-like text|00001007040300]]. +; Text formatting +: Every [[text formatting|00001007040100]] element begins with two same characters at the beginning. + It lasts until the same two characters occurred the second time. + Some of these elements explicitly support [[attributes|00001007050000]]. + +; Literal-like formatting +: Sometime you want to render the text as it is. +: This is the core motivation of [[literal-like formatting|00001007040200]]. + +; Reference-like text +: You can reference other zettel and (external) material within one zettel. + This kind of reference may be a link, or an images that is display inline when the zettel is rendered. + Footnotes sometimes factor out some useful text that hinders the flow of reading text. + Internal marks allow to reference something within a zettel. + An important aspect of all knowledge work is to reference others work, e.g. with citation keys. + All these elements can be subsumed under [[reference-like text|00001007040300]]. === Other inline elements ==== Comments A comment begins with two consecutive percent sign characters (""''%''"", ''U+0025''). It ends at the end of the line where it begins. @@ -38,11 +39,11 @@ * Every other character is taken as itself, but without the interpretation of a Zettelmarkup element. For example, if you want to enter a ""'']''"" into a footnote text, you should escape it with a backslash. ==== Tag Any text that begins with a number sign character (""''#''"", ''U+0023''), followed by a non-empty sequence of Unicode letters, Unicode digits, the hyphen-minus character (""''-''"", ''U+002D''), or the low line character (""''_''"", ''U+005F'') is interpreted as an //inline tag//. -They will be considered equivalent to tags in metadata. +They are be considered equivalent to tags in metadata. ==== Entities & more Sometimes it is not easy to enter special characters. If you know the Unicode code point of that character, or its name according to the [[HTML standard|https://html.spec.whatwg.org/multipage/named-characters.html]], you can enter it by number or by name. Index: docs/manual/00001007040300.zettel ================================================================== --- docs/manual/00001007040300.zettel +++ docs/manual/00001007040300.zettel @@ -1,98 +1,15 @@ id: 00001007040300 title: Zettelmarkup: Reference-like text role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk +modified: 20210810172531 An important aspect of knowledge work is to interconnect your zettel as well as provide links to (external) material. There are several kinds of references that are allowed in Zettelmarkup: -* Links to other zettel. -* Links to (external material). -* Embed images that are stored within your Zettelstore. -* Embed external images. -* Reference via a footnote. -* Reference via a citation key. -* Put a mark within your zettel that you can reference later with a link. - -=== Links -There are two kinds of links, regardless of links to (internal) other zettel or to (external) material. -Both kinds begin with two consecutive left square bracket characters (""''[''"", ''U+005B'') and ends with two consecutive right square bracket characters (""'']''"", ''U+005D''). - -The first form provides some text plus the link specification, delimited by a vertical bar character (""''|''"", ''U+007C''): ``[[text|linkspecification]]``. - -The second form just provides a link specification between the square brackets. -Its text is derived from the link specification, e.g. by interpreting the link specification as text: ``[[linkspecification]]``. - -The link specification for another zettel within the same Zettelstore is just the [[zettel identifier|00001006050000]]. -To reference some content within a zettel, you can append a number sign character (""''#''"", ''U+0023'') and the name of the mark to the zettel identifier. -The resulting reference is called ""zettel reference"". - -To specify some material outside the Zettelstore, just use an normal Uniform Resource Identifier (URI) as defined by [[RFC\ 3986|https://tools.ietf.org/html/rfc3986]]. -If the URL begins with the slash character (""/"", ''U+002F''), or if it begins with ""./"" or with ""../"", i.e. without scheme, user info, and host name, the reference will be treated as a ""local reference"", otherwise as an ""external reference"". -If the URL begins with two slash characters, it will be interpreted relative to the value of [[''url-prefix''|00001004010000#url-prefix]]. - -The text in the second form is just a sequence of inline elements. - -=== Images -To some degree, an image specification is conceptually not too far away from a link specification. -Both contain a link specification and optionally some text. -In contrast to a link, the link specification of an image must resolve to actual graphical image data. -That data is read when rendered as HTML, and is embedded inside the zettel as an inline image. - -An image specification begins with two consecutive left curly bracket characters (""''{''"", ''U+007B'') and ends with two consecutive right curly bracket characters (""''}''"", ''U+007D''). -The curly brackets delimits either a link specification or some text, a vertical bar character and the link specification, similar to a link. - -One difference to a link: if the text was not given, an empty string is assumed. - -The link specification must reference a graphical image representation if the image is about to be rendered. -Supported formats are: - -* Portable Network Graphics (""PNG""), as defined by [[RFC\ 2083|https://tools.ietf.org/html/rfc2083]]. -* Graphics Interchange Format (""GIF"), as defined by [[https://www.w3.org/Graphics/GIF/spec-gif89a.txt]]. -* JPEG / JPG, defined by the //Joint Photographic Experts Group//. -* Scalable Vector Graphics (SVG), defined by [[https://www.w3.org/Graphics/SVG/]] - -If the text is given, it will be interpreted as an alternative textual representation, to help persons with some visual disabilities. - -[[Attributes|00001007050000]] are supported. -They must follow the last right curly bracket character immediately. -One prominent example is to specify an explicit title attribute that is shown on certain web browsers when the zettel is rendered in HTML: - -Examples: -* ``{{Spinning Emoji|00000000040001}}{title=Emoji width=30}`` is rendered as ::{{Spinning Emoji|00000000040001}}{title=Emoji width=30}::{=example}. -* The above image is also a placeholder for a non-existent image: -** ``{{00000000000000}}`` will be rendered as ::{{00000000000000}}::{=example}. -** ``{{00000000009999}}`` will be rendered as ::{{00000000009999}}::{=example}. - -=== Footnotes - -A footnote begins with a left square bracket, followed by a circumflex accent (""''^''"", ''U+005E''), followed by some text, and ends with a right square bracket. - -Example: - -``Main text[^Footnote text.].`` is rendered in HTML as: ::Main text[^Footnote text.].::{=example}. - -=== Citation key - -A citation key references some external material that is part of a bibliografical collection. - -Currently, Zettelstore implements this only partially, it is ""work in progress"". - -However, the syntax is: beginning with a left square bracket and followed by an at sign character (""''@''"", ''U+0040''), a the citation key is given. -The key is typically a sequence of letters and digits. -If a comma character (""'',''"", ''U+002C'') or a vertical bar character is given, the following is interpreted as inline elements. -A right square bracket ends the text and the citation key element. - -=== Mark -A mark allows to name a point within a zettel. -This is useful if you want to reference some content in a bigger-sized zettel[^Other uses of marks will be given, if Zettelmarkup is extended by a concept called //transclusion//.]. - -A mark begins with a left square bracket, followed by an exclamation mark character (""''!''"", ''U+0021''). -Now the optional mark name follows. -It is a (possibly empty) sequence of Unicode letters, Unicode digits, the hyphen-minus character (""''-''"", ''U+002D''), or the low-line character (""''_''"", ''U+005F''). -The mark element ends with a right square bracket. - -Examples: -* ``[!]`` is a mark without a name, the empty mark. -* ``[!mark]`` is a mark with the name ""mark"". +* [[Links to other zettel or to (external) material|00001007040310]] +* [[Embedded zettel or (external) material|00001007040320]] (""inline transclusion"") +* [[Reference via a footnote|00001007040330]] +* [[Reference via a citation key|00001007040340]] +* Put a [[mark|00001007040350]] within your zettel that you can reference later with a link. ADDED docs/manual/00001007040310.zettel Index: docs/manual/00001007040310.zettel ================================================================== --- docs/manual/00001007040310.zettel +++ docs/manual/00001007040310.zettel @@ -0,0 +1,23 @@ +id: 00001007040310 +title: Zettelmarkup: Links +role: manual +tags: #manual #zettelmarkup #zettelstore +syntax: zmk + +There are two kinds of links, regardless of links to (internal) other zettel or to (external) material. +Both kinds begin with two consecutive left square bracket characters (""''[''"", ''U+005B'') and ends with two consecutive right square bracket characters (""'']''"", ''U+005D''). + +The first form provides some text plus the link specification, delimited by a vertical bar character (""''|''"", ''U+007C''): ``[[text|linkspecification]]``. + +The second form just provides a link specification between the square brackets. +Its text is derived from the link specification, e.g. by interpreting the link specification as text: ``[[linkspecification]]``. + +The link specification for another zettel within the same Zettelstore is just the [[zettel identifier|00001006050000]]. +To reference some content within a zettel, you can append a number sign character (""''#''"", ''U+0023'') and the name of the mark to the zettel identifier. +The resulting reference is called ""zettel reference"". + +To specify some material outside the Zettelstore, just use an normal Uniform Resource Identifier (URI) as defined by [[RFC\ 3986|https://tools.ietf.org/html/rfc3986]]. +If the URL begins with the slash character (""/"", ''U+002F''), or if it begins with ""./"" or with ""../"", i.e. without scheme, user info, and host name, the reference will be treated as a ""local reference"", otherwise as an ""external reference"". +If the URL begins with two slash characters, it will be interpreted relative to the value of [[''url-prefix''|00001004010000#url-prefix]]. + +The text in the second form is just a sequence of inline elements. ADDED docs/manual/00001007040320.zettel Index: docs/manual/00001007040320.zettel ================================================================== --- docs/manual/00001007040320.zettel +++ docs/manual/00001007040320.zettel @@ -0,0 +1,25 @@ +id: 00001007040320 +title: Zettelmarkup: Inline Embedding / Transclusion +role: manual +tags: #manual #zettelmarkup #zettelstore +syntax: zmk +modified: 20210811162201 + +To some degree, an specification for embedded material is conceptually not too far away from a specification for [[linked material|00001007040310]]. +Both contain a reference specification and optionally some text. +In contrast to a link, the specification of embedded material must currently resolve to some kind of real content. +This content replaces the embed specification. + +An image specification begins with two consecutive left curly bracket characters (""''{''"", ''U+007B'') and ends with two consecutive right curly bracket characters (""''}''"", ''U+007D''). +The curly brackets delimits either a reference specification or some text, a vertical bar character and the link specification, similar to a link. + +One difference to a link: if the text was not given, an empty string is assumed. + +The reference must point to some content, either zettel content or URL-referenced content. +If the referenced zettel does not exist, or is not readable, a spinning emoji is presented as a visual hint: + +Example: ``{{00000000000000}}`` will be rendered as ::{{00000000000000}}::{=example}. + +There are two kind of content: +# [[image content|00001007040322]], +# [[textual content|00001007040324]]. ADDED docs/manual/00001007040322.zettel Index: docs/manual/00001007040322.zettel ================================================================== --- docs/manual/00001007040322.zettel +++ docs/manual/00001007040322.zettel @@ -0,0 +1,26 @@ +id: 00001007040322 +title: Zettelmarkup: Image Embedding +role: manual +tags: #manual #zettelmarkup #zettelstore +syntax: zmk +modified: 20210811165719 + +Image content is assumed, if an URL is used or if the referenced zettel contains an image. + +Supported formats are: + +* Portable Network Graphics (""PNG""), as defined by [[RFC\ 2083|https://tools.ietf.org/html/rfc2083]]. +* Graphics Interchange Format (""GIF"), as defined by [[https://www.w3.org/Graphics/GIF/spec-gif89a.txt]]. +* JPEG / JPG, defined by the //Joint Photographic Experts Group//. +* Scalable Vector Graphics (SVG), defined by [[https://www.w3.org/Graphics/SVG/]] + +If the text is given, it will be interpreted as an alternative textual representation, to help persons with some visual disabilities. + +[[Attributes|00001007050000]] are supported. +They must follow the last right curly bracket character immediately. +One prominent example is to specify an explicit title attribute that is shown on certain web browsers when the zettel is rendered in HTML: + +Examples: +* [!spin] ``{{Spinning Emoji|00000000040001}}{title=Emoji width=30}`` is rendered as ::{{Spinning Emoji|00000000040001}}{title=Emoji width=30}::{=example}. +* The above image is also the placeholder for a non-existent zettel: +** ``{{00000000009999}}`` will be rendered as ::{{00000000009999}}::{=example}. ADDED docs/manual/00001007040324.zettel Index: docs/manual/00001007040324.zettel ================================================================== --- docs/manual/00001007040324.zettel +++ docs/manual/00001007040324.zettel @@ -0,0 +1,41 @@ +id: 00001007040324 +title: Zettelmarkup: Inline Transclusion of Text +role: manual +tags: #manual #zettelmarkup #zettelstore +syntax: zmk +modified: 20210811172440 + +Inline transclusion applies to all zettel that are parsed in an non-trivial way. +For example, textual content is assumed if the [[syntax|00001006020000#syntax]] of a zettel is ''zmk'' ([[Zettelmarkup|00001007000000]]), or ''markdown'' / ''md'' ([[Markdown|00001008010000]]). + +Since the transclusion is at the level of [[inline-structured elements|00001007040000]], the transclude specification must be replaced with some inline-structured elements. + +First, the referenced zettel is read. +If it contains inline transclusions, these will be expanded, recursively. +When an endless recursion is detected, expansion does not take place. +Instead an error message replaces the transclude specification. + +The result of this (indirect) transclusion is searched for inline-structured elements. + +* If only the zettel identifier was specified, the first top-level [[paragraph|00001007030000#paragraphs]] is used. + Since a paragraph is basically a sequence of inline-structured elements, these elements will replace the transclude specification. + + Example: ``{{00010000000000}}`` (see [[00010000000000]]) is rendered as ::{{00010000000000}}::{=example}. + +* If a fragment identifier was additionally specified, the element with the given fragment is searched: +** If it specifies a [[heading|00001007030300]], the next top-level paragraph is used. + + Example: ``{{00010000000000#reporting-errors}}`` is rendered as ::{{00010000000000#reporting-errors}}::{=example}. + +** In case the fragment names a [[mark|00001007040350]], the inline-structured elements after the mark are used. + Initial spaces and line breaks are ignored in this case. + + Example: ``{{00001007040322#spin}}`` is rendered as ::{{00001007040322#spin}}::{=example}. + +** Just specifying the fragment identifier will reference something in the current page. + This is not allowed, to prevent a possible endless recursion. + +If no inline-structured elements are found, the transclude specification is replaced by an error message. + +To avoid an exploding ""transclusion bomb"", a form of a [[billion laughs attack|https://en.wikipedia.org/wiki/Billion_laughs_attack]] (also known as ""XML bomb""), the total number of transclusions / expansions is limited. +The limit can be controlled by setting the value [[''max-transclusions''|00001004020000#max-transclusions]] of the runtime configuration zettel. ADDED docs/manual/00001007040330.zettel Index: docs/manual/00001007040330.zettel ================================================================== --- docs/manual/00001007040330.zettel +++ docs/manual/00001007040330.zettel @@ -0,0 +1,11 @@ +id: 00001007040330 +title: Zettelmarkup: Footnotes +role: manual +tags: #manual #zettelmarkup #zettelstore +syntax: zmk + +A footnote begins with a left square bracket, followed by a circumflex accent (""''^''"", ''U+005E''), followed by some text, and ends with a right square bracket. + +Example: + +``Main text[^Footnote text.].`` is rendered in HTML as: ::Main text[^Footnote text.].::{=example}. ADDED docs/manual/00001007040340.zettel Index: docs/manual/00001007040340.zettel ================================================================== --- docs/manual/00001007040340.zettel +++ docs/manual/00001007040340.zettel @@ -0,0 +1,15 @@ +id: 00001007040340 +title: Zettelmarkup: Citation Key +role: manual +tags: #manual #zettelmarkup #zettelstore +syntax: zmk +modified: 20210810172339 + +A citation key references some external material that is part of a bibliografical collection. + +Currently, Zettelstore implements this only partially, it is ""work in progress"". + +However, the syntax is: beginning with a left square bracket and followed by an at sign character (""''@''"", ''U+0040''), a the citation key is given. +The key is typically a sequence of letters and digits. +If a comma character (""'',''"", ''U+002C'') or a vertical bar character is given, the following is interpreted as inline elements. +A right square bracket ends the text and the citation key element. ADDED docs/manual/00001007040350.zettel Index: docs/manual/00001007040350.zettel ================================================================== --- docs/manual/00001007040350.zettel +++ docs/manual/00001007040350.zettel @@ -0,0 +1,17 @@ +id: 00001007040350 +title: Zettelmarkup: Mark +role: manual +tags: #manual #zettelmarkup #zettelstore +syntax: zmk + +A mark allows to name a point within a zettel. +This is useful if you want to reference some content in a bigger-sized zettel, currently with a [[link|00001007040310]] only[^Other uses of marks will be given, if Zettelmarkup is extended by a concept called //transclusion//.]. + +A mark begins with a left square bracket, followed by an exclamation mark character (""''!''"", ''U+0021''). +Now the optional mark name follows. +It is a (possibly empty) sequence of Unicode letters, Unicode digits, the hyphen-minus character (""''-''"", ''U+002D''), or the low-line character (""''_''"", ''U+005F''). +The mark element ends with a right square bracket. + +Examples: +* ``[!]`` is a mark without a name, the empty mark. +* ``[!mark]`` is a mark with the name ""mark"". Index: docs/manual/00001007050000.zettel ================================================================== --- docs/manual/00001007050000.zettel +++ docs/manual/00001007050000.zettel @@ -1,16 +1,17 @@ id: 00001007050000 title: Zettelmarkup: Attributes +role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk -role: manual +modified: 20210830161438 Attributes allows to modify the way how material is presented. Alternatively, they provide additional information to markup elements. To some degree, attributes are similar to [[HTML attributes|https://html.spec.whatwg.org/multipage/dom.html#global-attributes]]. -Typical use cases for attributes are to specify the (natural) [[language|00001007050100]] for a text region, to specify the [[programming language|00001007050200]] for highlighting program code, or to make white space visible in raw text. +Typical use cases for attributes are to specify the (natural) [[language|00001007050100]] for a text region, to specify the [[programming language|00001007050200]] for highlighting program code, or to make white space visible in plain text. Attributes are specified within curly brackets ``{...}``. Of course, more than one attribute can be specified. Attributes are separated by a sequence of space characters or by a comma character. @@ -45,11 +46,11 @@ In ``{=key1 =key2}``, the first key is ignored. Therefore it is equivalent to ``{=key2}``. The key ""''-''"" (just hyphen-minus) is special. It is called //default attribute//{-} and has a markup specific meaning. -For example, when used for raw text, it replaces the non-visible space with a visible representation: +For example, when used for plain text, it replaces the non-visible space with a visible representation: * ++``Hello, world``{-}++ produces ==Hello, world=={-}. * ++``Hello, world``++ produces ==Hello, world==. For some block elements, there is a syntax variant if you only want to specify a generic attribute. Index: docs/manual/00001007060000.zettel ================================================================== --- docs/manual/00001007060000.zettel +++ docs/manual/00001007060000.zettel @@ -1,10 +1,11 @@ id: 00001007060000 title: Zettelmarkup: Summary of Formatting Characters +role: manual tags: #manual #reference #zettelmarkup #zettelstore syntax: zmk -role: manual +modified: 20210728162830 The following table gives an overview about the use of all characters that begin a markup element. |= Character :|= Blocks <|= Inlines < | ''!'' | (free) | (free) @@ -27,15 +28,15 @@ | ''<'' | [[Quotation block|00001007030600]] | [[Short inline quote|00001007040100]] | ''='' | [[Headings|00001007030300]] | [[Computer output|00001007040200]] | ''>'' | [[Quotation lists|00001007030200]] | (blocked: to remove anyambiguity with quotation lists) | ''?'' | (free) | (free) | ''@'' | (free) | (reserved) -| ''['' | (reserved) | [[Link|00001007040300]], [[citation key|00001007040300]], [[footnote|00001007040300]], [[mark|00001007040300]] +| ''['' | (reserved) | [[Linked material|00001007040300]], [[citation key|00001007040300]], [[footnote|00001007040300]], [[mark|00001007040300]] | ''\\'' | (blocked by inline meaning) | [[Escape character|00001007040000]] | '']'' | (reserved) | End of link, citation key, footnote, mark | ''^'' | (free) | [[Superscripted text|00001007040100]] | ''_'' | (free) | [[Underlined text|00001007040100]] | ''`'' | [[Verbatim block|00001007030500]] | [[Literal text|00001007040200]] -| ''{'' | (reserved) | [[Image|00001007040300]], [[Attribute|00001007050000]] -| ''|'' | [[Table row / table cell|00001007031000]] | Separator within link and image formatting -| ''}'' | (reserved) | End of Image, End of Attribute +| ''{'' | (reserved) | [[Embedded material|00001007040300]], [[Attribute|00001007050000]] +| ''|'' | [[Table row / table cell|00001007031000]] | Separator within link and embed formatting +| ''}'' | (reserved) | End of embedded material, End of Attribute | ''~'' | (free) | [[Strike-through text|00001007040100]] Index: docs/manual/00001008010000.zettel ================================================================== --- docs/manual/00001008010000.zettel +++ docs/manual/00001008010000.zettel @@ -1,11 +1,11 @@ id: 00001008010000 title: Use Markdown within Zettelstore role: manual tags: #manual #markdown #zettelstore syntax: zmk -modified: 20210518102155 +modified: 20210726191610 If you are customized to use Markdown as your markup language, you can configure Zettelstore to support your decision. Zettelstore supports [[CommonMark|https://commonmark.org/]], an [[attempt|https://xkcd.com/927/]] to unify all the different, divergent dialects of Markdown. === Use Markdown as the default markup language of Zettelstore @@ -40,8 +40,8 @@ When a zettel is displayed, JavaScript code might be executed, sometimes with harmful results. Zettelstore mitigates this problem by ignoring suspicious text when it encodes a zettel as HTML. Any HTML text that might contain the ``