Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From trunk To v0.10.1
2024-11-28
| ||
13:29 | Manual: remove info about previous new-style zids; fix some typos ... (Leaf check-in: d5f6fa92b2 user: stern tags: trunk) | |
2024-11-27
| ||
17:18 | Remove support of mapping zids to previous new-style zids. ... (check-in: ca58093de8 user: stern tags: trunk) | |
2023-01-30
| ||
12:49 | Merge in 0.10.1 ... (check-in: ed2d3ce05d user: stern tags: trunk) | |
12:40 | Version 0.10.1 ... (Leaf check-in: 70b0cdc454 user: stern tags: release, release-0.10, v0.10.1) | |
2023-01-24
| ||
21:47 | Version 0.10.0 ... (check-in: b6301cf091 user: stern tags: trunk, release, v0.10.0) | |
Changes to .fossil-settings/ignore-glob.
1 2 | bin/* releases/* | > | 1 2 3 | bin/* releases/* parser/pikchr/*.out |
Changes to LICENSE.txt.
|
| | | 1 2 3 4 5 6 7 8 | Copyright (c) 2020-2023 Detlef Stern Licensed under the EUPL Zettelstore is licensed under the European Union Public License, version 1.2 or later (EUPL v. 1.2). The license is available in the official languages of the EU. The English version is included here. Please see https://joinup.ec.europa.eu/community/eupl/og_page/eupl for official |
︙ | ︙ |
Changes to Makefile.
1 |
| | | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | ## Copyright (c) 2020-2022 Detlef Stern ## ## This file is part of Zettelstore. ## ## Zettelstore is licensed under the latest version of the EUPL (European Union ## Public License). Please see file LICENSE.txt for your rights and obligations ## under this license. .PHONY: check relcheck api build release clean check: go run tools/build.go check relcheck: go run tools/build.go relcheck api: go run tools/build.go testapi version: @echo $(shell go run tools/build.go version) build: go run tools/build.go build release: go run tools/build.go release clean: go run tools/build.go clean |
Changes to README.md.
︙ | ︙ | |||
9 10 11 12 13 14 15 | gradually, one major focus is a long-term store of these notes, hence the name “Zettelstore”. To get an initial impression, take a look at the [manual](https://zettelstore.de/manual/). It is a live example of the zettelstore software, running in read-only mode. | | | > | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | gradually, one major focus is a long-term store of these notes, hence the name “Zettelstore”. To get an initial impression, take a look at the [manual](https://zettelstore.de/manual/). It is a live example of the zettelstore software, running in read-only mode. [Zettelstore Client](https://zettelstore.de/client) provides client software to access Zettelstore via its API more easily, [Zettelstore Contrib](https://zettelstore.de/contrib) contains contributed software, which often connects to Zettelstore via its API. Some of the software packages may be experimental. The software, including the manual, is licensed under the [European Union Public License 1.2 (or later)](https://zettelstore.de/home/file?name=LICENSE.txt&ci=trunk). [Stay](https://twitter.com/zettelstore) <a rel="me" href="https://nerdculture.de/@zettelstore">tuned</a> … |
Changes to VERSION.
|
| | | 1 | 0.10.1 |
Changes to ast/ast.go.
1 | //----------------------------------------------------------------------------- | | < < < | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package ast provides the abstract syntax tree for parsed zettel content. package ast import ( "net/url" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) // ZettelNode is the root node of the abstract syntax tree. // It is *not* part of the visitor pattern. type ZettelNode struct { Meta *meta.Meta // Original metadata Content domain.Content // Original content Zid id.Zid // Zettel identification. InhMeta *meta.Meta // Metadata of the zettel, with inherited values. Ast BlockSlice // Zettel abstract syntax tree is a sequence of block nodes. Syntax string // Syntax / parser that produced the Ast } // Node is the interface, all nodes must implement. |
︙ | ︙ |
Changes to ast/block.go.
1 | //----------------------------------------------------------------------------- | | < < < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2023 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package ast import "zettelstore.de/c/attrs" // Definition of Block nodes. // BlockSlice is a slice of BlockNodes. type BlockSlice []BlockNode func (*BlockSlice) blockNode() { /* Just a marker */ } |
︙ | ︙ |
Changes to ast/inline.go.
1 | //----------------------------------------------------------------------------- | | < < < > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package ast import ( "unicode/utf8" "zettelstore.de/c/attrs" ) // Definitions of inline nodes. // InlineSlice is a list of BlockNodes. type InlineSlice []InlineNode func (*InlineSlice) inlineNode() { /* Just a marker */ } // CreateInlineSliceFromWords makes a new inline list from words, // that will be space-separated. func CreateInlineSliceFromWords(words ...string) InlineSlice { inl := make(InlineSlice, 0, 2*len(words)-1) for i, word := range words { if i > 0 { inl = append(inl, &SpaceNode{Lexeme: " "}) } inl = append(inl, &TextNode{Text: word}) } return inl } // WalkChildren walks down to the list. func (is *InlineSlice) WalkChildren(v Visitor) { for _, in := range *is { Walk(v, in) } } // -------------------------------------------------------------------------- // TextNode just contains some text. type TextNode struct { Text string // The text itself. } func (*TextNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. func (*TextNode) WalkChildren(Visitor) { /* No children*/ } // -------------------------------------------------------------------------- // SpaceNode tracks inter-word space characters. type SpaceNode struct { Lexeme string } func (*SpaceNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. func (*SpaceNode) WalkChildren(Visitor) { /* No children*/ } // Count returns the number of space runes. func (sn *SpaceNode) Count() int { return utf8.RuneCountInString(sn.Lexeme) } // -------------------------------------------------------------------------- // BreakNode signals a new line that must / should be interpreted as a new line break. type BreakNode struct { Hard bool // Hard line break? } |
︙ | ︙ | |||
162 163 164 165 166 167 168 | // FormatKind specifies the format that is applied to the inline nodes. type FormatKind int // Constants for FormatCode const ( _ FormatKind = iota | | | | | | | | < | | 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | // FormatKind specifies the format that is applied to the inline nodes. type FormatKind int // Constants for FormatCode const ( _ FormatKind = iota FormatEmph // Emphasized text. FormatStrong // Strongly emphasized text. FormatInsert // Inserted text. FormatDelete // Deleted text. FormatSuper // Superscripted text. FormatSub // SubscriptedText. FormatQuote // Quoted text. FormatSpan // Generic inline container. ) func (*FormatNode) inlineNode() { /* Just a marker */ } // WalkChildren walks to the formatted text. func (fn *FormatNode) WalkChildren(v Visitor) { Walk(v, &fn.Inlines) } |
︙ | ︙ |
Changes to ast/ref.go.
1 | //----------------------------------------------------------------------------- | | < < < < | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package ast import ( "net/url" "strings" "zettelstore.de/z/domain/id" ) // QueryPrefix is the prefix that denotes a query expression. const QueryPrefix = "query:" // ParseReference parses a string and returns a reference. func ParseReference(s string) *Reference { if invalidReference(s) { return &Reference{URL: nil, Value: s, State: RefStateInvalid} } if strings.HasPrefix(s, QueryPrefix) { |
︙ | ︙ |
Changes to ast/ref_test.go.
1 | //----------------------------------------------------------------------------- | | < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package ast_test import ( "testing" |
︙ | ︙ |
Changes to ast/walk.go.
1 | //----------------------------------------------------------------------------- | | < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package ast // Visitor is a visitor for walking the AST. type Visitor interface { Visit(node Node) Visitor |
︙ | ︙ |
Changes to ast/walk_test.go.
1 | //----------------------------------------------------------------------------- | | < < < | | | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package ast_test import ( "testing" "zettelstore.de/c/attrs" "zettelstore.de/z/ast" ) func BenchmarkWalk(b *testing.B) { root := ast.BlockSlice{ &ast.HeadingNode{ Inlines: ast.CreateInlineSliceFromWords("A", "Simple", "Heading"), }, &ast.ParaNode{ Inlines: ast.CreateInlineSliceFromWords("This", "is", "the", "introduction."), }, &ast.NestedListNode{ Kind: ast.NestedListUnordered, Items: []ast.ItemSlice{ []ast.ItemNode{ &ast.ParaNode{ Inlines: ast.CreateInlineSliceFromWords("Item", "1"), }, }, []ast.ItemNode{ &ast.ParaNode{ Inlines: ast.CreateInlineSliceFromWords("Item", "2"), }, }, }, }, &ast.ParaNode{ Inlines: ast.CreateInlineSliceFromWords("This", "is", "some", "intermediate", "text."), }, ast.CreateParaNode( &ast.FormatNode{ Kind: ast.FormatEmph, Attrs: attrs.Attributes(map[string]string{ "": "class", "color": "green", }), Inlines: ast.CreateInlineSliceFromWords("This", "is", "some", "emphasized", "text."), }, &ast.SpaceNode{Lexeme: " "}, &ast.LinkNode{ Ref: &ast.Reference{Value: "http://zettelstore.de"}, Inlines: ast.CreateInlineSliceFromWords("URL", "text."), }, ), } v := benchVisitor{} b.ResetTimer() for n := 0; n < b.N; n++ { ast.Walk(&v, &root) } } type benchVisitor struct{} func (bv *benchVisitor) Visit(ast.Node) ast.Visitor { return bv } |
Changes to auth/auth.go.
1 | //----------------------------------------------------------------------------- | | < < < | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package auth provides services for authentification / authorization. package auth import ( "time" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) // BaseManager allows to check some base auth modes. type BaseManager interface { // IsReadonly returns true, if the systems is configured to run in read-only-mode. IsReadonly() bool } |
︙ | ︙ | |||
41 42 43 44 45 46 47 | // TokenKind specifies for which application / usage a token is/was requested. type TokenKind int // Allowed values of token kind const ( _ TokenKind = iota | | | | 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | // TokenKind specifies for which application / usage a token is/was requested. type TokenKind int // Allowed values of token kind const ( _ TokenKind = iota KindJSON KindHTML ) // TokenData contains some important elements from a token. type TokenData struct { Token []byte Now time.Time Issued time.Time |
︙ | ︙ | |||
90 91 92 93 94 95 96 97 98 99 100 101 102 103 | CanCreate(user, newMeta *meta.Meta) bool // User is allowed to read zettel CanRead(user, m *meta.Meta) bool // User is allowed to write zettel. CanWrite(user, oldMeta, newMeta *meta.Meta) bool // User is allowed to delete zettel. CanDelete(user, m *meta.Meta) bool // User is allowed to refresh box data. CanRefresh(user *meta.Meta) bool } | > > > | 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | CanCreate(user, newMeta *meta.Meta) bool // User is allowed to read zettel CanRead(user, m *meta.Meta) bool // User is allowed to write zettel. CanWrite(user, oldMeta, newMeta *meta.Meta) bool // User is allowed to rename zettel CanRename(user, m *meta.Meta) bool // User is allowed to delete zettel. CanDelete(user, m *meta.Meta) bool // User is allowed to refresh box data. CanRefresh(user *meta.Meta) bool } |
Changes to auth/cred/cred.go.
1 | //----------------------------------------------------------------------------- | | < < < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package cred provides some function for handling credentials. package cred import ( "bytes" "golang.org/x/crypto/bcrypt" "zettelstore.de/z/domain/id" ) // HashCredential returns a hashed vesion of the given credential func HashCredential(zid id.Zid, ident, credential string) (string, error) { fullCredential := createFullCredential(zid, ident, credential) res, err := bcrypt.GenerateFromPassword(fullCredential, bcrypt.DefaultCost) if err != nil { |
︙ | ︙ | |||
43 44 45 46 47 48 49 | return false, nil } return false, err } func createFullCredential(zid id.Zid, ident, credential string) []byte { var buf bytes.Buffer | | | 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | return false, nil } return false, err } func createFullCredential(zid id.Zid, ident, credential string) []byte { var buf bytes.Buffer buf.WriteString(zid.String()) buf.WriteByte(' ') buf.WriteString(ident) buf.WriteByte(' ') buf.WriteString(credential) return buf.Bytes() } |
Deleted auth/impl/digest.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to auth/impl/impl.go.
1 | //----------------------------------------------------------------------------- | | < < < > | | < | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package impl provides services for authentification / authorization. package impl import ( "errors" "hash/fnv" "io" "time" "github.com/pascaldekloe/jwt" "zettelstore.de/c/api" "zettelstore.de/z/auth" "zettelstore.de/z/auth/policy" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" ) type myAuth struct { readonly bool owner id.Zid secret []byte } |
︙ | ︙ | |||
66 67 68 69 70 71 72 | } return h.Sum(nil) } // IsReadonly returns true, if the systems is configured to run in read-only-mode. func (a *myAuth) IsReadonly() bool { return a.readonly } | | < | | | < | > > > | | > > > > | > > | < | < | | < < < < < < < | > > > | < | | | | > > > > | > | | > > | < < > | | | < < < < < | | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | } return h.Sum(nil) } // IsReadonly returns true, if the systems is configured to run in read-only-mode. func (a *myAuth) IsReadonly() bool { return a.readonly } const reqHash = jwt.HS512 // ErrNoIdent signals that the 'ident' key is missing. var ErrNoIdent = errors.New("auth: missing ident") // ErrOtherKind signals that the token was defined for another token kind. var ErrOtherKind = errors.New("auth: wrong token kind") // ErrNoZid signals that the 'zid' key is missing. var ErrNoZid = errors.New("auth: missing zettel id") // GetToken returns a token to be used for authentification. func (a *myAuth) GetToken(ident *meta.Meta, d time.Duration, kind auth.TokenKind) ([]byte, error) { subject, ok := ident.Get(api.KeyUserID) if !ok || subject == "" { return nil, ErrNoIdent } now := time.Now().Round(time.Second) claims := jwt.Claims{ Registered: jwt.Registered{ Subject: subject, Expires: jwt.NewNumericTime(now.Add(d)), Issued: jwt.NewNumericTime(now), }, Set: map[string]interface{}{ "zid": ident.Zid.String(), "_tk": int(kind), }, } token, err := claims.HMACSign(reqHash, a.secret) if err != nil { return nil, err } return token, nil } // ErrTokenExpired signals an exired token var ErrTokenExpired = errors.New("auth: token expired") // CheckToken checks the validity of the token and returns relevant data. func (a *myAuth) CheckToken(token []byte, k auth.TokenKind) (auth.TokenData, error) { h, err := jwt.NewHMAC(reqHash, a.secret) if err != nil { return auth.TokenData{}, err } claims, err := h.Check(token) if err != nil { return auth.TokenData{}, err } now := time.Now().Round(time.Second) expires := claims.Expires.Time() if expires.Before(now) { return auth.TokenData{}, ErrTokenExpired } ident := claims.Subject if ident == "" { return auth.TokenData{}, ErrNoIdent } if zidS, ok := claims.Set["zid"].(string); ok { if zid, err2 := id.Parse(zidS); err2 == nil { if kind, ok2 := claims.Set["_tk"].(float64); ok2 { if auth.TokenKind(kind) == k { return auth.TokenData{ Token: token, Now: now, Issued: claims.Issued.Time(), Expires: expires, Ident: ident, Zid: zid, }, nil } } return auth.TokenData{}, ErrOtherKind } } return auth.TokenData{}, ErrNoZid } func (a *myAuth) Owner() id.Zid { return a.owner } func (a *myAuth) IsOwner(zid id.Zid) bool { return zid.IsValid() && zid == a.owner } |
︙ | ︙ |
Changes to auth/policy/anon.go.
1 | //----------------------------------------------------------------------------- | | < < < | > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package policy import ( "zettelstore.de/z/auth" "zettelstore.de/z/config" "zettelstore.de/z/domain/meta" ) type anonPolicy struct { authConfig config.AuthConfig pre auth.Policy } func (ap *anonPolicy) CanCreate(user, newMeta *meta.Meta) bool { return ap.pre.CanCreate(user, newMeta) } func (ap *anonPolicy) CanRead(user, m *meta.Meta) bool { return ap.pre.CanRead(user, m) && ap.checkVisibility(m) } func (ap *anonPolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool { return ap.pre.CanWrite(user, oldMeta, newMeta) && ap.checkVisibility(oldMeta) } func (ap *anonPolicy) CanRename(user, m *meta.Meta) bool { return ap.pre.CanRename(user, m) && ap.checkVisibility(m) } func (ap *anonPolicy) CanDelete(user, m *meta.Meta) bool { return ap.pre.CanDelete(user, m) && ap.checkVisibility(m) } func (ap *anonPolicy) CanRefresh(user *meta.Meta) bool { if ap.authConfig.GetExpertMode() || ap.authConfig.GetSimpleMode() { |
︙ | ︙ |
Changes to auth/policy/box.go.
1 | //----------------------------------------------------------------------------- | | < < < | | | | | | > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package policy import ( "context" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/query" "zettelstore.de/z/web/server" ) // BoxWithPolicy wraps the given box inside a policy box. func BoxWithPolicy( manager auth.AuthzManager, box box.Box, authConfig config.AuthConfig, ) (box.Box, auth.Policy) { pol := newPolicy(manager, authConfig) return newBox(box, pol), pol } // polBox implements a policy box. type polBox struct { box box.Box |
︙ | ︙ | |||
50 51 52 53 54 55 56 | return pp.box.Location() } func (pp *polBox) CanCreateZettel(ctx context.Context) bool { return pp.box.CanCreateZettel(ctx) } | | | | | | | | | < < < < > > > > > > > > | | | | | | | > > > > > > > > > > > > > > > > | | < < < < < < < < | 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | return pp.box.Location() } func (pp *polBox) CanCreateZettel(ctx context.Context) bool { return pp.box.CanCreateZettel(ctx) } func (pp *polBox) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) { user := server.GetUser(ctx) if pp.policy.CanCreate(user, zettel.Meta) { return pp.box.CreateZettel(ctx, zettel) } return id.Invalid, box.NewErrNotAllowed("Create", user, id.Invalid) } func (pp *polBox) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) { zettel, err := pp.box.GetZettel(ctx, zid) if err != nil { return domain.Zettel{}, err } user := server.GetUser(ctx) if pp.policy.CanRead(user, zettel.Meta) { return zettel, nil } return domain.Zettel{}, box.NewErrNotAllowed("GetZettel", user, zid) } func (pp *polBox) GetAllZettel(ctx context.Context, zid id.Zid) ([]domain.Zettel, error) { return pp.box.GetAllZettel(ctx, zid) } func (pp *polBox) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { m, err := pp.box.GetMeta(ctx, zid) if err != nil { return nil, err } user := server.GetUser(ctx) if pp.policy.CanRead(user, m) { return m, nil } return nil, box.NewErrNotAllowed("GetMeta", user, zid) } func (pp *polBox) GetAllMeta(ctx context.Context, zid id.Zid) ([]*meta.Meta, error) { return pp.box.GetAllMeta(ctx, zid) } func (pp *polBox) FetchZids(ctx context.Context) (id.Set, error) { return nil, box.NewErrNotAllowed("fetch-zids", server.GetUser(ctx), id.Invalid) } func (pp *polBox) SelectMeta(ctx context.Context, q *query.Query) ([]*meta.Meta, error) { user := server.GetUser(ctx) canRead := pp.policy.CanRead q = q.SetPreMatch(func(m *meta.Meta) bool { return canRead(user, m) }) return pp.box.SelectMeta(ctx, q) } func (pp *polBox) CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool { return pp.box.CanUpdateZettel(ctx, zettel) } func (pp *polBox) UpdateZettel(ctx context.Context, zettel domain.Zettel) error { zid := zettel.Meta.Zid user := server.GetUser(ctx) if !zid.IsValid() { return &box.ErrInvalidID{Zid: zid} } // Write existing zettel oldMeta, err := pp.box.GetMeta(ctx, zid) if err != nil { return err } if pp.policy.CanWrite(user, oldMeta, zettel.Meta) { return pp.box.UpdateZettel(ctx, zettel) } return box.NewErrNotAllowed("Write", user, zid) } func (pp *polBox) AllowRenameZettel(ctx context.Context, zid id.Zid) bool { return pp.box.AllowRenameZettel(ctx, zid) } func (pp *polBox) RenameZettel(ctx context.Context, curZid, newZid id.Zid) error { meta, err := pp.box.GetMeta(ctx, curZid) if err != nil { return err } user := server.GetUser(ctx) if pp.policy.CanRename(user, meta) { return pp.box.RenameZettel(ctx, curZid, newZid) } return box.NewErrNotAllowed("Rename", user, curZid) } func (pp *polBox) CanDeleteZettel(ctx context.Context, zid id.Zid) bool { return pp.box.CanDeleteZettel(ctx, zid) } func (pp *polBox) DeleteZettel(ctx context.Context, zid id.Zid) error { meta, err := pp.box.GetMeta(ctx, zid) if err != nil { return err } user := server.GetUser(ctx) if pp.policy.CanDelete(user, meta) { return pp.box.DeleteZettel(ctx, zid) } return box.NewErrNotAllowed("Delete", user, zid) } func (pp *polBox) Refresh(ctx context.Context) error { user := server.GetUser(ctx) if pp.policy.CanRefresh(user) { return pp.box.Refresh(ctx) } return box.NewErrNotAllowed("Refresh", user, id.Invalid) } |
Changes to auth/policy/default.go.
1 | //----------------------------------------------------------------------------- | | < < < | | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package policy import ( "zettelstore.de/c/api" "zettelstore.de/z/auth" "zettelstore.de/z/domain/meta" ) type defaultPolicy struct { manager auth.AuthzManager } func (*defaultPolicy) CanCreate(_, _ *meta.Meta) bool { return true } func (*defaultPolicy) CanRead(_, _ *meta.Meta) bool { return true } func (d *defaultPolicy) CanWrite(user, oldMeta, _ *meta.Meta) bool { return d.canChange(user, oldMeta) } func (d *defaultPolicy) CanRename(user, m *meta.Meta) bool { return d.canChange(user, m) } func (d *defaultPolicy) CanDelete(user, m *meta.Meta) bool { return d.canChange(user, m) } func (*defaultPolicy) CanRefresh(user *meta.Meta) bool { return user != nil } func (d *defaultPolicy) canChange(user, m *meta.Meta) bool { metaRo, ok := m.Get(api.KeyReadOnly) if !ok { |
︙ | ︙ |
Changes to auth/policy/owner.go.
1 | //----------------------------------------------------------------------------- | | < < < | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package policy import ( "zettelstore.de/c/api" "zettelstore.de/z/auth" "zettelstore.de/z/config" "zettelstore.de/z/domain/meta" ) type ownerPolicy struct { manager auth.AuthzManager authConfig config.AuthConfig pre auth.Policy } |
︙ | ︙ | |||
110 111 112 113 114 115 116 117 118 119 120 121 122 123 | } switch userRole := o.manager.GetUserRole(user); userRole { case meta.UserRoleReader, meta.UserRoleCreator: return false } return o.userCanCreate(user, newMeta) } func (o *ownerPolicy) CanDelete(user, m *meta.Meta) bool { if user == nil || !o.pre.CanDelete(user, m) { return false } if res, ok := o.checkVisibility(user, o.authConfig.GetVisibility(m)); ok { return res | > > > > > > > > > > | 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | } switch userRole := o.manager.GetUserRole(user); userRole { case meta.UserRoleReader, meta.UserRoleCreator: return false } return o.userCanCreate(user, newMeta) } func (o *ownerPolicy) CanRename(user, m *meta.Meta) bool { if user == nil || !o.pre.CanRename(user, m) { return false } if res, ok := o.checkVisibility(user, o.authConfig.GetVisibility(m)); ok { return res } return o.userIsOwner(user) } func (o *ownerPolicy) CanDelete(user, m *meta.Meta) bool { if user == nil || !o.pre.CanDelete(user, m) { return false } if res, ok := o.checkVisibility(user, o.authConfig.GetVisibility(m)); ok { return res |
︙ | ︙ |
Changes to auth/policy/policy.go.
1 | //----------------------------------------------------------------------------- | | < < < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package policy provides some interfaces and implementation for authorizsation policies. package policy import ( "zettelstore.de/z/auth" "zettelstore.de/z/config" "zettelstore.de/z/domain/meta" ) // newPolicy creates a policy based on given constraints. func newPolicy(manager auth.AuthzManager, authConfig config.AuthConfig) auth.Policy { var pol auth.Policy if manager.IsReadonly() { pol = &roPolicy{} |
︙ | ︙ | |||
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | return m != nil && p.post.CanRead(user, m) } func (p *prePolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool { return oldMeta != nil && newMeta != nil && oldMeta.Zid == newMeta.Zid && p.post.CanWrite(user, oldMeta, newMeta) } func (p *prePolicy) CanDelete(user, m *meta.Meta) bool { return m != nil && p.post.CanDelete(user, m) } func (p *prePolicy) CanRefresh(user *meta.Meta) bool { return p.post.CanRefresh(user) } | > > > > | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | return m != nil && p.post.CanRead(user, m) } func (p *prePolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool { return oldMeta != nil && newMeta != nil && oldMeta.Zid == newMeta.Zid && p.post.CanWrite(user, oldMeta, newMeta) } func (p *prePolicy) CanRename(user, m *meta.Meta) bool { return m != nil && p.post.CanRename(user, m) } func (p *prePolicy) CanDelete(user, m *meta.Meta) bool { return m != nil && p.post.CanDelete(user, m) } func (p *prePolicy) CanRefresh(user *meta.Meta) bool { return p.post.CanRefresh(user) } |
Changes to auth/policy/policy_test.go.
1 | //----------------------------------------------------------------------------- | | < < < | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package policy import ( "fmt" "testing" "zettelstore.de/c/api" "zettelstore.de/z/auth" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) func TestPolicies(t *testing.T) { t.Parallel() testScene := []struct { readonly bool withAuth bool |
︙ | ︙ | |||
55 56 57 58 59 60 61 62 63 64 65 66 67 68 | ) name := fmt.Sprintf("readonly=%v/withauth=%v/expert=%v/simple=%v", ts.readonly, ts.withAuth, ts.expert, ts.simple) t.Run(name, func(tt *testing.T) { testCreate(tt, pol, ts.withAuth, ts.readonly) testRead(tt, pol, ts.withAuth, ts.expert) testWrite(tt, pol, ts.withAuth, ts.readonly, ts.expert) testDelete(tt, pol, ts.withAuth, ts.readonly, ts.expert) testRefresh(tt, pol, ts.withAuth, ts.expert, ts.simple) }) } } type testAuthzManager struct { | > | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | ) name := fmt.Sprintf("readonly=%v/withauth=%v/expert=%v/simple=%v", ts.readonly, ts.withAuth, ts.expert, ts.simple) t.Run(name, func(tt *testing.T) { 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) testRefresh(tt, pol, ts.withAuth, ts.expert, ts.simple) }) } } type testAuthzManager struct { |
︙ | ︙ | |||
389 390 391 392 393 394 395 396 397 398 399 400 401 402 | {writer, roTrue, roTrue, false}, {owner, roTrue, roTrue, false}, {owner2, roTrue, roTrue, false}, } for _, tc := range testCases { t.Run("Write", func(tt *testing.T) { got := pol.CanWrite(tc.user, tc.old, tc.new) if tc.exp != got { tt.Errorf("exp=%v, but got=%v", tc.exp, got) } }) } } | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 | {writer, roTrue, roTrue, false}, {owner, roTrue, roTrue, false}, {owner2, roTrue, roTrue, false}, } for _, tc := range testCases { t.Run("Write", func(tt *testing.T) { got := pol.CanWrite(tc.user, tc.old, tc.new) if tc.exp != got { tt.Errorf("exp=%v, but got=%v", tc.exp, got) } }) } } func testRename(t *testing.T, pol auth.Policy, withAuth, readonly, expert bool) { t.Helper() anonUser := newAnon() creator := newCreator() reader := newReader() writer := newWriter() owner := newOwner() owner2 := newOwner2() zettel := newZettel() expertZettel := newExpertZettel() roFalse := newRoFalseZettel() roTrue := newRoTrueZettel() roReader := newRoReaderZettel() roWriter := newRoWriterZettel() roOwner := newRoOwnerZettel() notAuthNotReadonly := !withAuth && !readonly testCases := []struct { user *meta.Meta meta *meta.Meta exp bool }{ // No meta {anonUser, nil, false}, {creator, nil, false}, {reader, nil, false}, {writer, nil, false}, {owner, nil, false}, {owner2, nil, false}, // Any zettel {anonUser, zettel, notAuthNotReadonly}, {creator, zettel, notAuthNotReadonly}, {reader, zettel, notAuthNotReadonly}, {writer, zettel, notAuthNotReadonly}, {owner, zettel, !readonly}, {owner2, zettel, !readonly}, // Expert zettel {anonUser, expertZettel, notAuthNotReadonly && expert}, {creator, expertZettel, notAuthNotReadonly && expert}, {reader, expertZettel, notAuthNotReadonly && expert}, {writer, expertZettel, notAuthNotReadonly && expert}, {owner, expertZettel, !readonly && expert}, {owner2, expertZettel, !readonly && expert}, // No r/o zettel {anonUser, roFalse, notAuthNotReadonly}, {creator, roFalse, notAuthNotReadonly}, {reader, roFalse, notAuthNotReadonly}, {writer, roFalse, notAuthNotReadonly}, {owner, roFalse, !readonly}, {owner2, roFalse, !readonly}, // Reader r/o zettel {anonUser, roReader, false}, {creator, roReader, false}, {reader, roReader, false}, {writer, roReader, notAuthNotReadonly}, {owner, roReader, !readonly}, {owner2, roReader, !readonly}, // Writer r/o zettel {anonUser, roWriter, false}, {creator, roWriter, false}, {reader, roWriter, false}, {writer, roWriter, false}, {owner, roWriter, !readonly}, {owner2, roWriter, !readonly}, // Owner r/o zettel {anonUser, roOwner, false}, {creator, roOwner, false}, {reader, roOwner, false}, {writer, roOwner, false}, {owner, roOwner, false}, {owner2, roOwner, false}, // r/o = true zettel {anonUser, roTrue, false}, {creator, roTrue, false}, {reader, roTrue, false}, {writer, roTrue, false}, {owner, roTrue, false}, {owner2, roTrue, false}, } for _, tc := range testCases { t.Run("Rename", func(tt *testing.T) { got := pol.CanRename(tc.user, tc.meta) if tc.exp != got { tt.Errorf("exp=%v, but got=%v", tc.exp, got) } }) } } |
︙ | ︙ |
Changes to auth/policy/readonly.go.
1 | //----------------------------------------------------------------------------- | | < < < | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package policy import "zettelstore.de/z/domain/meta" type roPolicy struct{} func (*roPolicy) CanCreate(_, _ *meta.Meta) bool { return false } func (*roPolicy) CanRead(_, _ *meta.Meta) bool { return true } func (*roPolicy) CanWrite(_, _, _ *meta.Meta) bool { return false } func (*roPolicy) CanRename(_, _ *meta.Meta) bool { return false } func (*roPolicy) CanDelete(_, _ *meta.Meta) bool { return false } func (*roPolicy) CanRefresh(user *meta.Meta) bool { return user != nil } |
Changes to box/box.go.
1 | //----------------------------------------------------------------------------- | | < < < > > | | | | | > > > > > > > | > > > > > > > > > > > > > > > < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < | < < < < | | > > > < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package box provides a generic interface to zettel boxes. package box import ( "context" "errors" "fmt" "io" "net/url" "strconv" "time" "zettelstore.de/c/api" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/query" ) // BaseBox is implemented by all Zettel boxes. type BaseBox interface { // Location returns some information where the box is located. // Format is dependent of the box. Location() string // CanCreateZettel returns true, if box could possibly create a new zettel. CanCreateZettel(ctx context.Context) bool // CreateZettel creates a new zettel. // Returns the new zettel id (and an error indication). CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) // GetZettel retrieves a specific zettel. 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) // 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 // AllowRenameZettel returns true, if box will not disallow renaming the zettel. AllowRenameZettel(ctx context.Context, zid id.Zid) bool // RenameZettel changes the current Zid to a new Zid. RenameZettel(ctx context.Context, curZid, newZid id.Zid) error // CanDeleteZettel returns true, if box could possibly delete the given zettel. 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 // Apply identifier of every zettel to the given function, if predicate returns true. ApplyZid(context.Context, ZidFunc, query.RetrievePredicate) error // Apply metadata of every zettel to the given function, if predicate returns true. ApplyMeta(context.Context, MetaFunc, query.RetrievePredicate) error // ReadStats populates st with box statistics ReadStats(st *ManagedBoxStats) } // ManagedBoxStats records statistics about the box. type ManagedBoxStats struct { // ReadOnly indicates that the content of a box cannot change. ReadOnly bool // Zettel is the number of zettel managed by the box. Zettel int } // StartStopper performs simple lifecycle management. type StartStopper interface { // Start the box. Now all other functions of the box are allowed. // Starting an already started box is not allowed. Start(ctx context.Context) error // Stop the started box. Now only the Start() function is allowed. Stop(ctx context.Context) } // Refresher allow to refresh their internal data. type Refresher interface { // Refresh the box data. Refresh(context.Context) } // 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, q *query.Query) ([]*meta.Meta, error) // GetAllZettel retrieves a specific zettel from all managed boxes. GetAllZettel(ctx context.Context, zid id.Zid) ([]domain.Zettel, error) // GetAllMeta retrieves the meta data of a specific zettel from all managed boxes. GetAllMeta(ctx context.Context, zid id.Zid) ([]*meta.Meta, error) // Refresh the data from the box and from its managed sub-boxes. Refresh(context.Context) error } // Stats record stattistics about a box. type Stats struct { // ReadOnly indicates that boxes cannot be modified. ReadOnly bool |
︙ | ︙ | |||
200 201 202 203 204 205 206 | // UpdateReason gives an indication, why the ObserverFunc was called. type UpdateReason uint8 // Values for Reason const ( _ UpdateReason = iota | < | < | < < < | 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | // UpdateReason gives an indication, why the ObserverFunc was called. type UpdateReason uint8 // Values for Reason const ( _ UpdateReason = iota OnReload // Box was reloaded OnZettel // Something with a zettel happened ) // UpdateInfo contains all the data about a changed zettel. type UpdateInfo struct { Box Box Reason UpdateReason Zid id.Zid } // UpdateFunc is a function to be called when a change is detected. type UpdateFunc func(UpdateInfo) // Subject is a box that notifies observers about changes. type Subject interface { // RegisterObserver registers an observer that will be notified // if one or all zettel are found to be changed. RegisterObserver(UpdateFunc) } |
︙ | ︙ | |||
244 245 246 247 248 249 250 | return context.WithValue(ctx, ctxNoEnrichKey, &ctxNoEnrichKey) } type ctxNoEnrichType struct{} var ctxNoEnrichKey ctxNoEnrichType | | | | | 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | return context.WithValue(ctx, ctxNoEnrichKey, &ctxNoEnrichKey) } type ctxNoEnrichType struct{} var ctxNoEnrichKey ctxNoEnrichType // DoNotEnrich determines if the context is marked to not enrich metadata. func DoNotEnrich(ctx context.Context) bool { _, ok := ctx.Value(ctxNoEnrichKey).(*ctxNoEnrichType) return ok } // NoEnrichQuery provides a context that signals not to enrich, if the query does not need this. func NoEnrichQuery(ctx context.Context, q *query.Query) context.Context { if q.EnrichNeeded() { return ctx } |
︙ | ︙ | |||
305 306 307 308 309 310 311 | // ErrStopped is returned if calling methods on a box that was not started. var ErrStopped = errors.New("box is stopped") // ErrReadOnly is returned if there is an attepmt to write to a read-only box. var ErrReadOnly = errors.New("read-only box") | | < | < | | | > > > > > > > > > > > > > > > > > > > > > > > > > | 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 | // ErrStopped is returned if calling methods on a box that was not started. var ErrStopped = errors.New("box is stopped") // ErrReadOnly is returned if there is an attepmt to write to a read-only box. var ErrReadOnly = errors.New("read-only box") // ErrNotFound is returned if a zettel was not found in the box. var ErrNotFound = errors.New("zettel not found") // ErrConflict is returned if a box operation detected a conflict.. // One example: if calculating a new zettel identifier takes too long. var ErrConflict = errors.New("conflict") // ErrCapacity is returned if a box has reached its capacity. var ErrCapacity = errors.New("capacity exceeded") // ErrInvalidID is returned if the zettel id is not appropriate for the box operation. type ErrInvalidID struct{ Zid id.Zid } func (err *ErrInvalidID) Error() string { return "invalid Zettel id: " + err.Zid.String() } // GetQueryBool is a helper function to extract bool values from a box URI. func GetQueryBool(u *url.URL, key string) bool { _, ok := u.Query()[key] return ok } // GetQueryInt is a helper function to extract int values of a specified range from a box URI. func GetQueryInt(u *url.URL, key string, min, def, max int) int { sVal := u.Query().Get(key) if sVal == "" { return def } iVal, err := strconv.Atoi(sVal) if err != nil { return def } if iVal < min { return min } if iVal > max { return max } return iVal } |
Changes to box/compbox/compbox.go.
1 | //----------------------------------------------------------------------------- | | < < < | > > > < < < | | | | | | < < < < < < | < < | > > > > > > > | | | | | | < | | | | > > > > | > > > > > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package compbox provides zettel that have computed content. package compbox import ( "context" "net/url" "zettelstore.de/c/api" "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/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/query" ) func init() { manager.Register( " comp", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { return getCompBox(cdata.Number, cdata.Enricher), nil }) } type compBox struct { log *logger.Logger number int enricher box.Enricher } var myConfig *meta.Meta var myZettel = map[id.Zid]struct { meta func(id.Zid) *meta.Meta content func(*meta.Meta) []byte }{ id.MustParse(api.ZidVersion): {genVersionBuildM, genVersionBuildC}, id.MustParse(api.ZidHost): {genVersionHostM, genVersionHostC}, id.MustParse(api.ZidOperatingSystem): {genVersionOSM, genVersionOSC}, id.MustParse(api.ZidLog): {genLogM, genLogC}, id.MustParse(api.ZidBoxManager): {genManagerM, genManagerC}, id.MustParse(api.ZidMetadataKey): {genKeysM, genKeysC}, id.MustParse(api.ZidParser): {genParserM, genParserC}, id.MustParse(api.ZidStartupConfiguration): {genConfigZettelM, genConfigZettelC}, } // Get returns the one program box. func getCompBox(boxNumber int, mf box.Enricher) box.ManagedBox { return &compBox{ log: kernel.Main.GetLogger(kernel.BoxService).Clone(). Str("box", "comp").Int("boxnum", int64(boxNumber)).Child(), number: boxNumber, enricher: mf, } } // Setup remembers important values. func Setup(cfg *meta.Meta) { myConfig = cfg.Clone() } func (*compBox) Location() string { return "" } func (*compBox) CanCreateZettel(context.Context) bool { return false } func (cb *compBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) { cb.log.Trace().Err(box.ErrReadOnly).Msg("CreateZettel") return id.Invalid, box.ErrReadOnly } func (cb *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 { cb.log.Trace().Msg("GetMeta/Content") return domain.Zettel{ Meta: m, Content: domain.NewContent(genContent(m)), }, nil } cb.log.Trace().Msg("GetMeta/NoContent") return domain.Zettel{Meta: m}, nil } } cb.log.Trace().Err(box.ErrNotFound).Msg("GetZettel/Err") return domain.Zettel{}, box.ErrNotFound } func (cb *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) cb.log.Trace().Msg("GetMeta") return m, nil } } } cb.log.Trace().Err(box.ErrNotFound).Msg("GetMeta/Err") return nil, box.ErrNotFound } func (cb *compBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error { cb.log.Trace().Int("entries", int64(len(myZettel))).Msg("ApplyMeta") for zid, gen := range myZettel { if !constraint(zid) { continue } if genMeta := gen.meta; genMeta != nil { if genMeta(zid) != nil { handle(zid) |
︙ | ︙ | |||
133 134 135 136 137 138 139 140 141 142 | cb.enricher.Enrich(ctx, m, cb.number) handle(m) } } } return nil } func (*compBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } | > > > > > > > > > > > > > > > > > > > > > | > < < < < < < < < < < < | 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | cb.enricher.Enrich(ctx, m, cb.number) handle(m) } } } return nil } func (*compBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return false } func (cb *compBox) UpdateZettel(context.Context, domain.Zettel) error { cb.log.Trace().Err(box.ErrReadOnly).Msg("UpdateZettel") return box.ErrReadOnly } func (*compBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool { _, ok := myZettel[zid] return !ok } func (cb *compBox) RenameZettel(_ context.Context, curZid, _ id.Zid) error { err := box.ErrNotFound if _, ok := myZettel[curZid]; ok { err = box.ErrReadOnly } cb.log.Trace().Err(err).Msg("RenameZettel") return err } func (*compBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } func (cb *compBox) DeleteZettel(_ context.Context, zid id.Zid) error { err := box.ErrNotFound if _, ok := myZettel[zid]; ok { err = box.ErrReadOnly } cb.log.Trace().Err(err).Msg("DeleteZettel") return err } func (cb *compBox) ReadStats(st *box.ManagedBoxStats) { st.ReadOnly = true st.Zettel = len(myZettel) cb.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats") } func updateMeta(m *meta.Meta) { if _, ok := m.Get(api.KeySyntax); !ok { m.Set(api.KeySyntax, meta.SyntaxZmk) } m.Set(api.KeyRole, api.ValueRoleConfiguration) m.Set(api.KeyLang, api.ValueLangEN) m.Set(api.KeyReadOnly, api.ValueTrue) if _, ok := m.Get(api.KeyVisibility); !ok { m.Set(api.KeyVisibility, api.ValueVisibilityExpert) } } |
Changes to box/compbox/config.go.
1 | //----------------------------------------------------------------------------- | | < < < < | | > > > | > > > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package compbox import ( "bytes" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" ) func genConfigZettelM(zid id.Zid) *meta.Meta { if myConfig == nil { return nil } m := meta.New(zid) m.Set(api.KeyTitle, "Zettelstore Startup Configuration") m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreStarted).(string)) m.Set(api.KeyVisibility, api.ValueVisibilityExpert) return m } func genConfigZettelC(*meta.Meta) []byte { var buf bytes.Buffer for i, p := range myConfig.Pairs() { if i > 0 { buf.WriteByte('\n') } buf.WriteString("; ''") buf.WriteString(p.Key) |
︙ | ︙ |
Changes to box/compbox/keys.go.
1 | //----------------------------------------------------------------------------- | | < < < < | | | | > | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package compbox import ( "bytes" "fmt" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" ) func genKeysM(zid id.Zid) *meta.Meta { m := meta.New(zid) m.Set(api.KeyTitle, "Zettelstore Supported Metadata Keys") m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVTime).(string)) m.Set(api.KeyVisibility, api.ValueVisibilityLogin) return m } func genKeysC(*meta.Meta) []byte { keys := meta.GetSortedKeyDescriptions() var buf bytes.Buffer buf.WriteString("|=Name<|=Type<|=Computed?:|=Property?:\n") for _, kd := range keys { fmt.Fprintf(&buf, "|%v|%v|%v|%v\n", kd.Name, kd.Type.Name, kd.IsComputed(), kd.IsProperty()) } return buf.Bytes() } |
Changes to box/compbox/log.go.
1 | //----------------------------------------------------------------------------- | | < < < < | | | | > | > | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package compbox import ( "bytes" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" ) func genLogM(zid id.Zid) *meta.Meta { m := meta.New(zid) m.Set(api.KeyTitle, "Zettelstore Log") m.Set(api.KeySyntax, meta.SyntaxText) m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreStarted).(string)) m.Set(api.KeyModified, kernel.Main.GetLastLogTime().Local().Format(id.ZidLayout)) return m } func genLogC(*meta.Meta) []byte { const tsFormat = "2006-01-02 15:04:05.999999" entries := kernel.Main.RetrieveLogEntries() var buf bytes.Buffer for _, entry := range entries { ts := entry.TS.Format(tsFormat) buf.WriteString(ts) for j := len(ts); j < len(tsFormat); j++ { |
︙ | ︙ |
Changes to box/compbox/manager.go.
1 | //----------------------------------------------------------------------------- | | < < < < > | | | > | > > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package compbox import ( "bytes" "fmt" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" ) func genManagerM(zid id.Zid) *meta.Meta { m := meta.New(zid) m.Set(api.KeyTitle, "Zettelstore Box Manager") m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreStarted).(string)) return m } func genManagerC(*meta.Meta) []byte { kvl := kernel.Main.GetServiceStatistics(kernel.BoxService) if len(kvl) == 0 { return nil } var buf bytes.Buffer buf.WriteString("|=Name|=Value>\n") for _, kv := range kvl { |
︙ | ︙ |
Deleted box/compbox/memory.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to box/compbox/parser.go.
1 | //----------------------------------------------------------------------------- | | < < < < | | | | | | > | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package compbox import ( "bytes" "fmt" "sort" "strings" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" "zettelstore.de/z/parser" ) func genParserM(zid id.Zid) *meta.Meta { m := meta.New(zid) m.Set(api.KeyTitle, "Zettelstore Supported Parser") m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVTime).(string)) m.Set(api.KeyVisibility, api.ValueVisibilityLogin) return m } func genParserC(*meta.Meta) []byte { var buf bytes.Buffer buf.WriteString("|=Syntax<|=Alt. Value(s):|=Text Parser?:|=Text Format?:|=Image Format?:\n") syntaxes := parser.GetSyntaxes() sort.Strings(syntaxes) for _, syntax := range syntaxes { info := parser.Get(syntax) if info.Name != syntax { continue } altNames := info.AltNames sort.Strings(altNames) fmt.Fprintf( &buf, "|%v|%v|%v|%v|%v\n", syntax, strings.Join(altNames, ", "), info.IsASTParser, info.IsTextFormat, info.IsImageFormat) } return buf.Bytes() } |
Deleted box/compbox/sx.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to box/compbox/version.go.
1 | //----------------------------------------------------------------------------- | | < < < < | < | | | > > > > > > > | | | > > | | > > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package compbox import ( "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" ) func getVersionMeta(zid id.Zid, title string) *meta.Meta { m := meta.New(zid) m.Set(api.KeyTitle, title) m.Set(api.KeyVisibility, api.ValueVisibilityExpert) return m } func genVersionBuildM(zid id.Zid) *meta.Meta { m := getVersionMeta(zid, "Zettelstore Version") m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVTime).(string)) m.Set(api.KeyVisibility, api.ValueVisibilityLogin) return m } func genVersionBuildC(*meta.Meta) []byte { return []byte(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) } func genVersionHostM(zid id.Zid) *meta.Meta { m := getVersionMeta(zid, "Zettelstore Host") m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreStarted).(string)) return m } func genVersionHostC(*meta.Meta) []byte { return []byte(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreHostname).(string)) } func genVersionOSM(zid id.Zid) *meta.Meta { m := getVersionMeta(zid, "Zettelstore Operating System") m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreStarted).(string)) return m } func genVersionOSC(*meta.Meta) []byte { goOS := kernel.Main.GetConfig(kernel.CoreService, kernel.CoreGoOS).(string) goArch := kernel.Main.GetConfig(kernel.CoreService, kernel.CoreGoArch).(string) result := make([]byte, 0, len(goOS)+len(goArch)+1) result = append(result, goOS...) result = append(result, '/') return append(result, goArch...) } |
Changes to box/constbox/base.css.
|
| < < < < < < < < < < < < < < > | 1 2 3 4 5 6 7 8 9 10 11 12 | *,*::before,*::after { box-sizing: border-box; } html { font-size: 1rem; font-family: serif; scroll-behavior: smooth; height: 100%; } body { margin: 0; min-height: 100vh; |
︙ | ︙ | |||
84 85 86 87 88 89 90 | .zs-dropdown:hover > .zs-dropdown-content { display: block } main { padding: 0 1rem } article > * + * { margin-top: .5rem } article header { padding: 0; margin: 0; } | | | | | | | | | < | | | | | | > | > < | | > > > | > | 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | .zs-dropdown:hover > .zs-dropdown-content { display: block } main { padding: 0 1rem } article > * + * { margin-top: .5rem } article header { padding: 0; margin: 0; } h1,h2,h3,h4,h5,h6 { font-family:sans-serif; font-weight:normal } h1 { font-size:1.5rem; margin:.65rem 0 } h2 { font-size:1.25rem; margin:.70rem 0 } h3 { font-size:1.15rem; margin:.75rem 0 } h4 { font-size:1.05rem; margin:.8rem 0; font-weight: bold } h5 { font-size:1.05rem; margin:.8rem 0 } h6 { font-size:1.05rem; margin:.8rem 0; font-weight: lighter } p { margin: .5rem 0 0 0 } li,figure,figcaption,dl { margin: 0 } dt { margin: .5rem 0 0 0 } dt+dd { margin-top: 0 } dd { margin: .5rem 0 0 2rem } dd > p:first-child { margin: 0 0 0 0 } blockquote { border-left: 0.5rem solid lightgray; padding-left: 1rem; margin-left: 1rem; margin-right: 2rem; font-style: italic; } blockquote p { margin-bottom: .5rem } blockquote cite { font-style: normal } table { border-collapse: collapse; border-spacing: 0; max-width: 100%; } thead>tr>td { border-bottom: 2px solid hsl(0, 0%, 70%); font-weight: bold } tfoot>tr>td { border-top: 2px solid hsl(0, 0%, 70%); font-weight: bold } td { text-align: left; padding: .25rem .5rem; border-bottom: 1px solid hsl(0, 0%, 85%) } main form { padding: 0 .5em; margin: .5em 0 0 0; } main form:after { content: "."; display: block; |
︙ | ︙ | |||
149 150 151 152 153 154 155 | input.zs-secondary { float:left } input.zs-upload { padding-left: 1em; padding-right: 1em; } a:not([class]) { text-decoration-skip-ink: auto } a.broken { text-decoration: line-through } | < | | | | | | | | | | | | | | | | | | | | | 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | input.zs-secondary { float:left } input.zs-upload { padding-left: 1em; padding-right: 1em; } a:not([class]) { text-decoration-skip-ink: auto } a.broken { text-decoration: line-through } img { max-width: 100% } img.right { float: right } ol.zs-endnotes { padding-top: .5rem; border-top: 1px solid; } kbd { font-family:monospace } code,pre { font-family: monospace; font-size: 85%; } code { padding: .1rem .2rem; background: #f0f0f0; border: 1px solid #ccc; border-radius: .25rem; } pre { padding: .5rem .7rem; max-width: 100%; overflow: auto; border: 1px solid #ccc; border-radius: .5rem; background: #f0f0f0; } pre code { font-size: 95%; position: relative; padding: 0; border: none; } div.zs-indication { padding: .5rem .7rem; max-width: 100%; border-radius: .5rem; border: 1px solid black; } div.zs-indication p:first-child { margin-top: 0 } span.zs-indication { border: 1px solid black; border-radius: .25rem; padding: .1rem .2rem; font-size: 95%; } .zs-info { background-color: lightblue; padding: .5rem 1rem; } .zs-warning { background-color: lightyellow; padding: .5rem 1rem; } .zs-error { background-color: lightpink; border-style: none !important; font-weight: bold; } td.left { text-align:left } td.center { text-align:center } td.right { text-align:right } .zs-font-size-0 { font-size:75% } .zs-font-size-1 { font-size:83% } .zs-font-size-2 { font-size:100% } .zs-font-size-3 { font-size:117% } .zs-font-size-4 { font-size:150% } .zs-font-size-5 { font-size:200% } .zs-deprecated { border-style: dashed; padding: .2rem } .zs-meta { font-size:.75rem; color:#444; margin-bottom:1rem; } .zs-meta a { color:#444 } h1+.zs-meta { margin-top:-1rem } nav > details { margin-top:1rem } details > summary { width: 100%; background-color: #eee; font-family:sans-serif; } details > ul { margin-top:0; padding-left:2rem; background-color: #eee; } footer { padding: 0 1rem } @media (prefers-reduced-motion: reduce) { * { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } |
Added box/constbox/base.mustache.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | <!DOCTYPE html> <html{{#Lang}} lang="{{Lang}}"{{/Lang}}> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="generator" content="Zettelstore"> <meta name="format-detection" content="telephone=no"> {{{MetaHeader}}} <link rel="stylesheet" href="{{{CSSBaseURL}}}"> <link rel="stylesheet" href="{{{CSSUserURL}}}"> {{#CSSRoleURL}}<link rel="stylesheet" href="{{{CSSRoleURL}}}">{{/CSSRoleURL}} <title>{{Title}}</title> </head> <body> <nav class="zs-menu"> <a href="{{{HomeURL}}}">Home</a> {{#WithUser}} <div class="zs-dropdown"> <button>User</button> <nav class="zs-dropdown-content"> {{#WithAuth}} {{#UserIsValid}} <a href="{{{UserZettelURL}}}">{{UserIdent}}</a> {{/UserIsValid}} {{^UserIsValid}} <a href="{{{LoginURL}}}">Login</a> {{/UserIsValid}} {{#UserIsValid}} <a href="{{{LogoutURL}}}">Logout</a> {{/UserIsValid}} {{/WithAuth}} </nav> </div> {{/WithUser}} <div class="zs-dropdown"> <button>Lists</button> <nav class="zs-dropdown-content"> <a href="{{{ListZettelURL}}}">List Zettel</a> <a href="{{{ListRolesURL}}}">List Roles</a> <a href="{{{ListTagsURL}}}">List Tags</a> {{#CanRefresh}} <a href="{{{RefreshURL}}}">Refresh</a> {{/CanRefresh}} </nav> </div> {{#NewZettelLinks.Has}} <div class="zs-dropdown"> <button>New</button> <nav class="zs-dropdown-content"> {{#NewZettelLinks.Links}} <a href="{{{URL}}}">{{Text}}</a> {{/NewZettelLinks.Links}} </nav> </div> {{/NewZettelLinks.Has}} <form action="{{{SearchURL}}}"> <input type="text" placeholder="Search.." name="{{QueryKeyQuery}}"> </form> </nav> <main class="content"> {{{Content}}} </main> {{#FooterHTML}}<footer><hr>{{{FooterHTML}}}</footer>{{/FooterHTML}} {{#DebugMode}}<div><b>WARNING: Debug mode is enabled. DO NOT USE IN PRODUCTION!</b></div>{{/DebugMode}} </body> </html> |
Deleted box/constbox/base.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to box/constbox/constbox.go.
1 | //----------------------------------------------------------------------------- | | < < < | > > > < < < | | > > > > > > > | | < | | | | > > > > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package constbox puts zettel inside the executable. package constbox import ( "context" _ "embed" // Allow to embed file content "net/url" "zettelstore.de/c/api" "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/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/query" ) func init() { manager.Register( " const", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { return &constBox{ log: kernel.Main.GetLogger(kernel.BoxService).Clone(). Str("box", "const").Int("boxnum", int64(cdata.Number)).Child(), number: cdata.Number, zettel: constZettelMap, enricher: cdata.Enricher, }, nil }) } type constHeader map[string]string type constZettel struct { header constHeader content domain.Content } type constBox struct { log *logger.Logger number int zettel map[id.Zid]constZettel enricher box.Enricher } func (*constBox) Location() string { return "const:" } func (*constBox) CanCreateZettel(context.Context) bool { return false } func (cb *constBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) { cb.log.Trace().Err(box.ErrReadOnly).Msg("CreateZettel") return id.Invalid, box.ErrReadOnly } func (cb *constBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) { if z, ok := cb.zettel[zid]; ok { cb.log.Trace().Msg("GetZettel") return domain.Zettel{Meta: meta.NewWithData(zid, z.header), Content: z.content}, nil } cb.log.Trace().Err(box.ErrNotFound).Msg("GetZettel") return domain.Zettel{}, box.ErrNotFound } func (cb *constBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) { if z, ok := cb.zettel[zid]; ok { cb.log.Trace().Msg("GetMeta") return meta.NewWithData(zid, z.header), nil } cb.log.Trace().Err(box.ErrNotFound).Msg("GetMeta") return nil, box.ErrNotFound } func (cb *constBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error { cb.log.Trace().Int("entries", int64(len(cb.zettel))).Msg("ApplyZid") for zid := range cb.zettel { if constraint(zid) { handle(zid) |
︙ | ︙ | |||
92 93 94 95 96 97 98 99 100 101 | m := meta.NewWithData(zid, zettel.header) cb.enricher.Enrich(ctx, m, cb.number) handle(m) } } return nil } func (*constBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } | > > > > > > > > > > > > > > > > > > > > > | > < < | | | | | | | | < | | < | | | < | | > > > > > > > > | | | > > > > > > > > | | | < | | | < | | < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | > > > > > > > > > | < | | | | | < < | | < < < < < < < < < < < < < < < < < < < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | | | | | | | | | | | < < < | | | | | | | | < < < < < < < < < < < < | 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 | m := meta.NewWithData(zid, zettel.header) cb.enricher.Enrich(ctx, m, cb.number) handle(m) } } return nil } func (*constBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return false } func (cb *constBox) UpdateZettel(context.Context, domain.Zettel) error { cb.log.Trace().Err(box.ErrReadOnly).Msg("UpdateZettel") return box.ErrReadOnly } func (cb *constBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool { _, ok := cb.zettel[zid] return !ok } func (cb *constBox) RenameZettel(_ context.Context, curZid, _ id.Zid) error { err := box.ErrNotFound if _, ok := cb.zettel[curZid]; ok { err = box.ErrReadOnly } cb.log.Trace().Err(err).Msg("RenameZettel") return err } func (*constBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } func (cb *constBox) DeleteZettel(_ context.Context, zid id.Zid) error { err := box.ErrNotFound if _, ok := cb.zettel[zid]; ok { err = box.ErrReadOnly } cb.log.Trace().Err(err).Msg("DeleteZettel") return err } func (cb *constBox) ReadStats(st *box.ManagedBoxStats) { st.ReadOnly = true st.Zettel = len(cb.zettel) cb.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats") } var constZettelMap = map[id.Zid]constZettel{ id.ConfigurationZid: { constHeader{ api.KeyTitle: "Zettelstore Runtime Configuration", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxNone, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityOwner, }, domain.NewContent(nil)}, id.MustParse(api.ZidLicense): { constHeader{ api.KeyTitle: "Zettelstore License", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxText, api.KeyCreated: "20210504135842", api.KeyLang: api.ValueLangEN, api.KeyModified: "20220131153422", api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityPublic, }, domain.NewContent(contentLicense)}, id.MustParse(api.ZidAuthors): { constHeader{ api.KeyTitle: "Zettelstore Contributors", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyCreated: "20210504135842", api.KeyLang: api.ValueLangEN, api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityLogin, }, domain.NewContent(contentContributors)}, id.MustParse(api.ZidDependencies): { constHeader{ api.KeyTitle: "Zettelstore Dependencies", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityLogin, api.KeyCreated: "20210504135842", api.KeyModified: "20221013105100", }, domain.NewContent(contentDependencies)}, id.BaseTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Base HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20210504135842", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentBaseMustache)}, id.LoginTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Login Form HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentLoginMustache)}, id.ZettelTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentZettelMustache)}, id.InfoTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Info HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentInfoMustache)}, id.ContextTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Context HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20210218181140", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentContextMustache)}, id.FormTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Form HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentFormMustache)}, id.RenameTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Rename Form HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentRenameMustache)}, id.DeleteTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Delete HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentDeleteMustache)}, id.ListTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore List Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentListZettelMustache)}, id.ErrorTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Error HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20210305133215", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentErrorMustache)}, id.MustParse(api.ZidBaseCSS): { constHeader{ api.KeyTitle: "Zettelstore Base CSS", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxCSS, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityPublic, }, domain.NewContent(contentBaseCSS)}, id.MustParse(api.ZidUserCSS): { constHeader{ api.KeyTitle: "Zettelstore User CSS", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxCSS, api.KeyCreated: "20210622110143", api.KeyVisibility: api.ValueVisibilityPublic, }, domain.NewContent([]byte("/* User-defined CSS */"))}, id.RoleCSSMapZid: { constHeader{ api.KeyTitle: "Zettelstore Role to CSS Map", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxNone, api.KeyCreated: "20220321183214", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(nil)}, id.EmojiZid: { constHeader{ api.KeyTitle: "Zettelstore Generic Emoji", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxGif, api.KeyReadOnly: api.ValueTrue, api.KeyCreated: "20210504175807", api.KeyVisibility: api.ValueVisibilityPublic, }, domain.NewContent(contentEmoji)}, id.TOCNewTemplateZid: { constHeader{ api.KeyTitle: "New Menu", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyCreated: "20210217161829", api.KeyVisibility: api.ValueVisibilityCreator, }, domain.NewContent(contentNewTOCZettel)}, id.MustParse(api.ZidTemplateNewZettel): { constHeader{ api.KeyTitle: "New Zettel", api.KeyRole: api.ValueRoleZettel, api.KeySyntax: meta.SyntaxZmk, api.KeyCreated: "20201028185209", api.KeyVisibility: api.ValueVisibilityCreator, }, domain.NewContent(nil)}, id.MustParse(api.ZidTemplateNewUser): { constHeader{ api.KeyTitle: "New User", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxNone, api.KeyCreated: "20201028185209", meta.NewPrefix + api.KeyCredential: "", meta.NewPrefix + api.KeyUserID: "", meta.NewPrefix + api.KeyUserRole: api.ValueUserRoleReader, api.KeyVisibility: api.ValueVisibilityOwner, }, domain.NewContent(nil)}, id.DefaultHomeZid: { constHeader{ api.KeyTitle: "Home", api.KeyRole: api.ValueRoleZettel, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyCreated: "20210210190757", }, domain.NewContent(contentHomeZettel)}, } //go:embed license.txt var contentLicense []byte //go:embed contributors.zettel var contentContributors []byte //go:embed dependencies.zettel var contentDependencies []byte //go:embed base.mustache var contentBaseMustache []byte //go:embed login.mustache var contentLoginMustache []byte //go:embed zettel.mustache var contentZettelMustache []byte //go:embed info.mustache var contentInfoMustache []byte //go:embed context.mustache var contentContextMustache []byte //go:embed form.mustache var contentFormMustache []byte //go:embed rename.mustache var contentRenameMustache []byte //go:embed delete.mustache var contentDeleteMustache []byte //go:embed listzettel.mustache var contentListZettelMustache []byte //go:embed error.mustache var contentErrorMustache []byte //go:embed base.css var contentBaseCSS []byte //go:embed emoji_spin.gif var contentEmoji []byte //go:embed newtoc.zettel var contentNewTOCZettel []byte //go:embed home.zettel var contentHomeZettel []byte |
Added box/constbox/context.mustache.
> > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 | <header> <h1>{{Title}}</h1> <div class="zs-meta"> <a href="{{{InfoURL}}}">Info</a> · <a href="?dir=backward">Backward</a> · <a href="?dir=both">Both</a> · <a href="?dir=forward">Forward</a> · Cost:{{#Costs}} <a href="{{{URL}}}">{{{Text}}}</a>{{/Costs}} </div> </header> {{{Content}}} |
Added box/constbox/delete.mustache.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | <article> <header> <h1>Delete Zettel {{Zid}}</h1> </header> <p>Do you really want to delete this zettel?</p> {{#HasShadows}} <div class="zs-info"> <h2>Infomation</h2> <p>If you delete this zettel, the previoulsy shadowed zettel from overlayed box {{ShadowedBox}} becomes available.</p> </div> {{/HasShadows}} {{#Incoming.Has}} <div class="zs-warning"> <h2>Warning!</h2> <p>If you delete this zettel, incoming references from the following zettel will become invalid.</p> <ul> {{#Incoming.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/Incoming.Links}} </ul> </div> {{/Incoming.Has}} {{#HasUselessFiles}} <div class="zs-warning"> <h2>Warning!</h2> <p>Deleting this zettel will also delete the following files, so that they will not be interpreted as content for this zettel.</p> <ul> {{#UselessFiles}} <li>{{{.}}}</li> {{/UselessFiles}} </ul> </div> {{/HasUselessFiles}} <dl> {{#MetaPairs}} <dt>{{Key}}:</dt><dd>{{Value}}</dd> {{/MetaPairs}} </dl> <form method="POST"> <input class="zs-primary" type="submit" value="Delete"> </form> </article> {{end}} |
Deleted box/constbox/delete.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to box/constbox/dependencies.zettel.
︙ | ︙ | |||
96 97 98 99 100 101 102 103 104 105 106 107 108 109 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ``` === yuin/goldmark ; URL & Source : [[https://github.com/yuin/goldmark]] ; License : MIT License ``` | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ``` === hoisie/mustache / cbroglie/mustache ; URL & Source : [[https://github.com/hoisie/mustache]] / [[https://github.com/cbroglie/mustache]] ; License : MIT License ; Remarks : cbroglie/mustache is a fork from hoisie/mustache (starting with commit [f9b4cbf]). cbroglie/mustache does not claim a copyright and includes just the license file from hoisie/mustache. cbroglie/mustache obviously continues with the original license. ``` Copyright (c) 2009 Michael Hoisie Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` === pascaldekloe/jwt ; URL & Source : [[https://github.com/pascaldekloe/jwt]] ; License : [[CC0 1.0 Universal|https://creativecommons.org/publicdomain/zero/1.0/legalcode]] ``` To the extent possible under law, Pascal S. de Kloe has waived all copyright and related or neighboring rights to JWT. This work is published from The Netherlands. https://creativecommons.org/publicdomain/zero/1.0/legalcode ``` === yuin/goldmark ; URL & Source : [[https://github.com/yuin/goldmark]] ; License : MIT License ``` |
︙ | ︙ | |||
125 126 127 128 129 130 131 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` | < < < < < < < < < < < < < < < | 170 171 172 173 174 175 176 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` |
Added box/constbox/error.mustache.
> > > > > > | 1 2 3 4 5 6 | <article> <header> <h1>{{ErrorTitle}}</h1> </header> {{ErrorText}} </article> |
Deleted box/constbox/error.sxn.
|
| < < < < < < < < < < < < < < < < < |
Added box/constbox/form.mustache.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | <article> <header> <h1>{{Heading}}</h1> </header> <form action="{{FormActionURL}}" method="POST" enctype="multipart/form-data"> <div> <label for="zs-title">Title <a title="Main heading of this zettel. You can use inline zettelmarkup.">ⓘ</a></label> <input class="zs-input" type="text" id="zs-title" name="title" placeholder="Title.." value="{{MetaTitle}}" autofocus> </div> <div> <div> <label for="zs-role">Role <a title="One word, without spaces, to set the main role of this zettel.">ⓘ</a></label> <input class="zs-input" type="text" id="zs-role" {{#HasRoleData}}list="zs-role-data"{{/HasRoleData}} name="role" placeholder="role.." value="{{MetaRole}}"> {{#HasRoleData}} <datalist id="zs-role-data"> {{#RoleData}} <option value="{{.}}"> {{/RoleData}} </datalist> {{/HasRoleData}} </div> <label for="zs-tags">Tags <a title="Tags must begin with an '#' sign. They are separated by spaces.">ⓘ</a></label> <input class="zs-input" type="text" id="zs-tags" name="tags" placeholder="#tag" value="{{MetaTags}}"> </div> <div> <label for="zs-meta">Metadata <a title="Other metadata for this zettel. Each line contains a key/value pair, separated by a colon ':'.">ⓘ</a></label> <textarea class="zs-input" id="zs-meta" name="meta" rows="4" placeholder="metakey: metavalue"> {{#MetaPairsRest}} {{Key}}: {{Value}} {{/MetaPairsRest}} </textarea> </div> <div> <label for="zs-syntax">Syntax <a title="Syntax of zettel content below, one word. Typically 'zmk' (for zettelmarkup).">ⓘ</a></label> <input class="zs-input" type="text" id="zs-syntax" {{#HasSyntaxData}}list="zs-syntax-data"{{/HasSyntaxData}} name="syntax" placeholder="syntax.." value="{{MetaSyntax}}"> {{#HasSyntaxData}} <datalist id="zs-syntax-data"> {{#SyntaxData}} <option value="{{.}}"> {{/SyntaxData}} </datalist> {{/HasSyntaxData}}</div> <div> {{#IsTextContent}} <label for="zs-content">Content <a title="Content for this zettel, according to above syntax.">ⓘ</a></label> <textarea class="zs-input zs-content" id="zs-content" name="content" rows="20" placeholder="Your content..">{{Content}}</textarea> {{/IsTextContent}} </div> <div> <input class="zs-primary" type="submit" value="Submit"> <input class="zs-secondary" type="submit" value="Save" formaction="?save"> <input class="zs-upload" type="file" id="zs-file" name="file"> </div> </form> </article> |
Deleted box/constbox/form.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added box/constbox/info.mustache.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | <article> <header> <h1>Information for Zettel {{Zid}}</h1> <a href="{{{WebURL}}}">Web</a> · <a href="{{{ContextURL}}}">Context</a> {{#CanWrite}} · <a href="{{{EditURL}}}">Edit</a>{{/CanWrite}} {{#CanCopy}} · <a href="{{{CopyURL}}}">Copy</a>{{/CanCopy}} {{#CanVersion}} · <a href="{{{VersionURL}}}">Version</a>{{/CanVersion}} {{#CanFolge}} · <a href="{{{FolgeURL}}}">Folge</a>{{/CanFolge}} {{#CanRename}}· <a href="{{{RenameURL}}}">Rename</a>{{/CanRename}} {{#CanDelete}}· <a href="{{{DeleteURL}}}">Delete</a>{{/CanDelete}} </header> <h2>Interpreted Metadata</h2> <table> {{#MetaData}}<tr><td>{{Key}}</td><td>{{{Value}}}</td></tr> {{/MetaData}}</table> <h2>References</h2> {{#HasLocLinks}} <h3>Local</h3> <ul> {{#LocLinks}} {{#Valid}}<li><a href="{{{Zid}}}">{{Zid}}</a></li>{{/Valid}} {{^Valid}}<li>{{Zid}}</li>{{/Valid}} {{/LocLinks}} </ul> {{/HasLocLinks}} {{#QueryLinks.Has}} <h3>Queries</h3> <ul> {{#QueryLinks.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/QueryLinks.Links}} </ul> {{/QueryLinks.Has}} {{#HasExtLinks}} <h3>External</h3> <ul> {{#ExtLinks}} <li><a href="{{{.}}}"{{{ExtNewWindow}}}>{{.}}</a></li> {{/ExtLinks}} </ul> {{/HasExtLinks}} <h3>Unlinked</h3> {{{UnLinksContent}}} <form> <label for="phrase">Search Phrase</label> <input class="zs-input" type="text" id="phrase" name="{{QueryKeyPhrase}}" placeholder="Phrase.." value="{{UnLinksPhrase}}"> </form> <h2>Parts and encodings</h2> <table> {{#EvalMatrix}} <tr> <th>{{Header}}</th> {{#Elements}}<td><a href="{{{URL}}}">{{Text}}</a></td> {{/Elements}} </tr> {{/EvalMatrix}} </table> <h3>Parsed (not evaluated)</h3> <table> {{#ParseMatrix}} <tr> <th>{{Header}}</th> {{#Elements}}<td><a href="{{{URL}}}">{{Text}}</a></td> {{/Elements}} </tr> {{/ParseMatrix}} </table> {{#HasShadowLinks}} <h2>Shadowed Boxes</h2> <ul>{{#ShadowLinks}}<li>{{.}}</li>{{/ShadowLinks}}</ul> {{/HasShadowLinks}} {{#Endnotes}}{{{Endnotes}}}{{/Endnotes}} </article> |
Deleted box/constbox/info.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to box/constbox/license.txt.
|
| | | 1 2 3 4 5 6 7 8 | Copyright (c) 2020-2023 Detlef Stern Licensed under the EUPL Zettelstore is licensed under the European Union Public License, version 1.2 or later (EUPL v. 1.2). The license is available in the official languages of the EU. The English version is included here. Please see https://joinup.ec.europa.eu/community/eupl/og_page/eupl for official |
︙ | ︙ |
Added box/constbox/listzettel.mustache.
> > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 | <header> <h1>{{Title}}</h1> </header> <form action="{{{SearchURL}}}"> <input class="zs-input" type="text" placeholder="Search.." name="{{QueryKeyQuery}}" value="{{QueryValue}}"> </form> {{{Content}}} {{#CanCreate}}<form action="{{{CreateURL}}}"> <input type="hidden" name="{{QueryKeyQuery}}" value="{{QueryValue}}"> <input type="hidden" name="{{QueryKeySeed}}" value="{{Seed}}"> <input class="zs-primary" type="submit" value="Save As Zettel"> </form>{{/CanCreate}} |
Deleted box/constbox/listzettel.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added box/constbox/login.mustache.
> > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <article> <header> <h1>{{Title}}</h1> </header> {{#Retry}} <div class="zs-indication zs-error">Wrong user name / password. Try again.</div> {{/Retry}} <form method="POST" action=""> <div> <label for="username">User name:</label> <input class="zs-input" type="text" id="username" name="username" placeholder="Your user name.." autofocus> </div> <div> <label for="password">Password:</label> <input class="zs-input" type="password" id="password" name="password" placeholder="Your password.."> </div> <div><input class="zs-primary" type="submit" value="Login"></div> </form> </article> |
Deleted box/constbox/login.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to box/constbox/newtoc.zettel.
1 2 3 | This zettel lists all zettel that should act as a template for new zettel. These zettel will be included in the ""New"" menu of the WebUI. * [[New Zettel|00000000090001]] | < < | 1 2 3 4 | This zettel lists all zettel that should act as a template for new zettel. These zettel will be included in the ""New"" menu of the WebUI. * [[New Zettel|00000000090001]] * [[New User|00000000090002]] |
Deleted box/constbox/prelude.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added box/constbox/rename.mustache.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | <article> <header> <h1>Rename Zettel {{Zid}}</h1> </header> <p>Do you really want to rename this zettel?</p> {{#Incoming.Has}} <div class="zs-warning"> <h2>Warning!</h2> <p>If you rename this zettel, incoming references from the following zettel will become invalid.</p> <ul> {{#Incoming.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/Incoming.Links}} </ul> </div> {{/Incoming.Has}} {{#HasUselessFiles}} <div class="zs-warning"> <h2>Warning!</h2> <p>Renaming this zettel will also delete the following files, so that they will not be interpreted as content for a zettel with identifier {{Zid}}.</p> <ul> {{#UselessFiles}} <li>{{{.}}}</li> {{/UselessFiles}} </ul> </div> {{/HasUselessFiles}} <form method="POST"> <div> <label for="newid">New zettel id</label> <input class="zs-input" type="text" id="newzid" name="newzid" placeholder="ZID.." value="{{Zid}}" autofocus> </div> <input type="hidden" id="curzid" name="curzid" value="{{Zid}}"> <div><input class="zs-primary" type="submit" value="Rename"></div> </form> <dl> {{#MetaPairs}} <dt>{{Key}}:</dt><dd>{{Value}}</dd> {{/MetaPairs}} </dl> </article> |
Deleted box/constbox/roleconfiguration.zettel.
|
| < < < < < < < < < < < < < < < < < < < < |
Deleted box/constbox/rolerole.zettel.
|
| < < < < < < < < < < |
Deleted box/constbox/roletag.zettel.
|
| < < < < < < |
Deleted box/constbox/rolezettel.zettel.
|
| < < < < < < < |
Deleted box/constbox/start.sxn.
|
| < < < < < < < < < < < < < < < < < |
Deleted box/constbox/wuicode.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added box/constbox/zettel.mustache.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | <article> <header> <h1>{{{HTMLTitle}}}</h1> <div class="zs-meta"> {{#CanWrite}}<a href="{{{EditURL}}}">Edit</a> ·{{/CanWrite}} {{Zid}} · <a href="{{{InfoURL}}}">Info</a> · (<a href="{{{RoleURL}}}">{{RoleText}}</a>) {{#Tags.Has}}· {{#Tags.Links}} <a href="{{{URL}}}">{{Text}}</a>{{/Tags.Links}}{{/Tags.Has}} {{#CanCopy}}· <a href="{{{CopyURL}}}">Copy</a>{{/CanCopy}} {{#CanVersion}}· <a href="{{{VersionURL}}}">Version</a>{{/CanVersion}} {{#CanFolge}}· <a href="{{{FolgeURL}}}">Folge</a>{{/CanFolge}} {{#PredecessorRefs}}<br>Predecessor: {{{PredecessorRefs}}}{{/PredecessorRefs}} {{#PrecursorRefs}}<br>Precursor: {{{PrecursorRefs}}}{{/PrecursorRefs}} {{#HasExtURL}}<br>URL: <a href="{{{ExtURL}}}"{{{ExtNewWindow}}}>{{ExtURL}}</a>{{/HasExtURL}} {{#Author}}<br>By {{Author}}{{/Author}} </div> </header> {{{Content}}} </article> {{#NeedBottomNav}}<nav>{{/NeedBottomNav}} {{#FolgeLinks.Has}} <details open> <summary>Folgezettel</summary> <ul> {{#FolgeLinks.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/FolgeLinks.Links}} </ul> </details> {{/FolgeLinks.Has}} {{#BackLinks.Has}} <details open> <summary>Incoming</summary> <ul> {{#BackLinks.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/BackLinks.Links}} </ul> </details> {{/BackLinks.Has}} {{#SuccessorLinks.Has}} <details open> <summary>Successors</summary> <ul> {{#SuccessorLinks.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/SuccessorLinks.Links}} </ul> </details> {{/SuccessorLinks.Has}} {{#NeedBottomNav}}</nav>{{/NeedBottomNav}} |
Deleted box/constbox/zettel.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to box/dirbox/dirbox.go.
1 | //----------------------------------------------------------------------------- | | < < < > > > < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2023 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 dirbox provides a directory-based zettel box. package dirbox import ( "context" "errors" "net/url" "os" "path/filepath" "sync" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/box/notify" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/query" ) func init() { manager.Register("dir", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { var log *logger.Logger if krnl := kernel.Main; krnl != nil { log = krnl.GetLogger(kernel.BoxService).Clone().Str("box", "dir").Int("boxnum", int64(cdata.Number)).Child() |
︙ | ︙ | |||
88 89 90 91 92 93 94 | _ notifyTypeSpec = iota dirNotifyAny dirNotifySimple dirNotifyFS ) func getDirSrvInfo(log *logger.Logger, notifyType string) notifyTypeSpec { | | | 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | _ notifyTypeSpec = iota dirNotifyAny dirNotifySimple dirNotifyFS ) func getDirSrvInfo(log *logger.Logger, notifyType string) notifyTypeSpec { for count := 0; count < 2; count++ { switch notifyType { case kernel.BoxDirTypeNotify: return dirNotifyFS case kernel.BoxDirTypeSimple: return dirNotifySimple default: notifyType = kernel.Main.GetConfig(kernel.BoxService, kernel.BoxDefaultDirType).(string) |
︙ | ︙ | |||
128 129 130 131 132 133 134 | mxCmds sync.RWMutex } func (dp *dirBox) Location() string { return dp.location } | < < < < < < < < < < < < < < < < < < | | < | 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | mxCmds sync.RWMutex } func (dp *dirBox) Location() string { return dp.location } func (dp *dirBox) Start(context.Context) error { dp.mxCmds.Lock() defer dp.mxCmds.Unlock() dp.fCmds = make([]chan fileCmd, 0, dp.fSrvs) for i := uint32(0); i < dp.fSrvs; i++ { cc := make(chan fileCmd) go fileService(i, dp.log.Clone().Str("sub", "file").Uint("fn", uint64(i)).Child(), dp.dir, cc) dp.fCmds = append(dp.fCmds, cc) } var notifier notify.Notifier var err error switch dp.notifySpec { case dirNotifySimple: notifier, err = notify.NewSimpleDirNotifier(dp.log.Clone().Str("notify", "simple").Child(), dp.dir) default: notifier, err = notify.NewFSDirNotifier(dp.log.Clone().Str("notify", "fs").Child(), dp.dir) } if err != nil { dp.log.Fatal().Err(err).Msg("Unable to create directory supervisor") dp.stopFileServices() return err } dp.dirSrv = notify.NewDirService( dp.log.Clone().Str("sub", "dirsrv").Child(), notifier, dp.cdata.Notify, ) dp.dirSrv.Start() return nil } |
︙ | ︙ | |||
199 200 201 202 203 204 205 | func (dp *dirBox) stopFileServices() { for _, c := range dp.fCmds { close(c) } } | | | | | | | | | | | | > > > > > | > > > > > > > > | 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | func (dp *dirBox) stopFileServices() { for _, c := range dp.fCmds { close(c) } } func (dp *dirBox) notifyChanged(zid id.Zid) { if chci := dp.cdata.Notify; chci != nil { dp.log.Trace().Zid(zid).Msg("notifyChanged") chci <- box.UpdateInfo{Reason: box.OnZettel, Zid: zid} } } func (dp *dirBox) getFileChan(zid id.Zid) chan fileCmd { // Based on https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function sum := 2166136261 ^ uint32(zid) sum *= 16777619 sum ^= uint32(zid >> 32) sum *= 16777619 dp.mxCmds.RLock() defer dp.mxCmds.RUnlock() return dp.fCmds[sum%dp.fSrvs] } func (dp *dirBox) CanCreateZettel(_ context.Context) bool { return !dp.readonly } func (dp *dirBox) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) { if dp.readonly { return id.Invalid, box.ErrReadOnly } newZid, err := dp.dirSrv.SetNewDirEntry() if err != nil { return id.Invalid, err } meta := zettel.Meta meta.Zid = newZid entry := notify.DirEntry{Zid: newZid} dp.updateEntryFromMetaContent(&entry, meta, zettel.Content) err = dp.srvSetZettel(ctx, &entry, zettel) if err == nil { err = dp.dirSrv.UpdateDirEntry(&entry) } dp.notifyChanged(meta.Zid) dp.log.Trace().Err(err).Zid(meta.Zid).Msg("CreateZettel") return meta.Zid, err } func (dp *dirBox) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) { entry := dp.dirSrv.GetDirEntry(zid) if !entry.IsValid() { return domain.Zettel{}, box.ErrNotFound } m, c, err := dp.srvGetMetaContent(ctx, entry, zid) if err != nil { return domain.Zettel{}, err } zettel := domain.Zettel{Meta: m, Content: domain.NewContent(c)} dp.log.Trace().Zid(zid).Msg("GetZettel") return zettel, nil } func (dp *dirBox) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { m, err := dp.doGetMeta(ctx, zid) dp.log.Trace().Zid(zid).Err(err).Msg("GetMeta") return m, err } func (dp *dirBox) doGetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { entry := dp.dirSrv.GetDirEntry(zid) if !entry.IsValid() { return nil, box.ErrNotFound } m, err := dp.srvGetMeta(ctx, entry, zid) if err != nil { return nil, err } return m, nil } func (dp *dirBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error { entries := dp.dirSrv.GetDirEntries(constraint) dp.log.Trace().Int("entries", int64(len(entries))).Msg("ApplyZid") for _, entry := range entries { handle(entry.Zid) |
︙ | ︙ | |||
289 290 291 292 293 294 295 | } dp.cdata.Enricher.Enrich(ctx, m, dp.number) handle(m) } return nil } | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 | } dp.cdata.Enricher.Enrich(ctx, m, dp.number) handle(m) } return nil } func (dp *dirBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return !dp.readonly } func (dp *dirBox) UpdateZettel(ctx context.Context, zettel domain.Zettel) error { if dp.readonly { return box.ErrReadOnly } meta := zettel.Meta zid := meta.Zid if !zid.IsValid() { return &box.ErrInvalidID{Zid: zid} } entry := dp.dirSrv.GetDirEntry(zid) if !entry.IsValid() { // Existing zettel, but new in this box. entry = ¬ify.DirEntry{Zid: zid} } dp.updateEntryFromMetaContent(entry, meta, zettel.Content) dp.dirSrv.UpdateDirEntry(entry) err := dp.srvSetZettel(ctx, entry, zettel) if err == nil { dp.notifyChanged(zid) } dp.log.Trace().Zid(zid).Err(err).Msg("UpdateZettel") return err } func (dp *dirBox) updateEntryFromMetaContent(entry *notify.DirEntry, m *meta.Meta, content domain.Content) { entry.SetupFromMetaContent(m, content, dp.cdata.Config.GetZettelFileSyntax) } 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 { return nil } curEntry := dp.dirSrv.GetDirEntry(curZid) if !curEntry.IsValid() { return box.ErrNotFound } if dp.readonly { return box.ErrReadOnly } // Check whether zettel with new ID already exists in this box. if _, err := dp.doGetMeta(ctx, newZid); err == nil { return &box.ErrInvalidID{Zid: newZid} } oldMeta, oldContent, err := dp.srvGetMetaContent(ctx, curEntry, curZid) if err != nil { return err } newEntry, err := dp.dirSrv.RenameDirEntry(curEntry, newZid) if err != nil { return err } oldMeta.Zid = newZid newZettel := domain.Zettel{Meta: oldMeta, Content: domain.NewContent(oldContent)} if err = dp.srvSetZettel(ctx, &newEntry, newZettel); err != nil { // "Rollback" rename. No error checking... dp.dirSrv.RenameDirEntry(&newEntry, curZid) return err } err = dp.srvDeleteZettel(ctx, curEntry, curZid) if err == nil { dp.notifyChanged(curZid) dp.notifyChanged(newZid) } dp.log.Trace().Zid(curZid).Zid(newZid).Err(err).Msg("RenameZettel") return err } func (dp *dirBox) CanDeleteZettel(_ context.Context, zid id.Zid) bool { if dp.readonly { return false } entry := dp.dirSrv.GetDirEntry(zid) return entry.IsValid() } func (dp *dirBox) DeleteZettel(ctx context.Context, zid id.Zid) error { if dp.readonly { return box.ErrReadOnly } entry := dp.dirSrv.GetDirEntry(zid) if !entry.IsValid() { return box.ErrNotFound } err := dp.dirSrv.DeleteDirEntry(zid) if err != nil { return nil } err = dp.srvDeleteZettel(ctx, entry, zid) if err == nil { dp.notifyChanged(zid) } dp.log.Trace().Zid(zid).Err(err).Msg("DeleteZettel") return err } func (dp *dirBox) ReadStats(st *box.ManagedBoxStats) { st.ReadOnly = dp.readonly st.Zettel = dp.dirSrv.NumDirEntries() dp.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats") } |
Changes to box/dirbox/dirbox_test.go.
1 | //----------------------------------------------------------------------------- | | < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package dirbox import "testing" func TestIsPrime(t *testing.T) { |
︙ | ︙ | |||
31 32 33 34 35 36 37 | if got != tc.exp { t.Errorf("isPrime(%d)=%v, but got %v", tc.n, tc.exp, got) } } } func TestMakePrime(t *testing.T) { | | | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | if got != tc.exp { t.Errorf("isPrime(%d)=%v, but got %v", tc.n, tc.exp, got) } } } func TestMakePrime(t *testing.T) { for i := uint32(0); i < 1500; i++ { np := makePrime(i) if np < i { t.Errorf("makePrime(%d) < %d", i, np) continue } if !isPrime(np) { t.Errorf("makePrime(%d) == %d is not prime", i, np) |
︙ | ︙ |
Changes to box/dirbox/service.go.
1 | //----------------------------------------------------------------------------- | | < < < < < | | | | | > | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package dirbox import ( "context" "io" "os" "path/filepath" "time" "zettelstore.de/z/box/filebox" "zettelstore.de/z/box/notify" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" ) func fileService(i uint32, log *logger.Logger, dirPath string, cmds <-chan fileCmd) { // Something may panic. Ensure a running service. defer func() { if r := recover(); r != nil { kernel.Main.LogRecover("FileService", r) go fileService(i, log, dirPath, cmds) } }() log.Trace().Uint("i", uint64(i)).Str("dirpath", dirPath).Msg("File service started") for cmd := range cmds { cmd.run(log, dirPath) } log.Trace().Uint("i", uint64(i)).Str("dirpath", dirPath).Msg("File service stopped") } type fileCmd interface { run(*logger.Logger, string) } const serviceTimeout = 5 * time.Second // must be shorter than the web servers timeout values for reading+writing. // COMMAND: srvGetMeta ---------------------------------------- // // Retrieves the meta data from a zettel. |
︙ | ︙ | |||
75 76 77 78 79 80 81 | rc chan<- resGetMeta } type resGetMeta struct { meta *meta.Meta err error } | | | > | | 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | rc chan<- resGetMeta } type resGetMeta struct { meta *meta.Meta err error } func (cmd *fileGetMeta) run(log *logger.Logger, dirPath string) { var m *meta.Meta var err error entry := cmd.entry zid := entry.Zid if metaName := entry.MetaName; metaName == "" { contentName := entry.ContentName contentExt := entry.ContentExt if contentName == "" || contentExt == "" { log.Panic().Zid(zid).Msg("No meta, no content in getMeta") } if entry.HasMetaInContent() { m, _, err = parseMetaContentFile(zid, filepath.Join(dirPath, contentName)) } else { m = filebox.CalcDefaultMeta(zid, contentExt) } } else { m, err = parseMetaFile(zid, filepath.Join(dirPath, metaName)) } |
︙ | ︙ | |||
127 128 129 130 131 132 133 | } type resGetMetaContent struct { meta *meta.Meta content []byte err error } | | | > | | 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | } type resGetMetaContent struct { meta *meta.Meta content []byte err error } func (cmd *fileGetMetaContent) run(log *logger.Logger, dirPath string) { var m *meta.Meta var content []byte var err error entry := cmd.entry zid := entry.Zid contentName := entry.ContentName contentExt := entry.ContentExt contentPath := filepath.Join(dirPath, contentName) if metaName := entry.MetaName; metaName == "" { if contentName == "" || contentExt == "" { log.Panic().Zid(zid).Msg("No meta, no content in getMetaContent") } if entry.HasMetaInContent() { m, content, err = parseMetaContentFile(zid, contentPath) } else { m = filebox.CalcDefaultMeta(zid, contentExt) content, err = os.ReadFile(contentPath) } } else { m, err = parseMetaFile(zid, filepath.Join(dirPath, metaName)) |
︙ | ︙ | |||
166 167 168 169 170 171 172 | cmd.rc <- resGetMetaContent{m, content, err} } // COMMAND: srvSetZettel ---------------------------------------- // // Writes a new or exsting zettel. | | | | < | < > | | | | | | | < | | 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | cmd.rc <- resGetMetaContent{m, content, err} } // COMMAND: srvSetZettel ---------------------------------------- // // Writes a new or exsting zettel. func (dp *dirBox) srvSetZettel(ctx context.Context, entry *notify.DirEntry, zettel domain.Zettel) error { rc := make(chan resSetZettel, 1) dp.getFileChan(zettel.Meta.Zid) <- &fileSetZettel{entry, zettel, rc} ctx, cancel := context.WithTimeout(ctx, serviceTimeout) defer cancel() select { case err := <-rc: return err case <-ctx.Done(): return ctx.Err() } } type fileSetZettel struct { entry *notify.DirEntry zettel domain.Zettel rc chan<- resSetZettel } type resSetZettel = error func (cmd *fileSetZettel) run(log *logger.Logger, dirPath string) { entry := cmd.entry zid := entry.Zid contentName := entry.ContentName m := cmd.zettel.Meta content := cmd.zettel.Content.AsBytes() metaName := entry.MetaName if metaName == "" { if contentName == "" { log.Panic().Zid(zid).Msg("No meta, no content in setZettel") } contentPath := filepath.Join(dirPath, contentName) if entry.HasMetaInContent() { err := writeZettelFile(contentPath, m, content) cmd.rc <- err return } err := writeFileContent(contentPath, content) cmd.rc <- err return } err := writeMetaFile(filepath.Join(dirPath, metaName), m) if err == nil && contentName != "" { err = writeFileContent(filepath.Join(dirPath, contentName), content) } cmd.rc <- err } func writeMetaFile(metaPath string, m *meta.Meta) error { |
︙ | ︙ | |||
237 238 239 240 241 242 243 | } func writeZettelFile(contentPath string, m *meta.Meta, content []byte) error { zettelFile, err := openFileWrite(contentPath) if err != nil { return err } | > | > | 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 | } func writeZettelFile(contentPath string, m *meta.Meta, content []byte) error { zettelFile, err := openFileWrite(contentPath) if err != nil { return err } if err == nil { err = writeMetaHeader(zettelFile, m) } if err == nil { _, err = zettelFile.Write(content) } if err1 := zettelFile.Close(); err == nil { err = err1 } return err |
︙ | ︙ | |||
298 299 300 301 302 303 304 | type fileDeleteZettel struct { entry *notify.DirEntry rc chan<- resDeleteZettel } type resDeleteZettel = error | | | < < > | 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 | type fileDeleteZettel struct { entry *notify.DirEntry rc chan<- resDeleteZettel } type resDeleteZettel = error func (cmd *fileDeleteZettel) run(log *logger.Logger, dirPath string) { var err error entry := cmd.entry contentName := entry.ContentName contentPath := filepath.Join(dirPath, contentName) if metaName := entry.MetaName; metaName == "" { if contentName == "" { log.Panic().Zid(entry.Zid).Msg("No meta, no content in getMetaContent") } err = os.Remove(contentPath) } else { if contentName != "" { err = os.Remove(contentPath) } err1 := os.Remove(filepath.Join(dirPath, metaName)) if err == nil { err = err1 |
︙ | ︙ | |||
359 360 361 362 363 364 365 | entry.Zid, entry.ContentExt, entry.MetaName != "", entry.UselessFiles, ) } | < < < < < < | | 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 | entry.Zid, entry.ContentExt, entry.MetaName != "", entry.UselessFiles, ) } func openFileWrite(path string) (*os.File, error) { return os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) } func writeFileZid(w io.Writer, zid id.Zid) error { _, err := io.WriteString(w, "id: ") if err == nil { _, err = w.Write(zid.Bytes()) if err == nil { |
︙ | ︙ |
Changes to box/filebox/filebox.go.
1 | //----------------------------------------------------------------------------- | | < < < | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package filebox provides boxes that are stored in a file. package filebox import ( "errors" "net/url" "path/filepath" "strings" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" ) func init() { manager.Register("file", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { path := getFilepathFromURL(u) ext := strings.ToLower(filepath.Ext(path)) if ext != ".zip" { |
︙ | ︙ |
Changes to box/filebox/zipbox.go.
1 | //----------------------------------------------------------------------------- | | < < < < < | | | | | > | < < < < < < < < < < < < < < < < < < | > | > | > > > > | > | | | | < | | | | | | > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2023 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 filebox import ( "archive/zip" "context" "io" "strings" "zettelstore.de/z/box" "zettelstore.de/z/box/notify" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" "zettelstore.de/z/logger" "zettelstore.de/z/query" ) type zipBox struct { log *logger.Logger number int name string enricher box.Enricher notify chan<- box.UpdateInfo dirSrv *notify.DirService } func (zb *zipBox) Location() string { if strings.HasPrefix(zb.name, "/") { return "file://" + zb.name } return "file:" + zb.name } func (zb *zipBox) Start(context.Context) error { reader, err := zip.OpenReader(zb.name) if err != nil { return err } reader.Close() zipNotifier := notify.NewSimpleZipNotifier(zb.log, zb.name) zb.dirSrv = notify.NewDirService(zb.log, zipNotifier, zb.notify) zb.dirSrv.Start() return nil } func (zb *zipBox) Refresh(_ context.Context) { zb.dirSrv.Refresh() zb.log.Trace().Msg("Refresh") } func (zb *zipBox) Stop(context.Context) { zb.dirSrv.Stop() } func (*zipBox) CanCreateZettel(context.Context) bool { return false } func (zb *zipBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) { err := box.ErrReadOnly zb.log.Trace().Err(err).Msg("CreateZettel") return id.Invalid, err } func (zb *zipBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) { entry := zb.dirSrv.GetDirEntry(zid) if !entry.IsValid() { return domain.Zettel{}, box.ErrNotFound } reader, err := zip.OpenReader(zb.name) if err != nil { return domain.Zettel{}, err } defer reader.Close() var m *meta.Meta var src []byte var inMeta bool contentName := entry.ContentName if metaName := entry.MetaName; metaName == "" { if contentName == "" { zb.log.Panic().Zid(zid).Msg("No meta, no content in zipBox.GetZettel") } src, err = readZipFileContent(reader, entry.ContentName) if err != nil { return domain.Zettel{}, err } if entry.HasMetaInContent() { inp := input.NewInput(src) m = meta.NewFromInput(zid, inp) src = src[inp.Pos:] } else { m = CalcDefaultMeta(zid, entry.ContentExt) } } else { m, err = readZipMetaFile(reader, zid, metaName) if err != nil { return domain.Zettel{}, err } inMeta = true if contentName != "" { src, err = readZipFileContent(reader, entry.ContentName) if err != nil { return domain.Zettel{}, err } } } CleanupMeta(m, zid, entry.ContentExt, inMeta, entry.UselessFiles) zb.log.Trace().Zid(zid).Msg("GetZettel") return domain.Zettel{Meta: m, Content: domain.NewContent(src)}, nil } func (zb *zipBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) { entry := zb.dirSrv.GetDirEntry(zid) if !entry.IsValid() { return nil, box.ErrNotFound } reader, err := zip.OpenReader(zb.name) if err != nil { return nil, err } defer reader.Close() m, err := zb.readZipMeta(reader, zid, entry) zb.log.Trace().Err(err).Zid(zid).Msg("GetMeta") return m, err } func (zb *zipBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error { entries := zb.dirSrv.GetDirEntries(constraint) zb.log.Trace().Int("entries", int64(len(entries))).Msg("ApplyZid") for _, entry := range entries { handle(entry.Zid) |
︙ | ︙ | |||
167 168 169 170 171 172 173 174 175 176 177 178 179 180 | continue } zb.enricher.Enrich(ctx, m, zb.number) handle(m) } return nil } func (*zipBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } func (zb *zipBox) DeleteZettel(_ context.Context, zid id.Zid) error { err := box.ErrReadOnly entry := zb.dirSrv.GetDirEntry(zid) if !entry.IsValid() { | > > > > > > > > > > > > > > > > > > > > > > > > > > | | > | | 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | continue } zb.enricher.Enrich(ctx, m, zb.number) handle(m) } return nil } func (*zipBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return false } func (zb *zipBox) UpdateZettel(context.Context, domain.Zettel) error { err := box.ErrReadOnly zb.log.Trace().Err(err).Msg("UpdateZettel") return err } func (zb *zipBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool { entry := zb.dirSrv.GetDirEntry(zid) return !entry.IsValid() } func (zb *zipBox) RenameZettel(_ context.Context, curZid, newZid id.Zid) error { err := box.ErrReadOnly if curZid == newZid { err = nil } curEntry := zb.dirSrv.GetDirEntry(curZid) if !curEntry.IsValid() { err = box.ErrNotFound } zb.log.Trace().Err(err).Msg("RenameZettel") return err } func (*zipBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } func (zb *zipBox) DeleteZettel(_ context.Context, zid id.Zid) error { err := box.ErrReadOnly entry := zb.dirSrv.GetDirEntry(zid) if !entry.IsValid() { err = box.ErrNotFound } zb.log.Trace().Err(err).Msg("DeleteZettel") return err } func (zb *zipBox) ReadStats(st *box.ManagedBoxStats) { st.ReadOnly = true st.Zettel = zb.dirSrv.NumDirEntries() zb.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats") } func (zb *zipBox) readZipMeta(reader *zip.ReadCloser, zid id.Zid, entry *notify.DirEntry) (m *meta.Meta, err error) { var inMeta bool if metaName := entry.MetaName; metaName == "" { contentName := entry.ContentName contentExt := entry.ContentExt if contentName == "" || contentExt == "" { zb.log.Panic().Zid(zid).Msg("No meta, no content in getMeta") } if entry.HasMetaInContent() { m, err = readZipMetaFile(reader, zid, contentName) } else { m = CalcDefaultMeta(zid, contentExt) } } else { m, err = readZipMetaFile(reader, zid, metaName) } |
︙ | ︙ |
Changes to box/helper.go.
1 | //----------------------------------------------------------------------------- | | < < < < < | | < < < < < < < < < < < < < < < < < < < < < < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package box import ( "time" "zettelstore.de/z/domain/id" ) // GetNewZid calculates a new and unused zettel identifier, based on the current date and time. func GetNewZid(testZid func(id.Zid) (bool, error)) (id.Zid, error) { withSeconds := false for i := 0; i < 90; i++ { // Must be completed within 9 seconds (less than web/server.writeTimeout) zid := id.New(withSeconds) found, err := testZid(zid) if err != nil { return id.Invalid, err } if found { return zid, nil } // TODO: do not wait here unconditionally. time.Sleep(100 * time.Millisecond) withSeconds = true } return id.Invalid, ErrConflict } |
Changes to box/manager/anteroom.go.
1 | //----------------------------------------------------------------------------- | | < < < | > | | > | | | | | > > > > > | | | | | | | > | > > | | | > | | < | | | > > | | | | | | | > | | | | | < | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package manager import ( "sync" "zettelstore.de/z/domain/id" ) type arAction int const ( arNothing arAction = iota arReload arZettel ) type anteroom struct { num uint64 next *anteroom waiting id.Set curLoad int reload bool } type anterooms struct { mx sync.Mutex nextNum uint64 first *anteroom last *anteroom maxLoad int } func newAnterooms(maxLoad int) *anterooms { return &anterooms{maxLoad: maxLoad} } func (ar *anterooms) EnqueueZettel(zid id.Zid) { if !zid.IsValid() { return } ar.mx.Lock() defer ar.mx.Unlock() if ar.first == nil { ar.first = ar.makeAnteroom(zid) ar.last = ar.first return } for room := ar.first; room != nil; room = room.next { if room.reload { continue // Do not put zettel in reload room } if _, ok := room.waiting[zid]; ok { // Zettel is already waiting. Nothing to do. return } } if room := ar.last; !room.reload && (ar.maxLoad == 0 || room.curLoad < ar.maxLoad) { room.waiting.Zid(zid) room.curLoad++ return } room := ar.makeAnteroom(zid) ar.last.next = room ar.last = room } func (ar *anterooms) makeAnteroom(zid id.Zid) *anteroom { ar.nextNum++ if zid == id.Invalid { return &anteroom{num: ar.nextNum, next: nil, waiting: nil, curLoad: 0, reload: true} } c := ar.maxLoad if c == 0 { c = 100 } waiting := id.NewSetCap(ar.maxLoad, zid) return &anteroom{num: ar.nextNum, next: nil, waiting: waiting, curLoad: 1, reload: false} } func (ar *anterooms) Reset() { ar.mx.Lock() defer ar.mx.Unlock() ar.first = ar.makeAnteroom(id.Invalid) ar.last = ar.first } func (ar *anterooms) Reload(newZids id.Set) uint64 { ar.mx.Lock() defer ar.mx.Unlock() ar.deleteReloadedRooms() if ns := len(newZids); ns > 0 { ar.nextNum++ ar.first = &anteroom{num: ar.nextNum, next: ar.first, waiting: newZids, curLoad: ns, reload: true} if ar.first.next == nil { ar.last = ar.first } return ar.nextNum } ar.first = nil ar.last = nil return 0 } func (ar *anterooms) deleteReloadedRooms() { room := ar.first for room != nil && room.reload { room = room.next } ar.first = room if room == nil { ar.last = nil } } func (ar *anterooms) Dequeue() (arAction, id.Zid, uint64) { ar.mx.Lock() defer ar.mx.Unlock() if ar.first == nil { return arNothing, id.Invalid, 0 } roomNo := ar.first.num if ar.first.waiting == nil { ar.removeFirst() return arReload, id.Invalid, roomNo } for zid := range ar.first.waiting { delete(ar.first.waiting, zid) if len(ar.first.waiting) == 0 { ar.removeFirst() } return arZettel, zid, roomNo } ar.removeFirst() return arNothing, id.Invalid, 0 } func (ar *anterooms) removeFirst() { ar.first = ar.first.next if ar.first == nil { ar.last = nil } } |
Changes to box/manager/anteroom_test.go.
1 | //----------------------------------------------------------------------------- | | < < < | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package manager import ( "testing" "zettelstore.de/z/domain/id" ) func TestSimple(t *testing.T) { t.Parallel() ar := newAnterooms(2) ar.EnqueueZettel(id.Zid(1)) action, zid, rno := ar.Dequeue() if zid != id.Zid(1) || action != arZettel || rno != 1 { t.Errorf("Expected arZettel/1/1, but got %v/%v/%v", action, zid, rno) } _, zid, _ = ar.Dequeue() if zid != id.Invalid { t.Errorf("Expected invalid Zid, but got %v", zid) } ar.EnqueueZettel(id.Zid(1)) ar.EnqueueZettel(id.Zid(2)) |
︙ | ︙ | |||
51 52 53 54 55 56 57 | if count != 3 { t.Errorf("Expected 3 dequeues, but got %v", count) } } func TestReset(t *testing.T) { t.Parallel() | | | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | if count != 3 { t.Errorf("Expected 3 dequeues, but got %v", count) } } func TestReset(t *testing.T) { t.Parallel() ar := newAnterooms(1) ar.EnqueueZettel(id.Zid(1)) ar.Reset() action, zid, _ := ar.Dequeue() if action != arReload || zid != id.Invalid { t.Errorf("Expected reload & invalid Zid, but got %v/%v", action, zid) } ar.Reload(id.NewSet(3, 4)) |
︙ | ︙ | |||
84 85 86 87 88 89 90 | t.Errorf("Expected 5/arZettel, but got %v/%v", zid, action) } action, zid, _ = ar.Dequeue() if action != arNothing || zid != id.Invalid { t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid) } | | | | 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | t.Errorf("Expected 5/arZettel, but got %v/%v", zid, action) } action, zid, _ = ar.Dequeue() if action != arNothing || zid != id.Invalid { t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid) } ar = newAnterooms(1) ar.Reload(id.NewSet(id.Zid(6))) action, zid, _ = ar.Dequeue() if zid != id.Zid(6) || action != arZettel { t.Errorf("Expected 6/arZettel, but got %v/%v", zid, action) } action, zid, _ = ar.Dequeue() if action != arNothing || zid != id.Invalid { t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid) } ar = newAnterooms(1) ar.EnqueueZettel(id.Zid(8)) ar.Reload(nil) action, zid, _ = ar.Dequeue() if action != arNothing || zid != id.Invalid { t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid) } } |
Changes to box/manager/box.go.
1 | //----------------------------------------------------------------------------- | | < < < > < | | | | | | | | | < < < < | < < | < < < < < < | < < | | | < < < > | | < < | | | < < < > > > | | | | > > > > > > > | > > > > > > > > > | | > > > > > | | < | | > > > > > > > | > > > | > | > | | < < < < < | < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < | < < < > > | < < < < | > | | | | | < < | | < < < < | < < < | > | > | > > > > > > > | | < | | > > > | | > | | | > > > > > > > > > > | > > > > > > > > > > > > | > < < | | < < < > > > | < | < | | < < < < < < < < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package manager import ( "bytes" "context" "errors" "zettelstore.de/z/box" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/query" ) // Conatains all box.Box related functions // Location returns some information where the box is located. func (mgr *Manager) Location() string { if len(mgr.boxes) <= 2 { return "NONE" } var buf bytes.Buffer for i := 0; i < len(mgr.boxes)-2; i++ { if i > 0 { buf.WriteString(", ") } buf.WriteString(mgr.boxes[i].Location()) } return buf.String() } // CanCreateZettel returns true, if box could possibly create a new zettel. func (mgr *Manager) CanCreateZettel(ctx context.Context) bool { mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() return mgr.started && mgr.boxes[0].CanCreateZettel(ctx) } // CreateZettel creates a new zettel. func (mgr *Manager) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) { mgr.mgrLog.Debug().Msg("CreateZettel") mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if !mgr.started { return id.Invalid, box.ErrStopped } return mgr.boxes[0].CreateZettel(ctx, zettel) } // GetZettel retrieves a specific zettel. func (mgr *Manager) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) { mgr.mgrLog.Debug().Zid(zid).Msg("GetZettel") mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if !mgr.started { return domain.Zettel{}, box.ErrStopped } for i, p := range mgr.boxes { if z, err := p.GetZettel(ctx, zid); err != box.ErrNotFound { if err == nil { mgr.Enrich(ctx, z.Meta, i+1) } return z, err } } return domain.Zettel{}, box.ErrNotFound } // GetAllZettel retrieves a specific zettel from all managed boxes. func (mgr *Manager) GetAllZettel(ctx context.Context, zid id.Zid) ([]domain.Zettel, error) { mgr.mgrLog.Debug().Zid(zid).Msg("GetAllZettel") mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if !mgr.started { return nil, box.ErrStopped } var result []domain.Zettel for i, p := range mgr.boxes { if z, err := p.GetZettel(ctx, zid); err == nil { mgr.Enrich(ctx, z.Meta, i+1) result = append(result, z) } } return result, nil } // GetMeta retrieves just the meta data of a specific zettel. func (mgr *Manager) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { mgr.mgrLog.Debug().Zid(zid).Msg("GetMeta") mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if !mgr.started { return nil, box.ErrStopped } return mgr.doGetMeta(ctx, zid) } func (mgr *Manager) doGetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { for i, p := range mgr.boxes { if m, err := p.GetMeta(ctx, zid); err != box.ErrNotFound { if err == nil { mgr.Enrich(ctx, m, i+1) } return m, err } } return nil, box.ErrNotFound } // GetAllMeta retrieves the meta data of a specific zettel from all managed boxes. func (mgr *Manager) GetAllMeta(ctx context.Context, zid id.Zid) ([]*meta.Meta, error) { mgr.mgrLog.Debug().Zid(zid).Msg("GetAllMeta") mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if !mgr.started { return nil, box.ErrStopped } var result []*meta.Meta for i, p := range mgr.boxes { if m, err := p.GetMeta(ctx, zid); err == nil { mgr.Enrich(ctx, m, i+1) result = append(result, m) } } return result, nil } // FetchZids returns the set of all zettel identifer managed by the box. func (mgr *Manager) FetchZids(ctx context.Context) (id.Set, error) { mgr.mgrLog.Debug().Msg("FetchZids") mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if !mgr.started { return nil, box.ErrStopped } result := id.Set{} for _, p := range mgr.boxes { err := p.ApplyZid(ctx, func(zid id.Zid) { result.Zid(zid) }, func(id.Zid) bool { return true }) if err != nil { return nil, err } } return result, nil } type metaMap map[id.Zid]*meta.Meta // SelectMeta returns all zettel meta data that match the selection // criteria. The result is ordered by descending zettel id. func (mgr *Manager) SelectMeta(ctx context.Context, q *query.Query) ([]*meta.Meta, error) { if msg := mgr.mgrLog.Debug(); msg.Enabled() { msg.Str("query", q.String()).Msg("SelectMeta") } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if !mgr.started { return nil, box.ErrStopped } compSearch := q.RetrieveAndCompile(mgr) selected := metaMap{} for _, term := range compSearch.Terms { rejected := id.Set{} handleMeta := func(m *meta.Meta) { zid := m.Zid if rejected.Contains(zid) { mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/alreadyRejected") return } if _, ok := selected[zid]; ok { mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/alreadySelected") return } if compSearch.PreMatch(m) && term.Match(m) { selected[zid] = m mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/match") } else { rejected.Zid(zid) mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/reject") } } for _, p := range mgr.boxes { if err := p.ApplyMeta(ctx, handleMeta, term.Retrieve); err != nil { return nil, err } } } result := make([]*meta.Meta, 0, len(selected)) for _, m := range selected { result = append(result, m) } return q.Sort(result), nil } // CanUpdateZettel returns true, if box could possibly update the given zettel. func (mgr *Manager) CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool { mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() return mgr.started && mgr.boxes[0].CanUpdateZettel(ctx, zettel) } // UpdateZettel updates an existing zettel. func (mgr *Manager) UpdateZettel(ctx context.Context, zettel domain.Zettel) error { mgr.mgrLog.Debug().Zid(zettel.Meta.Zid).Msg("UpdateZettel") mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if !mgr.started { return box.ErrStopped } // Remove all (computed) properties from metadata before storing the zettel. zettel.Meta = zettel.Meta.Clone() for _, p := range zettel.Meta.ComputedPairsRest() { if mgr.propertyKeys.Has(p.Key) { zettel.Meta.Delete(p.Key) } } return mgr.boxes[0].UpdateZettel(ctx, zettel) } // AllowRenameZettel returns true, if box will not disallow renaming the zettel. func (mgr *Manager) AllowRenameZettel(ctx context.Context, zid id.Zid) bool { mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if !mgr.started { return false } for _, p := range mgr.boxes { if !p.AllowRenameZettel(ctx, zid) { return false } } return true } // RenameZettel changes the current zid to a new zid. func (mgr *Manager) RenameZettel(ctx context.Context, curZid, newZid id.Zid) error { mgr.mgrLog.Debug().Zid(curZid).Zid(newZid).Msg("RenameZettel") mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if !mgr.started { return box.ErrStopped } for i, p := range mgr.boxes { err := p.RenameZettel(ctx, curZid, newZid) if err != nil && !errors.Is(err, box.ErrNotFound) { for j := 0; j < i; j++ { mgr.boxes[j].RenameZettel(ctx, newZid, curZid) } return err } } return nil } // CanDeleteZettel returns true, if box could possibly delete the given zettel. func (mgr *Manager) CanDeleteZettel(ctx context.Context, zid id.Zid) bool { mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if !mgr.started { return false } for _, p := range mgr.boxes { if p.CanDeleteZettel(ctx, zid) { return true } } return false } // DeleteZettel removes the zettel from the box. func (mgr *Manager) DeleteZettel(ctx context.Context, zid id.Zid) error { mgr.mgrLog.Debug().Zid(zid).Msg("DeleteZettel") mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if !mgr.started { return box.ErrStopped } for _, p := range mgr.boxes { err := p.DeleteZettel(ctx, zid) if err == nil { return nil } if !errors.Is(err, box.ErrNotFound) && !errors.Is(err, box.ErrReadOnly) { return err } } return box.ErrNotFound } |
Changes to box/manager/collect.go.
1 | //----------------------------------------------------------------------------- | | < < < | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package manager import ( "strings" "zettelstore.de/z/ast" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/domain/id" "zettelstore.de/z/strfun" ) type collectData struct { refs id.Set words store.WordSet urls store.WordSet } func (data *collectData) initialize() { data.refs = id.NewSet() data.words = store.NewWordSet() |
︙ | ︙ | |||
75 76 77 78 79 80 81 | if ref.IsExternal() { data.urls.Add(strings.ToLower(ref.Value)) } if !ref.IsZettel() { return } if zid, err := id.Parse(ref.URL.Path); err == nil { | | | 72 73 74 75 76 77 78 79 80 81 | if ref.IsExternal() { data.urls.Add(strings.ToLower(ref.Value)) } if !ref.IsZettel() { return } if zid, err := id.Parse(ref.URL.Path); err == nil { data.refs.Zid(zid) } } |
Changes to box/manager/enrich.go.
1 | //----------------------------------------------------------------------------- | | < < < | | | > | < | > > > > | < | < | < < < < < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package manager import ( "context" "strconv" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) // Enrich computes additional properties and updates the given metadata. func (mgr *Manager) Enrich(ctx context.Context, m *meta.Meta, boxNumber int) { // Calculate computed, but stored values. if _, ok := m.Get(api.KeyCreated); !ok { m.Set(api.KeyCreated, computeCreated(m.Zid)) } if box.DoNotEnrich(ctx) { // Enrich is called indirectly via indexer or enrichment is not requested // because of other reasons -> ignore this call, do not update metadata return } computePublished(m) m.Set(api.KeyBoxNumber, strconv.Itoa(boxNumber)) mgr.idxStore.Enrich(ctx, m) } func computeCreated(zid id.Zid) string { if zid <= 10101000000 { // A year 0000 is not allowed and therefore an artificaial Zid. // In the year 0001, the month must be > 0. // In the month 000101, the day must be > 0. return "00010101000000" } seconds := zid % 100 if seconds > 59 { seconds = 59 |
︙ | ︙ |
Changes to box/manager/indexer.go.
1 | //----------------------------------------------------------------------------- | | < < < > > > < < < | | | | | | | | | | | > > | > | > > > > > | < | > < | < | | < | < < < < < < < < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package manager import ( "context" "fmt" "net/url" "time" "zettelstore.de/z/box" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" "zettelstore.de/z/parser" "zettelstore.de/z/strfun" ) // SearchEqual returns all zettel that contains the given exact word. // The word must be normalized through Unicode NKFD, trimmed and not empty. func (mgr *Manager) SearchEqual(word string) id.Set { found := mgr.idxStore.SearchEqual(word) mgr.idxLog.Debug().Str("word", word).Int("found", int64(len(found))).Msg("SearchEqual") if msg := mgr.idxLog.Trace(); msg.Enabled() { msg.Str("ids", fmt.Sprint(found)).Msg("IDs") } return found } // SearchPrefix returns all zettel that have a word with the given prefix. // The prefix must be normalized through Unicode NKFD, trimmed and not empty. func (mgr *Manager) SearchPrefix(prefix string) id.Set { found := mgr.idxStore.SearchPrefix(prefix) mgr.idxLog.Debug().Str("prefix", prefix).Int("found", int64(len(found))).Msg("SearchPrefix") if msg := mgr.idxLog.Trace(); msg.Enabled() { msg.Str("ids", fmt.Sprint(found)).Msg("IDs") } return found } // SearchSuffix returns all zettel that have a word with the given suffix. // The suffix must be normalized through Unicode NKFD, trimmed and not empty. func (mgr *Manager) SearchSuffix(suffix string) id.Set { found := mgr.idxStore.SearchSuffix(suffix) mgr.idxLog.Debug().Str("suffix", suffix).Int("found", int64(len(found))).Msg("SearchSuffix") if msg := mgr.idxLog.Trace(); msg.Enabled() { msg.Str("ids", fmt.Sprint(found)).Msg("IDs") } return found } // SearchContains returns all zettel that contains the given string. // The string must be normalized through Unicode NKFD, trimmed and not empty. func (mgr *Manager) SearchContains(s string) id.Set { found := mgr.idxStore.SearchContains(s) mgr.idxLog.Debug().Str("s", s).Int("found", int64(len(found))).Msg("SearchContains") if msg := mgr.idxLog.Trace(); msg.Enabled() { msg.Str("ids", fmt.Sprint(found)).Msg("IDs") } return found } // idxIndexer runs in the background and updates the index data structures. // This is the main service of the idxIndexer. func (mgr *Manager) idxIndexer() { // Something may panic. Ensure a running indexer. defer func() { if r := recover(); r != nil { kernel.Main.LogRecover("Indexer", r) go mgr.idxIndexer() } }() timerDuration := 15 * time.Second timer := time.NewTimer(timerDuration) ctx := box.NoEnrichContext(context.Background()) for { // Sleep first, so the indexer will wait for boxes to initialize. if !mgr.idxSleepService(timer, timerDuration) { return } mgr.idxWorkService(ctx) } } func (mgr *Manager) idxWorkService(ctx context.Context) { var roomNum uint64 var start time.Time for { switch action, zid, arRoomNum := mgr.idxAr.Dequeue(); action { case arNothing: return case arReload: mgr.idxLog.Debug().Msg("reload") roomNum = 0 zids, err := mgr.FetchZids(ctx) if err == nil { start = time.Now() if rno := mgr.idxAr.Reload(zids); rno > 0 { roomNum = rno } mgr.idxMx.Lock() mgr.idxLastReload = time.Now().Local() mgr.idxSinceReload = 0 mgr.idxMx.Unlock() } case arZettel: mgr.idxLog.Debug().Zid(zid).Msg("zettel") zettel, err := mgr.GetZettel(ctx, zid) if err != nil { // Zettel was deleted or is not accessible b/c of other reasons mgr.idxLog.Trace().Zid(zid).Msg("delete") mgr.idxMx.Lock() mgr.idxSinceReload++ mgr.idxMx.Unlock() mgr.idxDeleteZettel(zid) continue } mgr.idxLog.Trace().Zid(zid).Msg("update") mgr.idxMx.Lock() if arRoomNum == roomNum { mgr.idxDurReload = time.Since(start) } mgr.idxSinceReload++ mgr.idxMx.Unlock() mgr.idxUpdateZettel(ctx, zettel) } } } func (mgr *Manager) idxSleepService(timer *time.Timer, timerDuration time.Duration) bool { select { case _, ok := <-mgr.idxReady: if !ok { return false } case _, ok := <-timer.C: if !ok { return false } timer.Reset(timerDuration) case <-mgr.done: if !timer.Stop() { <-timer.C } return false } return true } func (mgr *Manager) idxUpdateZettel(ctx context.Context, zettel domain.Zettel) { var cData collectData cData.initialize() collectZettelIndexData(parser.ParseZettel(ctx, zettel, "", mgr.rtConfig), &cData) m := zettel.Meta zi := store.NewZettelIndex(m.Zid) mgr.idxCollectFromMeta(ctx, m, zi, &cData) mgr.idxProcessData(ctx, zi, &cData) toCheck := mgr.idxStore.UpdateReferences(ctx, zi) mgr.idxCheckZettel(toCheck) } func (mgr *Manager) idxCollectFromMeta(ctx context.Context, m *meta.Meta, zi *store.ZettelIndex, cData *collectData) { for _, pair := range m.ComputedPairs() { descr := meta.GetDescription(pair.Key) if descr.IsProperty() { continue } switch descr.Type { |
︙ | ︙ | |||
197 198 199 200 201 202 203 | is := parser.ParseMetadata(pair.Value) collectInlineIndexData(&is, cData) case meta.TypeURL: if _, err := url.Parse(pair.Value); err == nil { cData.urls.Add(pair.Value) } default: | < | | < < < < < < < < < < < < < | | < > | | | | | | < | > | 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 | is := parser.ParseMetadata(pair.Value) collectInlineIndexData(&is, cData) case meta.TypeURL: if _, err := url.Parse(pair.Value); err == nil { cData.urls.Add(pair.Value) } default: for _, word := range strfun.NormalizeWords(pair.Value) { cData.words.Add(word) } } } } func (mgr *Manager) idxProcessData(ctx context.Context, zi *store.ZettelIndex, cData *collectData) { for ref := range cData.refs { if _, err := mgr.GetMeta(ctx, ref); err == nil { zi.AddBackRef(ref) } else { zi.AddDeadRef(ref) } } zi.SetWords(cData.words) zi.SetUrls(cData.urls) } func (mgr *Manager) idxUpdateValue(ctx context.Context, inverseKey, value string, zi *store.ZettelIndex) { zid, err := id.Parse(value) if err != nil { return } if _, err = mgr.GetMeta(ctx, zid); err != nil { zi.AddDeadRef(zid) return } if inverseKey == "" { zi.AddBackRef(zid) return } zi.AddMetaRef(inverseKey, zid) } func (mgr *Manager) idxDeleteZettel(zid id.Zid) { toCheck := mgr.idxStore.DeleteZettel(context.Background(), zid) mgr.idxCheckZettel(toCheck) } func (mgr *Manager) idxCheckZettel(s id.Set) { for zid := range s { mgr.idxAr.EnqueueZettel(zid) } } |
Changes to box/manager/manager.go.
1 | //----------------------------------------------------------------------------- | | < < < > | > > < < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package manager coordinates the various boxes and indexes of a Zettelstore. package manager import ( "context" "io" "net/url" "sync" "time" "zettelstore.de/c/maps" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/box/manager/memstore" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/strfun" ) // ConnectData contains all administration related values. type ConnectData struct { Number int // number of the box, starting with 1. Config config.Config Enricher box.Enricher Notify chan<- box.UpdateInfo } // Connect returns a handle to the specified box. func Connect(u *url.URL, authManager auth.BaseManager, cdata *ConnectData) (box.ManagedBox, error) { if authManager.IsReadonly() { rawURL := u.String() // TODO: the following is wrong under some circumstances: |
︙ | ︙ | |||
77 78 79 80 81 82 83 84 85 86 | func Register(scheme string, create createFunc) { if _, ok := registry[scheme]; ok { panic(scheme) } registry[scheme] = create } // Manager is a coordinating box. type Manager struct { mgrLog *logger.Logger | > > > < < > | < < < < < < < < < < < < < < | | < | | 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | func Register(scheme string, create createFunc) { if _, ok := registry[scheme]; ok { panic(scheme) } registry[scheme] = create } // GetSchemes returns all registered scheme, ordered by scheme string. func GetSchemes() []string { return maps.Keys(registry) } // Manager is a coordinating box. type Manager struct { mgrLog *logger.Logger mgrMx sync.RWMutex started bool rtConfig config.Config boxes []box.ManagedBox observers []box.UpdateFunc mxObserver sync.RWMutex done chan struct{} infos chan box.UpdateInfo propertyKeys strfun.Set // Set of property key names // Indexer data idxLog *logger.Logger idxStore store.Store idxAr *anterooms idxReady chan struct{} // Signal a non-empty anteroom to background task // Indexer stats data idxMx sync.RWMutex idxLastReload time.Time idxDurReload time.Duration idxSinceReload uint64 } // New creates a new managing box. func New(boxURIs []*url.URL, authManager auth.BaseManager, rtConfig config.Config) (*Manager, error) { descrs := meta.GetSortedKeyDescriptions() propertyKeys := make(strfun.Set, len(descrs)) for _, kd := range descrs { if kd.IsProperty() { propertyKeys.Set(kd.Name) } } boxLog := kernel.Main.GetLogger(kernel.BoxService) mgr := &Manager{ mgrLog: boxLog.Clone().Str("box", "manager").Child(), rtConfig: rtConfig, infos: make(chan box.UpdateInfo, len(boxURIs)*10), propertyKeys: propertyKeys, idxLog: boxLog.Clone().Str("box", "index").Child(), idxStore: memstore.New(), idxAr: newAnterooms(1000), idxReady: make(chan struct{}, 1), } cdata := ConnectData{Number: 1, Config: rtConfig, Enricher: mgr, Notify: mgr.infos} boxes := make([]box.ManagedBox, 0, len(boxURIs)+2) for _, uri := range boxURIs { p, err := Connect(uri, authManager, &cdata) if err != nil { return nil, err } if p != nil { |
︙ | ︙ | |||
167 168 169 170 171 172 173 | } cdata.Number++ boxes = append(boxes, constbox, compbox) mgr.boxes = boxes return mgr, nil } | < < < < | | | 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | } cdata.Number++ boxes = append(boxes, constbox, compbox) mgr.boxes = boxes return mgr, nil } // RegisterObserver registers an observer that will be notified // if a zettel was found to be changed. func (mgr *Manager) RegisterObserver(f box.UpdateFunc) { if f != nil { mgr.mxObserver.Lock() mgr.observers = append(mgr.observers, f) mgr.mxObserver.Unlock() } } func (mgr *Manager) notifier() { // The call to notify may panic. Ensure a running notifier. defer func() { if r := recover(); r != nil { kernel.Main.LogRecover("Notifier", r) go mgr.notifier() } }() tsLastEvent := time.Now() cache := destutterCache{} for { |
︙ | ︙ | |||
210 211 212 213 214 215 216 | reason, zid := ci.Reason, ci.Zid mgr.mgrLog.Debug().Uint("reason", uint64(reason)).Zid(zid).Msg("notifier") if ignoreUpdate(cache, now, reason, zid) { mgr.mgrLog.Trace().Uint("reason", uint64(reason)).Zid(zid).Msg("notifier ignored") continue } | < < < | < | 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | reason, zid := ci.Reason, ci.Zid mgr.mgrLog.Debug().Uint("reason", uint64(reason)).Zid(zid).Msg("notifier") if ignoreUpdate(cache, now, reason, zid) { mgr.mgrLog.Trace().Uint("reason", uint64(reason)).Zid(zid).Msg("notifier ignored") continue } mgr.idxEnqueue(reason, zid) if ci.Box == nil { ci.Box = mgr } mgr.notifyObserver(&ci) } case <-mgr.done: return } } } |
︙ | ︙ | |||
245 246 247 248 249 250 251 | cache[zid] = destutterData{ deadAt: now.Add(500 * time.Millisecond), reason: reason, } return false } | | < < | < < < | 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 | cache[zid] = destutterData{ deadAt: now.Add(500 * time.Millisecond), reason: reason, } return false } func (mgr *Manager) idxEnqueue(reason box.UpdateReason, zid id.Zid) { switch reason { case box.OnReload: mgr.idxAr.Reset() case box.OnZettel: mgr.idxAr.EnqueueZettel(zid) default: return } select { case mgr.idxReady <- struct{}{}: default: } } |
︙ | ︙ | |||
278 279 280 281 282 283 284 | } } // Start the box. Now all other functions of the box are allowed. // Starting an already started box is not allowed. func (mgr *Manager) Start(ctx context.Context) error { mgr.mgrMx.Lock() | > | < < < | < < < < < < < | | < < < < < < < < < < < < < < | < < < | < < < | < | > | > | < < < < < < < < < < < < | 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 | } } // Start the box. Now all other functions of the box are allowed. // Starting an already started box is not allowed. func (mgr *Manager) Start(ctx context.Context) error { mgr.mgrMx.Lock() if mgr.started { mgr.mgrMx.Unlock() return box.ErrStarted } for i := len(mgr.boxes) - 1; i >= 0; i-- { ssi, ok := mgr.boxes[i].(box.StartStopper) if !ok { continue } err := ssi.Start(ctx) if err == nil { continue } for j := i + 1; j < len(mgr.boxes); j++ { if ssj, ok2 := mgr.boxes[j].(box.StartStopper); ok2 { ssj.Stop(ctx) } } mgr.mgrMx.Unlock() return err } mgr.idxAr.Reset() // Ensure an initial index run mgr.done = make(chan struct{}) go mgr.notifier() go mgr.idxIndexer() mgr.started = true mgr.mgrMx.Unlock() return nil } // Stop the started box. Now only the Start() function is allowed. func (mgr *Manager) Stop(ctx context.Context) { mgr.mgrMx.Lock() defer mgr.mgrMx.Unlock() if !mgr.started { return } close(mgr.done) for _, p := range mgr.boxes { if ss, ok := p.(box.StartStopper); ok { ss.Stop(ctx) } } mgr.started = false } // Refresh internal box data. func (mgr *Manager) Refresh(ctx context.Context) error { mgr.mgrLog.Debug().Msg("Refresh") mgr.mgrMx.Lock() defer mgr.mgrMx.Unlock() if !mgr.started { return box.ErrStopped } mgr.infos <- box.UpdateInfo{Reason: box.OnReload, Zid: id.Invalid} for _, bx := range mgr.boxes { if rb, ok := bx.(box.Refresher); ok { rb.Refresh(ctx) } } return nil } // ReadStats populates st with box statistics. func (mgr *Manager) ReadStats(st *box.Stats) { mgr.mgrLog.Debug().Msg("ReadStats") mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() subStats := make([]box.ManagedBoxStats, len(mgr.boxes)) for i, p := range mgr.boxes { |
︙ | ︙ | |||
421 422 423 424 425 426 427 | st.IndexedUrls = storeSt.Urls } // Dump internal data structures to a Writer. func (mgr *Manager) Dump(w io.Writer) { mgr.idxStore.Dump(w) } | < < < < < < < < < < < < < | 353 354 355 356 357 358 359 | st.IndexedUrls = storeSt.Urls } // Dump internal data structures to a Writer. func (mgr *Manager) Dump(w io.Writer) { mgr.idxStore.Dump(w) } |
Deleted box/manager/mapstore/mapstore.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added box/manager/memstore/memstore.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package memstore stored the index in main memory. package memstore import ( "context" "fmt" "io" "sort" "strings" "sync" "zettelstore.de/c/api" "zettelstore.de/c/maps" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) type metaRefs struct { forward id.Slice backward id.Slice } type zettelIndex struct { dead id.Slice forward id.Slice backward id.Slice meta map[string]metaRefs words []string urls []string } 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 len(zi.meta) == 0 } type stringRefs map[string]id.Slice type memStore struct { mx sync.RWMutex idx map[id.Zid]*zettelIndex dead map[id.Zid]id.Slice // map dead refs where they occur words stringRefs urls stringRefs // Stats updates uint64 } // New returns a new memory-based index store. func New() store.Store { return &memStore{ idx: make(map[id.Zid]*zettelIndex), dead: make(map[id.Zid]id.Slice), words: make(stringRefs), urls: make(stringRefs), } } 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(m *meta.Meta) bool { ms.mx.RLock() defer ms.mx.RUnlock() zi, ok := ms.idx[m.Zid] if !ok { return false } var updated bool if len(zi.dead) > 0 { m.Set(api.KeyDead, zi.dead.String()) updated = true } back := removeOtherMetaRefs(m, zi.backward.Copy()) if len(zi.backward) > 0 { m.Set(api.KeyBackward, zi.backward.String()) updated = true } if len(zi.forward) > 0 { m.Set(api.KeyForward, zi.forward.String()) back = remRefs(back, zi.forward) updated = true } for k, refs := range zi.meta { if len(refs.backward) > 0 { m.Set(k, refs.backward.String()) back = remRefs(back, refs.backward) updated = true } } if len(back) > 0 { m.Set(api.KeyBack, back.String()) updated = true } return updated } // SearchEqual returns all zettel that contains the given exact word. // The word must be normalized through Unicode NKFD, trimmed and not empty. func (ms *memStore) SearchEqual(word string) id.Set { ms.mx.RLock() defer ms.mx.RUnlock() result := id.NewSet() if refs, ok := ms.words[word]; ok { result.AddSlice(refs) } if refs, ok := ms.urls[word]; ok { result.AddSlice(refs) } zid, err := id.Parse(word) if err != nil { return result } zi, ok := ms.idx[zid] if !ok { return result } addBackwardZids(result, zid, zi) return result } // SearchPrefix returns all zettel that have a word with the given prefix. // The prefix must be normalized through Unicode NKFD, trimmed and not empty. func (ms *memStore) SearchPrefix(prefix string) id.Set { ms.mx.RLock() defer ms.mx.RUnlock() result := ms.selectWithPred(prefix, strings.HasPrefix) l := len(prefix) if l > 14 { return result } maxZid, err := id.Parse(prefix + "99999999999999"[:14-l]) if err != nil { return result } var minZid id.Zid if l < 14 && prefix == "0000000000000"[:l] { minZid = id.Zid(1) } else { minZid, err = id.Parse(prefix + "00000000000000"[:14-l]) if err != nil { return result } } for zid, zi := range ms.idx { if minZid <= zid && zid <= maxZid { addBackwardZids(result, zid, zi) } } return result } // SearchSuffix returns all zettel that have a word with the given suffix. // The suffix must be normalized through Unicode NKFD, trimmed and not empty. func (ms *memStore) SearchSuffix(suffix string) id.Set { ms.mx.RLock() defer ms.mx.RUnlock() result := ms.selectWithPred(suffix, strings.HasSuffix) l := len(suffix) if l > 14 { return result } val, err := id.ParseUint(suffix) if err != nil { return result } modulo := uint64(1) for i := 0; i < l; i++ { modulo *= 10 } for zid, zi := range ms.idx { if uint64(zid)%modulo == val { addBackwardZids(result, zid, zi) } } return result } // SearchContains returns all zettel that contains the given string. // The string must be normalized through Unicode NKFD, trimmed and not empty. func (ms *memStore) SearchContains(s string) id.Set { ms.mx.RLock() defer ms.mx.RUnlock() result := ms.selectWithPred(s, strings.Contains) if len(s) > 14 { return result } if _, err := id.ParseUint(s); err != nil { return result } for zid, zi := range ms.idx { if strings.Contains(zid.String(), s) { addBackwardZids(result, zid, zi) } } return result } func (ms *memStore) selectWithPred(s string, pred func(string, string) bool) id.Set { // Must only be called if ms.mx is read-locked! result := id.NewSet() for word, refs := range ms.words { if !pred(word, s) { continue } result.AddSlice(refs) } for u, refs := range ms.urls { if !pred(u, s) { continue } result.AddSlice(refs) } return result } func addBackwardZids(result id.Set, zid id.Zid, zi *zettelIndex) { // Must only be called if ms.mx is read-locked! result.Zid(zid) result.AddSlice(zi.backward) for _, mref := range zi.meta { result.AddSlice(mref.backward) } } func removeOtherMetaRefs(m *meta.Meta, back id.Slice) id.Slice { for _, p := range m.PairsRest() { switch meta.Type(p.Key) { case meta.TypeID: if zid, err := id.Parse(p.Value); err == nil { back = remRef(back, zid) } case meta.TypeIDSet: for _, val := range meta.ListFromValue(p.Value) { if zid, err := id.Parse(val); err == nil { back = remRef(back, zid) } } } } return back } 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{} ziExist = false } // Is this zettel an old dead reference mentioned in other zettel? var toCheck id.Set if refs, ok := ms.dead[zidx.Zid]; ok { // These must be checked later again toCheck = id.NewSet(refs...) delete(ms.dead, zidx.Zid) } 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()) // Check if zi must be inserted into ms.idx if !ziExist && !zi.isEmpty() { ms.idx[zidx.Zid] = zi } return toCheck } func (ms *memStore) updateDeadReferences(zidx *store.ZettelIndex, zi *zettelIndex) { // Must only be called if ms.mx is write-locked! drefs := zidx.GetDeadRefs() newRefs, remRefs := refsDiff(drefs, zi.dead) zi.dead = drefs for _, ref := range remRefs { ms.dead[ref] = remRef(ms.dead[ref], zidx.Zid) } for _, ref := range newRefs { ms.dead[ref] = addRef(ms.dead[ref], zidx.Zid) } } func (ms *memStore) updateForwardBackwardReferences(zidx *store.ZettelIndex, zi *zettelIndex) { // Must only be called if ms.mx is write-locked! brefs := zidx.GetBackRefs() newRefs, remRefs := refsDiff(brefs, zi.forward) zi.forward = brefs for _, ref := range remRefs { bzi := ms.getEntry(ref) bzi.backward = remRef(bzi.backward, zidx.Zid) } for _, ref := range newRefs { bzi := ms.getEntry(ref) bzi.backward = addRef(bzi.backward, zidx.Zid) } } func (ms *memStore) updateMetadataReferences(zidx *store.ZettelIndex, zi *zettelIndex) { // Must only be called if ms.mx is write-locked! metarefs := zidx.GetMetaRefs() for key, mr := range zi.meta { if _, ok := metarefs[key]; ok { continue } ms.removeInverseMeta(zidx.Zid, key, mr.forward) } if zi.meta == nil { zi.meta = make(map[string]metaRefs) } for key, mrefs := range metarefs { mr := zi.meta[key] newRefs, remRefs := refsDiff(mrefs, mr.forward) mr.forward = mrefs zi.meta[key] = mr for _, ref := range newRefs { bzi := ms.getEntry(ref) if bzi.meta == nil { bzi.meta = make(map[string]metaRefs) } bmr := bzi.meta[key] bmr.backward = addRef(bmr.backward, zidx.Zid) bzi.meta[key] = bmr } ms.removeInverseMeta(zidx.Zid, key, remRefs) } } func updateWordSet(zid id.Zid, srefs stringRefs, prev []string, next store.WordSet) []string { // Must only be called if ms.mx is write-locked! newWords, removeWords := next.Diff(prev) for _, word := range newWords { if refs, ok := srefs[word]; ok { srefs[word] = addRef(refs, zid) continue } srefs[word] = id.Slice{zid} } for _, word := range removeWords { refs, ok := srefs[word] if !ok { continue } refs2 := remRef(refs, zid) if len(refs2) == 0 { delete(srefs, word) continue } srefs[word] = refs2 } return next.Words() } 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 } zi := &zettelIndex{} ms.idx[zid] = zi return zi } 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 { return nil } ms.deleteDeadSources(zid, zi) toCheck := ms.deleteForwardBackward(zid, zi) if len(zi.meta) > 0 { for key, mrefs := range zi.meta { ms.removeInverseMeta(zid, key, mrefs.forward) } } ms.deleteWords(zid, zi.words) delete(ms.idx, zid) return toCheck } func (ms *memStore) deleteDeadSources(zid id.Zid, zi *zettelIndex) { // Must only be called if ms.mx is write-locked! for _, ref := range zi.dead { if drefs, ok := ms.dead[ref]; ok { drefs = remRef(drefs, zid) if len(drefs) > 0 { ms.dead[ref] = drefs } else { delete(ms.dead, ref) } } } } func (ms *memStore) deleteForwardBackward(zid id.Zid, zi *zettelIndex) id.Set { // Must only be called if ms.mx is write-locked! var toCheck id.Set for _, ref := range zi.forward { if fzi, ok := ms.idx[ref]; ok { fzi.backward = remRef(fzi.backward, zid) } } for _, ref := range zi.backward { if bzi, ok := ms.idx[ref]; ok { bzi.forward = remRef(bzi.forward, zid) if toCheck == nil { toCheck = id.NewSet() } toCheck.Zid(ref) } } return toCheck } func (ms *memStore) removeInverseMeta(zid id.Zid, key string, forward id.Slice) { // Must only be called if ms.mx is write-locked! for _, ref := range forward { bzi, ok := ms.idx[ref] if !ok || bzi.meta == nil { continue } bmr, ok := bzi.meta[key] if !ok { continue } bmr.backward = remRef(bmr.backward, zid) if len(bmr.backward) > 0 || len(bmr.forward) > 0 { bzi.meta[key] = bmr } else { delete(bzi.meta, key) if len(bzi.meta) == 0 { bzi.meta = nil } } } } func (ms *memStore) deleteWords(zid id.Zid, words []string) { // Must only be called if ms.mx is write-locked! for _, word := range words { refs, ok := ms.words[word] if !ok { continue } refs2 := remRef(refs, zid) if len(refs2) == 0 { delete(ms.words, word) continue } ms.words[word] = refs2 } } func (ms *memStore) ReadStats(st *store.Stats) { ms.mx.RLock() st.Zettel = len(ms.idx) st.Updates = ms.updates st.Words = uint64(len(ms.words)) st.Urls = uint64(len(ms.urls)) ms.mx.RUnlock() } func (ms *memStore) Dump(w io.Writer) { ms.mx.RLock() defer ms.mx.RUnlock() io.WriteString(w, "=== Dump\n") ms.dumpIndex(w) ms.dumpDead(w) dumpStringRefs(w, "Words", "", "", ms.words) dumpStringRefs(w, "URLs", "[[", "]]", ms.urls) } func (ms *memStore) dumpIndex(w io.Writer) { if len(ms.idx) == 0 { return } io.WriteString(w, "==== Zettel Index\n") zids := make(id.Slice, 0, len(ms.idx)) for id := range ms.idx { zids = append(zids, id) } zids.Sort() for _, id := range zids { fmt.Fprintln(w, "=====", id) zi := ms.idx[id] if len(zi.dead) > 0 { fmt.Fprintln(w, "* Dead:", zi.dead) } dumpZids(w, "* Forward:", zi.forward) dumpZids(w, "* Backward:", zi.backward) for k, fb := range zi.meta { fmt.Fprintln(w, "* Meta", k) dumpZids(w, "** Forward:", fb.forward) dumpZids(w, "** Backward:", fb.backward) } dumpStrings(w, "* Words", "", "", zi.words) dumpStrings(w, "* URLs", "[[", "]]", zi.urls) } } func (ms *memStore) dumpDead(w io.Writer) { if len(ms.dead) == 0 { return } fmt.Fprintf(w, "==== Dead References\n") zids := make(id.Slice, 0, len(ms.dead)) for id := range ms.dead { zids = append(zids, id) } zids.Sort() for _, id := range zids { fmt.Fprintln(w, ";", id) fmt.Fprintln(w, ":", ms.dead[id]) } } func dumpZids(w io.Writer, prefix string, zids id.Slice) { if len(zids) > 0 { io.WriteString(w, prefix) for _, zid := range zids { io.WriteString(w, " ") w.Write(zid.Bytes()) } fmt.Fprintln(w) } } func dumpStrings(w io.Writer, title, preString, postString string, slice []string) { if len(slice) > 0 { sl := make([]string, len(slice)) copy(sl, slice) sort.Strings(sl) fmt.Fprintln(w, title) for _, s := range sl { fmt.Fprintf(w, "** %s%s%s\n", preString, s, postString) } } } func dumpStringRefs(w io.Writer, title, preString, postString string, srefs stringRefs) { if len(srefs) == 0 { return } fmt.Fprintln(w, "====", title) for _, s := range maps.Keys(srefs) { fmt.Fprintf(w, "; %s%s%s\n", preString, s, postString) fmt.Fprintln(w, ":", srefs[s]) } } |
Added box/manager/memstore/refs.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package memstore import "zettelstore.de/z/domain/id" func refsDiff(refsN, refsO id.Slice) (newRefs, remRefs id.Slice) { npos, opos := 0, 0 for npos < len(refsN) && opos < len(refsO) { rn, ro := refsN[npos], refsO[opos] if rn == ro { npos++ opos++ continue } if rn < ro { newRefs = append(newRefs, rn) npos++ continue } remRefs = append(remRefs, ro) opos++ } if npos < len(refsN) { newRefs = append(newRefs, refsN[npos:]...) } if opos < len(refsO) { remRefs = append(remRefs, refsO[opos:]...) } return newRefs, remRefs } func addRef(refs id.Slice, ref id.Zid) id.Slice { hi := len(refs) for lo := 0; lo < hi; { m := lo + (hi-lo)/2 if r := refs[m]; r == ref { return refs } else if r < ref { lo = m + 1 } else { hi = m } } refs = append(refs, id.Invalid) copy(refs[hi+1:], refs[hi:]) refs[hi] = ref return refs } func remRefs(refs, rem id.Slice) id.Slice { if len(refs) == 0 || len(rem) == 0 { return refs } result := make(id.Slice, 0, len(refs)) rpos, dpos := 0, 0 for rpos < len(refs) && dpos < len(rem) { rr, dr := refs[rpos], rem[dpos] if rr < dr { result = append(result, rr) rpos++ continue } if dr < rr { dpos++ continue } rpos++ dpos++ } if rpos < len(refs) { result = append(result, refs[rpos:]...) } return result } func remRef(refs id.Slice, ref id.Zid) id.Slice { hi := len(refs) for lo := 0; lo < hi; { m := lo + (hi-lo)/2 if r := refs[m]; r == ref { copy(refs[m:], refs[m+1:]) refs = refs[:len(refs)-1] return refs } else if r < ref { lo = m + 1 } else { hi = m } } return refs } |
Added box/manager/memstore/refs_test.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package memstore import ( "testing" "zettelstore.de/z/domain/id" ) func assertRefs(t *testing.T, i int, got, exp id.Slice) { t.Helper() if got == nil && exp != nil { t.Errorf("%d: got nil, but expected %v", i, exp) return } if got != nil && exp == nil { t.Errorf("%d: expected nil, but got %v", i, got) return } if len(got) != len(exp) { t.Errorf("%d: expected len(%v)==%d, but got len(%v)==%d", i, exp, len(exp), got, len(got)) return } for p, n := range exp { if got := got[p]; got != id.Zid(n) { t.Errorf("%d: pos %d: expected %d, but got %d", i, p, n, got) } } } func TestRefsDiff(t *testing.T) { t.Parallel() testcases := []struct { in1, in2 id.Slice exp1, exp2 id.Slice }{ {nil, nil, nil, nil}, {id.Slice{1}, nil, id.Slice{1}, nil}, {nil, id.Slice{1}, nil, id.Slice{1}}, {id.Slice{1}, id.Slice{1}, nil, nil}, {id.Slice{1, 2}, id.Slice{1}, id.Slice{2}, nil}, {id.Slice{1, 2}, id.Slice{1, 3}, id.Slice{2}, id.Slice{3}}, {id.Slice{1, 4}, id.Slice{1, 3}, id.Slice{4}, id.Slice{3}}, } for i, tc := range testcases { got1, got2 := refsDiff(tc.in1, tc.in2) assertRefs(t, i, got1, tc.exp1) assertRefs(t, i, got2, tc.exp2) } } func TestAddRef(t *testing.T) { t.Parallel() testcases := []struct { ref id.Slice zid uint exp id.Slice }{ {nil, 5, id.Slice{5}}, {id.Slice{1}, 5, id.Slice{1, 5}}, {id.Slice{10}, 5, id.Slice{5, 10}}, {id.Slice{5}, 5, id.Slice{5}}, {id.Slice{1, 10}, 5, id.Slice{1, 5, 10}}, {id.Slice{1, 5, 10}, 5, id.Slice{1, 5, 10}}, } for i, tc := range testcases { got := addRef(tc.ref, id.Zid(tc.zid)) assertRefs(t, i, got, tc.exp) } } func TestRemRefs(t *testing.T) { t.Parallel() testcases := []struct { in1, in2 id.Slice exp id.Slice }{ {nil, nil, nil}, {nil, id.Slice{}, nil}, {id.Slice{}, nil, id.Slice{}}, {id.Slice{}, id.Slice{}, id.Slice{}}, {id.Slice{1}, id.Slice{5}, id.Slice{1}}, {id.Slice{10}, id.Slice{5}, id.Slice{10}}, {id.Slice{1, 5}, id.Slice{5}, id.Slice{1}}, {id.Slice{5, 10}, id.Slice{5}, id.Slice{10}}, {id.Slice{1, 10}, id.Slice{5}, id.Slice{1, 10}}, {id.Slice{1}, id.Slice{2, 5}, id.Slice{1}}, {id.Slice{10}, id.Slice{2, 5}, id.Slice{10}}, {id.Slice{1, 5}, id.Slice{2, 5}, id.Slice{1}}, {id.Slice{5, 10}, id.Slice{2, 5}, id.Slice{10}}, {id.Slice{1, 2, 5}, id.Slice{2, 5}, id.Slice{1}}, {id.Slice{2, 5, 10}, id.Slice{2, 5}, id.Slice{10}}, {id.Slice{1, 10}, id.Slice{2, 5}, id.Slice{1, 10}}, {id.Slice{1}, id.Slice{5, 9}, id.Slice{1}}, {id.Slice{10}, id.Slice{5, 9}, id.Slice{10}}, {id.Slice{1, 5}, id.Slice{5, 9}, id.Slice{1}}, {id.Slice{5, 10}, id.Slice{5, 9}, id.Slice{10}}, {id.Slice{1, 5, 9}, id.Slice{5, 9}, id.Slice{1}}, {id.Slice{5, 9, 10}, id.Slice{5, 9}, id.Slice{10}}, {id.Slice{1, 10}, id.Slice{5, 9}, id.Slice{1, 10}}, } for i, tc := range testcases { got := remRefs(tc.in1, tc.in2) assertRefs(t, i, got, tc.exp) } } func TestRemRef(t *testing.T) { t.Parallel() testcases := []struct { ref id.Slice zid uint exp id.Slice }{ {nil, 5, nil}, {id.Slice{}, 5, id.Slice{}}, {id.Slice{5}, 5, id.Slice{}}, {id.Slice{1}, 5, id.Slice{1}}, {id.Slice{10}, 5, id.Slice{10}}, {id.Slice{1, 5}, 5, id.Slice{1}}, {id.Slice{5, 10}, 5, id.Slice{10}}, {id.Slice{1, 5, 10}, 5, id.Slice{1, 10}}, } for i, tc := range testcases { got := remRef(tc.ref, id.Zid(tc.zid)) assertRefs(t, i, got, tc.exp) } } |
Changes to box/manager/store/store.go.
1 | //----------------------------------------------------------------------------- | | < < < | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package store contains general index data for storing a zettel index. package store import ( "context" "io" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/query" ) // Stats records statistics about the store. type Stats struct { // Zettel is the number of zettel managed by the indexer. Zettel int |
︙ | ︙ | |||
39 40 41 42 43 44 45 | } // Store all relevant zettel data. There may be multiple implementations, i.e. // memory-based, file-based, based on SQLite, ... type Store interface { query.Searcher | < < < | | < < < | 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | } // Store all relevant zettel data. There may be multiple implementations, i.e. // memory-based, file-based, based on SQLite, ... type Store interface { query.Searcher // Entrich metadata with data from store. Enrich(ctx context.Context, m *meta.Meta) // UpdateReferences for a specific zettel. // Returns set of zettel identifier that must also be checked for changes. UpdateReferences(context.Context, *ZettelIndex) id.Set // DeleteZettel removes index data for given zettel. // Returns set of zettel identifier that must also be checked for changes. DeleteZettel(context.Context, id.Zid) id.Set // ReadStats populates st with store statistics. ReadStats(st *Stats) // Dump the content to a Writer. Dump(io.Writer) } |
Changes to box/manager/store/wordset.go.
1 | //----------------------------------------------------------------------------- | | < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package store // WordSet contains the set of all words, with the count of their occurrences. type WordSet map[string]int |
︙ | ︙ |
Changes to box/manager/store/wordset_test.go.
1 | //----------------------------------------------------------------------------- | | < < < | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package store_test import ( "sort" "testing" "zettelstore.de/z/box/manager/store" ) func equalWordList(exp, got []string) bool { if len(exp) != len(got) { return false } if len(got) == 0 { return len(exp) == 0 } sort.Strings(got) for i, w := range exp { if w != got[i] { return false } } return true } |
︙ | ︙ |
Changes to box/manager/store/zettel.go.
1 | //----------------------------------------------------------------------------- | | < < < < | < < | < | | | | | | | < | | | | > | > | | | | | | | > | < < | > | > | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package store import "zettelstore.de/z/domain/id" // ZettelIndex contains all index data of a zettel. type ZettelIndex struct { Zid id.Zid // zid of the indexed zettel 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 } // NewZettelIndex creates a new zettel index. func NewZettelIndex(zid id.Zid) *ZettelIndex { return &ZettelIndex{ Zid: zid, backrefs: id.NewSet(), metarefs: make(map[string]id.Set), deadrefs: id.NewSet(), } } // AddBackRef adds a reference to a zettel where the current zettel links to // without any more information. func (zi *ZettelIndex) AddBackRef(zid id.Zid) { zi.backrefs.Zid(zid) } // AddMetaRef adds a named reference to a zettel. On that zettel, the given // metadata key should point back to the current zettel. func (zi *ZettelIndex) AddMetaRef(key string, zid id.Zid) { if zids, ok := zi.metarefs[key]; ok { zids.Zid(zid) return } zi.metarefs[key] = id.NewSet(zid) } // AddDeadRef adds a dead reference to a zettel. func (zi *ZettelIndex) AddDeadRef(zid id.Zid) { zi.deadrefs.Zid(zid) } // SetWords sets the words to the given value. 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 } // GetDeadRefs returns all dead references as a sorted list. func (zi *ZettelIndex) GetDeadRefs() id.Slice { return zi.deadrefs.Sorted() } // GetBackRefs returns all back references as a sorted list. func (zi *ZettelIndex) GetBackRefs() id.Slice { return zi.backrefs.Sorted() } // GetMetaRefs returns all meta references as a map of strings to a sorted list of references func (zi *ZettelIndex) GetMetaRefs() map[string]id.Slice { if len(zi.metarefs) == 0 { return nil } result := make(map[string]id.Slice, len(zi.metarefs)) for key, refs := range zi.metarefs { result[key] = refs.Sorted() } return result } // GetWords returns a reference to the set of words. It must not be modified. func (zi *ZettelIndex) GetWords() WordSet { return zi.words } |
︙ | ︙ |
Changes to box/membox/membox.go.
1 | //----------------------------------------------------------------------------- | | < < < > > > < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2023 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 membox stores zettel volatile in main memory. package membox import ( "context" "net/url" "sync" "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/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/query" ) func init() { manager.Register( "mem", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { return &memBox{ |
︙ | ︙ | |||
46 47 48 49 50 51 52 | type memBox struct { log *logger.Logger u *url.URL cdata manager.ConnectData maxZettel int maxBytes int mx sync.RWMutex // Protects the following fields | | | | | < < < < < < < < < | | < | | | | | | | | > | > > > | 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | type memBox struct { log *logger.Logger u *url.URL cdata manager.ConnectData maxZettel int maxBytes int mx sync.RWMutex // Protects the following fields zettel map[id.Zid]domain.Zettel curBytes int } func (mb *memBox) notifyChanged(zid id.Zid) { if chci := mb.cdata.Notify; chci != nil { chci <- box.UpdateInfo{Reason: box.OnZettel, Zid: zid} } } func (mb *memBox) Location() string { return mb.u.String() } func (mb *memBox) Start(context.Context) error { mb.mx.Lock() mb.zettel = make(map[id.Zid]domain.Zettel) mb.curBytes = 0 mb.mx.Unlock() mb.log.Trace().Int("max-zettel", int64(mb.maxZettel)).Int("max-bytes", int64(mb.maxBytes)).Msg("Start Box") return nil } func (mb *memBox) Stop(context.Context) { mb.mx.Lock() mb.zettel = nil mb.mx.Unlock() } func (mb *memBox) CanCreateZettel(context.Context) bool { mb.mx.RLock() defer mb.mx.RUnlock() return len(mb.zettel) < mb.maxZettel } func (mb *memBox) CreateZettel(_ context.Context, zettel domain.Zettel) (id.Zid, error) { mb.mx.Lock() newBytes := mb.curBytes + zettel.Length() if mb.maxZettel < len(mb.zettel) || mb.maxBytes < newBytes { mb.mx.Unlock() return id.Invalid, box.ErrCapacity } zid, err := box.GetNewZid(func(zid id.Zid) (bool, error) { _, ok := mb.zettel[zid] return !ok, nil }) if err != nil { mb.mx.Unlock() return id.Invalid, err } meta := zettel.Meta.Clone() meta.Zid = zid zettel.Meta = meta mb.zettel[zid] = zettel mb.curBytes = newBytes mb.mx.Unlock() mb.notifyChanged(zid) mb.log.Trace().Zid(zid).Msg("CreateZettel") return zid, nil } func (mb *memBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) { mb.mx.RLock() zettel, ok := mb.zettel[zid] mb.mx.RUnlock() if !ok { return domain.Zettel{}, box.ErrNotFound } zettel.Meta = zettel.Meta.Clone() mb.log.Trace().Msg("GetZettel") return zettel, nil } func (mb *memBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) { mb.mx.RLock() zettel, ok := mb.zettel[zid] mb.mx.RUnlock() if !ok { return nil, box.ErrNotFound } mb.log.Trace().Msg("GetMeta") return zettel.Meta.Clone(), nil } func (mb *memBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error { mb.mx.RLock() defer mb.mx.RUnlock() mb.log.Trace().Int("entries", int64(len(mb.zettel))).Msg("ApplyZid") for zid := range mb.zettel { |
︙ | ︙ | |||
162 163 164 165 166 167 168 | mb.cdata.Enricher.Enrich(ctx, m, mb.cdata.Number) handle(m) } } return nil } | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 | mb.cdata.Enricher.Enrich(ctx, m, mb.cdata.Number) handle(m) } } return nil } func (mb *memBox) CanUpdateZettel(_ context.Context, zettel domain.Zettel) bool { mb.mx.RLock() defer mb.mx.RUnlock() zid := zettel.Meta.Zid if !zid.IsValid() { return false } newBytes := mb.curBytes + zettel.Length() if prevZettel, found := mb.zettel[zid]; found { newBytes -= prevZettel.Length() } return newBytes < mb.maxBytes } func (mb *memBox) UpdateZettel(_ context.Context, zettel domain.Zettel) error { m := zettel.Meta.Clone() if !m.Zid.IsValid() { return &box.ErrInvalidID{Zid: m.Zid} } mb.mx.Lock() newBytes := mb.curBytes + zettel.Length() if prevZettel, found := mb.zettel[m.Zid]; found { newBytes -= prevZettel.Length() } if mb.maxBytes < newBytes { mb.mx.Unlock() return box.ErrCapacity } zettel.Meta = m mb.zettel[m.Zid] = zettel mb.curBytes = newBytes mb.mx.Unlock() mb.notifyChanged(m.Zid) mb.log.Trace().Msg("UpdateZettel") return nil } func (*memBox) AllowRenameZettel(context.Context, id.Zid) bool { return true } func (mb *memBox) RenameZettel(_ context.Context, curZid, newZid id.Zid) error { mb.mx.Lock() zettel, ok := mb.zettel[curZid] if !ok { mb.mx.Unlock() return box.ErrNotFound } // Check that there is no zettel with newZid if _, ok = mb.zettel[newZid]; ok { mb.mx.Unlock() return &box.ErrInvalidID{Zid: newZid} } meta := zettel.Meta.Clone() meta.Zid = newZid zettel.Meta = meta mb.zettel[newZid] = zettel delete(mb.zettel, curZid) mb.mx.Unlock() mb.notifyChanged(curZid) mb.notifyChanged(newZid) mb.log.Trace().Msg("RenameZettel") return nil } func (mb *memBox) CanDeleteZettel(_ context.Context, zid id.Zid) bool { mb.mx.RLock() _, ok := mb.zettel[zid] mb.mx.RUnlock() return ok } func (mb *memBox) DeleteZettel(_ context.Context, zid id.Zid) error { mb.mx.Lock() oldZettel, found := mb.zettel[zid] if !found { mb.mx.Unlock() return box.ErrNotFound } delete(mb.zettel, zid) mb.curBytes -= oldZettel.Length() mb.mx.Unlock() mb.notifyChanged(zid) mb.log.Trace().Msg("DeleteZettel") return nil } func (mb *memBox) ReadStats(st *box.ManagedBoxStats) { st.ReadOnly = false mb.mx.RLock() st.Zettel = len(mb.zettel) mb.mx.RUnlock() mb.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats") } |
Changes to box/notify/directory.go.
1 | //----------------------------------------------------------------------------- | | < < < > > < | | < | | | | | < | | | < | | < < < < < < < < | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2023 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 notify import ( "errors" "fmt" "path/filepath" "regexp" "strings" "sync" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/parser" "zettelstore.de/z/query" "zettelstore.de/z/strfun" ) type entrySet map[id.Zid]*DirEntry // directoryState signal the internal state of the service. // // The following state transitions are possible: // --newDirService--> dsCreated // dsCreated --Start--> dsStarting // dsStarting --last list notification--> dsWorking // dsWorking --directory missing--> dsMissing // dsMissing --last list notification--> dsWorking // --Stop--> dsStopping type directoryState uint8 const ( dsCreated directoryState = iota dsStarting // Reading inital scan dsWorking // Initial scan complete, fully operational dsMissing // Directory is missing dsStopping // Service is shut down ) // DirService specifies a directory service for file based zettel. type DirService struct { log *logger.Logger dirPath string notifier Notifier infos chan<- box.UpdateInfo mx sync.RWMutex // protects status, entries state directoryState entries entrySet } // ErrNoDirectory signals missing directory data. var ErrNoDirectory = errors.New("unable to retrieve zettel directory information") // NewDirService creates a new directory service. func NewDirService(log *logger.Logger, notifier Notifier, chci chan<- box.UpdateInfo) *DirService { return &DirService{ log: log, notifier: notifier, infos: chci, state: dsCreated, } } // Start the directory service. func (ds *DirService) Start() { ds.mx.Lock() ds.state = dsStarting ds.mx.Unlock() var newEntries entrySet go ds.updateEvents(newEntries) } // Refresh the directory entries. func (ds *DirService) Refresh() { ds.notifier.Refresh() } // Stop the directory service. func (ds *DirService) Stop() { ds.mx.Lock() ds.state = dsStopping ds.mx.Unlock() ds.notifier.Close() } func (ds *DirService) logMissingEntry(action string) error { err := ErrNoDirectory ds.log.Info().Err(err).Str("action", action).Msg("Unable to get directory information") |
︙ | ︙ | |||
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | defer ds.mx.Unlock() if ds.entries == nil { return ds.logMissingEntry("update") } ds.entries[entry.Zid] = &entry return nil } // DeleteDirEntry removes a entry from the directory. func (ds *DirService) DeleteDirEntry(zid id.Zid) error { ds.mx.Lock() defer ds.mx.Unlock() if ds.entries == nil { return ds.logMissingEntry("delete") } delete(ds.entries, zid) return nil } func (ds *DirService) updateEvents(newEntries entrySet) { // Something may panic. Ensure a running service. defer func() { | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | | | | | | 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 | defer ds.mx.Unlock() if ds.entries == nil { return ds.logMissingEntry("update") } ds.entries[entry.Zid] = &entry return nil } // RenameDirEntry replaces an existing directory entry with a new one. func (ds *DirService) RenameDirEntry(oldEntry *DirEntry, newZid id.Zid) (DirEntry, error) { ds.mx.Lock() defer ds.mx.Unlock() if ds.entries == nil { return DirEntry{}, ds.logMissingEntry("rename") } if _, found := ds.entries[newZid]; found { return DirEntry{}, &box.ErrInvalidID{Zid: newZid} } oldZid := oldEntry.Zid newEntry := DirEntry{ Zid: newZid, MetaName: renameFilename(oldEntry.MetaName, oldZid, newZid), ContentName: renameFilename(oldEntry.ContentName, oldZid, newZid), ContentExt: oldEntry.ContentExt, // Duplicates must not be set, because duplicates will be deleted } delete(ds.entries, oldZid) ds.entries[newZid] = &newEntry return newEntry, nil } func renameFilename(name string, curID, newID id.Zid) string { if cur := curID.String(); strings.HasPrefix(name, cur) { name = newID.String() + name[len(cur):] } return name } // DeleteDirEntry removes a entry from the directory. func (ds *DirService) DeleteDirEntry(zid id.Zid) error { ds.mx.Lock() defer ds.mx.Unlock() if ds.entries == nil { return ds.logMissingEntry("delete") } delete(ds.entries, zid) return nil } func (ds *DirService) updateEvents(newEntries entrySet) { // Something may panic. Ensure a running service. defer func() { if r := recover(); r != nil { kernel.Main.LogRecover("DirectoryService", r) go ds.updateEvents(newEntries) } }() for ev := range ds.notifier.Events() { e, ok := ds.handleEvent(ev, newEntries) if !ok { break } newEntries = e } } func (ds *DirService) handleEvent(ev Event, newEntries entrySet) (entrySet, bool) { ds.mx.RLock() state := ds.state ds.mx.RUnlock() if msg := ds.log.Trace(); msg.Enabled() { msg.Uint("state", uint64(state)).Str("op", ev.Op.String()).Str("name", ev.Name).Msg("notifyEvent") } if state == dsStopping { return nil, false } switch ev.Op { case Error: newEntries = nil if state != dsMissing { ds.log.Warn().Err(ev.Err).Msg("Notifier confused") } case Make: newEntries = make(entrySet) case List: if ev.Name == "" { zids := getNewZids(newEntries) ds.mx.Lock() fromMissing := ds.state == dsMissing prevEntries := ds.entries ds.entries = newEntries ds.state = dsWorking ds.mx.Unlock() ds.onCreateDirectory(zids, prevEntries) if fromMissing { ds.log.Info().Str("path", ds.dirPath).Msg("Zettel directory found") } return nil, true } if newEntries != nil { ds.onUpdateFileEvent(newEntries, ev.Name) } case Destroy: ds.onDestroyDirectory() ds.log.Error().Str("path", ds.dirPath).Msg("Zettel directory missing") return nil, true case Update: ds.mx.Lock() zid := ds.onUpdateFileEvent(ds.entries, ev.Name) ds.mx.Unlock() if zid != id.Invalid { ds.notifyChange(zid) } case Delete: ds.mx.Lock() zid := ds.onDeleteFileEvent(ds.entries, ev.Name) ds.mx.Unlock() if zid != id.Invalid { ds.notifyChange(zid) } default: ds.log.Warn().Str("event", fmt.Sprintf("%v", ev)).Msg("Unknown zettel notification event") } return newEntries, true } func getNewZids(entries entrySet) id.Slice { zids := make(id.Slice, 0, len(entries)) for zid := range entries { zids = append(zids, zid) } return zids } func (ds *DirService) onCreateDirectory(zids id.Slice, prevEntries entrySet) { for _, zid := range zids { ds.notifyChange(zid) delete(prevEntries, zid) } // These were previously stored, by are not found now. // Notify system that these were deleted, e.g. for updating the index. for zid := range prevEntries { ds.notifyChange(zid) } } func (ds *DirService) onDestroyDirectory() { ds.mx.Lock() entries := ds.entries ds.entries = nil ds.state = dsMissing ds.mx.Unlock() for zid := range entries { ds.notifyChange(zid) } } var validFileName = regexp.MustCompile(`^(\d{14})`) func matchValidFileName(name string) []string { return validFileName.FindStringSubmatch(name) |
︙ | ︙ | |||
342 343 344 345 346 347 348 | zid := seekZid(name) if zid == id.Invalid { return id.Invalid } entry := fetchdirEntry(entries, zid) dupName1, dupName2 := ds.updateEntry(entry, name) if dupName1 != "" { | | | | 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 | zid := seekZid(name) if zid == id.Invalid { return id.Invalid } entry := fetchdirEntry(entries, zid) dupName1, dupName2 := ds.updateEntry(entry, name) if dupName1 != "" { ds.log.Warn().Str("name", dupName1).Msg("Duplicate content (is ignored)") if dupName2 != "" { ds.log.Warn().Str("name", dupName2).Msg("Duplicate content (is ignored)") } return id.Invalid } return zid } func (ds *DirService) onDeleteFileEvent(entries entrySet, name string) id.Zid { |
︙ | ︙ | |||
571 572 573 574 575 576 577 | newLen := len(newExt) if oldLen != newLen { return newLen < oldLen } return newExt < oldExt } | | | | | | 588 589 590 591 592 593 594 595 596 597 598 599 600 | newLen := len(newExt) if oldLen != newLen { return newLen < oldLen } return newExt < oldExt } func (ds *DirService) notifyChange(zid id.Zid) { if chci := ds.infos; chci != nil { ds.log.Trace().Zid(zid).Msg("notifyChange") chci <- box.UpdateInfo{Reason: box.OnZettel, Zid: zid} } } |
Changes to box/notify/directory_test.go.
1 | //----------------------------------------------------------------------------- | | < < < > > < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | //----------------------------------------------------------------------------- // Copyright (c) 2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package notify import ( "testing" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. _ "zettelstore.de/z/parser/draw" // Allow to use draw parser. _ "zettelstore.de/z/parser/markdown" // Allow to use markdown parser. _ "zettelstore.de/z/parser/none" // Allow to use none parser. _ "zettelstore.de/z/parser/plain" // Allow to use plain parser. _ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser. ) func TestSeekZid(t *testing.T) { testcases := []struct { name string zid id.Zid }{ |
︙ | ︙ | |||
50 51 52 53 54 55 56 | } func TestNewExtIsBetter(t *testing.T) { extVals := []string{ // Main Formats meta.SyntaxZmk, meta.SyntaxDraw, meta.SyntaxMarkdown, meta.SyntaxMD, // Other supported text formats | | | | 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | } func TestNewExtIsBetter(t *testing.T) { extVals := []string{ // Main Formats meta.SyntaxZmk, meta.SyntaxDraw, meta.SyntaxMarkdown, meta.SyntaxMD, // Other supported text formats meta.SyntaxCSS, meta.SyntaxTxt, meta.SyntaxHTML, meta.SyntaxMustache, meta.SyntaxText, meta.SyntaxPlain, // Supported text graphics formats meta.SyntaxSVG, meta.SyntaxNone, // Supported binary graphic formats meta.SyntaxGif, meta.SyntaxPNG, meta.SyntaxJPEG, meta.SyntaxWebp, meta.SyntaxJPG, // Unsupported syntax values |
︙ | ︙ |
Changes to box/notify/entry.go.
1 | //----------------------------------------------------------------------------- | | < < < | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package notify import ( "path/filepath" "zettelstore.de/c/api" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/parser" ) const ( extZettel = "zettel" // file contains metadata and content extBin = "bin" // file contains binary content extTxt = "txt" // file contains non-binary content ) |
︙ | ︙ | |||
47 48 49 50 51 52 53 | // HasMetaInContent returns true, if metadata will be stored in the content file. func (e *DirEntry) HasMetaInContent() bool { return e.IsValid() && extIsMetaAndContent(e.ContentExt) } // SetupFromMetaContent fills entry data based on metadata and zettel content. | | | | | 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | // HasMetaInContent returns true, if metadata will be stored in the content file. func (e *DirEntry) HasMetaInContent() bool { return e.IsValid() && extIsMetaAndContent(e.ContentExt) } // SetupFromMetaContent fills entry data based on metadata and zettel content. func (e *DirEntry) SetupFromMetaContent(m *meta.Meta, content domain.Content, getZettelFileSyntax func() []string) { if e.Zid != m.Zid { panic("Zid differ") } if contentName := e.ContentName; contentName != "" { if !extIsMetaAndContent(e.ContentExt) && e.MetaName == "" { e.MetaName = e.calcBaseName(contentName) } return } syntax := m.GetDefault(api.KeySyntax, "") ext := calcContentExt(syntax, m.YamlSep, getZettelFileSyntax) metaName := e.MetaName eimc := extIsMetaAndContent(ext) if eimc { if metaName != "" { ext = contentExtWithMeta(syntax, content) } e.ContentName = e.calcBaseName(metaName) + "." + ext e.ContentExt = ext } else { if len(content.AsBytes()) > 0 { e.ContentName = e.calcBaseName(metaName) + "." + ext e.ContentExt = ext } if metaName == "" { e.MetaName = e.calcBaseName(e.ContentName) } } } func contentExtWithMeta(syntax string, content domain.Content) string { p := parser.Get(syntax) if content.IsBinary() { if p.IsImageFormat { return syntax } return extBin } |
︙ | ︙ |
Changes to box/notify/fsdir.go.
1 | //----------------------------------------------------------------------------- | | < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package notify import ( "os" "path/filepath" |
︙ | ︙ | |||
54 55 56 57 58 59 60 | log.Error(). Str("parentDir", absParentDir).Err(errParent). Str("path", absPath).Err(err). Msg("Unable to access Zettel directory and its parent directory") watcher.Close() return nil, err } | > | | | | 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | log.Error(). Str("parentDir", absParentDir).Err(errParent). Str("path", absPath).Err(err). Msg("Unable to access Zettel directory and its parent directory") watcher.Close() return nil, err } log.Warn(). Str("parentDir", absParentDir).Err(errParent). Msg("Parent of Zettel directory cannot be supervised") log.Warn().Str("path", absPath). Msg("Zettelstore might not detect a deletion or movement of the Zettel directory") } else if err != nil { // Not a problem, if container is not available. It might become available later. log.Warn().Err(err).Str("path", absPath).Msg("Zettel directory not available") } fsdn := &fsdirNotifier{ log: log, events: make(chan Event), refresh: make(chan struct{}), done: make(chan struct{}), |
︙ | ︙ | |||
92 93 94 95 96 97 98 | func (fsdn *fsdirNotifier) eventLoop() { defer fsdn.base.Close() defer close(fsdn.events) defer close(fsdn.refresh) if !listDirElements(fsdn.log, fsdn.fetcher, fsdn.events, fsdn.done) { return } | < | 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | func (fsdn *fsdirNotifier) eventLoop() { defer fsdn.base.Close() defer close(fsdn.events) defer close(fsdn.refresh) if !listDirElements(fsdn.log, fsdn.fetcher, fsdn.events, fsdn.done) { return } for fsdn.readAndProcessEvent() { } } func (fsdn *fsdirNotifier) readAndProcessEvent() bool { select { case <-fsdn.done: |
︙ | ︙ | |||
165 166 167 168 169 170 171 | } return true } if ev.Has(fsnotify.Create) { err := fsdn.base.Add(fsdn.path) if err != nil { | | | 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | } return true } if ev.Has(fsnotify.Create) { err := fsdn.base.Add(fsdn.path) if err != nil { fsdn.log.IfErr(err).Str("name", fsdn.path).Msg("Unable to add directory") select { case fsdn.events <- Event{Op: Error, Err: err}: case <-fsdn.done: fsdn.log.Trace().Int("i", 2).Msg("done dir event processing") return false } } |
︙ | ︙ |
Changes to box/notify/helper.go.
1 | //----------------------------------------------------------------------------- | | < < < > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package notify import ( "archive/zip" "os" "zettelstore.de/z/logger" ) // MakeMetaFilename builds the name of the file containing metadata. func MakeMetaFilename(basename string) string { return basename //+ ".meta" } // EntryFetcher return a list of (file) names of an directory. type EntryFetcher interface { Fetch() ([]string, error) } type dirPathFetcher struct { |
︙ | ︙ |
Changes to box/notify/notify.go.
1 | //----------------------------------------------------------------------------- | | < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package notify provides some notification services to be used by box services. package notify import "fmt" |
︙ | ︙ |
Changes to box/notify/simpledir.go.
1 | //----------------------------------------------------------------------------- | | < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2023 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 notify import ( "path/filepath" |
︙ | ︙ |
Changes to cmd/cmd_file.go.
1 | //----------------------------------------------------------------------------- | | < < < | | | | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package cmd import ( "context" "flag" "fmt" "io" "os" "zettelstore.de/c/api" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" "zettelstore.de/z/input" "zettelstore.de/z/parser" ) // ---------- Subcommand: file ----------------------------------------------- func cmdFile(fs *flag.FlagSet) (int, error) { enc := fs.Lookup("t").Value.String() m, inp, err := getInput(fs.Args()) if m == nil { return 2, err } z := parser.ParseZettel( context.Background(), domain.Zettel{ Meta: m, Content: domain.NewContent(inp.Src[inp.Pos:]), }, m.GetDefault(api.KeySyntax, meta.SyntaxZmk), nil, ) encdr := encoder.Create(api.Encoder(enc)) if encdr == nil { fmt.Fprintf(os.Stderr, "Unknown format %q\n", enc) return 2, nil } _, err = encdr.WriteZettel(os.Stdout, z, parser.ParseMetadata) if err != nil { return 2, err |
︙ | ︙ |
Changes to cmd/cmd_password.go.
1 | //----------------------------------------------------------------------------- | | < < < | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package cmd import ( "flag" "fmt" "os" "golang.org/x/term" "zettelstore.de/c/api" "zettelstore.de/z/auth/cred" "zettelstore.de/z/domain/id" ) // ---------- Subcommand: password ------------------------------------------- func cmdPassword(fs *flag.FlagSet) (int, error) { if fs.NArg() == 0 { fmt.Fprintln(os.Stderr, "User name and user zettel identification missing") |
︙ | ︙ |
Changes to cmd/cmd_run.go.
1 | //----------------------------------------------------------------------------- | | < < < > < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2023 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 cmd import ( "context" "flag" "net/http" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter/api" "zettelstore.de/z/web/adapter/webui" "zettelstore.de/z/web/server" ) // ---------- Subcommand: run ------------------------------------------------ func flgRun(fs *flag.FlagSet) { fs.String("c", "", "configuration file") fs.Uint("a", 0, "port number kernel service (0=disable)") |
︙ | ︙ | |||
55 56 57 58 59 60 61 | protectedBoxManager, authPolicy := authManager.BoxWithPolicy(boxManager, rtConfig) kern := kernel.Main webLog := kern.GetLogger(kernel.WebService) var getUser getUserImpl logAuth := kern.GetLogger(kernel.AuthService) logUc := kern.GetLogger(kernel.CoreService).WithUser(&getUser) | < | | > | | < < < > > > < > > > | | > | | | > > > > > > > | | > | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | protectedBoxManager, authPolicy := authManager.BoxWithPolicy(boxManager, rtConfig) kern := kernel.Main webLog := kern.GetLogger(kernel.WebService) var getUser getUserImpl logAuth := kern.GetLogger(kernel.AuthService) logUc := kern.GetLogger(kernel.CoreService).WithUser(&getUser) ucAuthenticate := usecase.NewAuthenticate(logAuth, authManager, authManager, boxManager) ucIsAuth := usecase.NewIsAuthenticated(logUc, &getUser, authManager) ucCreateZettel := usecase.NewCreateZettel(logUc, rtConfig, protectedBoxManager) ucGetMeta := usecase.NewGetMeta(protectedBoxManager) ucGetAllMeta := usecase.NewGetAllMeta(protectedBoxManager) ucGetZettel := usecase.NewGetZettel(protectedBoxManager) ucParseZettel := usecase.NewParseZettel(rtConfig, ucGetZettel) ucListMeta := usecase.NewListMeta(protectedBoxManager) ucEvaluate := usecase.NewEvaluate(rtConfig, ucGetZettel, ucGetMeta, ucListMeta) ucListSyntax := usecase.NewListSyntax(protectedBoxManager) ucListRoles := usecase.NewListRoles(protectedBoxManager) ucZettelContext := usecase.NewZettelContext(protectedBoxManager, rtConfig) ucDelete := usecase.NewDeleteZettel(logUc, protectedBoxManager) ucUpdate := usecase.NewUpdateZettel(logUc, protectedBoxManager) ucRename := usecase.NewRenameZettel(logUc, protectedBoxManager) ucUnlinkedRefs := usecase.NewUnlinkedReferences(protectedBoxManager, rtConfig) ucRefresh := usecase.NewRefresh(logUc, protectedBoxManager) ucVersion := usecase.NewVersion(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) a := api.New( webLog.Clone().Str("adapter", "api").Child(), webSrv, authManager, authManager, rtConfig, authPolicy) wui := webui.New( webLog.Clone().Str("adapter", "wui").Child(), webSrv, authManager, rtConfig, authManager, boxManager, authPolicy, &ucEvaluate) webSrv.Handle("/", wui.MakeGetRootHandler(protectedBoxManager)) if assetDir := kern.GetConfig(kernel.WebService, kernel.WebAssetDir).(string); assetDir != "" { const assetPrefix = "/assets/" webSrv.Handle(assetPrefix, http.StripPrefix(assetPrefix, http.FileServer(http.Dir(assetDir)))) webSrv.Handle("/favicon.ico", wui.MakeFaviconHandler(assetDir)) } // Web user interface if !authManager.IsReadonly() { webSrv.AddZettelRoute('b', server.MethodGet, wui.MakeGetRenameZettelHandler( ucGetMeta, &ucEvaluate)) webSrv.AddZettelRoute('b', server.MethodPost, wui.MakePostRenameZettelHandler(&ucRename)) webSrv.AddListRoute('c', server.MethodGet, wui.MakeGetZettelFromListHandler(ucListMeta, &ucEvaluate, ucListRoles, ucListSyntax)) webSrv.AddListRoute('c', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel)) webSrv.AddZettelRoute('c', server.MethodGet, wui.MakeGetCreateZettelHandler( ucGetZettel, &ucCreateZettel, ucListRoles, ucListSyntax)) webSrv.AddZettelRoute('c', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel)) webSrv.AddZettelRoute('d', server.MethodGet, wui.MakeGetDeleteZettelHandler( ucGetMeta, ucGetAllMeta, &ucEvaluate)) webSrv.AddZettelRoute('d', server.MethodPost, wui.MakePostDeleteZettelHandler(&ucDelete)) webSrv.AddZettelRoute('e', server.MethodGet, wui.MakeEditGetZettelHandler(ucGetZettel, ucListRoles, ucListSyntax)) webSrv.AddZettelRoute('e', server.MethodPost, wui.MakeEditSetZettelHandler(&ucUpdate)) } webSrv.AddListRoute('g', server.MethodGet, wui.MakeGetGoActionHandler(&ucRefresh)) webSrv.AddListRoute('h', server.MethodGet, wui.MakeListHTMLMetaHandler(ucListMeta, &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, ucUnlinkedRefs)) webSrv.AddZettelRoute('k', server.MethodGet, wui.MakeZettelContextHandler( ucZettelContext, &ucEvaluate)) // API webSrv.AddListRoute('a', server.MethodPost, a.MakePostLoginHandler(&ucAuthenticate)) webSrv.AddListRoute('a', server.MethodPut, a.MakeRenewAuthHandler()) webSrv.AddZettelRoute('o', server.MethodGet, a.MakeGetOrderHandler( usecase.NewZettelOrder(protectedBoxManager, ucEvaluate))) webSrv.AddZettelRoute('u', server.MethodGet, a.MakeListUnlinkedMetaHandler( ucGetMeta, ucUnlinkedRefs, &ucEvaluate)) webSrv.AddListRoute('x', server.MethodGet, a.MakeGetDataHandler(ucVersion)) webSrv.AddListRoute('x', server.MethodPost, a.MakePostCommandHandler(&ucIsAuth, &ucRefresh)) webSrv.AddZettelRoute('x', server.MethodGet, a.MakeZettelContextHandler(ucZettelContext)) webSrv.AddListRoute('z', server.MethodGet, a.MakeQueryHandler(ucListMeta)) webSrv.AddZettelRoute('z', server.MethodGet, a.MakeGetZettelHandler(ucGetMeta, ucGetZettel, ucParseZettel, ucEvaluate)) if !authManager.IsReadonly() { webSrv.AddListRoute('z', server.MethodPost, a.MakePostCreateZettelHandler(&ucCreateZettel)) webSrv.AddZettelRoute('z', server.MethodPut, a.MakeUpdateZettelHandler(&ucUpdate)) webSrv.AddZettelRoute('z', server.MethodDelete, a.MakeDeleteZettelHandler(&ucDelete)) webSrv.AddZettelRoute('z', server.MethodMove, a.MakeRenameZettelHandler(&ucRename)) } if authManager.WithAuth() { webSrv.SetUserRetriever(usecase.NewGetUserByZid(boxManager)) } } type getUserImpl struct{} func (*getUserImpl) GetUser(ctx context.Context) *meta.Meta { return server.GetUser(ctx) } |
Changes to cmd/command.go.
1 | //----------------------------------------------------------------------------- | | < < < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package cmd import ( "flag" "zettelstore.de/c/maps" "zettelstore.de/z/logger" ) // Command stores information about commands / sub-commands. type Command struct { Name string // command name as it appears on the command line Func CommandFunc // function that executes a command |
︙ | ︙ |
Changes to cmd/main.go.
1 | //----------------------------------------------------------------------------- | | < < < | < > > > < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package cmd import ( "crypto/sha256" "flag" "fmt" "net" "net/url" "os" "runtime/debug" "strconv" "strings" "time" "zettelstore.de/c/api" "zettelstore.de/z/auth" "zettelstore.de/z/auth/impl" "zettelstore.de/z/box" "zettelstore.de/z/box/compbox" "zettelstore.de/z/box/manager" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/web/server" ) const strRunSimple = "run-simple" func init() { RegisterCommand(Command{ Name: "help", |
︙ | ︙ | |||
87 88 89 90 91 92 93 | }) RegisterCommand(Command{ Name: "password", Func: cmdPassword, }) } | | | | | | | | | | | | 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | }) RegisterCommand(Command{ Name: "password", Func: cmdPassword, }) } func fetchStartupConfiguration(fs *flag.FlagSet) (cfg *meta.Meta) { if configFlag := fs.Lookup("c"); configFlag != nil { if filename := configFlag.Value.String(); filename != "" { content, err := readConfiguration(filename) return createConfiguration(content, err) } } content, err := searchAndReadConfiguration() return createConfiguration(content, err) } func createConfiguration(content []byte, err error) *meta.Meta { if err != nil { return meta.New(id.Invalid) } return meta.NewFromInput(id.Invalid, input.NewInput(content)) } func readConfiguration(filename string) ([]byte, error) { return os.ReadFile(filename) } func searchAndReadConfiguration() ([]byte, error) { for _, filename := range []string{"zettelstore.cfg", "zsconfig.txt", "zscfg.txt", "_zscfg"} { if content, err := readConfiguration(filename); err == nil { return content, nil } } return readConfiguration(".zscfg") } func getConfig(fs *flag.FlagSet) *meta.Meta { cfg := fetchStartupConfiguration(fs) fs.Visit(func(flg *flag.Flag) { switch flg.Name { case "p": cfg.Set(keyListenAddr, net.JoinHostPort("127.0.0.1", flg.Value.String())) case "a": cfg.Set(keyAdminPort, flg.Value.String()) case "d": |
︙ | ︙ | |||
143 144 145 146 147 148 149 | cfg.Set(keyDebug, flg.Value.String()) case "r": cfg.Set(keyReadOnly, flg.Value.String()) case "v": cfg.Set(keyVerbose, flg.Value.String()) } }) | | < > < | 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | cfg.Set(keyDebug, flg.Value.String()) case "r": cfg.Set(keyReadOnly, flg.Value.String()) case "v": cfg.Set(keyVerbose, flg.Value.String()) } }) return cfg } func deleteConfiguredBoxes(cfg *meta.Meta) { for _, p := range cfg.PairsRest() { if key := p.Key; strings.HasPrefix(key, kernel.BoxURIs) { cfg.Delete(key) } } } const ( keyAdminPort = "admin-port" keyAssetDir = "asset-dir" keyBaseURL = "base-url" keyDebug = "debug-mode" keyDefaultDirBoxType = "default-dir-box-type" keyInsecureCookie = "insecure-cookie" keyInsecureHTML = "insecure-html" keyListenAddr = "listen-addr" keyLogLevel = "log-level" keyMaxRequestSize = "max-request-size" keyOwner = "owner" keyPersistentCookie = "persistent-cookie" keyBoxOneURI = kernel.BoxURIs + "1" keyReadOnly = "read-only-mode" keyTokenLifetimeHTML = "token-lifetime-html" keyTokenLifetimeAPI = "token-lifetime-api" keyURLPrefix = "url-prefix" keyVerbose = "verbose-mode" ) func setServiceConfig(cfg *meta.Meta) bool { |
︙ | ︙ | |||
206 207 208 209 210 211 212 | val, found := cfg.Get(key) if !found { break } err = setConfigValue(err, kernel.BoxService, key, val) } | < | < | < | | | 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 | val, found := cfg.Get(key) if !found { break } err = setConfigValue(err, kernel.BoxService, key, val) } err = setConfigValue(err, kernel.ConfigService, kernel.ConfigInsecureHTML, cfg.GetDefault(keyInsecureHTML, kernel.ConfigSecureHTML)) err = setConfigValue(err, kernel.WebService, kernel.WebListenAddress, cfg.GetDefault(keyListenAddr, "127.0.0.1:23123")) if val, found := cfg.Get(keyBaseURL); found { err = setConfigValue(err, kernel.WebService, kernel.WebBaseURL, val) } if val, found := cfg.Get(keyURLPrefix); found { err = setConfigValue(err, kernel.WebService, kernel.WebURLPrefix, val) } err = setConfigValue(err, kernel.WebService, kernel.WebSecureCookie, !cfg.GetBool(keyInsecureCookie)) err = setConfigValue(err, kernel.WebService, kernel.WebPersistentCookie, cfg.GetBool(keyPersistentCookie)) if val, found := cfg.Get(keyMaxRequestSize); found { err = setConfigValue(err, kernel.WebService, kernel.WebMaxRequestSize, val) } err = setConfigValue( err, kernel.WebService, kernel.WebTokenLifetimeAPI, cfg.GetDefault(keyTokenLifetimeAPI, "")) err = setConfigValue( err, kernel.WebService, kernel.WebTokenLifetimeHTML, cfg.GetDefault(keyTokenLifetimeHTML, "")) if val, found := cfg.Get(keyAssetDir); found { err = setConfigValue(err, kernel.WebService, kernel.WebAssetDir, val) } return err == nil } func setConfigValue(err error, subsys kernel.Service, key string, val any) error { if err == nil { err = kernel.Main.SetConfig(subsys, key, fmt.Sprint(val)) if err != nil { kernel.Main.GetKernelLogger().Fatal().Str("key", key).Str("value", fmt.Sprint(val)).Err(err).Msg("Unable to set configuration") } } return err } func executeCommand(name string, args ...string) int { command, ok := Get(name) if !ok { fmt.Fprintf(os.Stderr, "Unknown command %q\n", name) return 1 } fs := command.GetFlags() if err := fs.Parse(args); err != nil { fmt.Fprintf(os.Stderr, "%s: unable to parse flags: %v %v\n", name, args, err) return 1 } cfg := getConfig(fs) if !setServiceConfig(cfg) { fs.Usage() return 2 } kern := kernel.Main var createManager kernel.CreateBoxManagerFunc |
︙ | ︙ | |||
293 294 295 296 297 298 299 | return nil }, ) if command.Simple { kern.SetConfig(kernel.ConfigService, kernel.ConfigSimpleMode, "true") } | | | | 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 | return nil }, ) if command.Simple { kern.SetConfig(kernel.ConfigService, kernel.ConfigSimpleMode, "true") } kern.Start(command.Header, command.LineServer) exitCode, err := command.Func(fs) if err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", name, err) } kern.Shutdown(true) return exitCode } // runSimple is called, when the user just starts the software via a double click // or via a simple call “./zettelstore“ on the command line. func runSimple() int { if _, err := searchAndReadConfiguration(); err == nil { return executeCommand(strRunSimple) } dir := "./zettel" if err := os.MkdirAll(dir, 0750); err != nil { fmt.Fprintf(os.Stderr, "Unable to create zettel directory %q (%s)\n", dir, err) return 1 } |
︙ | ︙ |
Changes to cmd/register.go.
1 | //----------------------------------------------------------------------------- | | < < < < | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package cmd provides command generic functions. package cmd // Mention all needed encoders, parsers and stores to have them registered. import ( _ "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/htmlenc" // Allow to use HTML encoder. _ "zettelstore.de/z/encoder/mdenc" // Allow to use markdown encoder. _ "zettelstore.de/z/encoder/sexprenc" // Allow to use sexpr encoder. _ "zettelstore.de/z/encoder/textenc" // Allow to use text encoder. _ "zettelstore.de/z/encoder/zjsonenc" // Allow to use ZJSON encoder. _ "zettelstore.de/z/encoder/zmkenc" // Allow to use zmk encoder. _ "zettelstore.de/z/kernel/impl" // Allow kernel implementation to create itself _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. _ "zettelstore.de/z/parser/draw" // Allow to use draw parser. _ "zettelstore.de/z/parser/markdown" // Allow to use markdown parser. _ "zettelstore.de/z/parser/none" // Allow to use none parser. _ "zettelstore.de/z/parser/plain" // Allow to use plain parser. _ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser. ) |
Changes to cmd/zettelstore/main.go.
1 | //----------------------------------------------------------------------------- | | < < < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package main is the starting point for the zettelstore command. package main import ( "os" "zettelstore.de/z/cmd" ) // Version variable. Will be filled by build process. var version string = "" func main() { exitCode := cmd.Main("Zettelstore", version) os.Exit(exitCode) } |
Changes to collect/collect.go.
1 | //----------------------------------------------------------------------------- | | < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package collect provides functions to collect items from a syntax tree. package collect import "zettelstore.de/z/ast" |
︙ | ︙ |
Changes to collect/collect_test.go.
1 | //----------------------------------------------------------------------------- | | < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package collect_test provides some unit test for collectors. package collect_test import ( "testing" |
︙ | ︙ |
Changes to collect/order.go.
1 | //----------------------------------------------------------------------------- | | < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package collect provides functions to collect items from a syntax tree. package collect import "zettelstore.de/z/ast" |
︙ | ︙ |
Added collect/split.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package collect provides functions to collect items from a syntax tree. package collect import ( "zettelstore.de/z/ast" "zettelstore.de/z/strfun" ) // DivideReferences divides the given list of rederences into zettel, local, and external References. func DivideReferences(all []*ast.Reference) (zettel, local, external []*ast.Reference) { if len(all) == 0 { return nil, nil, nil } mapZettel := make(strfun.Set) mapLocal := make(strfun.Set) mapExternal := make(strfun.Set) for _, ref := range all { if ref.State == ast.RefStateSelf { continue } if ref.IsZettel() { zettel = appendRefToList(zettel, mapZettel, ref) } else if ref.IsExternal() { external = appendRefToList(external, mapExternal, ref) } else { local = appendRefToList(local, mapLocal, ref) } } return zettel, local, external } func appendRefToList(reflist []*ast.Reference, refSet strfun.Set, ref *ast.Reference) []*ast.Reference { s := ref.String() if !refSet.Has(s) { reflist = append(reflist, ref) refSet.Set(s) } return reflist } |
Changes to config/config.go.
1 | //----------------------------------------------------------------------------- | | < < < | | | < < < < > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package config provides functions to retrieve runtime configuration data. package config import ( "context" "zettelstore.de/z/domain/meta" ) // Key values that are supported by Config.Get const ( KeyFooterZettel = "footer-zettel" KeyHomeZettel = "home-zettel" // api.KeyLang KeyMarkerExternal = "marker-external" ) // Config allows to retrieve all defined configuration values that can be changed during runtime. type Config interface { AuthConfig // Get returns the value of the given key. It searches first in the given metadata, |
︙ | ︙ |
Changes to docs/development/00010000000000.zettel.
1 2 3 4 5 | id: 00010000000000 title: Developments Notes role: zettel syntax: zmk created: 00010101000000 | | < | 1 2 3 4 5 6 7 8 9 10 | id: 00010000000000 title: Developments Notes role: zettel syntax: zmk created: 00010101000000 modified: 20221026184905 * [[Required Software|20210916193200]] * [[Fuzzing tests|20221026184300]] * [[Checklist for Release|20210916194900]] |
Changes to docs/development/20210916193200.zettel.
1 2 3 4 5 | id: 20210916193200 title: Required Software role: zettel syntax: zmk created: 20210916193200 | | > > > > < < < < < < < < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | id: 20210916193200 title: Required Software role: zettel syntax: zmk created: 20210916193200 modified: 20230109121417 The following software must be installed: * A current, supported [[release of Go|https://go.dev/doc/devel/release]], * [[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 install honnef.co/go/tools/cmd/staticcheck@latest``, * [[unparam|https://mvdan.cc/unparam]][^[[GitHub|https://github.com/mvdan/unparam]]] via ``go install mvdan.cc/unparam@latest``, * [[Fossil|https://fossil-scm.org/]], * [[Git|https://git-scm.org/]] (most dependencies are accessible via Git only). 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 ``` |
Changes to docs/development/20210916194900.zettel.
1 2 3 4 | id: 20210916194900 title: Checklist for Release role: zettel syntax: zmk | < | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | id: 20210916194900 title: Checklist for Release role: zettel syntax: zmk modified: 20220309105459 # Sync with the official repository #* ``fossil sync -u`` # Make sure that there is no workspace defined. #* ``ls ..`` must not have a file ''go.work'', in no parent folder. # Make sure that all dependencies are up-to-date. #* ``cat go.mod`` # Clean up your Go workspace: #* ``go run tools/build.go clean`` (alternatively: ``make clean``). # All internal tests must succeed: #* ``go run tools/build.go relcheck`` (alternatively: ``make relcheck``). # The API tests must succeed on every development platform: #* ``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 |
︙ | ︙ | |||
40 41 42 43 44 45 46 | # Disable Fossil autosync mode: #* ``fossil setting autosync off`` # Commit the new release version: #* ``fossil commit --tag release --tag vVERSION -m "Version VERSION"`` #* **Important:** the tag must follow the given pattern, e.g. ''v0.0.15''. Otherwise client will not be able to import ''zettelkasten.de/z''. # Clean up your Go workspace: | | | | | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | # Disable Fossil autosync mode: #* ``fossil setting autosync off`` # Commit the new release version: #* ``fossil commit --tag release --tag vVERSION -m "Version VERSION"`` #* **Important:** the tag must follow the given pattern, e.g. ''v0.0.15''. Otherwise client will not be able to import ''zettelkasten.de/z''. # 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`` |
Deleted docs/development/20231218181900.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to docs/manual/00000000000100.zettel.
1 2 3 4 | id: 00000000000100 title: Zettelstore Runtime Configuration role: configuration syntax: none | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00000000000100 title: Zettelstore Runtime Configuration role: configuration syntax: none created: 00010101000000 default-copyright: (c) 2020-2022 by Detlef Stern <ds@zettelstore.de> default-license: EUPL-1.2-or-later default-visibility: public footer-zettel: 00001000000100 home-zettel: 00001000000000 modified: 20221205173642 site-name: Zettelstore Manual visibility: owner |
︙ | ︙ |
Changes to docs/manual/00001000000000.zettel.
1 2 3 4 5 | id: 00001000000000 title: Zettelstore Manual role: manual tags: #manual #zettelstore syntax: zmk | < | < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | id: 00001000000000 title: Zettelstore Manual role: manual tags: #manual #zettelstore syntax: zmk modified: 20220803183647 * [[Introduction|00001001000000]] * [[Design goals|00001002000000]] * [[Installation|00001003000000]] * [[Configuration|00001004000000]] * [[Structure of Zettelstore|00001005000000]] * [[Layout of a zettel|00001006000000]] * [[Zettelmarkup|00001007000000]] * [[Other markup languages|00001008000000]] * [[Security|00001010000000]] * [[API|00001012000000]] * [[Web user interface|00001014000000]] * [[Tips and Tricks|00001017000000]] * [[Troubleshooting|00001018000000]] * Frequently asked questions Licensed under the EUPL-1.2-or-later. |
Deleted docs/manual/00001000000001.zettel.
|
| < < < < < < < < |
Deleted docs/manual/00001000000002.zettel.
|
| < < < < < < < |
Changes to docs/manual/00001001000000.zettel.
1 2 3 4 5 | id: 00001001000000 title: Introduction to the Zettelstore role: manual tags: #introduction #manual #zettelstore syntax: zmk | < < > | > > | > | | > | | > > > | > | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | id: 00001001000000 title: Introduction to the Zettelstore role: manual tags: #introduction #manual #zettelstore syntax: zmk [[Personal knowledge management|https://en.wikipedia.org/wiki/Personal_knowledge_management]] is about collecting, classifying, storing, searching, retrieving, assessing, evaluating, and sharing knowledge as a daily activity. Personal knowledge management is done by most people, not necessarily as part of their main business. It is essential for knowledge workers, like students, researchers, lecturers, software developers, scientists, engineers, architects, to name a few. Many hobbyists build up a significant amount of knowledge, even if the do not need to think for a living. Personal knowledge management can be seen as a prerequisite for many kinds of collaboration. Zettelstore is a software that collects and relates your notes (""zettel"") to represent and enhance your knowledge. It helps with many tasks of personal knowledge management by explicitly supporting the ""[[Zettelkasten method|https://en.wikipedia.org/wiki/Zettelkasten]]"". The method is based on creating many individual notes, each with one idea or information, that are related to each other. Since knowledge is typically build up gradually, one major focus is a long-term store of these notes, hence the name ""Zettelstore"". |
Changes to docs/manual/00001002000000.zettel.
1 2 3 4 5 6 | id: 00001002000000 title: Design goals for the Zettelstore role: manual tags: #design #goal #manual #zettelstore syntax: zmk created: 20210126175322 | | < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | id: 00001002000000 title: Design goals for the Zettelstore role: manual tags: #design #goal #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20221018105415 Zettelstore supports the following design goals: ; Longevity of stored notes / zettel : Every zettel you create should be readable without the help of any tool, even without Zettelstore. : It should be not hard to write other software that works with your zettel. ; Single user : All zettel belong to you, only to you. Zettelstore provides its services only to one person: you. If the computer running Zettelstore is securely configured, there should be no risk that others are able to read or update your zettel. : If you want, you can customize Zettelstore in a way that some specific or all persons are able to read some of your zettel. ; Ease of installation : If you want to use the Zettelstore software, all you need is to copy the executable to an appropriate file directory and start working. |
︙ | ︙ | |||
35 36 37 38 39 40 41 | ; Simple service : The purpose of Zettelstore is to safely store your zettel and to provide some initial relations between them. : External software can be written to deeply analyze your zettel and the structures they form. ; Security by default : Without any customization, Zettelstore provides its services in a safe and secure manner and does not expose you (or other users) to security risks. : If you know what use are doing, Zettelstore allows you to relax some security-related preferences. However, even in this case, the more secure way is chosen. | < < | 31 32 33 34 35 36 37 | ; Simple service : The purpose of Zettelstore is to safely store your zettel and to provide some initial relations between them. : External software can be written to deeply analyze your zettel and the structures they form. ; Security by default : Without any customization, Zettelstore provides its services in a safe and secure manner and does not expose you (or other users) to security risks. : If you know what use are doing, Zettelstore allows you to relax some security-related preferences. However, even in this case, the more secure way is chosen. |
Changes to docs/manual/00001003000000.zettel.
1 2 3 4 5 | id: 00001003000000 title: Installation of the Zettelstore software role: manual tags: #installation #manual #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001003000000 title: Installation of the Zettelstore software role: manual tags: #installation #manual #zettelstore syntax: zmk modified: 20220119145756 === The curious user You just want to check out the Zettelstore software * Grab the appropriate executable and copy it into any directory * Start the Zettelstore software, e.g. with a double click[^On Windows and macOS, the operating system tries to protect you from possible malicious software. If you encounter problem, please take a look on the [[Troubleshooting|00001018000000]]Â page.] |
︙ | ︙ |
Changes to docs/manual/00001003300000.zettel.
1 2 3 4 5 | id: 00001003300000 title: Zettelstore installation for the intermediate user role: manual tags: #installation #manual #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001003300000 title: Zettelstore installation for the intermediate user role: manual tags: #installation #manual #zettelstore syntax: zmk modified: 20220114175754 You already tried the Zettelstore software and now you want to use it permanently. Zettelstore should start automatically when you log into your computer. * Grab the appropriate executable and copy it into the appropriate directory * If you want to place your zettel into another directory, or if you want more than one [[Zettelstore box|00001004011200]], or if you want to [[enable authentication|00001010040100]], or if you want to tweak your Zettelstore in some other way, create an appropriate [[startup configuration file|00001004010000]]. |
︙ | ︙ |
Changes to docs/manual/00001003305000.zettel.
1 2 3 4 5 | id: 00001003305000 title: Enable Zettelstore to start automatically on Windows role: manual tags: #installation #manual #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001003305000 title: Enable Zettelstore to start automatically on Windows role: manual tags: #installation #manual #zettelstore syntax: zmk modified: 20220218125541 Windows is a complicated beast. There are several ways to automatically start Zettelstore. === Startup folder One way is to use the [[autostart folder|https://support.microsoft.com/en-us/windows/add-an-app-to-run-automatically-at-startup-in-windows-10-150da165-dcd9-7230-517b-cf3c295d89dd]]. |
︙ | ︙ |
Changes to docs/manual/00001003310000.zettel.
1 2 3 4 5 | id: 00001003310000 title: Enable Zettelstore to start automatically on macOS role: manual tags: #installation #manual #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001003310000 title: Enable Zettelstore to start automatically on macOS role: manual tags: #installation #manual #zettelstore syntax: zmk modified: 20220119124635 There are several ways to automatically start Zettelstore. * [[Login Items|#login-items]] * [[Launch Agent|#launch-agent]] |
︙ | ︙ |
Changes to docs/manual/00001003315000.zettel.
1 2 3 4 5 | id: 00001003315000 title: Enable Zettelstore to start automatically on Linux role: manual tags: #installation #manual #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001003315000 title: Enable Zettelstore to start automatically on Linux role: manual tags: #installation #manual #zettelstore syntax: zmk modified: 20220307104944 Since there is no such thing as the one Linux, there are too many different ways to automatically start Zettelstore. * One way is to interpret your Linux desktop system as a server and use the [[recipe to install Zettelstore on a server|00001003600000]]. ** See below for a lighter alternative. * If you are using the [[Gnome Desktop|https://www.gnome.org/]], you could use the tool [[Tweak|https://wiki.gnome.org/action/show/Apps/Tweaks]] (formerly known as ""GNOME Tweak Tool"" or just ""Tweak Tool""). |
︙ | ︙ |
Changes to docs/manual/00001003600000.zettel.
1 2 3 4 5 | id: 00001003600000 title: Installation of Zettelstore on a server role: manual tags: #installation #manual #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001003600000 title: Installation of Zettelstore on a server role: manual tags: #installation #manual #zettelstore syntax: zmk modified: 20211125185833 You want to provide a shared Zettelstore that can be used from your various devices. Installing Zettelstore as a Linux service is not that hard. Grab the appropriate executable and copy it into the appropriate directory: ```sh |
︙ | ︙ |
Changes to docs/manual/00001004000000.zettel.
1 2 3 4 5 | id: 00001004000000 title: Configuration of Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004000000 title: Configuration of Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk modified: 20210510153233 There are some levels to change the behavior and/or the appearance of Zettelstore. # The first level is the way to start Zettelstore services and to manage it via command line (and, in part, via a graphical user interface). #* [[Command line parameters|00001004050000]] # As an intermediate user, you usually want to have more control over how Zettelstore is started. |
︙ | ︙ |
Changes to docs/manual/00001004010000.zettel.
1 2 3 4 5 6 | id: 00001004010000 title: Zettelstore startup configuration role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 | | | | | | | | > | | | | | < | > | | | | < | | | | > | | | | < < < < < | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | id: 00001004010000 title: Zettelstore startup configuration role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20221128155143 The configuration file, as specified by the ''-c CONFIGFILE'' [[command line option|00001004051000]], allows you to specify some startup options. These options cannot be stored in a [[configuration zettel|00001004020000]] because either they are needed before Zettelstore can start or because of security reasons. For example, Zettelstore need to know in advance, on which network address is must listen or where zettel are stored. An attacker that is able to change the owner can do anything. Therefore only the owner of the computer on which Zettelstore runs can change this information. The file for startup configuration must be created via a text editor in advance. The syntax of the configuration file is the same as for any zettel metadata. The following keys are supported: ; [!admin-port|''admin-port''] : Specifies the TCP port through which you can reach the [[administrator console|00001004100000]]. A value of ""0"" (the default) disables the administrator console. The administrator console will only be enabled if Zettelstore is started with the [[''run'' sub-command|00001004051000]]. On most operating systems, the value must be greater than ""1024"" unless you start Zettelstore with the full privileges of a system administrator (which is not recommended). Default: ""0"" ; [!asset-dir|''asset-dir''] : Allows to specify a directory whose files are allowed be transferred directly with the help of the web server. The URL prefix for these files is ''/assets/''. You can use this if you want to transfer files that are too large for a note to users. Examples would be presentation files, PDF files, music files or video files. Files within the given directory will not be managed by Zettelstore.[^They will be managed by Zettelstore just in the case that the directory is one of the configured [[boxes|#box-uri-x]].] If you specify only the URL prefix, then the contents of the directory are listed to the user. To avoid this, create an empty file in the directory named ""index.html"". Default: """", no asset directory is set, the URL prefix ''/assets/'' is invalid. ; [!base-url|''base-url''] : Sets the absolute base URL for the service. Note: [[''url-prefix''|#url-prefix]] must be the suffix of ''base-url'', otherwise the web service will not start. Default: ""http://127.0.0.1:23123/"". ; [!box-uri-x|''box-uri-X''], where __X__ is a number greater or equal to one : Specifies a [[box|00001004011200]] where zettel are stored. During startup __X__ is counted up, starting with one, until no key is found. This allows to configure more than one box. If no ''box-uri-1'' key is given, the overall effect will be the same as if only ''box-uri-1'' was specified with the value ""dir://.zettel"". In this case, even a key ''box-uri-2'' will be ignored. ; [!debug-mode|''debug-mode''] : Allows to debug the Zettelstore software (mostly used by the developers) if set to [[true|00001006030500]] Disables any timeout values of the internal web server and does not send some security-related data. Sets [[''log-level''|#log-level]] to ""debug"". Do not enable it for a production server. Default: ""false"" ; [!default-dir-box-type|''default-dir-box-type''] : Specifies the default value for the (sub-) type of [[directory boxes|00001004011400#type]]. Zettel are typically stored in such boxes. Default: ""notify"" ; [!insecure-cookie|''insecure-cookie''] : Must be set to [[true|00001006030500]], if authentication is enabled and Zettelstore is not accessible not via HTTPS (but via HTTP). Otherwise web browser are free to ignore the authentication cookie. Default: ""false"" ; [!insecure-html|''insecure-html''] : Allows to use HTML, e.g. within supported markup languages, even if this might introduce security-related problems. However, HTML containing the ``<script>`` or the ``<iframe>`` tag is always ignored. But due to ""clever"" ways of combining HTML, CSS, JavaScript, there might be some negative security consequences. Please be aware of this! Allowed values: ""html"" (allow zettel with [[syntax ""html""|00001008000000#html]]), ""markdown"" (""html"", plus allow inline HTML for Markdown markup only), ""zettelmarkup"" (""markdown"", plus allow inline HTML for Zettelmarkup). Any other value is interpreted as ""secure"". Default: ""secure"". ; [!listen-addr|''listen-addr''] : Configures the network address, where the Zettelstore service is listening for requests. Syntax is: ''[NETWORKIP]:PORT'', where ''NETWORKIP'' is the IP-address of the networking interface (or something like ""0.0.0.0"" if you want to listen on all network interfaces, and ''PORT'' is the TCP port. Default value: ""127.0.0.1:23123"" ; [!log-level|''log-level''] : Specify the [[logging level|00001004059700]] for the whole application or for a given (internal) service, overwriting the level ""debug"" set by configuration [[''debug-mode''|#debug-mode]]. Can be changed at runtime, even for specific internal services, with the ''log-level'' command of the [[administrator console|00001004101000#log-level]]. Several specifications are separated by the semicolon character (""'';''"", U+003B). Each specification consists of an optional service name, together with the colon character (""'':''"", U+003A), followed by the logging level. Default: ""info"". Examples: ""sense"" will produce sensing messages (e.g. a little more than ""info""); ""sense;web:debug"" will emit debugging messages for the web component of Zettelstore while still producing sensing messages for all other components. When you are familiar to operate the Zettelstore, you might set the level to ""warn"" or ""error"" to receive less noisy messages from the Zettelstore. ; [!max-request-size|''max-request-size''] : Limits the maximum byte size of a web request body to prevent clients from accidentally or maliciously sending a large request and wasting server resources. The minimum value is 1024. Default: 16777216 (16 MiB). ; [!owner|''owner''] : [[Identifier|00001006050000]] of a zettel that contains data about the owner of the Zettelstore. The owner has full authorization for the Zettelstore. Only if owner is set to some value, user [[authentication|00001010000000]] is enabled. Ensure that key [[''secret''|#secret]] is set to a value of at least 16 bytes. Otherwise the Zettelstore will not start for security reasons. ; [!persistent-cookie|''persistent-cookie''] : A [[boolean value|00001006030500]] to make the access cookie persistent. This is helpful if you access the Zettelstore via a mobile device. On these devices, the operating system is free to stop the web browser and to remove temporary cookies. Therefore, an authenticated user will be logged off. If ""true"", a persistent cookie is used. Its lifetime exceeds the lifetime of the authentication token (see option ''token-lifetime-html'') by 30 seconds. Default: ""false"" ; [!read-only-mode|''read-only-mode''] : Puts the Zettelstore service into a read-only mode, if set to a [[true value|00001006030500]]. No changes are possible. Default: ""false"". ; [!secret|''secret''] : A string value to make the communication with external clients strong enough so that sessions of the [[web user interface|00001014000000]] or [[API access token|00001010040700]] cannot be modified by some external unfriendly party. The string must have a length of at least 16 bytes. It is only needed to set this value, if [[authentication is enabled|00001010040100]] by setting key [[''owner''|#owner]] to some user identification. ; [!token-lifetime-api|''token-lifetime-api''], [!token-lifetime-html|''token-lifetime-html''] : Define lifetime of access tokens in minutes. Values are only valid if authentication is enabled, i.e. key ''owner'' is set. ''token-lifetime-api'' is for accessing Zettelstore via its [[API|00001012000000]]. Default: ""10"". ''token-lifetime-html'' specifies the lifetime for the HTML views. It is automatically extended, when a new HTML view is rendered. Default: ""60"". ; [!url-prefix|''url-prefix''] : Add the given string as a prefix to the local part of a Zettelstore local URL/URI when rendering zettel representations. Must begin and end with a slash character (""''/''"", U+002F). Note: ''url-prefix'' must be the suffix of [[''base-url''|#base-url]], otherwise the web service will not start. Default: ""/"". This allows to use a forwarding proxy [[server|00001010090100]] in front of the Zettelstore. ; [!verbose-mode|''verbose-mode''] : Be more verbose when logging data, if set to a [[true value|00001006030500]]. Default: ""false"" |
Deleted docs/manual/00001004010200.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to docs/manual/00001004011200.zettel.
1 2 3 4 5 | id: 00001004011200 title: Zettelstore boxes role: manual tags: #configuration #manual #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004011200 title: Zettelstore boxes role: manual tags: #configuration #manual #zettelstore syntax: zmk modified: 20220307121547 A Zettelstore must store its zettel somehow and somewhere. In most cases you want to store your zettel as files in a directory. Under certain circumstances you may want to store your zettel elsewhere. An example are the [[predefined zettel|00001005090000]] that come with a Zettelstore. |
︙ | ︙ |
Changes to docs/manual/00001004011400.zettel.
1 2 3 4 5 | id: 00001004011400 title: Configure file directory boxes role: manual tags: #configuration #manual #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001004011400 title: Configure file directory boxes role: manual tags: #configuration #manual #zettelstore syntax: zmk modified: 20220724200512 Under certain circumstances, it is preferable to further configure a file directory box. This is done by appending query parameters after the base box URI ''dir:\//DIR''. The following parameters are supported: |= Parameter:|Description|Default value:| |
︙ | ︙ | |||
53 54 55 56 57 58 59 | === Readonly Sometimes you may want to provide zettel from a file directory box, but you want to disallow any changes. If you provide the query parameter ''readonly'' (with or without a corresponding value), the box will disallow any changes. ``` box-uri-1: dir:///home/zettel?readonly ``` | | | 52 53 54 55 56 57 58 59 | === Readonly Sometimes you may want to provide zettel from a file directory box, but you want to disallow any changes. If you provide the query parameter ''readonly'' (with or without a corresponding value), the box will disallow any changes. ``` box-uri-1: dir:///home/zettel?readonly ``` If you put the whole Zettelstore in [[read-only|00001004010000]] [[mode|00001004051000]], all configured file directory boxes will be in read-only mode too, even if not explicitly configured. |
Changes to docs/manual/00001004011600.zettel.
1 2 3 4 5 | id: 00001004011600 title: Configure memory boxes role: manual tags: #configuration #manual #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004011600 title: Configure memory boxes role: manual tags: #configuration #manual #zettelstore syntax: zmk modified: 20220307122554 Under most circumstances, it is preferable to further configure a memory box. This is done by appending query parameters after the base box URI ''mem:''. The following parameters are supported: |
︙ | ︙ |
Changes to docs/manual/00001004020000.zettel.
1 2 3 4 5 6 | id: 00001004020000 title: Configure the running Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 | | < < < < | > > > > > > < < < < < < < < < < < < < < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | id: 00001004020000 title: Configure the running Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20221219175720 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. Some of them can be overwritten in an [[user zettel|00001010040200]], a subset of those may be overwritten in zettel that is currently used. See the full list of [[metadata that may be overwritten|00001004020200]]. ; [!default-copyright|''default-copyright''] : Copyright value to be used when rendering content. Can be overwritten in a zettel with [[meta key|00001006020000]] ''copyright''. Default: (the empty string). ; [!default-license|''default-license''] : License value to be used when rendering content. Can be overwritten in a zettel with [[meta key|00001006020000]] ''license''. Default: (the empty string). ; [!default-visibility|''default-visibility''] : Visibility to be used, if zettel does not specify a value for the [[''visibility''|00001006020000#visibility]] metadata key. Default: ""login"". ; [!expert-mode|''expert-mode''] : If set to a [[boolean true value|00001006030500]], all zettel with [[visibility ""expert""|00001010070200]] will be shown (to the owner, if [[authentication is enabled|00001010040100]]; to all, otherwise). This affects most computed zettel. Default: ""False"". ; [!footer-zettel|''footer-zettel''] : Identifier of a zettel that is rendered as HTML and will be placed as the footer of every zettel in the [[web user interface|00001014000000]]. Zettel content, delivered via the [[API|00001012000000]] as JSON, etc. is not affected. If the zettel identifier is invalid or references a zettel that could not be read (possibly because of a limited [[visibility setting|00001010070200]]), nothing is written as the footer. May be [[overwritten|00001004020200]] in a user zettel. Default: (an invalid zettel identifier) ; [!home-zettel|''home-zettel''] : Specifies the identifier of the zettel, that should be presented for the default view / home view. If not given or if the identifier does not identify a zettel, the zettel with the identifier ''00010000000000'' is shown. May be [[overwritten|00001004020200]] in a user zettel. ; [!lang|''lang''] : Language to be used when displaying content. Default: ""en"". This value is used as a default value, if it is not set in an user's zettel or in a zettel. It is also used to specify the language for all non-zettel content, e.g. lists or search results. Use values according to the language definition of [[RFC-5646|https://tools.ietf.org/html/rfc5646]]. ; [!marker-external|''marker-external''] : Some HTML code that is displayed after a [[reference to external material|00001007040310]]. May be [[overwritten|00001004020200]] in a user zettel. 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''] : If [[true|00001006030500]], metadata and content will be separated by ''---\\n'' instead of an empty line (''\\n\\n''). Default: ""False"". You will probably use this key, if you are working with another software processing [[Markdown|https://daringfireball.net/projects/markdown/]] that uses a subset of [[YAML|https://yaml.org/]] to specify metadata. ; [!zettel-file-syntax|''zettel-file-syntax''] : If you create a new zettel with a syntax different to ""zmk"", Zettelstore will store the zettel as two files: one for the metadata (file without a filename extension) and another for the content (file extension based on the syntax value). If you want to specify alternative syntax values, for which you want new zettel to be stored in one file (file extension ''.zettel''), you can use this key. All values are case-insensitive, duplicate values are removed. For example, you could use this key if you're working with Markdown syntax and you want to store metadata and content in one ''.zettel'' file. If ''yaml-header'' evaluates to true, a zettel is always stored in one ''.zettel'' file. |
Changes to docs/manual/00001004020200.zettel.
1 2 3 4 5 6 | id: 00001004020200 title: Runtime configuration data that may be user specific or zettel specific role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20221205155521 | | | < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | id: 00001004020200 title: Runtime configuration data that may be user specific or zettel specific role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20221205155521 modified: 20221212195530 Some metadata of the [[runtime configuration|00001004020000]] may be overwritten in an [[user zettel|00001010040200]]. A subset of those may be overwritten in zettel that is currently used. This allows to specify user specific or zettel specific behavior. The following metadata keys are supported to provide a more specific behavior: |=Key|User:|Zettel:|Remarks |[[''footer-zettel''|00001004020000#footer-zettel]]|Y|N| |[[''home-zettel''|00001004020000#home-zettel]]|Y|N| |[[''lang''|00001004020000#lang]]|Y|Y|Making it user-specific could make zettel for other user less useful |[[''marker-external''|00001004020000#marker-external]]|Y|Y| |
Changes to docs/manual/00001004050200.zettel.
1 2 3 4 5 | id: 00001004050200 title: The ''help'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004050200 title: The ''help'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk modified: 20210712233414 Lists all implemented sub-commands. Example: ``` # zettelstore help |
︙ | ︙ |
Changes to docs/manual/00001004050400.zettel.
1 2 3 4 5 | id: 00001004050400 title: The ''version'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004050400 title: The ''version'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk modified: 20211124182041 Emits some information about the Zettelstore's version. This allows you to check, whether your installed Zettelstore is The name of the software (""Zettelstore"") and the build version information is given, as well as the compiler version, and an indication about the operating system and the processor architecture of that computer. |
︙ | ︙ |
Changes to docs/manual/00001004051000.zettel.
1 2 3 4 5 | id: 00001004051000 title: The ''run'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004051000 title: The ''run'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk modified: 20220724162050 === ``zettelstore run`` This starts the web service. ``` zettelstore run [-a PORT] [-c CONFIGFILE] [-d DIR] [-debug] [-p PORT] [-r] [-v] |
︙ | ︙ |
Changes to docs/manual/00001004051200.zettel.
1 2 3 4 5 6 | id: 00001004051200 title: The ''file'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk created: 20210126175322 | | < | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | id: 00001004051200 title: The ''file'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20230109105434 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]] ``` ; ''-t FORMAT'' : Specifies the output format. Supported values are: [[''html''|00001012920510]] (default), [[''md''|00001012920513]], [[''sexpr''|00001012920516]], [[''text''|00001012920519]], [[''zjson''|00001012920503]] (deprecated in v0.11), 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. |
︙ | ︙ |
Changes to docs/manual/00001004051400.zettel.
1 2 3 4 5 | id: 00001004051400 title: The ''password'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004051400 title: The ''password'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk modified: 20210712234305 This sub-command is used to create a hashed password for to be authenticated users. It reads a password from standard input (two times, both must be equal) and writes the hashed password to standard output. The general usage is: |
︙ | ︙ |
Changes to docs/manual/00001004059700.zettel.
1 2 3 4 5 | id: 00001004059700 title: List of supported logging levels role: manual tags: #configuration #manual #zettelstore syntax: zmk | < | | > | > | > > | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | id: 00001004059700 title: List of supported logging levels role: manual tags: #configuration #manual #zettelstore syntax: zmk modified: 20220113183606 Zettelstore supports various levels of logging output. This allows you to see the inner workings of Zettelstore, or to avoid it. Each level has an associated name and number. A lower number signals more logging output. |= Name | Number >| Description | Trace | 1 | Show most of the inner workings | Debug | 2 | Show many internal values that might be interesting for a [[Zettelstore developer|00000000000005]]. | Sense | 3 | Display sensing events, which are not essential information. | Info | 4 | Display information about an event. In most cases, there is no required action expected from you. | Warn | 5 | Show a warning, i.e. an event that might become an error or more. Mostly invalid data. | Error | 6 | Notify about an error, which was handled automatically. Something is broken. User intervention is not required, in most cases. Monitor the application. | Fatal | 7 | Notify about a significant error that cannot be handled automatically. At least some important functionality is disabled. | Panic | 8 | The application is in an uncertain state and notifies you about its panic. At least some part of the application is possibly restarted. | Mandatory | 9 | Important message will be shown, e.g. the Zettelstore version at startup time. | Disabled | 10 | No messages will be shown If you set the logging level to a certain value, only messages with the same or higher numerical value will be shown. E.g. if you set the logging level to ""warn"", no ""trace"", ""debug"", ""sense", and ""info"" messages are shown, but ""warn"", ""error"", ""fatal"", ""panic"", and ""mandatory"" messages. |
Changes to docs/manual/00001004059900.zettel.
1 2 3 4 5 | id: 00001004059900 title: Command line flags for profiling the application role: manual tags: #command #configuration #manual #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004059900 title: Command line flags for profiling the application role: manual tags: #command #configuration #manual #zettelstore syntax: zmk modified: 20211122174951 If you want to measure potential bottlenecks within the software Zettelstore, there are two [[command line|00001004050000]] flags for enabling the measurement (also called __profiling__): ; ''-cpuprofile FILE'' : Enables CPU profiling. |
︙ | ︙ |
Changes to docs/manual/00001004100000.zettel.
1 2 3 4 5 | id: 00001004100000 title: Zettelstore Administrator Console role: manual tags: #configuration #manual #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004100000 title: Zettelstore Administrator Console role: manual tags: #configuration #manual #zettelstore syntax: zmk modified: 20211103162926 The administrator console is a service accessible only on the same computer on which Zettelstore is running. It allows an experienced user to monitor and control some of the inner workings of Zettelstore. You enable the administrator console by specifying a TCP port number greater than zero (better: greater than 1024) for it, either via the [[command-line parameter ''-a''|00001004051000#a]] or via the ''admin-port'' key of the [[startup configuration file|00001004010000#admin-port]]. |
︙ | ︙ |
Changes to docs/manual/00001004101000.zettel.
1 2 3 4 5 | id: 00001004101000 title: List of supported commands of the administrator console role: manual tags: #configuration #manual #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004101000 title: List of supported commands of the administrator console role: manual tags: #configuration #manual #zettelstore syntax: zmk modified: 20220823194553 ; [!bye|''bye''] : Closes the connection to the administrator console. ; [!config|''config SERVICE''] : Displays all valid configuration keys for the given service. |
︙ | ︙ |
Changes to docs/manual/00001005000000.zettel.
1 2 3 4 5 | id: 00001005000000 title: Structure of Zettelstore role: manual tags: #design #manual #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001005000000 title: Structure of Zettelstore role: manual tags: #design #manual #zettelstore syntax: zmk modified: 20220104213511 Zettelstore is a software that manages your zettel. Since every zettel must be readable without any special tool, most zettel has to be stored as ordinary files within specific directories. Typically, file names and file content must comply to specific rules so that Zettelstore can manage them. If you add, delete, or change zettel files with other tools, e.g. a text editor, Zettelstore will monitor these actions. Zettelstore provides additional services to the user. |
︙ | ︙ | |||
29 30 31 32 33 34 35 | Every file in this directory that should be monitored by Zettelstore must have a file name that begins with 14 digits (0-9), the [[zettel identifier|00001006050000]]. If you create a new zettel via the [[web user interface|00001014000000]] or via the [[API|00001012053200]], the zettel identifier will be the timestamp of the current date and time (format is ''YYYYMMDDhhmmss''). This allows zettel to be sorted naturally by creation time. Since the only restriction on zettel identifiers are the 14 digits, you are free to use other digit sequences. The [[configuration zettel|00001004020000]] is one prominent example, as well as these manual zettel. | | | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | Every file in this directory that should be monitored by Zettelstore must have a file name that begins with 14 digits (0-9), the [[zettel identifier|00001006050000]]. If you create a new zettel via the [[web user interface|00001014000000]] or via the [[API|00001012053200]], the zettel identifier will be the timestamp of the current date and time (format is ''YYYYMMDDhhmmss''). This allows zettel to be sorted naturally by creation time. Since the only restriction on zettel identifiers are the 14 digits, you are free to use other digit sequences. The [[configuration zettel|00001004020000]] is one prominent example, as well as these manual zettel. You can create these special zettel identifiers either with the __rename__ function of Zettelstore or by manually renaming the underlying zettel files. It is allowed that the file name contains other characters after the 14 digits. These are ignored by Zettelstore. Two filename extensions are used by Zettelstore: # ''.zettel'' is a format that stores metadata and content together in one file, # the empty file extension is used, when the content must be stored in its own file, e.g. image data; |
︙ | ︙ | |||
70 71 72 73 74 75 76 | To allow changing predefined zettel, both the file store and the internal zettel store are internally chained together. If you change a zettel, it will be always stored as a file. If a zettel is requested, Zettelstore will first try to read that zettel from a file. If such a file was not found, the internal zettel store is searched secondly. Therefore, the file store ""shadows"" the internal zettel store. | | | 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | To allow changing predefined zettel, both the file store and the internal zettel store are internally chained together. If you change a zettel, it will be always stored as a file. If a zettel is requested, Zettelstore will first try to read that zettel from a file. If such a file was not found, the internal zettel store is searched secondly. Therefore, the file store ""shadows"" the internal zettel store. If you want to read the original zettel, you either have to delete the zettel (which removes it from the file directory), or you have to rename it to another zettel identifier. Now we have two places where zettel are stored: in the specific directory and within the Zettelstore software. * [[List of predefined zettel|00001005090000]] === Boxes: alternative ways to store zettel As described above, a zettel may be stored as a file inside a directory or inside the Zettelstore software itself. Zettelstore allows other ways to store zettel by providing an abstraction called __box__.[^Formerly, zettel were stored physically in boxes, often made of wood.] |
︙ | ︙ |
Changes to docs/manual/00001005090000.zettel.
1 2 3 4 5 6 | id: 00001005090000 title: List of predefined zettel role: manual tags: #manual #reference #zettelstore syntax: zmk created: 20210126175322 | | | < < < > < < < > < < < < | | < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | id: 00001005090000 title: List of predefined zettel role: manual tags: #manual #reference #zettelstore syntax: zmk created: 20210126175322 modified: 20220909180240 The following table lists all predefined zettel with their purpose. |= Identifier :|= Title | Purpose | [[00000000000001]] | Zettelstore Version | Contains the version string of the running Zettelstore | [[00000000000002]] | Zettelstore Host | Contains the name of the computer running the Zettelstore | [[00000000000003]] | Zettelstore Operating System | Contains the operating system and CPU architecture of the computer running the Zettelstore | [[00000000000004]] | Zettelstore License | Lists the license of Zettelstore | [[00000000000005]] | Zettelstore Contributors | Lists all contributors of Zettelstore | [[00000000000006]] | Zettelstore Dependencies | Lists all licensed content | [[00000000000007]] | Zettelstore Log | Lists the last 8192 log messages | [[00000000000020]] | Zettelstore Box Manager | Contains some statistics about zettel boxes and the the index process | [[00000000000090]] | Zettelstore Supported Metadata Keys | Contains all supported metadata keys, their [[types|00001006030000]], and more | [[00000000000092]] | Zettelstore Supported Parser | Lists all supported values for metadata [[syntax|00001006020000#syntax]] that are recognized by Zettelstore | [[00000000000096]] | Zettelstore Startup Configuration | Contains the effective values of the [[startup configuration|00001004010000]] | [[00000000000100]] | Zettelstore Runtime Configuration | Allows to [[configure Zettelstore at runtime|00001004020000]] | [[00000000010100]] | Zettelstore Base HTML Template | Contains the general layout of the HTML view | [[00000000010200]] | Zettelstore Login Form HTML Template | Layout of the login form, when authentication is [[enabled|00001010040100]] | [[00000000010300]] | Zettelstore List Zettel HTML Template | Used when displaying a list of zettel | [[00000000010401]] | Zettelstore Detail HTML Template | Layout for the HTML detail view of one zettel | [[00000000010402]] | Zettelstore Info HTML Template | Layout for the information view of a specific zettel | [[00000000010403]] | Zettelstore Form HTML Template | Form that is used to create a new or to change an existing zettel that contains text | [[00000000010404]] | Zettelstore Rename Form HTML Template | View that is displayed to change the [[zettel identifier|00001006050000]] | [[00000000010405]] | Zettelstore Delete HTML Template | View to confirm the deletion of a zettel | [[00000000020001]] | Zettelstore Base CSS | System-defined CSS file that is included by the [[Base HTML Template|00000000010100]] | [[00000000025001]] | Zettelstore User CSS | User-defined CSS file that is included by the [[Base HTML Template|00000000010100]] | [[00000000029000]] | Zettelstore Role to CSS Map | [[Maps|00001017000000#role-css]] [[role|00001006020000#role]] to a zettel identifier that is included by the [[Base HTML Template|00000000010100]] as an CSS file | [[00000000040001]] | Generic Emoji | Image that is shown if [[original image reference|00001007040322]] is invalid | [[00000000090000]] | New Menu | Contains items that should contain in the zettel template menu | [[00000000090001]] | New Zettel | Template for a new zettel with role ""[[zettel|00001006020100]]"" | [[00000000090002]] | New User | Template for a new [[user zettel|00001010040200]] | [[00010000000000]] | Home | Default home zettel, contains some welcome information If a zettel is not linked, it is not accessible for the current user. **Important:** All identifier may change until a stable version of the software is released. |
Changes to docs/manual/00001006000000.zettel.
1 2 3 4 5 6 | id: 00001006000000 title: Layout of a Zettel role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001006000000 title: Layout of a Zettel role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20221219162703 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. |
︙ | ︙ | |||
28 29 30 31 32 33 34 | 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 representation: SVG. And there is support for some binary image formats, like GIF, PNG, and JPEG. === Plain, parsed, and evaluated zettel Zettelstore may present your zettel in various forms, typically retrieved with the [[endpoint|00001012920000]] ''/z/{ID}''. One way is to present the zettel as it was read by Zettelstore. | | | | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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 representation: SVG. And there is support for some binary image formats, like GIF, PNG, and JPEG. === Plain, parsed, and evaluated zettel Zettelstore may present your zettel in various forms, typically retrieved with the [[endpoint|00001012920000]] ''/z/{ID}''. One way is to present the zettel as it was read by Zettelstore. This is called ""[[plain zettel|00001003000000]]"". The second way is to present the zettel as it was recognized by Zettelstore. This is called ""[[parsed zettel|00001012053600]]"", also retrieved with the [[endpoint|00001012920000]] ''/z/{ID}'', but with additional query parameter. Such a zettel was read and analyzed. It can be presented in various [[encodings|00001012920500]].[^The [[zmk encoding|00001012920522]] allows you to compare the plain, the parsed, and the evaluated form of a zettel.] However, a zettel such as this one you are currently reading, is a ""[[evaluated zettel|00001012053500]]"", also retrieved with the [[endpoint|00001012920000]] ''/z/{ID}'' and specifying an encoding. The biggest difference to a parsed zettel is the inclusion of [[block transclusions|00001007031100]] or [[inline transclusions|00001007040324]] for an evaluated zettel. It can also be presented in various encoding, including the ""zmk"" encoding. Evaluations also applies to metadata of a zettel, if appropriate. Please note, that searching for content is based on parsed zettel. Transcluded content will only be found in transcluded zettel, but not in the zettel that transcluded the content. However, you will easily pick up that zettel by follow the [[backward|00001006020000#backward]] metadata key of the transcluded zettel. |
Changes to docs/manual/00001006010000.zettel.
1 2 3 4 5 | id: 00001006010000 title: Syntax of Metadata role: manual tags: #manual #syntax #zettelstore syntax: zmk | < | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | id: 00001006010000 title: Syntax of Metadata role: manual tags: #manual #syntax #zettelstore syntax: zmk modified: 20220218131923 The metadata of a zettel is a collection of key-value pairs. The syntax roughly resembles the internal header of an email ([[RFC5322|https://tools.ietf.org/html/rfc5322]]). The key is a sequence of alphanumeric characters, a hyphen-minus character (""''-''"", U+002D) is also allowed. It begins at the first position of a new line. A key is separated from its value either by * a colon character (""'':''""), * a non-empty sequence of space characters, * a sequence of space characters, followed by a colon, followed by a sequence of space characters. A Value is a sequence of printable characters. If the value should be continued in the following line, that following line (""continuation line"") must begin with a non-empty sequence of space characters. The rest of the following line will be interpreted as the next part of the value. There can be more than one continuation line for a value. A non-continuation line that contains a possibly empty sequence of characters, followed by the percent sign character (""''%''"") is treated as a comment line. It will be ignored. |
︙ | ︙ |
Changes to docs/manual/00001006020000.zettel.
1 2 3 4 5 6 | id: 00001006020000 title: Supported Metadata Keys role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001006020000 title: Supported Metadata Keys role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 modified: 20221004134841 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]]. ; [!author|''author''] |
︙ | ︙ | |||
33 34 35 36 37 38 39 | This is a computed value. There is no need to set it via Zettelstore. If it is not stored within a zettel, it will be computed based on the value of the [[Zettel Identifier|00001006050000]]: if it contains a value >= 19700101000000, it will be coerced to da date/time; otherwise the version time of the running software will be used. Please note that the value von ''created'' will be different (in most cases) to the value of [[''id''|#id]] / the zettel identifier, because it is exact up to the second. When calculating a zettel identifier, Zettelstore tries to set the second value to zero, if possible. | < < < < < < < < < < < < < < < | 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | This is a computed value. There is no need to set it via Zettelstore. If it is not stored within a zettel, it will be computed based on the value of the [[Zettel Identifier|00001006050000]]: if it contains a value >= 19700101000000, it will be coerced to da date/time; otherwise the version time of the running software will be used. Please note that the value von ''created'' will be different (in most cases) to the value of [[''id''|#id]] / the zettel identifier, because it is exact up to the second. When calculating a zettel identifier, Zettelstore tries to set the second value to zero, if possible. ; [!credential|''credential''] : Contains the hashed password, as it was emitted by [[``zettelstore password``|00001004051400]]. It is internally created by hashing the password, the [[zettel identifier|00001006050000]], and the value of the ''ident'' key. It is only used for zettel with a ''role'' value of ""user"". ; [!dead|''dead''] : Property that contains all references that does __not__ identify a zettel. ; [!folge|''folge''] : Is a property that contains identifier of all zettel that reference this zettel through the [[''precursor''|#precursor]] value. ; [!forward|''forward''] : Property that contains all references that identify another zettel within the content of the zettel. ; [!id|''id''] : Contains the [[zettel identifier|00001006050000]], as given by the Zettelstore. It cannot be set manually, because it is a computed value. ; [!lang|''lang''] : Language for the zettel. |
︙ | ︙ | |||
85 86 87 88 89 90 91 | ; [!precursor|''precursor''] : References zettel for which this zettel is a ""Folgezettel"" / follow-up zettel. Basically the inverse of key [[''folge''|#folge]]. ; [!predecessor|''predecessor''] : References the zettel that contains a previous version of the content. In contrast to [[''precursor''|#precurso]] / [[''folge''|#folge]], this is a reference because of technical reasons, not because of content-related reasons. Basically the inverse of key [[''successors''|#successors]]. | < < < < < < < < < < < > > | | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | ; [!precursor|''precursor''] : References zettel for which this zettel is a ""Folgezettel"" / follow-up zettel. Basically the inverse of key [[''folge''|#folge]]. ; [!predecessor|''predecessor''] : References the zettel that contains a previous version of the content. In contrast to [[''precursor''|#precurso]] / [[''folge''|#folge]], this is a reference because of technical reasons, not because of content-related reasons. Basically the inverse of key [[''successors''|#successors]]. ; [!published|''published''] : This property contains the timestamp of the mast modification / creation of the zettel. If [[''modified''|#modified]] is set with a valid timestamp, it contains the its value. Otherwise, if [[''created''|#created]] is set with a valid timestamp, it contains the its value. Otherwise, if the zettel identifier contains a valid timestamp, the identifier is used. In all other cases, this property is not set. It can be used for [[sorting|00001007700000]] zettel based on their publication date. It is a computed value. There is no need to set it via Zettelstore. ; [!read-only|''read-only''] : Marks a zettel as read-only. The interpretation of [[supported values|00001006020400]] for this key depends, whether authentication is [[enabled|00001010040100]] or not. ; [!role|''role''] : Defines the role of the zettel. Can be used for selecting zettel. See [[supported zettel roles|00001006020100]]. If not given, it is ignored. ; [!successors|''successors''] : Is a property that contains identifier of all zettel that reference this zettel through the [[''predecessor''|#predecessor]] value. Therefore, it references all zettel that contain a new version of the content and/or metadata. In contrast to [[''folge''|#folge]], these are references because of technical reasons, not because of content-related reasons. In most cases, zettel referencing the current zettel should be updated to reference a successor zettel. The [[query reference|00001007040310]] [[query:backward? successors?]] lists all such zettel. ; [!syntax|''syntax''] : Specifies the syntax that should be used for interpreting the zettel. The zettel about [[other markup languages|00001008000000]] defines supported values. If it is not given, it defaults to ''plain''. ; [!tags|''tags''] : Contains a space separated list of tags to describe the zettel further. Each Tag must begin with the number sign character (""''#''"", U+0023). ; [!title|''title''] : Specifies the title of the zettel. If not given, the value of [[''id''|#id]] will be used. You can use all [[inline-structured elements|00001007040000]] of Zettelmarkup. ; [!url|''url''] : Defines an URL / URI for this zettel that possibly references external material. One use case is to specify the document that the current zettel comments on. The URL will be rendered special in the [[web user interface|00001014000000]] if you use the default template. ; [!useless-files|''useless-files''] : Contains the file names that are rejected to serve the content of a zettel. Is used for [[directory boxes|00001004011400]] and [[file boxes|00001004011200#file]]. If a zettel is renamed or deleted, these files will be deleted. ; [!user-id|''user-id''] : Provides some unique user identification for an [[user zettel|00001010040200]]. It is used as a user name for authentication. It is only used for zettel with a ''role'' value of ""user"". ; [!user-role|''user-role''] : Defines the basic privileges of an authenticated user, e.g. reading / changing zettel. |
︙ | ︙ |
Changes to docs/manual/00001006020100.zettel.
1 2 3 4 5 | id: 00001006020100 title: Supported Zettel Roles role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk | < | | < < < < < < < < < | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | id: 00001006020100 title: Supported Zettel Roles role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk modified: 20220623183234 The [[''role'' key|00001006020000#role]] defines what kind of zettel you are writing. You are free to define your own roles. It is allowed to set an empty value or to omit the role. Some roles are defined for technical reasons: ; [!configuration|''configuration''] : A zettel that contains some configuration data for the Zettelstore. Most prominent is [[00000000000100]], as described in [[00001004020000]]. ; [!manual|''manual''] : All zettel that document the inner workings of the Zettelstore software. This role is only used in this specific Zettelstore. If you adhere to the process outlined by Niklas Luhmann, a zettel could have one of the following three roles: ; [!note|''note''] : A small note, to remember something. Notes are not real zettel, they just help to create a real zettel. Think of them as Post-it notes. ; [!literature|''literature''] : 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 text of a scientific paper, ''project'' to define a project, ... |
Changes to docs/manual/00001006020400.zettel.
1 2 3 4 5 | id: 00001006020400 title: Supported values for metadata key ''read-only'' role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001006020400 title: Supported values for metadata key ''read-only'' role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk modified: 20211124132040 A zettel can be marked as read-only, if it contains a metadata value for key [[''read-only''|00001006020000#read-only]]. If user authentication is [[enabled|00001010040100]], it is possible to allow some users to change the zettel, depending on their [[user role|00001010070300]]. Otherwise, the read-only mark is just a binary value. |
︙ | ︙ |
Changes to docs/manual/00001006030000.zettel.
1 2 3 4 5 | id: 00001006030000 title: Supported Key Types role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk | < | < | | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | id: 00001006030000 title: Supported Key Types role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk modified: 20220304114106 All [[supported metadata keys|00001006020000]] conform to a type. User-defined metadata keys conform also to a type, based on the suffix of the key. |=Suffix|Type | ''-number'' | [[Number|00001006033000]] | ''-role'' | [[Word|00001006035500]] | ''-set'' | [[WordSet|00001006036000]] | ''-title'' | [[Zettelmarkup|00001006036500]] | ''-url'' | [[URL|00001006035000]] | ''-zettel'' | [[Identifier|00001006032000]] | ''-zid'' | [[Identifier|00001006032000]] | ''-zids'' | [[IdentifierSet|00001006032500]] | any other suffix | [[EString|00001006031500]] The name of the metadata key is bound to the key type Every key type has an associated validation rule to check values of the given type. There is also a rule how values are matched, e.g. against a search term when selecting some zettel. And there is a rule how values compare for sorting. * [[Credential|00001006031000]] * [[EString|00001006031500]] * [[Identifier|00001006032000]] * [[IdentifierSet|00001006032500]] * [[Number|00001006033000]] * [[String|00001006033500]] * [[TagSet|00001006034000]] * [[Timestamp|00001006034500]] * [[URL|00001006035000]] * [[Word|00001006035500]] * [[WordSet|00001006036000]] * [[Zettelmarkup|00001006036500]] |
Changes to docs/manual/00001006030500.zettel.
1 2 3 4 5 | id: 00001006030500 title: Boolean Value role: manual tags: #manual #reference #zettel #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001006030500 title: Boolean Value role: manual tags: #manual #reference #zettel #zettelstore syntax: zmk modified: 20220304114040 On some places, metadata values are interpreted as a truth value. Every character sequence that begins with a ""0"", ""F"", ""N"", ""f"", or a ""n"" is interpreted as the boolean ""false"" value. All values are interpreted as the boolean ""true"" value. |
︙ | ︙ |
Changes to docs/manual/00001006031000.zettel.
1 2 3 4 5 6 | id: 00001006031000 title: Credential Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | id: 00001006031000 title: Credential Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914130324 Values of this type denote a credential value, e.g. an encrypted password. === Allowed values All printable characters are allowed. Since a credential contains some kind of secret, the sequence of characters might have some hidden syntax to be interpreted by other parts of Zettelstore. === Query operators A credential never compares to any other value. A comparison will never match in any way. === Sorting If a list of zettel should be sorted based on a credential value, the identifier of the respective zettel is used instead. |
Changes to docs/manual/00001006031500.zettel.
1 2 3 4 5 6 | id: 00001006031500 title: EString Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | id: 00001006031500 title: EString Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914130448 Values of this type are just a sequence of character, possibly an empty sequence. An EString is the most general metadata key type, as it places no restrictions to the character sequence.[^Well, there are some minor restrictions that follow from the [[metadata syntax|00001006010000]].] === Allowed values All printable characters are allowed. === Query operator All comparisons are done case-insensitive, i.e. ""hell"" will be the prefix of ""Hello"". === Sorting To sort two values, the underlying encoding is used to determine which value is less than the other. Uppercase letters are typically interpreted as less than their corresponding lowercase letters, i.e. ``A < a``. |
︙ | ︙ |
Changes to docs/manual/00001006032000.zettel.
1 2 3 4 5 6 | id: 00001006032000 title: Identifier Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | < < < < < < < | < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | id: 00001006032000 title: Identifier Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914134914 Values of this type denote a [[zettel identifier|00001006050000]]. === Allowed values Must be a sequence of 14 digits (""0""--""9""). === Query operator Comparison is done with the string representation of the identifiers. For example, ""000010"" matches ""[[00001006032000]]"". === Sorting Sorting is done by comparing the [[String|00001006033500]] values. If both values are identifiers, this works well because both have the same length. |
Changes to docs/manual/00001006032500.zettel.
1 2 3 4 5 6 | id: 00001006032500 title: IdentifierSet Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | id: 00001006032500 title: IdentifierSet Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914131354 Values of this type denote a (sorted) set of [[zettel identifier|00001006050000]]. A set is different to a list, as no duplicate values are allowed. === Allowed values Must be at least one sequence of 14 digits (""0""--""9""), separated by space characters. === Query operator A value matches an identifier set value, if the value matches any of the identifier set values. For example, ""000010060325"" is a prefix ""[[00001006032000]] [[00001006032500]]"". === Sorting Sorting is done by comparing the [[String|00001006033500]] values. |
Changes to docs/manual/00001006033000.zettel.
1 2 3 4 5 6 | id: 00001006033000 title: Number Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | < < < < < | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | id: 00001006033000 title: Number Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914131211 Values of this type denote a numeric integer value. === Allowed values Must be a sequence of digits (""0""--""9""), optionally prefixed with a ""-"" or a ""+"" character. === Query operator All comparisons are done on the given string representation of the number, ""+12"" will be treated as a different number of ""12"". === Sorting Sorting is done by comparing the numeric values. |
Changes to docs/manual/00001006033500.zettel.
1 2 3 4 5 6 | id: 00001006033500 title: String Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | id: 00001006033500 title: String Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914130505 Values of this type are just a sequence of character, but not an empty sequence. === Allowed values All printable characters are allowed. There must be at least one such character. === Query operator All comparisons are done case-insensitive, i.e. ""hell"" will be the prefix of ""Hello"". === Sorting To sort two values, the underlying encoding is used to determine which value is less than the other. Uppercase letters are typically interpreted as less than their corresponding lowercase letters, i.e. ``A < a``. |
︙ | ︙ |
Changes to docs/manual/00001006034000.zettel.
1 2 3 4 5 6 | id: 00001006034000 title: TagSet Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | id: 00001006034000 title: TagSet Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914131048 Values of this type denote a (sorted) set of tags. A set is different to a list, as no duplicate values are allowed. === Allowed values Every tag must must begin with the number sign character (""''#''"", U+0023), followed by at least one printable character. Tags are separated by space characters. All characters are mapped to their lower case values. === Query operator All comparisons are done case-sensitive, i.e. ""#hell"" will not be the prefix of ""#Hello"". === Sorting Sorting is done by comparing the [[String|00001006033500]] values. |
Changes to docs/manual/00001006034500.zettel.
1 2 3 4 5 6 | id: 00001006034500 title: Timestamp Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | < < < < < < < < | < < | < < | > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | id: 00001006034500 title: Timestamp Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914130919 Values of this type denote a point in time. === Allowed values Must be a sequence of 14 digits (""0""--""9"") (same as an [[Identifier|00001006032000]]), with the restriction that is conforms to the pattern ""YYYYMMDDhhmmss"". * YYYY is the year, * MM is the month, * DD is the day, * hh is the hour, * mm is the minute, * ss is the second. === Query operator All comparisons assume that up to 14 digits are given. === Sorting Sorting is done by comparing the [[String|00001006033500]] values. If both values are timestamp values, this works well because both have the same length. |
Changes to docs/manual/00001006035000.zettel.
1 2 3 4 5 6 | id: 00001006035000 title: URL Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | id: 00001006035000 title: URL Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914130809 Values of this type denote an URL. === Allowed values All characters of an URL / URI are allowed. === Query operator All comparisons are done case-insensitive. For example, ""hello"" is the suffix of ""http://example.com/Hello"". === Sorting Sorting is done by comparing the [[String|00001006033500]] values. |
Changes to docs/manual/00001006035500.zettel.
1 2 3 4 5 6 | id: 00001006035500 title: Word Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | id: 00001006035500 title: Word Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914130655 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. === Query operator All comparisons are done case-insensitive, i.e. ""hell"" will be the prefix of ""Hello"". === Sorting Sorting is done by comparing the [[String|00001006033500]] values. |
Added docs/manual/00001006036000.zettel.
> > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | id: 00001006036000 title: WordSet Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914130725 Values of this type denote a (sorted) set of [[words|00001006035500]]. A set is different to a list, as no duplicate values are allowed. === Allowed values Must be a sequence of at least one word, separated by space characters. === Query operator All comparisons are done case-insensitive, i.e. ""hell"" will be the prefix of ""World, Hello"". === Sorting Sorting is done by comparing the [[String|00001006033500]] values. |
Changes to docs/manual/00001006036500.zettel.
1 2 3 4 5 6 | id: 00001006036500 title: Zettelmarkup Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | id: 00001006036500 title: Zettelmarkup Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914135405 Values of this type are [[String|00001006033500]] values, interpreted as [[Zettelmarkup|00001007000000]]. === Allowed values All printable characters are allowed. There must be at least one such character. === Query operator Comparison is done similar to the full-text search: both the value to compare and the metadata value are normalized according to Unicode NKFD, ignoring everything except letters and numbers. Letters are mapped to the corresponding lower-case value. For example, ""Brücke"" will be the prefix of ""(Bruckenpfeiler,"". === Sorting To sort two values, the underlying encoding is used to determine which value is less than the other. |
︙ | ︙ |
Changes to docs/manual/00001006050000.zettel.
1 2 3 4 5 | id: 00001006050000 title: Zettel identifier role: manual tags: #design #manual #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001006050000 title: Zettel identifier role: manual tags: #design #manual #zettelstore syntax: zmk modified: 20210721123222 Each zettel is given a unique identifier. To some degree, the zettel identifier is part of the metadata. Basically, the identifier is given by the [[Zettelstore|00001005000000]] software. Every zettel identifier consists of 14 digits. They resemble a timestamp: the first four digits could represent the year, the |
︙ | ︙ |
Changes to docs/manual/00001006055000.zettel.
1 2 3 4 5 | id: 00001006055000 title: Reserved zettel identifier role: manual tags: #design #manual #zettelstore syntax: zmk | < | | > | | | > > > | > > > | > > | | | | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | id: 00001006055000 title: Reserved zettel identifier role: manual tags: #design #manual #zettelstore syntax: zmk modified: 20220311111751 [[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. To make things easier, you normally should not use zettel identifier that begin with four zeroes (''0000''). All zettel provided by an empty zettelstore begin with six zeroes[^Exception: the predefined home zettel ''00010000000000''. But you can [[configure|00001004020000#home-zettel]] another zettel with another identifier as the new home zettel.]. Zettel identifier of this manual have be chosen to begin with ''000010''. However, some external applications may need a range of specific zettel identifier to work properly. Identifier that begin with ''00009'' can be used for such purpose. To request a reservation, please send an email to the maintainer of Zettelstore. The request must include the following data: ; Title : Title of you application ; Description : A brief description what the application is used for and why you need to reserve some zettel identifier ; Number : Specify the amount of zettel identifier you are planning to use. Minimum size is 100. If you need more than 10.000, your justification will contain more words. === Reserved Zettel Identifier |= From | To | Description | 00000000000000 | 0000000000000 | This is an invalid zettel identifier | 00000000000001 | 0000009999999 | [[Predefined zettel|00001005090000]] | 00000100000000 | 0000019999999 | This [[Zettelstore manual|00001000000000]] | 00000200000000 | 0000899999999 | Reserved for future use | 00009000000000 | 0000999999999 | Reserved for applications This list may change in the future. ==== External Applications |= From | To | Description | 00009000001000 | 00009000001999 | [[Zettel Presenter|https://zettelstore.de/contrib]], an application to display zettel as a HTML-based slideshow | 00009000002000 | 00009000002999 | [[Zettel Blog|https://zettelstore.de/contrib]], an application to collect and transform zettel into a blog |
Changes to docs/manual/00001007010000.zettel.
1 2 3 4 5 | id: 00001007010000 title: Zettelmarkup: General Principles role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007010000 title: Zettelmarkup: General Principles role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20211124175047 Any document can be thought as a sequence of paragraphs and other [[block-structured elements|00001007030000]] (""blocks""), such as [[headings|00001007030300]], [[lists|00001007030200]], quotations, and code blocks. Some of these blocks can contain other blocks, for example lists may contain other lists or paragraphs. Other blocks contain [[inline-structured elements|00001007040000]] (""inlines""), such as text, [[links|00001007040310]], emphasized text, and images. With the exception of lists and tables, the markup for blocks always begins at the first position of a line with three or more identical characters. |
︙ | ︙ |
Changes to docs/manual/00001007020000.zettel.
1 2 3 4 5 | id: 00001007020000 title: Zettelmarkup: Basic Definitions role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007020000 title: Zettelmarkup: Basic Definitions role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218130713 Every Zettelmarkup content consists of a sequence of Unicode code-points. Unicode code-points are called in the following as **character**s. Characters are encoded with UTF-8. |
︙ | ︙ |
Changes to docs/manual/00001007030000.zettel.
1 2 3 4 5 | id: 00001007030000 title: Zettelmarkup: Block-Structured Elements role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007030000 title: Zettelmarkup: Block-Structured Elements role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220311181036 Every markup for blocks-structured elements (""blocks"") begins at the very first position of a line. There are five kinds of blocks: lists, one-line blocks, line-range blocks, tables, and paragraphs. === Lists |
︙ | ︙ |
Changes to docs/manual/00001007030100.zettel.
1 2 3 4 5 | id: 00001007030100 title: Zettelmarkup: Description Lists role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007030100 title: Zettelmarkup: Description Lists role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218131155 A description list is a sequence of terms to be described together with the descriptions of each term. Every term can described in multiple ways. A description term (short: __term__) is specified with one semicolon (""'';''"", U+003B) at the first position, followed by a space character and the described term, specified as a sequence of line elements. If the following lines should also be part of the term, exactly two spaces must be given at the beginning of each following line. |
︙ | ︙ |
Changes to docs/manual/00001007030200.zettel.
1 2 3 4 5 | id: 00001007030200 title: Zettelmarkup: Nested Lists role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007030200 title: Zettelmarkup: Nested Lists role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218133902 There are thee kinds of lists that can be nested: ordered lists, unordered lists, and quotation lists. Ordered lists are specified with the number sign (""''#''"", U+0023), unordered lists use the asterisk (""''*''"", U+002A), and quotation lists are specified with the greater-than sing (""''>''"", U+003E). Let's call these three characters __list characters__. |
︙ | ︙ |
Changes to docs/manual/00001007030300.zettel.
1 2 3 4 5 | id: 00001007030300 title: Zettelmarkup: Headings role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007030300 title: Zettelmarkup: Headings role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218133755 To specify a (sub-) section of a zettel, you should use the headings syntax: at the beginning of a new line type at least three equal signs (""''=''"", U+003D), plus at least one space and enter the text of the heading as [[inline elements|00001007040000]]. ```zmk |
︙ | ︙ |
Changes to docs/manual/00001007030500.zettel.
1 2 3 4 5 | id: 00001007030500 title: Zettelmarkup: Verbatim Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007030500 title: Zettelmarkup: Verbatim Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218131500 Verbatim blocks are used to enter text that should not be interpreted. They begin with at least three grave accent characters (""''`''"", U+0060) at the first position of a line. Alternatively, a modifier letter grave accent (""''Ë‹''"", U+02CB) is also allowed[^On some devices, such as an iPhone / iPad, a grave accent character is harder to enter and is often confused with a modifier letter grave accent.]. You can add some [[attributes|00001007050000]] on the beginning line of a verbatim block, following the initiating characters. |
︙ | ︙ |
Changes to docs/manual/00001007030600.zettel.
1 2 3 4 5 | id: 00001007030600 title: Zettelmarkup: Quotation Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007030600 title: Zettelmarkup: Quotation Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218131806 A simple way to enter a quotation is to use the [[quotation list|00001007030200]]. A quotation list loosely follows the convention of quoting text within emails. However, if you want to attribute the quotation to someone, a quotation block is more appropriately. This kind of line-range block begins with at least three less-than characters (""''<''"", U+003C) at the first position of a line. |
︙ | ︙ |
Changes to docs/manual/00001007030700.zettel.
1 2 3 4 5 | id: 00001007030700 title: Zettelmarkup: Verse Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007030700 title: Zettelmarkup: Verse Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218132432 Sometimes, you want to enter text with significant space characters at the beginning of each line and with significant line endings. Poetry is one typical example. Of course, you could help yourself with hard space characters and hard line breaks, by entering a backslash character before a space character and at the end of each line. Using a verse block might be easier. |
︙ | ︙ |
Changes to docs/manual/00001007030800.zettel.
1 2 3 4 5 | id: 00001007030800 title: Zettelmarkup: Region Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007030800 title: Zettelmarkup: Region Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220323190829 Region blocks does not directly have a visual representation. They just group a range of lines. You can use region blocks to enter [[attributes|00001007050000]] that apply only to this range of lines. One example is to enter a multi-line warning that should be visible. |
︙ | ︙ |
Changes to docs/manual/00001007030900.zettel.
1 2 3 4 5 | id: 00001007030900 title: Zettelmarkup: Comment Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | id: 00001007030900 title: Zettelmarkup: Comment Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218130330 Comment blocks are quite similar to [[verbatim blocks|00001007030500]]: both are used to enter text that should not be interpreted. While the text entered inside a verbatim block will be processed somehow, text inside a comment block will be ignored[^Well, not completely ignored: text is read, but it will typically not rendered visible.]. Comment blocks are typically used to give some internal comments, e.g. the license of a text or some internal remarks. Comment blocks begin with at least three percent sign characters (""''%''"", U+0025) at the first position of a line. You can add some [[attributes|00001007050000]] on the beginning line of a comment block, following the initiating characters. The comment block supports the default attribute: when given, the text will be rendered, e.g. as an HTML comment. When rendered to JSON, the comment block will not be ignored but it will output some JSON text. Same for other renderer. Any other character in this line will be ignored Text following the beginning line will not be interpreted, until a line begins with at least the same number of the same characters given at the beginning line. This allows to enter some percent sign characters in the text that should not be interpreted. |
︙ | ︙ |
Changes to docs/manual/00001007031000.zettel.
1 2 3 4 5 | id: 00001007031000 title: Zettelmarkup: Tables role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007031000 title: Zettelmarkup: Tables role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218131107 Tables are used to show some data in a two-dimensional fashion. In zettelmarkup, table are not specified explicitly, but by entering __table rows__. Therefore, a table can be seen as a sequence of table rows. A table row is nothing as a sequence of __table cells__. The length of a table is the number of table rows, the width of a table is the maximum length of its rows. |
︙ | ︙ |
Changes to docs/manual/00001007031140.zettel.
1 2 3 4 5 6 | id: 00001007031140 title: Zettelmarkup: Query Transclusion role: manual tags: #manual #search #zettelmarkup #zettelstore syntax: zmk created: 20220809132350 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001007031140 title: Zettelmarkup: Query Transclusion role: manual tags: #manual #search #zettelmarkup #zettelstore syntax: zmk created: 20220809132350 modified: 20230116183656 A query transclusion is specified by the following sequence, starting at the first position in a line: ''{{{query:query-expression}}}''. The line must literally start with the sequence ''{{{query:''. Everything after this prefix is interpreted as a [[query expression|00001007700000]]. When evaluated, the query expression is evaluated, often resulting in a list of [[links|00001007040310]] to zettel, matching the query expression. The result replaces the query transclusion element. |
︙ | ︙ | |||
45 46 47 48 49 50 51 | : Transform the zettel list into an [[Atom 1.0|https://www.rfc-editor.org/rfc/rfc4287]]-conformant document / feed. The document is embedded into the referencing zettel. ; ''RSS'' (aggregate) : Transform the zettel list into a [[RSS 2.0|https://www.rssboard.org/rss-specification]]-conformant document / feed. The document is embedded into the referencing zettel. ; ''KEYS'' (aggregate) : Emit a list of all metadata keys, together with the number of zettel having the key. | < < < | < < < | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | : Transform the zettel list into an [[Atom 1.0|https://www.rfc-editor.org/rfc/rfc4287]]-conformant document / feed. The document is embedded into the referencing zettel. ; ''RSS'' (aggregate) : Transform the zettel list into a [[RSS 2.0|https://www.rssboard.org/rss-specification]]-conformant document / feed. The document is embedded into the referencing zettel. ; ''KEYS'' (aggregate) : Emit a list of all metadata keys, together with the number of zettel having the key. ; Any [[metadata key|00001006020000]] of type [[Word|00001006035500]], [[WordSet|00001006036000]], or [[TagSet|00001006034000]] (aggregates) : Emit an aggregate of the given metadata key. The key can be given in any letter case[^Except if the key name collides with one of the above names. In this case use at least one lower case letter.]. Example: ```zmk {{{query:tags:#search | tags}}} ``` This is a tag cloud of all tags that are used together with the tag #search: :::example {{{query:tags:#search | tags}}} ::: |
Changes to docs/manual/00001007031300.zettel.
︙ | ︙ | |||
10 11 12 13 14 15 16 | They begin with at least three tilde characters (""''~''"", U+007E) at the first position of a line. You can add some [[attributes|00001007050000]] on the beginning line of a verbatim block, following the initiating characters. The evaluation block supports the default attribute[^Depending on the syntax value.]: when given, all spaces in the text are rendered in HTML as open box characters (U+2423). If you want to give only one attribute and this attribute is the generic attribute, you can omit the most of the attribute syntax and just specify the value. It will be interpreted as a [[syntax|00001008000000]] value to evaluate its content. Not all syntax values are supported by Zettelstore.[^Currently just ""[[draw|00001008050000]]"".] | | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | They begin with at least three tilde characters (""''~''"", U+007E) at the first position of a line. You can add some [[attributes|00001007050000]] on the beginning line of a verbatim block, following the initiating characters. The evaluation block supports the default attribute[^Depending on the syntax value.]: when given, all spaces in the text are rendered in HTML as open box characters (U+2423). If you want to give only one attribute and this attribute is the generic attribute, you can omit the most of the attribute syntax and just specify the value. It will be interpreted as a [[syntax|00001008000000]] value to evaluate its content. Not all syntax values are supported by Zettelstore.[^Currently just ""[[draw|00001008050000]]"".] The main reason for an evaluation block is to be used with external software via the [[Sexpr encoding|00001012920516]]. Any other character in this line will be ignored Text following the beginning line will not be interpreted, until a line begins with at least the same number of the same characters given at the beginning line. This allows to enter some tilde characters in the text that should not be interpreted. For example: |
︙ | ︙ |
Changes to docs/manual/00001007031400.zettel.
︙ | ︙ | |||
12 13 14 15 16 17 18 | You can add some [[attributes|00001007050000]] on the beginning line of a verbatim block, following the initiating characters. A math-mode block supports the default attribute[^Depending on the syntax value.]: when given, all spaces in the text are rendered in HTML as open box characters (U+2423). If you want to give only one attribute and this attribute is the generic attribute, you can omit the most of the attribute syntax and just specify the value. It will be interpreted as a [[syntax|00001008000000]] value to evaluate its content. Alternatively, you could provide an attribute with the key ""syntax"" and use the value to specify the syntax. Not all syntax values are supported by Zettelstore.[^Currently: none.] | | | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | You can add some [[attributes|00001007050000]] on the beginning line of a verbatim block, following the initiating characters. A math-mode block supports the default attribute[^Depending on the syntax value.]: when given, all spaces in the text are rendered in HTML as open box characters (U+2423). If you want to give only one attribute and this attribute is the generic attribute, you can omit the most of the attribute syntax and just specify the value. It will be interpreted as a [[syntax|00001008000000]] value to evaluate its content. Alternatively, you could provide an attribute with the key ""syntax"" and use the value to specify the syntax. Not all syntax values are supported by Zettelstore.[^Currently: none.] External software might support several values via the [[Sexpr encoding|00001012920516]]. Any other character in this line will be ignored Text following the beginning line will not be interpreted, until a line begins with at least the same number of the same characters given at the beginning line. This allows to enter some dollar-sign characters in the text that should not be interpreted. For example: |
︙ | ︙ |
Changes to docs/manual/00001007040100.zettel.
1 2 3 4 5 | id: 00001007040100 title: Zettelmarkup: Text Formatting role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007040100 title: Zettelmarkup: Text Formatting role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218131003 Text formatting is the way to make your text visually different. Every text formatting element begins with two same characters. It ends when these two same characters occur the second time. It is possible that some [[attributes|00001007050000]] follow immediately, without any separating character. Text formatting can be nested, up to a reasonable limit. |
︙ | ︙ | |||
26 27 28 29 30 31 32 | * The circumflex accent character (""''^''"", U+005E) allows to enter super-scripted text. ** Example: ``e=mc^^2^^`` is rendered in HTML as: ::e=mc^^2^^::{=example}. * The comma character (""'',''"", U+002C) produces sub-scripted text. ** Example: ``H,,2,,O`` is rendered in HTML as: ::H,,2,,O::{=example}. * The quotation mark character (""''"''"", U+0022) marks an inline quotation, according to the [[specified language|00001007050100]]. ** Example: ``""To be or not""`` is rendered in HTML as: ::""To be or not""::{=example}. ** Example: ``""Sein oder nicht""{lang=de}`` is rendered in HTML as: ::""Sein oder nicht""{lang=de}::{=example}. | < < < | 25 26 27 28 29 30 31 32 33 | * The circumflex accent character (""''^''"", U+005E) allows to enter super-scripted text. ** Example: ``e=mc^^2^^`` is rendered in HTML as: ::e=mc^^2^^::{=example}. * The comma character (""'',''"", U+002C) produces sub-scripted text. ** Example: ``H,,2,,O`` is rendered in HTML as: ::H,,2,,O::{=example}. * The quotation mark character (""''"''"", U+0022) marks an inline quotation, according to the [[specified language|00001007050100]]. ** Example: ``""To be or not""`` is rendered in HTML as: ::""To be or not""::{=example}. ** Example: ``""Sein oder nicht""{lang=de}`` is rendered in HTML as: ::""Sein oder nicht""{lang=de}::{=example}. * The colon character (""'':''"", U+003A) mark some text that should belong together. It fills a similar role as [[region blocks|00001007030800]], but just for inline elements. ** Example: ``abc ::def::{=example} ghi`` is rendered in HTML as: abc ::def::{=example} ghi. |
Changes to docs/manual/00001007040200.zettel.
1 2 3 4 5 | id: 00001007040200 title: Zettelmarkup: Literal-like formatting role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007040200 title: Zettelmarkup: Literal-like formatting role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220311185110 There are some reasons to mark text that should be rendered as uninterpreted: * Mark text as literal, sometimes as part of a program. * Mark text as input you give into a computer via a keyboard. * Mark text as output from some computer, e.g. shown at the command line. |
︙ | ︙ |
Changes to docs/manual/00001007040300.zettel.
1 2 3 4 5 | id: 00001007040300 title: Zettelmarkup: Reference-like text role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | 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 or to (external) material|00001007040310]] * [[Embedded zettel or (external) material|00001007040320]] (""inline transclusion"") |
︙ | ︙ |
Changes to docs/manual/00001007040324.zettel.
1 2 3 4 5 6 | id: 00001007040324 title: Zettelmarkup: Inline-mode Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210811154251 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001007040324 title: Zettelmarkup: Inline-mode Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210811154251 modified: 20221116165428 Inline-mode transclusion applies to all zettel that are parsed in a non-trivial way, e.g. as structured textual content. For example, textual content is assumed if the [[syntax|00001006020000#syntax]] of a zettel is ""zmk"" ([[Zettelmarkup|00001007000000]]), or ""markdown"" / ""md"" ([[Markdown|00001008010000]]). Since this type of 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. |
︙ | ︙ | |||
34 35 36 37 38 39 40 | 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 the reference is a [[hosted or based|00001007040310#link-specifications]] link / URL to an image, that image will be rendered. | | | 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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 the reference is a [[hosted or based|00001007040310#link-specifications]] link / URL to an image, that image will be rendered. Example: ``{{//z/00000000040001}}`` is rendered as ::{{//z/00000000040001}}::{=example} 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. === See also [[Full transclusion|00001007031100]] does not work inside some text, but is used for [[block-structured elements|00001007030000]]. |
Changes to docs/manual/00001007040330.zettel.
1 2 3 4 5 | id: 00001007040330 title: Zettelmarkup: Footnotes role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007040330 title: Zettelmarkup: Footnotes role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218130100 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}. |
Changes to docs/manual/00001007040340.zettel.
1 2 3 4 5 | id: 00001007040340 title: Zettelmarkup: Citation Key role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007040340 title: Zettelmarkup: Citation Key role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218133447 A citation key references some external material that is part of a bibliographical 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. |
︙ | ︙ |
Changes to docs/manual/00001007040350.zettel.
1 2 3 4 5 | id: 00001007040350 title: Zettelmarkup: Mark role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007040350 title: Zettelmarkup: Mark role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218133206 A mark allows to name a point within a zettel. This is useful if you want to reference some content in a zettel, either with a [[link|00001007040310]] or with an [[inline-mode transclusion|00001007040324]]. A mark begins with a left square bracket, followed by an exclamation mark character (""''!''"", U+0021). Now the optional mark name follows. |
︙ | ︙ |
Changes to docs/manual/00001007050000.zettel.
1 2 3 4 5 | id: 00001007050000 title: Zettelmarkup: Attributes role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007050000 title: Zettelmarkup: Attributes role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220630194106 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 plain text. |
︙ | ︙ |
Changes to docs/manual/00001007050200.zettel.
1 2 3 4 5 | id: 00001007050200 title: Zettelmarkup: Supported Attribute Values for Programming Languages tags: #manual #reference #zettelmarkup #zettelstore syntax: zmk role: manual | < | 1 2 3 4 5 6 7 | id: 00001007050200 title: Zettelmarkup: Supported Attribute Values for Programming Languages tags: #manual #reference #zettelmarkup #zettelstore syntax: zmk role: manual TBD |
Changes to docs/manual/00001007700000.zettel.
1 | id: 00001007700000 | | | | | | > | > > | > | | | | > > > | | | | < | > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | id: 00001007700000 title: Query expression role: manual tags: #manual #search #zettelstore syntax: zmk created: 20220805150154 modified: 20220913135434 A query expression allows you to search for specific zettel and to perform some actions on them. You may select zettel based on a full-text search, based on specific metadata values, or both. A query expression consists of a __search expression__ and of an optional __action list__. Both are separated by a vertical bar character (""''|''"", U+007C). A query expression follows a [[formal syntax|00001007780000]]. === Search expression In its simplest form, a search expression just contains a string to be search for with the help of a full-text search. For example, the string ''syntax'' will search for all zettel containing the word ""syntax"". If you want to search for all zettel with a title containing the word ""syntax"", you must specify ''title:syntax''. ""title"" names the [[metadata key|00001006010000]], in this case the [[supported metadata key ""title""|00001006020000#title]]. The colon character (""'':''"") is a [[search operator|00001007705000]], in this example to specify a match. ""syntax"" is the [[search value|00001007706000]] that must match to the value of the given metadata key, here ""title"". A search expression may contain more than one search term, such as ''title:syntax''. Search terms must be separated by one or more space characters, for example ''title:syntax title:search''. All terms of a select expression must be true so that a zettel is selected. * [[Search terms|00001007702000]] * [[Search operator|00001007705000]] * [[Search value|00001007706000]] Here are [[some examples|00001007790000]] of search expressions, which can be used to manage a Zettelstore: {{{00001007790000}}} === Action List With a search expression, a list of zettel is selected. Actions allow to modify this list to a certain degree. Which actions are allowed depends on the context. However, actions are further separated into __parameter action__ and __aggregate actions__. A parameter action just sets a parameter for an aggregate action. An aggregate action transforms the list of selected zettel into a different, aggregate form. Only the first aggregate form is executed, following aggregate actions are ignored. In most contexts, valid actions include the name of metadata keys, at least of type [[Word|00001006035500]], [[WordSet|00001006036000]], or [[TagSet|00001006034000]]. |
Deleted docs/manual/00001007701000.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to docs/manual/00001007702000.zettel.
1 2 3 4 5 | id: 00001007702000 title: Search term role: manual tags: #manual #search #zettelstore syntax: zmk | < | < < < < < < < < < < < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | id: 00001007702000 title: Search term role: manual tags: #manual #search #zettelstore syntax: zmk modified: 20220821163727 A search term allows you to specify one search restriction. The result [[search expression|00001007700000]], which contains more than one search term, will be the applications of all restrictions. A search term can be one of the following (the first three term are collectively called __search literals__): * A metadata-based search, by specifying the name of a [[metadata key|00001006010000]], followed by a [[search operator|00001007705000]], followed by an optional [[search value|00001007706000]]. All zettel containing the given metadata key with a allowed value (depending on the search operator) are selected. If no search value is given, then all zettel containing the given metadata key are selected (or ignored, for a negated search operator). * An optional [[search operator|00001007705000]], followed by a [[search value|00001007706000]]. This specifies a full-text search for the given search value. **Note:** the search value will be normalized according to Unicode NKFD, ignoring everything except letters and numbers. Therefore, the following search expression are essentially the same: ''"search syntax"'' and ''search syntax''. The first is a search expression with one search value, which is normalized to two strings to be searched for. The second is a search expression containing two search values, giving two string to be searched for. * A metadata key followed by ""''?''"" or ""''!?''"". Is true, if zettel metadata contains / does not contain the given key. * The string ''OR'' signals that following search literals may occur alternatively in the result. Since search literals may be negated, it is possible to form any boolean search expression. Any search expression will be in a [[disjunctive normal form|https://en.wikipedia.org/wiki/Disjunctive_normal_form]]. It has no effect on the following search terms initiated with a special uppercase word. * The string ''ORDER'', followed by a non-empty sequence of spaces and the name of a metadata key, will specify an ordering of the result list. If you include the string ''REVERSE'' after ''ORDER'' but before the metadata key, the ordering will be reversed. Example: ''ORDER published'' will order the resulting list based on the publishing data, while ''ORDER REVERSED published'' will return a reversed result order. An explicit order field will take precedence over the random order described below. If no random order is effective, a ``ORDER REVERSE id`` will be added. This makes the sort stable. Example: ``ORDER created`` will be interpreted as ``ORDER created ORDER REVERSE id``. |
︙ | ︙ | |||
84 85 86 87 88 89 90 | You may have noted that the specifications of first two items overlap somehow. This is resolved by the following rule: * A search term containing no [[search operator character|00001007705000]] is treated as a full-text search. * The first search operator character found in a search term divides the term into two pieces. If the first piece, from the beginning of the search term to the search operator character, is syntactically a metadata key, the search term is treated as a metadata-based search. * Otherwise, the search term is treated as a full-text search. | | < | 72 73 74 75 76 77 78 79 | You may have noted that the specifications of first two items overlap somehow. This is resolved by the following rule: * A search term containing no [[search operator character|00001007705000]] is treated as a full-text search. * The first search operator character found in a search term divides the term into two pieces. If the first piece, from the beginning of the search term to the search operator character, is syntactically a metadata key, the search term is treated as a metadata-based search. * Otherwise, the search term is treated as a full-text search. If a term like ''ORDER'', ''ORDER REVERSE'', ''OFFSET'', or ''LIMIT'' is not followed by an appropriate value, it is interpreted as a search value for a full-text search. For example, ''ORDER 123'' will search for a zettel conatining the strings ""ORDER"" (case-insensitive) and ""123"". |
Changes to docs/manual/00001007705000.zettel.
1 2 3 4 5 | id: 00001007705000 title: Search operator role: manual tags: #manual #search #zettelstore syntax: zmk | < | | < | | | | < < < | < | | | | | | | < < < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | id: 00001007705000 title: Search operator role: manual tags: #manual #search #zettelstore syntax: zmk modified: 20220819194709 A search operator specifies how the comparison of a search value and a zettel should be executed. Every comparison is done case-insensitive, treating all uppercase letters the same as lowercase letters. The following are allowed search operator characters: * The exclamation mark character (""!"", U+0021) negates the meaning * The tilde character (""''~''"", U+007E) compares on matching (""match operator"") * The greater-than sign character (""''>''"", U+003E) matches if there is some prefix (""prefix operator"") * The less-than sign character (""''<''"", U+003C) compares a suffix relationship (""suffix operator"") * The colon character (""'':''"", U+003A) compares on equal words (""has operator"") * The question mark (""''?''"", U+003F) checks for an existing metadata key (""exist operator"") Since the exclamation mark character can be combined with the other, there are 10 possible combinations: # ""''!''"": is an abbreviation of the ""''!~''"" operator. # ""''~''"": is successful if the search value matched the value to be compared. # ""''!~''"": is successful if the search value does not match the value to be compared. # ""'':''"": is successful if the search value is equal to one word of the value to be compared. # ""''!:''"": is successful if the search value is not equal to any word of the value to be compared. # ""''>''"": is successful if the search value is a prefix of the value to be compared. # ""''!>''"": is successful if the search value is not a prefix of the value to be compared. # ""''<''"": is successful if the search value is a suffix of the value to be compared. # ""''!<''"": is successful if the search value is not a suffix of the value to be compared. # ""''?''"": is successful if the metadata contains the given key. # ""''!?''"": is successful if the metadata does not contain the given key. # ""''''"": a missing search operator can only occur for a full-text search. It is equal to the ""''~''"" operator. |
Changes to docs/manual/00001007706000.zettel.
1 2 3 4 5 | id: 00001007706000 title: Search value role: manual tags: #manual #search #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 10 | id: 00001007706000 title: Search value role: manual tags: #manual #search #zettelstore syntax: zmk modified: 20220807162031 A search value specifies a value to be searched for, depending on the [[search operator|00001007705000]]. A search value should be lower case, because all comparisons are done in a case-insensitive way and there are some upper case keywords planned. |
Deleted docs/manual/00001007710000.zettel.
|
| < < < < < < < < < < < < < < < < < |
Deleted docs/manual/00001007720000.zettel.
|
| < < < < < < < < < < < < < < < < < < < |
Deleted docs/manual/00001007720300.zettel.
|
|