Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From v0.7.1 To v0.6.0
2022-09-18
| ||
13:45 | Merge update from v0.7.1 ... (check-in: 9f49bb2b40 user: stern tags: trunk) | |
13:36 | Version 0.7.1 ... (Leaf check-in: 1b017311d2 user: stern tags: release, v0.7.1, release-0.7) | |
13:28 | Fix: make RSS compatible to Miniflux; always enrich metadata when search actions are specified ... (check-in: f653781a13 user: stern tags: release-0.7) | |
2022-08-22
| ||
09:31 | Version 0.6.1 ... (check-in: d953a740f6 user: stern tags: release, release-0.6, v0.6.1) | |
2022-08-12
| ||
09:31 | Increase version to 0.7.0-dev to begin next development cycle ... (check-in: 12f09c3193 user: stern tags: trunk) | |
2022-08-11
| ||
17:09 | Version 0.6.0 ... (check-in: d495df0b57 user: stern tags: trunk, release, v0.6.0) | |
17:03 | Upgrade to newest client ... (check-in: 9673c31db1 user: stern tags: trunk) | |
Changes to .fossil-settings/ignore-glob.
1 2 | bin/* releases/* | < | 1 2 | bin/* releases/* |
Changes to VERSION.
|
| | | 1 | 0.6.0 |
Changes to ast/ast.go.
︙ | ︙ | |||
80 81 82 83 84 85 86 | RefStateInvalid RefState = iota // Invalid Reference RefStateZettel // Reference to an internal zettel RefStateSelf // Reference to same zettel with a fragment RefStateFound // Reference to an existing internal zettel, URL is ajusted RefStateBroken // Reference to a non-existing internal zettel RefStateHosted // Reference to local hosted non-Zettel, without URL change RefStateBased // Reference to local non-Zettel, to be prefixed | | | 80 81 82 83 84 85 86 87 88 89 | RefStateInvalid RefState = iota // Invalid Reference RefStateZettel // Reference to an internal zettel RefStateSelf // Reference to same zettel with a fragment RefStateFound // Reference to an existing internal zettel, URL is ajusted RefStateBroken // Reference to a non-existing internal zettel RefStateHosted // Reference to local hosted non-Zettel, without URL change RefStateBased // Reference to local non-Zettel, to be prefixed RefStateSearch // Reference to a zettel search RefStateExternal // Reference to external material ) |
Changes to ast/block.go.
︙ | ︙ | |||
268 269 270 271 272 273 274 | } //-------------------------------------------------------------------------- // TranscludeNode specifies block content from other zettel to embedded in // current zettel type TranscludeNode struct { | < | | 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 | } //-------------------------------------------------------------------------- // TranscludeNode specifies block content from other zettel to embedded in // current zettel type TranscludeNode struct { Ref *Reference } func (*TranscludeNode) blockNode() { /* Just a marker */ } // WalkChildren does nothing. func (*TranscludeNode) WalkChildren(Visitor) { /* No children*/ } |
︙ | ︙ |
Changes to ast/ref.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 | import ( "net/url" "strings" "zettelstore.de/z/domain/id" ) | | | | | | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | import ( "net/url" "strings" "zettelstore.de/z/domain/id" ) // SearchPrefix is the prefix that denotes a search expression. const SearchPrefix = "search:" // ParseReference parses a string and returns a reference. func ParseReference(s string) *Reference { if s == "" || s == "00000000000000" { return &Reference{URL: nil, Value: s, State: RefStateInvalid} } if strings.HasPrefix(s, SearchPrefix) { return &Reference{URL: nil, Value: s[len(SearchPrefix):], State: RefStateSearch} } if state, ok := localState(s); ok { if state == RefStateBased { s = s[1:] } u, err := url.Parse(s) if err == nil { |
︙ | ︙ | |||
69 70 71 72 73 74 75 | } // String returns the string representation of a reference. func (r Reference) String() string { if r.URL != nil { return r.URL.String() } | | | | 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | } // String returns the string representation of a reference. func (r Reference) String() string { if r.URL != nil { return r.URL.String() } if r.State == RefStateSearch { return SearchPrefix + r.Value } return r.Value } // IsValid returns true if reference is valid func (r *Reference) IsValid() bool { return r.State != RefStateInvalid } |
︙ | ︙ |
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 28 | //----------------------------------------------------------------------------- // Copyright (c) 2021 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package 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" "zettelstore.de/z/web/server" ) // 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 } |
︙ | ︙ | |||
74 75 76 77 78 79 80 | } // Manager is the main interface for providing the service. type Manager interface { TokenManager AuthzManager | | | 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | } // Manager is the main interface for providing the service. type Manager interface { TokenManager AuthzManager BoxWithPolicy(auth server.Auth, unprotectedBox box.Box, rtConfig config.Config) (box.Box, Policy) } // Policy is an interface for checking access authorization. type Policy interface { // User is allowed to create a new zettel. CanCreate(user, newMeta *meta.Meta) bool |
︙ | ︙ |
Changes to auth/impl/impl.go.
︙ | ︙ | |||
23 24 25 26 27 28 29 30 31 32 33 34 35 36 | "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 } | > | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | "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" "zettelstore.de/z/web/server" ) type myAuth struct { readonly bool owner id.Zid secret []byte } |
︙ | ︙ | |||
169 170 171 172 173 174 175 | if ur := meta.GetUserRole(val); ur != meta.UserRoleUnknown { return ur } } return meta.UserRoleReader } | | | | 170 171 172 173 174 175 176 177 178 179 | if ur := meta.GetUserRole(val); ur != meta.UserRoleUnknown { return ur } } return meta.UserRoleReader } func (a *myAuth) BoxWithPolicy(auth server.Auth, unprotectedBox box.Box, rtConfig config.Config) (box.Box, auth.Policy) { return policy.BoxWithPolicy(auth, a, unprotectedBox, rtConfig) } |
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 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2021 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package 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/search" "zettelstore.de/z/web/server" ) // BoxWithPolicy wraps the given box inside a policy box. func BoxWithPolicy( auth server.Auth, manager auth.AuthzManager, box box.Box, authConfig config.AuthConfig, ) (box.Box, auth.Policy) { pol := newPolicy(manager, authConfig) return newBox(auth, box, pol), pol } // polBox implements a policy box. type polBox struct { auth server.Auth box box.Box policy auth.Policy } // newBox creates a new policy box. func newBox(auth server.Auth, box box.Box, policy auth.Policy) box.Box { return &polBox{ auth: auth, box: box, policy: policy, } } func (pp *polBox) Location() string { 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 := pp.auth.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 := pp.auth.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 := pp.auth.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", pp.auth.GetUser(ctx), id.Invalid) } func (pp *polBox) SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) { user := pp.auth.GetUser(ctx) canRead := pp.policy.CanRead s = s.AddPreMatch(func(m *meta.Meta) bool { return canRead(user, m) }) return pp.box.SelectMeta(ctx, s) } 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 := pp.auth.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 |
︙ | ︙ | |||
132 133 134 135 136 137 138 | } func (pp *polBox) RenameZettel(ctx context.Context, curZid, newZid id.Zid) error { meta, err := pp.box.GetMeta(ctx, curZid) if err != nil { return err } | | | | | 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 | } 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 := pp.auth.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 := pp.auth.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 := pp.auth.GetUser(ctx) if pp.policy.CanRefresh(user) { return pp.box.Refresh(ctx) } return box.NewErrNotAllowed("Refresh", user, id.Invalid) } |
Changes to box/box.go.
︙ | ︙ | |||
20 21 22 23 24 25 26 | "strconv" "time" "zettelstore.de/c/api" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" | | | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | "strconv" "time" "zettelstore.de/c/api" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/search" ) // 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 |
︙ | ︙ | |||
72 73 74 75 76 77 78 | 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. | | | | 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | 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, search.RetrievePredicate) error // Apply metadata of every zettel to the given function, if predicate returns true. ApplyMeta(context.Context, MetaFunc, search.RetrievePredicate) error // ReadStats populates st with box statistics ReadStats(st *ManagedBoxStats) } // ManagedBoxStats records statistics about the box. type ManagedBoxStats struct { |
︙ | ︙ | |||
114 115 116 117 118 119 120 | 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. | | | 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | type Box interface { BaseBox // FetchZids returns the set of all zettel identifer managed by the box. FetchZids(ctx context.Context) (id.Set, error) // SelectMeta returns a list of metadata that comply to the given selection criteria. SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) // GetAllZettel retrieves a specific zettel from all managed boxes. 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) |
︙ | ︙ | |||
180 181 182 183 184 185 186 | // UpdateReason gives an indication, why the ObserverFunc was called. type UpdateReason uint8 // Values for Reason const ( _ UpdateReason = iota OnReload // Box was reloaded | > | | 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | // UpdateReason gives an indication, why the ObserverFunc was called. type UpdateReason uint8 // Values for Reason const ( _ UpdateReason = iota OnReload // Box was reloaded OnUpdate // A zettel was created or changed OnDelete // A zettel was removed ) // UpdateInfo contains all the data about a changed zettel. type UpdateInfo struct { Box Box Reason UpdateReason Zid id.Zid |
︙ | ︙ |
Changes to box/compbox/compbox.go.
︙ | ︙ | |||
19 20 21 22 23 24 25 | "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" | | | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | "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/search" ) func init() { manager.Register( " comp", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { return getCompBox(cdata.Number, cdata.Enricher), nil |
︙ | ︙ | |||
106 107 108 109 110 111 112 | } } } cb.log.Trace().Err(box.ErrNotFound).Msg("GetMeta/Err") return nil, box.ErrNotFound } | | | | 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 | } } } cb.log.Trace().Err(box.ErrNotFound).Msg("GetMeta/Err") return nil, box.ErrNotFound } func (cb *compBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint search.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) } } } return nil } func (cb *compBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint search.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 m := genMeta(zid); m != nil { |
︙ | ︙ |
Changes to box/compbox/config.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 | import ( "bytes" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" | < < | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | import ( "bytes" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) 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.KeyVisibility, api.ValueVisibilityExpert) return m } func genConfigZettelC(*meta.Meta) []byte { var buf bytes.Buffer for i, p := range myConfig.Pairs() { |
︙ | ︙ |
Changes to box/compbox/keys.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 | import ( "bytes" "fmt" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" | < < | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import ( "bytes" "fmt" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) func genKeysM(zid id.Zid) *meta.Meta { m := meta.New(zid) m.Set(api.KeyTitle, "Zettelstore Supported Metadata Keys") m.Set(api.KeyVisibility, api.ValueVisibilityLogin) return m } func genKeysC(*meta.Meta) []byte { keys := meta.GetSortedKeyDescriptions() var buf bytes.Buffer |
︙ | ︙ |
Changes to box/compbox/log.go.
1 | //----------------------------------------------------------------------------- | | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // Copyright (c) 2021 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package compbox |
︙ | ︙ | |||
19 20 21 22 23 24 25 | "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, api.ValueSyntaxText) | < < | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | "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, api.ValueSyntaxText) return m } func genLogC(*meta.Meta) []byte { const tsFormat = "2006-01-02 15:04:05.999999" entries := kernel.Main.RetrieveLogEntries() var buf bytes.Buffer |
︙ | ︙ |
Changes to box/compbox/manager.go.
︙ | ︙ | |||
19 20 21 22 23 24 25 | "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") | < | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | "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") return m } func genManagerC(*meta.Meta) []byte { kvl := kernel.Main.GetServiceStatistics(kernel.BoxService) if len(kvl) == 0 { return nil |
︙ | ︙ |
Changes to box/compbox/parser.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 | "fmt" "sort" "strings" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" | < < | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | "fmt" "sort" "strings" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "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.KeyVisibility, api.ValueVisibilityLogin) return m } func genParserC(*meta.Meta) []byte { var buf bytes.Buffer buf.WriteString("|=Syntax<|=Alt. Value(s):|=Text Parser?:|=Image Format?:\n") |
︙ | ︙ |
Changes to box/compbox/version.go.
︙ | ︙ | |||
22 23 24 25 26 27 28 | 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") | < | < < | < < | 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 | 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.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 { return getVersionMeta(zid, "Zettelstore Host") } func genVersionHostC(*meta.Meta) []byte { return []byte(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreHostname).(string)) } func genVersionOSM(zid id.Zid) *meta.Meta { return getVersionMeta(zid, "Zettelstore Operating System") } 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, '/') |
︙ | ︙ |
Changes to box/constbox/base.css.
︙ | ︙ | |||
79 80 81 82 83 84 85 86 87 88 89 90 91 92 | 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; | > | 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | 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 } ol,ul { padding-left: 1.1rem } li,figure,figcaption,dl { margin: 0 } dt { margin: .5rem 0 0 0 } dt+dd { margin-top: 0 } dd { margin: .5rem 0 0 2rem } dd > p:first-child { margin: 0 0 0 0 } blockquote { border-left: 0.5rem solid lightgray; |
︙ | ︙ |
Changes to box/constbox/base.mustache.
︙ | ︙ | |||
50 51 52 53 54 55 56 | {{#NewZettelLinks}} <a href="{{{URL}}}">{{Text}}</a> {{/NewZettelLinks}} </nav> </div> {{/HasNewZettelLinks}} <form action="{{{SearchURL}}}"> | | | 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | {{#NewZettelLinks}} <a href="{{{URL}}}">{{Text}}</a> {{/NewZettelLinks}} </nav> </div> {{/HasNewZettelLinks}} <form action="{{{SearchURL}}}"> <input type="text" placeholder="Search.." name="{{QueryKeySearch}}"> </form> </nav> <main class="content"> {{{Content}}} </main> {{#FooterHTML}}<footer>{{{FooterHTML}}}</footer>{{/FooterHTML}} {{#DebugMode}}<div><b>WARNING: Debug mode is enabled. DO NOT USE IN PRODUCTION!</b></div>{{/DebugMode}} |
︙ | ︙ |
Changes to box/constbox/constbox.go.
︙ | ︙ | |||
20 21 22 23 24 25 26 | "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" | | | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | "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/search" ) func init() { manager.Register( " const", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { return &constBox{ |
︙ | ︙ | |||
78 79 80 81 82 83 84 | cb.log.Trace().Msg("GetMeta") return meta.NewWithData(zid, z.header), nil } cb.log.Trace().Err(box.ErrNotFound).Msg("GetMeta") return nil, box.ErrNotFound } | | | | 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 | 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 search.RetrievePredicate) error { cb.log.Trace().Int("entries", int64(len(cb.zettel))).Msg("ApplyZid") for zid := range cb.zettel { if constraint(zid) { handle(zid) } } return nil } func (cb *constBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint search.RetrievePredicate) error { cb.log.Trace().Int("entries", int64(len(cb.zettel))).Msg("ApplyMeta") for zid, zettel := range cb.zettel { if constraint(zid) { m := meta.NewWithData(zid, zettel.header) cb.enricher.Enrich(ctx, m, cb.number) handle(m) } |
︙ | ︙ | |||
146 147 148 149 150 151 152 | var constZettelMap = map[id.Zid]constZettel{ id.ConfigurationZid: { constHeader{ api.KeyTitle: "Zettelstore Runtime Configuration", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: api.ValueSyntaxNone, | < < < < < < < < < < < < < < < > > > > > > > > > > > > > > > > < < < < < < < < | | | | < | 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 | var constZettelMap = map[id.Zid]constZettel{ id.ConfigurationZid: { constHeader{ api.KeyTitle: "Zettelstore Runtime Configuration", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: api.ValueSyntaxNone, api.KeyVisibility: api.ValueVisibilityOwner, }, domain.NewContent(nil)}, id.MustParse(api.ZidLicense): { constHeader{ api.KeyTitle: "Zettelstore License", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: api.ValueSyntaxText, api.KeyLang: api.ValueLangEN, 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: api.ValueSyntaxZmk, 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: api.ValueSyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityLogin, }, domain.NewContent(contentDependencies)}, id.BaseTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Base HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentBaseMustache)}, id.LoginTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Login Form HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentLoginMustache)}, id.ZettelTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentZettelMustache)}, id.InfoTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Info HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentInfoMustache)}, id.ContextTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Context HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentContextMustache)}, id.FormTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Form HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentFormMustache)}, id.RenameTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Rename Form HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentRenameMustache)}, id.DeleteTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Delete HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentDeleteMustache)}, id.ListTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore List Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentListZettelMustache)}, id.RolesTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore List Roles HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentListRolesMustache)}, id.TagsTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore List Tags HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentListTagsMustache)}, id.ErrorTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Error HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentErrorMustache)}, id.MustParse(api.ZidBaseCSS): { constHeader{ api.KeyTitle: "Zettelstore Base CSS", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: "css", api.KeyVisibility: api.ValueVisibilityPublic, }, domain.NewContent(contentBaseCSS)}, id.MustParse(api.ZidUserCSS): { constHeader{ api.KeyTitle: "Zettelstore User CSS", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: "css", 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: api.ValueSyntaxNone, api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(nil)}, id.EmojiZid: { constHeader{ api.KeyTitle: "Zettelstore Generic Emoji", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: api.ValueSyntaxGif, api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityPublic, }, domain.NewContent(contentEmoji)}, id.TOCNewTemplateZid: { constHeader{ api.KeyTitle: "New Menu", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: api.ValueSyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyVisibility: api.ValueVisibilityCreator, }, domain.NewContent(contentNewTOCZettel)}, id.MustParse(api.ZidTemplateNewZettel): { constHeader{ api.KeyTitle: "New Zettel", api.KeyRole: api.ValueRoleZettel, api.KeySyntax: api.ValueSyntaxZmk, api.KeyVisibility: api.ValueVisibilityCreator, }, domain.NewContent(nil)}, id.MustParse(api.ZidTemplateNewUser): { constHeader{ api.KeyTitle: "New User", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: api.ValueSyntaxNone, 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: api.ValueSyntaxZmk, api.KeyLang: api.ValueLangEN, }, domain.NewContent(contentHomeZettel)}, } //go:embed license.txt var contentLicense []byte |
︙ | ︙ | |||
390 391 392 393 394 395 396 397 398 399 400 401 402 403 | //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 | > > > > > > | 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 | //go:embed delete.mustache var contentDeleteMustache []byte //go:embed listzettel.mustache var contentListZettelMustache []byte //go:embed listroles.mustache var contentListRolesMustache []byte //go:embed listtags.mustache var contentListTagsMustache []byte //go:embed error.mustache var contentErrorMustache []byte //go:embed base.css var contentBaseCSS []byte //go:embed emoji_spin.gif |
︙ | ︙ |
Changes to box/constbox/context.mustache.
1 2 3 4 5 6 7 8 9 10 | <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> · Depth:{{#Depths}} <a href="{{{URL}}}">{{{Text}}}</a>{{/Depths}} </div> </header> | > > > > | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <nav> <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> · Depth:{{#Depths}} <a href="{{{URL}}}">{{{Text}}}</a>{{/Depths}} </div> </header> <p><a href="{{{Start.URL}}}">{{{Start.Text}}}</a></p> <ul> {{#Metas}}<li><a href="{{{URL}}}">{{{Text}}}</a></li> {{/Metas}}</ul> </nav> |
Changes to box/constbox/dependencies.zettel.
︙ | ︙ | |||
100 101 102 103 104 105 106 | 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. ``` | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | 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]). |
︙ | ︙ |
Changes to box/constbox/info.mustache.
︙ | ︙ | |||
17 18 19 20 21 22 23 | <ul> {{#LocLinks}} {{#Valid}}<li><a href="{{{Zid}}}">{{Zid}}</a></li>{{/Valid}} {{^Valid}}<li>{{Zid}}</li>{{/Valid}} {{/LocLinks}} </ul> {{/HasLocLinks}} | | | | | | > | > > > | 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 | <ul> {{#LocLinks}} {{#Valid}}<li><a href="{{{Zid}}}">{{Zid}}</a></li>{{/Valid}} {{^Valid}}<li>{{Zid}}</li>{{/Valid}} {{/LocLinks}} </ul> {{/HasLocLinks}} {{#HasSearchLinks}} <h3>Searches</h3> <ul> {{#SearchLinks}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/SearchLinks}} </ul> {{/HasSearchLinks}} {{#HasExtLinks}} <h3>External</h3> <ul> {{#ExtLinks}} <li><a href="{{{.}}}"{{{ExtNewWindow}}}>{{.}}</a></li> {{/ExtLinks}} </ul> {{/HasExtLinks}} <h3>Unlinked</h3> <ul> {{#UnLinks}} <li><a href="{{{URL}}}">{{{Text}}}</a></li> {{/UnLinks}} </ul> <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}} |
︙ | ︙ |
Added box/constbox/listroles.mustache.
> > > > > > > > | 1 2 3 4 5 6 7 8 | <nav> <header> <h1>Currently used roles</h1> </header> <ul> {{#Roles}}<li><a href="{{{URL}}}">{{Text}}</a></li> {{/Roles}}</ul> </nav> |
Added box/constbox/listtags.mustache.
> > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 | <nav> <header> <h1>Currently used tags</h1> <div class="zs-meta"> <a href="{{{ListTagsURL}}}">All</a>{{#MinCounts}}, <a href="{{{URL}}}">{{Count}}</a>{{/MinCounts}} </div> </header> {{#Tags}} <a href="{{{URL}}}" class="zs-font-size-{{Size}}">{{Name}}</a><sup>{{Count}}</sup> {{/Tags}} </nav> |
Changes to box/constbox/listzettel.mustache.
1 2 3 4 | <header> <h1>{{Title}}</h1> </header> <form action="{{{SearchURL}}}"> | | > > | | 1 2 3 4 5 6 7 8 9 | <header> <h1>{{Title}}</h1> </header> <form action="{{{SearchURL}}}"> <input class="zs-input" type="text" placeholder="Search.." name="{{QueryKeySearch}}" value="{{SearchValue}}"> </form> <ul> {{#Metas}}<li><a href="{{{URL}}}">{{{Text}}}</a></li> {{/Metas}}</ul> |
Changes to box/constbox/zettel.mustache.
1 2 3 4 5 6 7 8 9 10 11 12 13 | <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>) {{#HasTags}}· {{#Tags}} <a href="{{{URL}}}">{{Text}}</a>{{/Tags}}{{/HasTags}} {{#CanCopy}}· <a href="{{{CopyURL}}}">Copy</a>{{/CanCopy}} {{#CanFolge}}· <a href="{{{FolgeURL}}}">Folge</a>{{/CanFolge}} {{#PrecursorRefs}}<br>Precursor: {{{PrecursorRefs}}}{{/PrecursorRefs}} {{#HasExtURL}}<br>URL: <a href="{{{ExtURL}}}"{{{ExtNewWindow}}}>{{ExtURL}}</a>{{/HasExtURL}} | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <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>) {{#HasTags}}· {{#Tags}} <a href="{{{URL}}}">{{Text}}</a>{{/Tags}}{{/HasTags}} {{#CanCopy}}· <a href="{{{CopyURL}}}">Copy</a>{{/CanCopy}} {{#CanFolge}}· <a href="{{{FolgeURL}}}">Folge</a>{{/CanFolge}} {{#PrecursorRefs}}<br>Precursor: {{{PrecursorRefs}}}{{/PrecursorRefs}} {{#HasExtURL}}<br>URL: <a href="{{{ExtURL}}}"{{{ExtNewWindow}}}>{{ExtURL}}</a>{{/HasExtURL}} </div> </header> {{{Content}}} </article> {{#HasFolgeLinks}} <nav> <details open> |
︙ | ︙ |
Changes to box/dirbox/dirbox.go.
︙ | ︙ | |||
23 24 25 26 27 28 29 | "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" | | | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | "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/search" ) 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() |
︙ | ︙ | |||
218 219 220 221 222 223 224 | 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) } | | | 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | 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(box.OnUpdate, 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() { |
︙ | ︙ | |||
254 255 256 257 258 259 260 | m, err := dp.srvGetMeta(ctx, entry, zid) if err != nil { return nil, err } return m, nil } | | | | 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 | 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 search.RetrievePredicate) error { entries := dp.dirSrv.GetDirEntries(constraint) dp.log.Trace().Int("entries", int64(len(entries))).Msg("ApplyZid") for _, entry := range entries { handle(entry.Zid) } return nil } func (dp *dirBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint search.RetrievePredicate) error { entries := dp.dirSrv.GetDirEntries(constraint) dp.log.Trace().Int("entries", int64(len(entries))).Msg("ApplyMeta") // The following loop could be parallelized if needed for performance. for _, entry := range entries { m, err := dp.srvGetMeta(ctx, entry, entry.Zid) if err != nil { |
︙ | ︙ | |||
303 304 305 306 307 308 309 | // 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 { | | | 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | // 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(box.OnUpdate, 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) |
︙ | ︙ | |||
352 353 354 355 356 357 358 | 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 { | | | | 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 | 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(box.OnDelete, curZid) dp.notifyChanged(box.OnUpdate, 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 { |
︙ | ︙ | |||
382 383 384 385 386 387 388 | } err := dp.dirSrv.DeleteDirEntry(zid) if err != nil { return nil } err = dp.srvDeleteZettel(ctx, entry, zid) if err == nil { | | | 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 | } err := dp.dirSrv.DeleteDirEntry(zid) if err != nil { return nil } err = dp.srvDeleteZettel(ctx, entry, zid) if err == nil { dp.notifyChanged(box.OnDelete, 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/filebox/zipbox.go.
︙ | ︙ | |||
19 20 21 22 23 24 25 | "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" | | | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | "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/search" ) type zipBox struct { log *logger.Logger number int name string enricher box.Enricher |
︙ | ︙ | |||
135 136 137 138 139 140 141 | } defer reader.Close() m, err := zb.readZipMeta(reader, zid, entry) zb.log.Trace().Err(err).Zid(zid).Msg("GetMeta") return m, err } | | | | 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | } 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 search.RetrievePredicate) error { entries := zb.dirSrv.GetDirEntries(constraint) zb.log.Trace().Int("entries", int64(len(entries))).Msg("ApplyZid") for _, entry := range entries { handle(entry.Zid) } return nil } func (zb *zipBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint search.RetrievePredicate) error { reader, err := zip.OpenReader(zb.name) if err != nil { return err } defer reader.Close() entries := zb.dirSrv.GetDirEntries(constraint) zb.log.Trace().Int("entries", int64(len(entries))).Msg("ApplyMeta") |
︙ | ︙ |
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 | //----------------------------------------------------------------------------- // Copyright (c) 2021 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package manager import ( "sync" "zettelstore.de/z/domain/id" ) type arAction int const ( arNothing arAction = iota arReload arUpdate arDelete ) type anteroom struct { num uint64 next *anteroom waiting map[id.Zid]arAction curLoad int |
︙ | ︙ | |||
40 41 42 43 44 45 46 | maxLoad int } func newAnterooms(maxLoad int) *anterooms { return &anterooms{maxLoad: maxLoad} } | | | | | > | > > > > > > > > | | | 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 | maxLoad int } func newAnterooms(maxLoad int) *anterooms { return &anterooms{maxLoad: maxLoad} } func (ar *anterooms) Enqueue(zid id.Zid, action arAction) { if !zid.IsValid() || action == arNothing || action == arReload { return } ar.mx.Lock() defer ar.mx.Unlock() if ar.first == nil { ar.first = ar.makeAnteroom(zid, action) 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 } a, ok := room.waiting[zid] if !ok { continue } switch action { case a: return case arUpdate: room.waiting[zid] = action case arDelete: room.waiting[zid] = action } return } if room := ar.last; !room.reload && (ar.maxLoad == 0 || room.curLoad < ar.maxLoad) { room.waiting[zid] = action room.curLoad++ return } room := ar.makeAnteroom(zid, action) ar.last.next = room ar.last = room } func (ar *anterooms) makeAnteroom(zid id.Zid, action arAction) *anteroom { c := ar.maxLoad if c == 0 { |
︙ | ︙ | |||
91 92 93 94 95 96 97 | ar.first = ar.makeAnteroom(id.Invalid, arReload) ar.last = ar.first } func (ar *anterooms) Reload(newZids id.Set) uint64 { ar.mx.Lock() defer ar.mx.Unlock() | | | | | 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 | ar.first = ar.makeAnteroom(id.Invalid, arReload) ar.last = ar.first } func (ar *anterooms) Reload(newZids id.Set) uint64 { ar.mx.Lock() defer ar.mx.Unlock() newWaiting := createWaitingSet(newZids, arUpdate) ar.deleteReloadedRooms() if ns := len(newWaiting); ns > 0 { ar.nextNum++ ar.first = &anteroom{num: ar.nextNum, next: ar.first, waiting: newWaiting, curLoad: ns} if ar.first.next == nil { ar.last = ar.first } return ar.nextNum } ar.first = nil ar.last = nil return 0 } func createWaitingSet(zids id.Set, action arAction) map[id.Zid]arAction { waitingSet := make(map[id.Zid]arAction, len(zids)) for zid := range zids { if zid.IsValid() { waitingSet[zid] = action } } return waitingSet } func (ar *anterooms) deleteReloadedRooms() { room := ar.first |
︙ | ︙ |
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 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 | //----------------------------------------------------------------------------- // Copyright (c) 2021 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package manager import ( "testing" "zettelstore.de/z/domain/id" ) func TestSimple(t *testing.T) { t.Parallel() ar := newAnterooms(2) ar.Enqueue(id.Zid(1), arUpdate) action, zid, rno := ar.Dequeue() if zid != id.Zid(1) || action != arUpdate || rno != 1 { t.Errorf("Expected arUpdate/1/1, but got %v/%v/%v", action, zid, rno) } action, zid, _ = ar.Dequeue() if zid != id.Invalid && action != arDelete { t.Errorf("Expected invalid Zid, but got %v", zid) } ar.Enqueue(id.Zid(1), arUpdate) ar.Enqueue(id.Zid(2), arUpdate) if ar.first != ar.last { t.Errorf("Expected one room, but got more") } ar.Enqueue(id.Zid(3), arUpdate) if ar.first == ar.last { t.Errorf("Expected more than one room, but got only one") } count := 0 for ; count < 1000; count++ { action, _, _ = ar.Dequeue() if action == arNothing { break } } if count != 3 { t.Errorf("Expected 3 dequeues, but got %v", count) } } func TestReset(t *testing.T) { t.Parallel() ar := newAnterooms(1) ar.Enqueue(id.Zid(1), arUpdate) 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)) ar.Enqueue(id.Zid(5), arUpdate) ar.Enqueue(id.Zid(5), arDelete) ar.Enqueue(id.Zid(5), arDelete) ar.Enqueue(id.Zid(5), arUpdate) if ar.first == ar.last || ar.first.next != ar.last /*|| ar.first.next.next != ar.last*/ { t.Errorf("Expected 2 rooms") } action, zid1, _ := ar.Dequeue() if action != arUpdate { t.Errorf("Expected arUpdate, but got %v", action) } action, zid2, _ := ar.Dequeue() if action != arUpdate { t.Errorf("Expected arUpdate, but got %v", action) } if !(zid1 == id.Zid(3) && zid2 == id.Zid(4) || zid1 == id.Zid(4) && zid2 == id.Zid(3)) { t.Errorf("Zids must be 3 or 4, but got %v/%v", zid1, zid2) } action, zid, _ = ar.Dequeue() if zid != id.Zid(5) || action != arUpdate { t.Errorf("Expected 5/arUpdate, 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 != arUpdate { t.Errorf("Expected 6/arUpdate, 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.Enqueue(id.Zid(8), arUpdate) 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.
︙ | ︙ | |||
15 16 17 18 19 20 21 | "context" "errors" "zettelstore.de/z/box" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" | | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | "context" "errors" "zettelstore.de/z/box" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/search" ) // 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 { |
︙ | ︙ | |||
153 154 155 156 157 158 159 | 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. | | | | < < | | | | | | | | | | | | | | | | | | | | | | | | < | | 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 | 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, s *search.Search) ([]*meta.Meta, error) { if msg := mgr.mgrLog.Debug(); msg.Enabled() { msg.Str("query", s.String()).Msg("SelectMeta") } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if !mgr.started { return nil, box.ErrStopped } searchPred, match := s.RetrieveAndCompileMatch(mgr) selected, rejected := metaMap{}, 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 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, searchPred); err != nil { return nil, err } } result := make([]*meta.Meta, 0, len(selected)) for _, m := range selected { result = append(result, m) } return s.Sort(result), nil } // CanUpdateZettel returns true, if box could possibly update the given zettel. 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) |
︙ | ︙ |
Changes to box/manager/collect.go.
︙ | ︙ | |||
52 53 54 55 56 57 58 | case *ast.TagNode: data.addText(n.Tag) data.itags.Add("#" + strings.ToLower(n.Tag)) case *ast.LinkNode: data.addRef(n.Ref) case *ast.EmbedRefNode: data.addRef(n.Ref) | < < | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | case *ast.TagNode: data.addText(n.Tag) data.itags.Add("#" + strings.ToLower(n.Tag)) case *ast.LinkNode: data.addRef(n.Ref) case *ast.EmbedRefNode: data.addRef(n.Ref) case *ast.LiteralNode: data.addText(string(n.Content)) } return data } func (data *collectData) addText(s string) { |
︙ | ︙ |
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 51 52 | //----------------------------------------------------------------------------- // Copyright (c) 2021 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package manager import ( "context" "strconv" "zettelstore.de/c/api" "zettelstore.de/z/box" "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) { 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 meta data return } m.Set(api.KeyBoxNumber, strconv.Itoa(boxNumber)) computePublished(m) mgr.idxStore.Enrich(ctx, m) } func computePublished(m *meta.Meta) { if _, ok := m.Get(api.KeyPublished); ok { return } if modified, ok := m.Get(api.KeyModified); ok { if _, ok = meta.TimeValue(modified); ok { m.Set(api.KeyPublished, modified) return } } zid := m.Zid.String() if _, ok := meta.TimeValue(zid); ok { m.Set(api.KeyPublished, zid) return } // Neither the zettel was modified nor the zettel identifer contains a valid // timestamp. In this case do not set the "published" property. } |
Changes to box/manager/indexer.go.
︙ | ︙ | |||
105 106 107 108 109 110 111 | zids, err := mgr.FetchZids(ctx) if err == nil { start = time.Now() if rno := mgr.idxAr.Reload(zids); rno > 0 { roomNum = rno } mgr.idxMx.Lock() | | | | | < < < < < < > > > > > > > > > > > > > > | 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 | 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() mgr.idxSinceReload = 0 mgr.idxMx.Unlock() } case arUpdate: mgr.idxLog.Debug().Zid(zid).Msg("update") zettel, err := mgr.GetZettel(ctx, zid) if err != nil { // TODO: on some errors put the zid into a "try later" set continue } mgr.idxMx.Lock() if arRoomNum == roomNum { mgr.idxDurReload = time.Since(start) } mgr.idxSinceReload++ mgr.idxMx.Unlock() mgr.idxUpdateZettel(ctx, zettel) case arDelete: mgr.idxLog.Debug().Zid(zid).Msg("delete") if _, err := mgr.GetMeta(ctx, zid); err == nil { // Zettel was not deleted. This might occur, if zettel was // deleted in secondary dirbox, but is still present in // first dirbox (or vice versa). Re-index zettel in case // a hidden zettel was recovered mgr.idxLog.Debug().Zid(zid).Msg("not deleted") mgr.idxAr.Enqueue(zid, arUpdate) } mgr.idxMx.Lock() mgr.idxSinceReload++ mgr.idxMx.Unlock() mgr.idxDeleteZettel(zid) } } } func (mgr *Manager) idxSleepService(timer *time.Timer, timerDuration time.Duration) bool { select { case _, ok := <-mgr.idxReady: |
︙ | ︙ | |||
156 157 158 159 160 161 162 | } return true } func (mgr *Manager) idxUpdateZettel(ctx context.Context, zettel domain.Zettel) { var cData collectData cData.initialize() | | | | | 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 | } return true } func (mgr *Manager) idxUpdateZettel(ctx context.Context, zettel domain.Zettel) { var cData collectData cData.initialize() collectZettelIndexData(parser.ParseZettel(zettel, "", mgr.rtConfig), &cData) m := zettel.Meta zi := store.NewZettelIndex(m.Zid) mgr.idxCollectFromMeta(ctx, m, zi, &cData) mgr.idxProcessData(ctx, zi, &cData) toCheck := mgr.idxStore.UpdateReferences(ctx, zi) mgr.idxCheckZettel(toCheck) } func (mgr *Manager) idxCollectFromMeta(ctx context.Context, m *meta.Meta, zi *store.ZettelIndex, cData *collectData) { for _, pair := range m.Pairs() { descr := meta.GetDescription(pair.Key) if descr.IsComputed() { continue } switch descr.Type { case meta.TypeID: mgr.idxUpdateValue(ctx, descr.Inverse, pair.Value, zi) case meta.TypeIDSet: for _, val := range meta.ListFromValue(pair.Value) { |
︙ | ︙ | |||
230 231 232 233 234 235 236 | 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 { | | | 238 239 240 241 242 243 244 245 246 247 | 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.Enqueue(zid, arUpdate) } } |
Changes to box/manager/manager.go.
︙ | ︙ | |||
226 227 228 229 230 231 232 | return false } func (mgr *Manager) idxEnqueue(reason box.UpdateReason, zid id.Zid) { switch reason { case box.OnReload: mgr.idxAr.Reset() | | | > > | 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 | return false } func (mgr *Manager) idxEnqueue(reason box.UpdateReason, zid id.Zid) { switch reason { case box.OnReload: mgr.idxAr.Reset() case box.OnUpdate: mgr.idxAr.Enqueue(zid, arUpdate) case box.OnDelete: mgr.idxAr.Enqueue(zid, arDelete) default: return } select { case mgr.idxReady <- struct{}{}: default: } |
︙ | ︙ |
Changes to box/manager/memstore/memstore.go.
︙ | ︙ | |||
157 158 159 160 161 162 163 164 165 166 167 168 | 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 } | > > > > < < < < < < < < < | 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | 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 } minZid, err := id.Parse(prefix + "00000000000000"[:14-l]) if err != nil { return result } maxZid, err := id.Parse(prefix + "99999999999999"[:14-l]) if err != nil { return result } for zid, zi := range ms.idx { if minZid <= zid && zid <= maxZid { addBackwardZids(result, zid, zi) } } return result } |
︙ | ︙ |
Changes to box/manager/store/store.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 | import ( "context" "io" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" | | | | 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 | import ( "context" "io" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/search" ) // Stats records statistics about the store. type Stats struct { // Zettel is the number of zettel managed by the indexer. Zettel int // Updates count the number of metadata updates. Updates uint64 // Words count the different words stored in the store. Words uint64 // Urls count the different URLs stored in the store. Urls uint64 } // Store all relevant zettel data. There may be multiple implementations, i.e. // memory-based, file-based, based on SQLite, ... type Store interface { search.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 |
︙ | ︙ |
Changes to box/membox/membox.go.
︙ | ︙ | |||
19 20 21 22 23 24 25 | "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" | | | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | "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/search" ) func init() { manager.Register( "mem", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { return &memBox{ |
︙ | ︙ | |||
100 101 102 103 104 105 106 | } meta := zettel.Meta.Clone() meta.Zid = zid zettel.Meta = meta mb.zettel[zid] = zettel mb.curBytes = newBytes mb.mx.Unlock() | | | 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | } meta := zettel.Meta.Clone() meta.Zid = zid zettel.Meta = meta mb.zettel[zid] = zettel mb.curBytes = newBytes mb.mx.Unlock() mb.notifyChanged(box.OnUpdate, zid) mb.log.Trace().Zid(zid).Msg("CreateZettel") return zid, nil } func (mb *memBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) { mb.mx.RLock() zettel, ok := mb.zettel[zid] |
︙ | ︙ | |||
128 129 130 131 132 133 134 | if !ok { return nil, box.ErrNotFound } mb.log.Trace().Msg("GetMeta") return zettel.Meta.Clone(), nil } | | | | 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 | 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 search.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 { if constraint(zid) { handle(zid) } } return nil } func (mb *memBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint search.RetrievePredicate) error { mb.mx.RLock() defer mb.mx.RUnlock() mb.log.Trace().Int("entries", int64(len(mb.zettel))).Msg("ApplyMeta") for zid, zettel := range mb.zettel { if constraint(zid) { m := zettel.Meta.Clone() mb.cdata.Enricher.Enrich(ctx, m, mb.cdata.Number) |
︙ | ︙ | |||
189 190 191 192 193 194 195 | return box.ErrCapacity } zettel.Meta = m mb.zettel[m.Zid] = zettel mb.curBytes = newBytes mb.mx.Unlock() | | | 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | return box.ErrCapacity } zettel.Meta = m mb.zettel[m.Zid] = zettel mb.curBytes = newBytes mb.mx.Unlock() mb.notifyChanged(box.OnUpdate, m.Zid) mb.log.Trace().Msg("UpdateZettel") return nil } func (*memBox) AllowRenameZettel(context.Context, id.Zid) bool { return true } func (mb *memBox) RenameZettel(_ context.Context, curZid, newZid id.Zid) error { |
︙ | ︙ | |||
216 217 218 219 220 221 222 | meta := zettel.Meta.Clone() meta.Zid = newZid zettel.Meta = meta mb.zettel[newZid] = zettel delete(mb.zettel, curZid) mb.mx.Unlock() | | | | 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | meta := zettel.Meta.Clone() meta.Zid = newZid zettel.Meta = meta mb.zettel[newZid] = zettel delete(mb.zettel, curZid) mb.mx.Unlock() mb.notifyChanged(box.OnDelete, curZid) mb.notifyChanged(box.OnUpdate, 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] |
︙ | ︙ | |||
239 240 241 242 243 244 245 | if !found { mb.mx.Unlock() return box.ErrNotFound } delete(mb.zettel, zid) mb.curBytes -= oldZettel.Length() mb.mx.Unlock() | | | 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 | if !found { mb.mx.Unlock() return box.ErrNotFound } delete(mb.zettel, zid) mb.curBytes -= oldZettel.Length() mb.mx.Unlock() mb.notifyChanged(box.OnDelete, zid) mb.log.Trace().Msg("DeleteZettel") return nil } 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.
︙ | ︙ | |||
18 19 20 21 22 23 24 | "strings" "sync" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/logger" "zettelstore.de/z/parser" | | | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | "strings" "sync" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/logger" "zettelstore.de/z/parser" "zettelstore.de/z/search" "zettelstore.de/z/strfun" ) type entrySet map[id.Zid]*DirEntry // directoryState signal the internal state of the service. // |
︙ | ︙ | |||
105 106 107 108 109 110 111 | if ds.entries == nil { return 0 } return len(ds.entries) } // GetDirEntries returns a list of directory entries, which satisfy the given constraint. | | | 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | if ds.entries == nil { return 0 } return len(ds.entries) } // GetDirEntries returns a list of directory entries, which satisfy the given constraint. func (ds *DirService) GetDirEntries(constraint search.RetrievePredicate) []*DirEntry { ds.mx.RLock() defer ds.mx.RUnlock() if ds.entries == nil { return nil } result := make([]*DirEntry, 0, len(ds.entries)) for zid, entry := range ds.entries { |
︙ | ︙ | |||
256 257 258 259 260 261 262 | ds.onDestroyDirectory() ds.log.Error().Str("path", ds.dirPath).Msg("Zettel directory missing") case Update: ds.mx.Lock() zid := ds.onUpdateFileEvent(ds.entries, ev.Name) ds.mx.Unlock() if zid != id.Invalid { | | | < < < | | | | 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 | ds.onDestroyDirectory() ds.log.Error().Str("path", ds.dirPath).Msg("Zettel directory missing") case Update: ds.mx.Lock() zid := ds.onUpdateFileEvent(ds.entries, ev.Name) ds.mx.Unlock() if zid != id.Invalid { ds.notifyChange(box.OnUpdate, zid) } case Delete: ds.mx.Lock() ds.onDeleteFileEvent(ds.entries, ev.Name) ds.mx.Unlock() default: ds.log.Warn().Str("event", fmt.Sprintf("%v", ev)).Msg("Unknown zettel notification event") } } } 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(box.OnUpdate, 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(box.OnDelete, 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(box.OnDelete, zid) } } var validFileName = regexp.MustCompile(`^(\d{14})`) func matchValidFileName(name string) []string { return validFileName.FindStringSubmatch(name) |
︙ | ︙ | |||
345 346 347 348 349 350 351 | 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)") } | < | | | | | > < | 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 | 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 zid } func (ds *DirService) onDeleteFileEvent(entries entrySet, name string) { if entries == nil { return } zid := seekZid(name) if zid == id.Invalid { return } entry, found := entries[zid] if !found { return } for i, dupName := range entry.UselessFiles { if dupName == name { removeDuplicate(entry, i) return } } if name == entry.ContentName { entry.ContentName = "" entry.ContentExt = "" ds.replayUpdateUselessFiles(entry) } else if name == entry.MetaName { entry.MetaName = "" ds.replayUpdateUselessFiles(entry) } if entry.ContentName == "" && entry.MetaName == "" { delete(entries, zid) ds.notifyChange(box.OnDelete, zid) } } func removeDuplicate(entry *DirEntry, i int) { if len(entry.UselessFiles) == 1 { entry.UselessFiles = nil return } |
︙ | ︙ |
Changes to box/notify/directory_test.go.
︙ | ︙ | |||
14 15 16 17 18 19 20 | "testing" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. _ "zettelstore.de/z/parser/markdown" // Allow to use markdown parser. _ "zettelstore.de/z/parser/none" // Allow to use none parser. | < | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | "testing" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. _ "zettelstore.de/z/parser/markdown" // Allow to use markdown parser. _ "zettelstore.de/z/parser/none" // Allow to use none parser. _ "zettelstore.de/z/parser/plain" // Allow to use plain parser. _ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser. ) func TestSeekZid(t *testing.T) { testcases := []struct { name string |
︙ | ︙ | |||
45 46 47 48 49 50 51 | } } } func TestNewExtIsBetter(t *testing.T) { extVals := []string{ // Main Formats | | | 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | } } } func TestNewExtIsBetter(t *testing.T) { extVals := []string{ // Main Formats api.ValueSyntaxZmk, "markdown", "md", // Other supported text formats "css", "txt", api.ValueSyntaxHTML, api.ValueSyntaxNone, "mustache", api.ValueSyntaxText, "plain", // Supported graphics formats api.ValueSyntaxGif, "png", api.ValueSyntaxSVG, "jpeg", "jpg", // Unsupported syntax values "gz", "cpp", "tar", "cppc", } |
︙ | ︙ |
Changes to box/notify/fsdir.go.
1 | //----------------------------------------------------------------------------- | | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // Copyright (c) 2021 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package notify |
︙ | ︙ | |||
135 136 137 138 139 140 141 | return fsdn.processDirEvent(ev) } return fsdn.processFileEvent(ev) } return true } | < | > > | > > | < < | | > > > > | > > | | < < < < < < < < < | 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 | return fsdn.processDirEvent(ev) } return fsdn.processFileEvent(ev) } return true } const deleteFsOps = fsnotify.Remove | fsnotify.Rename const updateFsOps = fsnotify.Create | fsnotify.Write func (fsdn *fsdirNotifier) processDirEvent(ev *fsnotify.Event) bool { if ev.Op&deleteFsOps != 0 { fsdn.log.Debug().Str("name", fsdn.path).Msg("Directory removed") fsdn.base.Remove(fsdn.path) select { case fsdn.events <- Event{Op: Destroy}: case <-fsdn.done: return false } return true } if ev.Op&fsnotify.Create != 0 { 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: return false } } fsdn.log.Debug().Str("name", fsdn.path).Msg("Directory added") return listDirElements(fsdn.log, fsdn.fetcher, fsdn.events, fsdn.done) } return true } func (fsdn *fsdirNotifier) processFileEvent(ev *fsnotify.Event) bool { if ev.Op&deleteFsOps != 0 { fsdn.log.Trace().Str("name", ev.Name).Uint("op", uint64(ev.Op)).Msg("File deleted") select { case fsdn.events <- Event{Op: Delete, Name: filepath.Base(ev.Name)}: case <-fsdn.done: return false } return true } if ev.Op&updateFsOps != 0 { if fi, err := os.Lstat(ev.Name); err != nil || !fi.Mode().IsRegular() { return true } fsdn.log.Trace().Str("name", ev.Name).Uint("op", uint64(ev.Op)).Msg("File updated") select { case fsdn.events <- Event{Op: Update, Name: filepath.Base(ev.Name)}: case <-fsdn.done: return false } } return true } func (fsdn *fsdirNotifier) Close() { close(fsdn.done) } |
Changes to cmd/cmd_file.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 | //----------------------------------------------------------------------------- // 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 ( | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //----------------------------------------------------------------------------- // 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" "io" "os" "zettelstore.de/c/api" "zettelstore.de/z/domain" |
︙ | ︙ | |||
31 32 33 34 35 36 37 | 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( | < | 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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( domain.Zettel{ Meta: m, Content: domain.NewContent(inp.Src[inp.Pos:]), }, m.GetDefault(api.KeySyntax, api.ValueSyntaxZmk), nil, ) |
︙ | ︙ |
Changes to cmd/cmd_run.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 | //----------------------------------------------------------------------------- // 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 ( | < < < | 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 cmd import ( "flag" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/config" "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" ) |
︙ | ︙ | |||
45 46 47 48 49 50 51 | exitCode = 1 } kernel.Main.WaitForShutdown() return exitCode, err } func setupRouting(webSrv server.Server, boxManager box.Manager, authManager auth.Manager, rtConfig config.Config) { | | | < | | | | | | | | | < < < < | > < | 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 | exitCode = 1 } kernel.Main.WaitForShutdown() return exitCode, err } func setupRouting(webSrv server.Server, boxManager box.Manager, authManager auth.Manager, rtConfig config.Config) { protectedBoxManager, authPolicy := authManager.BoxWithPolicy(webSrv, boxManager, rtConfig) kern := kernel.Main webLog := kern.GetLogger(kernel.WebService) a := api.New( webLog.Clone().Str("adapter", "api").Child(), webSrv, authManager, authManager, webSrv, rtConfig, authPolicy) wui := webui.New( webLog.Clone().Str("adapter", "wui").Child(), webSrv, authManager, rtConfig, authManager, boxManager, authPolicy) authLog := kern.GetLogger(kernel.AuthService) ucLog := kern.GetLogger(kernel.CoreService).WithUser(webSrv) ucAuthenticate := usecase.NewAuthenticate(authLog, authManager, authManager, boxManager) ucIsAuth := usecase.NewIsAuthenticated(ucLog, webSrv, authManager) ucCreateZettel := usecase.NewCreateZettel(ucLog, 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) ucListTags := usecase.NewListTags(protectedBoxManager) ucZettelContext := usecase.NewZettelContext(protectedBoxManager, rtConfig) ucDelete := usecase.NewDeleteZettel(ucLog, protectedBoxManager) ucUpdate := usecase.NewUpdateZettel(ucLog, protectedBoxManager) ucRename := usecase.NewRenameZettel(ucLog, protectedBoxManager) ucUnlinkedRefs := usecase.NewUnlinkedReferences(protectedBoxManager, rtConfig) ucRefresh := usecase.NewRefresh(ucLog, protectedBoxManager) ucVersion := usecase.NewVersion(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) webSrv.Handle("/", wui.MakeGetRootHandler(protectedBoxManager)) // Web user interface if !authManager.IsReadonly() { webSrv.AddZettelRoute('b', server.MethodGet, wui.MakeGetRenameZettelHandler( ucGetMeta, &ucEvaluate)) webSrv.AddZettelRoute('b', server.MethodPost, wui.MakePostRenameZettelHandler(&ucRename)) webSrv.AddZettelRoute('c', server.MethodGet, wui.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, ucListRoles, ucListTags, &ucEvaluate)) webSrv.AddZettelRoute('h', server.MethodGet, wui.MakeGetHTMLZettelHandler( &ucEvaluate, ucGetMeta)) webSrv.AddListRoute('i', server.MethodGet, wui.MakeGetLoginOutHandler()) webSrv.AddListRoute('i', server.MethodPost, wui.MakePostLoginHandler(&ucAuthenticate)) webSrv.AddZettelRoute('i', server.MethodGet, wui.MakeGetInfoHandler( ucParseZettel, &ucEvaluate, ucGetMeta, ucGetAllMeta, 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.AddListRoute('j', server.MethodGet, a.MakeListMetaHandler(ucListMeta)) webSrv.AddZettelRoute('j', server.MethodGet, a.MakeGetZettelHandler(ucGetZettel)) webSrv.AddListRoute('m', server.MethodGet, a.MakeListMapMetaHandler(ucListRoles, ucListTags)) webSrv.AddZettelRoute('m', server.MethodGet, a.MakeGetMetaHandler(ucGetMeta)) webSrv.AddZettelRoute('o', server.MethodGet, a.MakeGetOrderHandler( usecase.NewZettelOrder(protectedBoxManager, ucEvaluate))) webSrv.AddZettelRoute('p', server.MethodGet, a.MakeGetParsedZettelHandler(ucParseZettel)) webSrv.AddZettelRoute('u', server.MethodGet, a.MakeListUnlinkedMetaHandler( ucGetMeta, ucUnlinkedRefs, &ucEvaluate)) webSrv.AddZettelRoute('v', server.MethodGet, a.MakeGetEvalZettelHandler(ucEvaluate)) webSrv.AddListRoute('x', server.MethodGet, a.MakeGetDataHandler(ucVersion)) webSrv.AddListRoute('x', server.MethodPost, a.MakePostCommandHandler(&ucIsAuth, &ucRefresh)) webSrv.AddZettelRoute('x', server.MethodGet, a.MakeZettelContextHandler(ucZettelContext)) webSrv.AddListRoute('z', server.MethodGet, a.MakeListPlainHandler(ucListMeta)) |
︙ | ︙ | |||
143 144 145 146 147 148 149 | webSrv.AddZettelRoute('z', server.MethodMove, a.MakeRenameZettelHandler(&ucRename)) } if authManager.WithAuth() { webSrv.SetUserRetriever(usecase.NewGetUserByZid(boxManager)) } } | < < < < | 135 136 137 138 139 140 141 | webSrv.AddZettelRoute('z', server.MethodMove, a.MakeRenameZettelHandler(&ucRename)) } if authManager.WithAuth() { webSrv.SetUserRetriever(usecase.NewGetUserByZid(boxManager)) } } |
Changes to cmd/main.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 | //----------------------------------------------------------------------------- // 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 ( | < < | 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 ( "errors" "flag" "fmt" "net" "net/url" "os" "runtime/debug" "strconv" "strings" "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" |
︙ | ︙ | |||
167 168 169 170 171 172 173 | cfg.Delete(key) } } } const ( keyAdminPort = "admin-port" | < < | 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | cfg.Delete(key) } } } const ( keyAdminPort = "admin-port" keyDebug = "debug-mode" keyDefaultDirBoxType = "default-dir-box-type" keyInsecureCookie = "insecure-cookie" keyListenAddr = "listen-addr" keyLogLevel = "log-level" keyMaxRequestSize = "max-request-size" keyOwner = "owner" |
︙ | ︙ | |||
220 221 222 223 224 225 226 | } ok = setConfigValue(ok, kernel.BoxService, key, val) } ok = setConfigValue( ok, kernel.WebService, kernel.WebListenAddress, cfg.GetDefault(keyListenAddr, "127.0.0.1:23123")) | < < < < | < < < < | 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 | } ok = setConfigValue(ok, kernel.BoxService, key, val) } ok = setConfigValue( ok, kernel.WebService, kernel.WebListenAddress, cfg.GetDefault(keyListenAddr, "127.0.0.1:23123")) ok = setConfigValue(ok, kernel.WebService, kernel.WebURLPrefix, cfg.GetDefault(keyURLPrefix, "/")) ok = setConfigValue(ok, kernel.WebService, kernel.WebSecureCookie, !cfg.GetBool(keyInsecureCookie)) ok = setConfigValue(ok, kernel.WebService, kernel.WebPersistentCookie, cfg.GetBool(keyPersistentCookie)) if val, found := cfg.Get(keyMaxRequestSize); found { ok = setConfigValue(ok, kernel.WebService, kernel.WebMaxRequestSize, val) } ok = setConfigValue( ok, kernel.WebService, kernel.WebTokenLifetimeAPI, cfg.GetDefault(keyTokenLifetimeAPI, "")) ok = setConfigValue( ok, kernel.WebService, kernel.WebTokenLifetimeHTML, cfg.GetDefault(keyTokenLifetimeHTML, "")) if !ok { return errors.New("unable to set configuration") } return nil } |
︙ | ︙ | |||
286 287 288 289 290 291 292 | } secret := cfg.GetDefault("secret", "") if len(secret) < 16 && cfg.GetDefault(keyOwner, "") != "" { fmt.Fprintf(os.Stderr, "secret must have at least length 16 when authentication is enabled, but is %q\n", secret) return 2 } | < < | 274 275 276 277 278 279 280 281 282 283 284 285 286 287 | } secret := cfg.GetDefault("secret", "") if len(secret) < 16 && cfg.GetDefault(keyOwner, "") != "" { fmt.Fprintf(os.Stderr, "secret must have at least length 16 when authentication is enabled, but is %q\n", secret) return 2 } kern.SetCreators( func(readonly bool, owner id.Zid) (auth.Manager, error) { return impl.New(readonly, owner, secret), nil }, createManager, func(srv server.Server, plMgr box.Manager, authMgr auth.Manager, rtConfig config.Config) error { |
︙ | ︙ | |||
331 332 333 334 335 336 337 | } var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") var memprofile = flag.String("memprofile", "", "write memory profile to `file`") // Main is the real entrypoint of the zettelstore. func Main(progName, buildVersion string) int { | < | < < < | > < < < < < < | < | < > | < | < < < < | | 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 | } var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") var memprofile = flag.String("memprofile", "", "write memory profile to `file`") // Main is the real entrypoint of the zettelstore. func Main(progName, buildVersion string) int { fullVersion := retrieveFullVersion(buildVersion) kernel.Main.SetConfig(kernel.CoreService, kernel.CoreProgname, progName) kernel.Main.SetConfig(kernel.CoreService, kernel.CoreVersion, fullVersion) flag.Parse() if *cpuprofile != "" || *memprofile != "" { if *cpuprofile != "" { kernel.Main.StartProfiling(kernel.ProfileCPU, *cpuprofile) } else { kernel.Main.StartProfiling(kernel.ProfileHead, *memprofile) } defer kernel.Main.StopProfiling() } args := flag.Args() if len(args) == 0 { return runSimple() } return executeCommand(args[0], args[1:]...) } func retrieveFullVersion(version string) string { info, ok := debug.ReadBuildInfo() if !ok { return version } var revision, dirty string for _, kv := range info.Settings { switch kv.Key { case "vcs.revision": revision = "+" + kv.Value if len(revision) > 11 { revision = revision[:11] } case "vcs.modified": if kv.Value == "true" { dirty = "-dirty" } } } return version + revision + dirty } |
Changes to cmd/register.go.
︙ | ︙ | |||
23 24 25 26 27 28 29 | _ "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/markdown" // Allow to use markdown parser. _ "zettelstore.de/z/parser/none" // Allow to use none parser. | < | 23 24 25 26 27 28 29 30 31 32 | _ "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/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 config/config.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 | // under this license. //----------------------------------------------------------------------------- // Package config provides functions to retrieve runtime configuration data. package config import ( | < | < < < < < < < | < | | | > > > > > > > > > > > > > > > > | 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 | // under this license. //----------------------------------------------------------------------------- // Package config provides functions to retrieve runtime configuration data. package config import ( "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) // Config allows to retrieve all defined configuration values that can be changed during runtime. type Config interface { AuthConfig // AddDefaultValues enriches the given meta data with its default values. AddDefaultValues(m *meta.Meta) *meta.Meta // GetDefaultLang returns the current value of the "default-lang" key. GetDefaultLang() string // GetSiteName returns the current value of the "site-name" key. GetSiteName() string // GetHomeZettel returns the value of the "home-zettel" key. GetHomeZettel() id.Zid // GetMaxTransclusions return the maximum number of indirect transclusions. GetMaxTransclusions() int // GetYAMLHeader returns the current value of the "yaml-header" key. GetYAMLHeader() bool // GetZettelFileSyntax returns the current value of the "zettel-file-syntax" key. GetZettelFileSyntax() []string // GetMarkerExternal returns the current value of the "marker-external" key. GetMarkerExternal() string // GetFooterHTML returns HTML code that should be embedded into the footer // of each WebUI page. GetFooterHTML() string } // AuthConfig are relevant configuration values for authentication. type AuthConfig interface { // GetSimpleMode returns true if system tuns in simple-mode. GetSimpleMode() bool // GetExpertMode returns the current value of the "expert-mode" key. GetExpertMode() bool // GetVisibility returns the visibility value of the metadata. GetVisibility(m *meta.Meta) meta.Visibility } // GetLang returns the value of the "lang" key of the given meta. If there is // no such value, GetDefaultLang is returned. func GetLang(m *meta.Meta, cfg Config) string { if val, ok := m.Get(api.KeyLang); ok { return val } return cfg.GetDefaultLang() } |
Changes to docs/manual/00001004010000.zettel.
1 2 3 4 5 | id: 00001004010000 title: Zettelstore startup configuration 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 28 29 30 31 32 33 | id: 00001004010000 title: Zettelstore startup configuration role: manual tags: #configuration #manual #zettelstore syntax: zmk modified: 20220419193611 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"" ; [!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. |
︙ | ︙ | |||
122 123 124 125 126 127 128 | ''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). | < < | 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | ''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). 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"" |
Changes to docs/manual/00001004020000.zettel.
1 2 3 4 5 | id: 00001004020000 title: Configure the running Zettelstore 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 28 29 30 | id: 00001004020000 title: Configure the running Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk modified: 20220628110920 You can configure a running Zettelstore by modifying the special zettel with the ID [[00000000000100]]. This zettel is called __configuration zettel__. The following metadata keys change the appearance / behavior of Zettelstore: ; [!default-copyright|''default-copyright''] : Copyright value to be used when rendering content. Can be overwritten in a zettel with [[meta key|00001006020000]] ''copyright''. Default: (the empty string). ; [!default-lang|''default-lang''] : Default language to be used when displaying content. Can be overwritten in a zettel with [[meta key|00001006020000]] ''lang''. Default: ""en"". This value is also used to specify the language for all non-zettel content, e.g. lists or search results. Use values according to the language definition of [[RFC-5646|https://tools.ietf.org/html/rfc5646]]. ; [!default-license|''default-license''] : 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"". |
︙ | ︙ | |||
33 34 35 36 37 38 39 | Default: (the empty string). ; [!home-zettel|''home-zettel''] : Specifies the identifier of the zettel, that should be presented for the default view / home view. If not given or if the identifier does not identify a zettel, the zettel with the identifier ''00010000000000'' is shown. ; [!marker-external|''marker-external''] : Some HTML code that is displayed after a [[reference to external material|00001007040310]]. Default: ""&\#10138;"", to display a ""➚"" sign. | < < < < < < < < < | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | Default: (the empty string). ; [!home-zettel|''home-zettel''] : Specifies the identifier of the zettel, that should be presented for the default view / home view. If not given or if the identifier does not identify a zettel, the zettel with the identifier ''00010000000000'' is shown. ; [!marker-external|''marker-external''] : Some HTML code that is displayed after a [[reference to external material|00001007040310]]. 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. |
︙ | ︙ |
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 13 | id: 00001004101000 title: List of supported commands of the administrator console role: manual tags: #configuration #manual #zettelstore syntax: zmk modified: 20220218133526 ; [!bye|''bye''] : Closes the connection to the administrator console. ; [!config|''config SERVICE''] : Displays all valid configuration keys for the given service. If a key ends with the hyphen-minus character (""''-''"", U+002D), the key denotes a list value. |
︙ | ︙ | |||
69 70 71 72 73 74 75 | Other values for ''PROFILE'' are: ''goroutine'', ''heap'', ''allocs'', ''threadcreate'', ''block'', and ''mutex''. In the future, more values may be appropriate. See the [[Go documentation|https://pkg.go.dev/runtime/pprof#Profile]] for details. This feature is dependent on the internal implementation language of Zettelstore, Go. It may be removed without any further notice at any time. In most cases, it is a tool for software developers to optimize Zettelstore's internal workings. | < < | 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | Other values for ''PROFILE'' are: ''goroutine'', ''heap'', ''allocs'', ''threadcreate'', ''block'', and ''mutex''. In the future, more values may be appropriate. See the [[Go documentation|https://pkg.go.dev/runtime/pprof#Profile]] for details. This feature is dependent on the internal implementation language of Zettelstore, Go. It may be removed without any further notice at any time. In most cases, it is a tool for software developers to optimize Zettelstore's internal workings. ; [!restart|''restart SERVICE''] : Restart the given service and all other that depend on this. ; [!services|''services''] : Displays s list of all available services and their current status. ; [!set-config|''set-config SERVICE KEY VALUE''] : Sets a single configuration value for the next configuration of a given service. It will become effective if the service is restarted. |
︙ | ︙ |
Changes to docs/manual/00001005090000.zettel.
1 2 3 4 5 | id: 00001005090000 title: List of predefined zettel role: manual tags: #manual #reference #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: 00001005090000 title: List of predefined zettel role: manual tags: #manual #reference #zettelstore syntax: zmk modified: 20220321192401 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 Meta 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 | [[00000000010500]] | Zettelstore List Roles HTML Template | Layout for listing all roles | [[00000000010600]] | Zettelstore List Tags HTML Template | Layout of tags lists | [[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 [[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/00001006020000.zettel.
1 2 3 4 5 | id: 00001006020000 title: Supported Metadata Keys 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 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 | id: 00001006020000 title: Supported Metadata Keys role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk modified: 20220810111207 Although you are free to define your own metadata, by using any key (according to the [[syntax|00001006010000]]), some keys have a special meaning that is enforced by Zettelstore. See the [[computed list of supported metadata keys|00000000000090]] for details. Most keys conform to a [[type|00001006030000]]. ; [!all-tags|''all-tags''] : A property (a computed values that is not stored) that contains both the value of [[''tags''|#tags]] and the value of [[''content-tags''|#content-tags]]. ; [!back|''back''] : Is a property that contains the identifier of all zettel that reference the zettel of this metadata, that are not referenced by this zettel. Basically, it is the value of [[''backward''|#backward]], but without any zettel identifier that is contained in [[''forward''|#forward]]. ; [!backward|''backward''] : Is a property that contains the identifier of all zettel that reference the zettel of this metadata. References within invertible values are not included here, e.g. [[''precursor''|#precursor]]. ; [!box-number|''box-number''] : Is a computed value and contains the number of the box where the zettel was found. For all but the [[predefined zettel|00001005090000]], this number is equal to the number __X__ specified in startup configuration key [[''box-uri-__X__''|00001004010000#box-uri-x]]. ; [!content-tags|''content-tags''] : A property that contains all [[inline tags|00001007040000#tag]] defined within the content. ; [!copyright|''copyright''] : Defines a copyright string that will be encoded. If not given, the value ''default-copyright'' from the [[configuration zettel|00001004020000#default-copyright]] will be used. ; [!credential|''credential''] : 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. Mostly used for HTML rendering of the zettel. If not given, the value ''default-lang'' from the [[configuration zettel|00001004020000#default-lang]] will be used. Use values according to the language definition of [[RFC-5646|https://tools.ietf.org/html/rfc5646]]. ; [!license|''license''] : Defines a license string that will be rendered. If not given, the value ''default-license'' from the [[configuration zettel|00001004020000#default-license]] will be used. ; [!modified|''modified''] : Date and time when a zettel was modified through Zettelstore. If you edit a zettel with an editor software outside Zettelstore, you should set it manually to an appropriate value. This is a computed value. There is no need to set it via Zettelstore. ; [!precursor|''precursor''] : References zettel for which this zettel is a ""Folgezettel"" / follow-up zettel. Basically the inverse of key [[''folge''|#folge]]. ; [!published|''published''] : This property contains the timestamp of the mast modification / creation of the zettel. If [[''modified''|#modified]] is set, it contains the same value. Otherwise, if the zettel identifier contains a valid timestamp, the identifier is used. In all other cases, this property is not set. It can be used for [[sorting|00001012052000]] zettel based on their publication date. It is a computed value. There is no need to set it via Zettelstore. ; [!read-only|''read-only''] : 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''] |
︙ | ︙ |
Changes to docs/manual/00001006031000.zettel.
1 2 3 4 5 | id: 00001006031000 title: Credential Key Type 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 | id: 00001006031000 title: Credential Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk 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. === Match operator A credential never matches to any other value. === 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 | id: 00001006031500 title: EString Key Type 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 | id: 00001006031500 title: EString Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk 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. === Match operator A value matches an EString value, if the first value is part of the EString value. This check is done case-insensitive. For example, ""hell"" matches ""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``. Comparison is done character-wise by finding the first difference in the respective character sequence. |
︙ | ︙ |
Changes to docs/manual/00001006032000.zettel.
1 2 3 4 5 | id: 00001006032000 title: Identifier Key Type 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 | id: 00001006032000 title: Identifier Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk Values of this type denote a [[zettel identifier|00001006050000]]. === Allowed values Must be a sequence of 14 digits (""0""--""9""). === Match operator A value matches an identifier value, if the first value is the prefix of the identifier value. 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 | id: 00001006032500 title: IdentifierSet Key Type 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 | id: 00001006032500 title: IdentifierSet Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk modified: 20220111103731 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. === Match operator A value matches an identifier set value, if the first value is a prefix of one of the identifier value. For example, ""000010"" matches ""[[00001006032000]] [[00001006032500]]"". === Sorting Sorting is done by comparing the [[String|00001006033500]] values. |
Changes to docs/manual/00001006033000.zettel.
1 2 3 4 5 | id: 00001006033000 title: Number Key Type 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 | id: 00001006033000 title: Number Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk 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. === Match operator The match operator is the equals operator, i.e. two values must be numeric equal to match. This includes that ""+12"" is equal to ""12"", therefore both values match. === Sorting Sorting is done by comparing the numeric values. |
Changes to docs/manual/00001006033500.zettel.
1 2 3 4 5 | id: 00001006033500 title: String Key Type 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 | id: 00001006033500 title: String Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk 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. === Match operator A value matches a String value, if the first value is part of the String value. This check is done case-insensitive. For example, ""hell"" matches ""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``. Comparison is done character-wise by finding the first difference in the respective character sequence. |
︙ | ︙ |
Changes to docs/manual/00001006034000.zettel.
1 2 3 4 5 | id: 00001006034000 title: TagSet Key Type 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 | id: 00001006034000 title: TagSet Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk modified: 20220218130413 Values of this type denote a (sorted) set of tags. A set is different to a list, as no duplicate values are allowed. === Allowed values Every tag must must begin with the number sign character (""''#''"", U+0023), followed by at least one printable character. Tags are separated by space characters. All characters are mapped to their lower case values. === Match operator It depends of the first character of a search string how it is matched against a tag set value: * If the first character of the search string is a number sign character, it must exactly match one of the values of a tag. * In other cases, the search string must be the prefix of at least one tag. Conceptually, all number sign characters are removed at the beginning of the search string and of all tags. === Sorting Sorting is done by comparing the [[String|00001006033500]] values. |
Changes to docs/manual/00001006034500.zettel.
1 2 3 4 5 | id: 00001006034500 title: Timestamp Key Type 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 | id: 00001006034500 title: Timestamp Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk modified: 20210511131903 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. === Match operator A value matches a timestamp value, if the first value is the prefix of the timestamp value. For example, ""202102"" matches ""20210212143200"". === 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 | id: 00001006035000 title: URL Key Type 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 | id: 00001006035000 title: URL Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk Values of this type denote an URL. === Allowed values All characters of an URL / URI are allowed. === Match operator A value matches a URL value, if the first value is part of the URL value. This check is done case-insensitive. For example, ""hell"" matches ""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 | id: 00001006035500 title: Word Key Type 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 | id: 00001006035500 title: Word Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk modified: 20210817201304 Values of this type denote a single word. === Allowed values Must be a non-empty sequence of characters, but without the space character. All characters are mapped to their lower case values. === Match operator A value matches a word value, if both value are character-wise equal, ignoring upper / lower case. === Sorting Sorting is done by comparing the [[String|00001006033500]] values. |
Changes to docs/manual/00001006036000.zettel.
1 2 3 4 5 | id: 00001006036000 title: WordSet Key Type 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 | id: 00001006036000 title: WordSet Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk modified: 20220724201056 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. === Match operator A value matches a WordSet value, if the first value is equal to one of the word values in the word set. === Sorting Sorting is done by comparing the [[String|00001006033500]] values. |
Changes to docs/manual/00001006036500.zettel.
1 2 3 4 5 | id: 00001006036500 title: Zettelmarkup Key Type 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 | id: 00001006036500 title: Zettelmarkup Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk 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. === Match operator A value matches a String value, if the first value is part of the String value. This check is done case-insensitive. For example, ""hell"" matches ""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``. Comparison is done character-wise by finding the first difference in the respective character sequence. |
︙ | ︙ |
Changes to docs/manual/00001007000000.zettel.
1 2 3 4 5 | id: 00001007000000 title: Zettelmarkup role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007000000 title: Zettelmarkup role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220810194655 Zettelmarkup is a rich plain-text based markup language for writing zettel content. Besides the zettel content, Zettelmarkup is also used for specifying the title of a zettel, regardless of the syntax of a zettel. Zettelmarkup supports the longevity of stored notes by providing a syntax that any person can easily read, as well as a computer. Zettelmarkup can be much easier parsed / consumed by a software compared to other markup languages. Writing a parser for [[Markdown|https://daringfireball.net/projects/markdown/syntax]] is quite challenging. |
︙ | ︙ | |||
31 32 33 34 35 36 37 | However, the Zettelstore supports CommonMark as a zettel syntax, so you can mix both Zettelmarkup zettel and CommonMark zettel in one store to get the best of both worlds. * [[General principles|00001007010000]] * [[Basic definitions|00001007020000]] * [[Block-structured elements|00001007030000]] * [[Inline-structured element|00001007040000]] * [[Attributes|00001007050000]] | | | 30 31 32 33 34 35 36 37 38 39 | However, the Zettelstore supports CommonMark as a zettel syntax, so you can mix both Zettelmarkup zettel and CommonMark zettel in one store to get the best of both worlds. * [[General principles|00001007010000]] * [[Basic definitions|00001007020000]] * [[Block-structured elements|00001007030000]] * [[Inline-structured element|00001007040000]] * [[Attributes|00001007050000]] * [[Search expressions|00001007700000]] * [[Summary of formatting characters|00001007800000]] * [[Tutorial|00001007900000]] |
Changes to docs/manual/00001007030400.zettel.
1 2 3 4 5 | id: 00001007030400 title: Zettelmarkup: Horizontal Rules / Thematic Break 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 | id: 00001007030400 title: Zettelmarkup: Horizontal Rules / Thematic Break role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218133651 To signal a thematic break, you can specify a horizontal rule. This is done by entering at least three hyphen-minus characters (""''-''"", U+002D) at the first position of a line. You can add some [[attributes|00001007050000]], although the horizontal rule does not support the default attribute. Any other character in this line will be ignored If you do not enter the three hyphen-minus character at the very first position of a line, the are interpreted as [[inline elements|00001007040000]], typically as an ""en-dash" followed by a hyphen-minus. Example: ```zmk --- |
︙ | ︙ |
Changes to docs/manual/00001007031100.zettel.
1 2 3 4 5 | id: 00001007031100 title: Zettelmarkup: Transclusion 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 | id: 00001007031100 title: Zettelmarkup: Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220809144920 A transclusion allows to include the content of other zettel into the current zettel. The transclusion specification begins with three consecutive left curly bracket characters (""''{''"", U+007B) at the first position of a line and ends with three consecutive right curly bracket characters (""''}''"", U+007D). The curly brackets delimit either a [[zettel identifier|00001006050000]] or a searched zettel list. This leads to two variants of transclusion: # Transclusion of the content of another zettel into the current zettel. This is done if you specify a zettel identifier, and is called ""zettel transclusion"". # Transclusion of the list of zettel references that satisfy a [[search expression|00001007700000]]. This is called ""search transclusion"". The variants are described on separate zettel: * [[Zettel transclusion|00001007031110]] * [[Search transclusion|00001007031140]] |
Changes to docs/manual/00001007031110.zettel.
1 2 3 4 5 | id: 00001007031110 title: Zettelmarkup: Zettel Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < < | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007031110 title: Zettelmarkup: Zettel Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk A zettel transclusion is specified by the following sequence, starting at the first position in a line: ''{{{zettel-identifier}}}''. When evaluated, the referenced zettel is read. If it contains some transclusions itself, these will be expanded, recursively. When a recursion is detected, expansion does not take place. Instead an error message replaces the transclude specification. |
︙ | ︙ | |||
31 32 33 34 35 36 37 | This allows, for example, to create a bigger document just by transcluding smaller zettel. In addition, if a zettel __z__ transcludes a zettel __t__, but the current user is not allowed to view zettel __t__ (but zettel __z__), then the transclusion will not take place. To the current user, it seems that there was no transclusion in zettel __z__. This allows to create a zettel with content that seems to be changed, depending on the authorization of the current user. | < < < < < | 29 30 31 32 33 34 35 36 37 | This allows, for example, to create a bigger document just by transcluding smaller zettel. In addition, if a zettel __z__ transcludes a zettel __t__, but the current user is not allowed to view zettel __t__ (but zettel __z__), then the transclusion will not take place. To the current user, it seems that there was no transclusion in zettel __z__. This allows to create a zettel with content that seems to be changed, depending on the authorization of the current user. === See also [[Inline-mode transclusion|00001007040324]] does not work at the paragraph / block level, but is used for [[inline-structured elements|00001007040000]]. |
Changes to docs/manual/00001007031140.zettel.
1 | id: 00001007031140 | | < | | | | | > > | | | | > > | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 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 | id: 00001007031140 title: Zettelmarkup: Search Transclusion role: manual tags: #manual #search #zettelmarkup #zettelstore syntax: zmk modified: 20220811141604 A search transclusion is specified by the following sequence, starting at the first position in a line: ''{{{search:search-expression}}}''. The line must literally start with the sequence ''{{{search:''. Everything after this prefix is interpreted as a [[search expression|00001007700000]]. When evaluated, the search expression is evaluated, leading to a list of [[links|00001007040310]] to zettel, matching the search expression. Every link references the found zettel, with its title as link text. This list replaces the search transclusion element. For example, to include the list of all zettel with the [[all-tags|00001006020000#all-tags]] ""#search"", ordered by title specify the following search transclude element: ```zmk {{{search:all-tags:#search ORDER title}}} ``` This will result in: :::zs-example {{{search:all-tags:#search ORDER title}}} ::: Please note: if the referenced zettel is changed, all transclusions will also change. For example, this allows to create a dynamic list of zettel inside a zettel, maybe to provide some introductory text followed by a list of child zettel. The search will deliver only those zettel, which the current user is allowed to read. |
Changes to docs/manual/00001007040000.zettel.
1 2 3 4 5 | id: 00001007040000 title: Zettelmarkup: Inline-Structured Elements role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007040000 title: Zettelmarkup: Inline-Structured Elements role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220809171453 Most characters you type is concerned with inline-structured elements. The content of a zettel contains is many cases just ordinary text, lightly formatted. Inline-structured elements allow to format your text and add some helpful links or images. Sometimes, you want to enter characters that have no representation on your keyboard. ; Text formatting |
︙ | ︙ | |||
40 41 42 43 44 45 46 | * Every other character is taken as itself, but without the interpretation of a Zettelmarkup element. For example, if you want to enter a ""'']''"" into a [[footnote text|00001007040330]], you should escape it with a backslash. ==== Tag Any text that begins with a number sign character (""''#''"", U+0023), followed by a non-empty sequence of Unicode letters, Unicode digits, the hyphen-minus character (""''-''"", U+002D), or the low line character (""''_''"", U+005F) is interpreted as an __inline tag__. They are be considered equivalent to tags in metadata. | < < < < < < < < < < < < < < < < < < < < | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | * Every other character is taken as itself, but without the interpretation of a Zettelmarkup element. For example, if you want to enter a ""'']''"" into a [[footnote text|00001007040330]], you should escape it with a backslash. ==== Tag Any text that begins with a number sign character (""''#''"", U+0023), followed by a non-empty sequence of Unicode letters, Unicode digits, the hyphen-minus character (""''-''"", U+002D), or the low line character (""''_''"", U+005F) is interpreted as an __inline tag__. They are be considered equivalent to tags in metadata. ==== Entities & more Sometimes it is not easy to enter special characters. If you know the Unicode code point of that character, or its name according to the [[HTML standard|https://html.spec.whatwg.org/multipage/named-characters.html]], you can enter it by number or by name. Regardless which method you use, an entity always begins with an ampersand character (""''&''"", U+0026) and ends with a semicolon character (""'';''"", U+003B). If you know the HTML name of the character you want to enter, put it between these two character. Example: ``&`` is rendered as ::&::{=example}. |
︙ | ︙ |
Changes to docs/manual/00001007040310.zettel.
1 2 3 4 5 | id: 00001007040310 title: Zettelmarkup: Links 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 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | id: 00001007040310 title: Zettelmarkup: Links role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220808161918 There are two kinds of links, regardless of links to (internal) other zettel or to (external) material. Both kinds begin with two consecutive left square bracket characters (""''[''"", U+005B) and ends with two consecutive right square bracket characters (""'']''"", U+005D). The first form provides some text plus the link specification, delimited by a vertical bar character (""''|''"", U+007C): ``[[text|linkspecification]]``. The text is a sequence of [[inline elements|00001007040000]]. However, it should not contain links itself. The second form just provides a link specification between the square brackets. Its text is derived from the link specification, e.g. by interpreting the link specification as text: ``[[linkspecification]]``. === Link specifications The link specification for another zettel within the same Zettelstore is just the [[zettel identifier|00001006050000]]. To reference some content within a zettel, you can append a number sign character (""''#''"", U+0023) and the name of the mark to the zettel identifier. The resulting reference is called ""zettel reference"". If the link specification begins with the string ''search:'', the text following this string will be interpreted as a [[search expression|00001007700000]]. The resulting reference is called ""search reference"". When this type of references is rendered, it will reference a list of all zettel that fulfills the search expression. A link specification starting with one slash character (""''/''"", U+002F), or one or two full stop characters (""''.''"", U+002E) followed by a slash character, will be interpreted as a local reference, called ""hosted reference"". Such references will be interpreted relative to the web server hosting the Zettelstore. If a link specification begins with two slash characters, it will be interpreted relative to the value of [[''url-prefix''|00001004010000#url-prefix]]. To specify some material outside the Zettelstore, just use an normal Uniform Resource Identifier (URI) as defined by [[RFC\ 3986|https://tools.ietf.org/html/rfc3986]]. === Other topics If the link references another zettel, and this zettel is not readable for the current user, because of a missing access rights, then only the associated text is presented. |
Changes to docs/manual/00001007050100.zettel.
1 2 | id: 00001007050100 title: Zettelmarkup: Supported Attribute Values for Natural Languages | < | < | > > > > > > > | 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: 00001007050100 title: Zettelmarkup: Supported Attribute Values for Natural Languages tags: #manual #reference #zettelmarkup #zettelstore syntax: zmk role: manual With an [[attribute|00001007050000]] it is possible to specify the natural language of a text region. This is important, if you want to render your markup into an environment, where this is significant. HTML is such an environment. To specify the language within an attribute, you must use the key ''lang''. The language itself is specified according to the language definition of [[RFC-5646|https://tools.ietf.org/html/rfc5646]]. Examples: * ``{lang=en}`` for the english language * ``{lang=en-us}`` for the english dialect spoken in the United States of America * ``{lang=de}`` for the german language * ``{lang=de-at}`` for the german language dialect spoken in Austria * ``{lang=de-de}`` for the german language dialect spoken in Germany The actual [[typographic quotations marks|00001007040100]] (``""...""``) are derived from the current language. The language of a zettel (meta key ''lang'') or of the whole Zettelstore (''default-lang'' of the [[configuration zettel|00001004020000#default-lang]]) can be overwritten by an attribute: ``""...""{lang=fr}``{=zmk}. Currently, Zettelstore supports the following primary languages: * ''de'' * ''en'' * ''fr'' These are used, even if a dialect was specified. |
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 | id: 00001007700000 title: Search expression role: manual tags: #manual #search #zettelstore syntax: zmk modified: 20220810164112 A search expression allows you to search for specific zettel. You may select zettel based on a full-text search, based on specifc metadata values, or both. 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''. * [[Search terms|00001007702000]] * [[Search operator|00001007705000]] * [[Search value|00001007706000]] A search expression follows a [[formal syntax|00001007780000]]. Here are some examples of search expressions, which can be used to manage a Zettelstore: {{{00001007790000}}} |
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | id: 00001007702000 title: Search term role: manual tags: #manual #search #zettelstore syntax: zmk modified: 20220808130055 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: * 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. * The string ''NEGATE'' will negate (sic!) the behavior of the whole search expression. If it occurs multiple times, the negation will be negated. * 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. Currently, only the first term specifying the order of the resulting list will be used. Other ordering terms will be ignored. An explicit order field will take precedence over the random order described below. * The string ''RANDOM'' will provide a random order of the resulting list. Currently, only the first term specifying the order of the resulting list will be used. Other ordering terms will be ignored. A random order specification will be ignored, if there is an explicit ordering given. Example: ''RANDOM ORDER published'' will be interpreted as ''ORDER published''. * The string ''OFFSET'', followed by a non-empty sequence of spaces and a number greater zero (called ""N""). This will ignore the first N elements of the result list, based on the specified sort order. A zero value of N will produce the same result as if nothing was specified. If specified multiple times, the higher value takes precedence. Example: ''OFFSET 4 OFFSET 8'' will be interpreted as ''OFFSET 8''. * The string ''LIMIT'', followed by a non-empty sequence of spaces and a number greater zero (called ""N""). This will limit the result list to the first N elements, based on the specified sort order. A zero value of N will produce the same result as if nothing was specified. If specified multiple times, the lower value takes precedence. Example: ''LIMIT 4 LIMIT 8'' will be interpreted as ''LIMIT 4''. |
︙ | ︙ |
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 33 34 35 36 37 38 39 40 41 42 43 44 45 | id: 00001007705000 title: Search operator role: manual tags: #manual #search #zettelstore syntax: zmk modified: 20220811141050 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 containment (""contains 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) specifies the __default comparison__, i.e. one of the previous comparisons. **Please note:** this operator will be changed in version 0.7.0. It was included to allow the transition of the previous mechanism into search expressions. With version 0.7.0, this operator will take the role of the ''=''-operator. * The equal sign character (""''=''"", U+003D) compares on equal words (""equal operator"") **Please note:** this operator will be removed in version 0.7.0. It was included to allow the transition of the previous mechanism into search expressions. Since the exclamation mark character can be combined with the other, there are 12 possible combinations: # ""''!''"": is an abbreviation of the ""''!:''"" operator. # ""'':''"": depending on the [[metadata key type|00001006030000]] one of the other operators is chosen. For example, a [[numeric key type|00001006033000]] will execute the equals operator, while for a [[string type|00001006033500]] a contains operator will be executed. With version 0.7.0 its meaning will be changed to that of the ''='' operator. # ""''!:''"": similar to the ""match operator"" above, the appropriate negated search operator will be chosen, depending on the metadata key type With version 0.7.0 its meaning will be changed to that of the ''!='' operator. # ""''~''"": is successful if the search value is contained in the value to be compared. # ""''!~''"": is successful if the search value is not contained in 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. # ""''''"": a missing search operator can only occur for a full-text search. It is equal to the ""''~''"" operator. |
Changes to docs/manual/00001007780000.zettel.
1 | id: 00001007780000 | | < | < | > < < | > | < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | id: 00001007780000 title: Forma syntax of search expressions role: manual tags: #manual #reference #search #zettelstore syntax: zmk modified: 20220811141423 ``` SearchExpression := SearchTerm (SPACE+ SearchTerm)*. SearchTerm := "NEGATE" | SearchOperator? SearchValue | SearchKey SearchOperator SearchValue? | "RANDOM" | "ORDER" SPACE+ ("REVERSE" SPACE+)? SearchKey | "OFFSET" SPACE+ PosInt | "LIMIT" SPACE+ PosInt. SearchValue := NO-SPACE (NO-SPACE)*. SearchKey := MetadataKey. SearchOperator := '!' | ('!')? '=' ← removed in version 0.7.0 | ('!')? (':' | '<' | '>'). PosInt := '0' | ('1' .. '9') DIGIT*. ``` |
Changes to docs/manual/00001007790000.zettel.
1 | id: 00001007790000 | | < | | | | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | id: 00001007790000 title: Useful search expressions role: manual tags: #example #manual #search #zettelstore syntax: zmk modified: 20220811141224 |= Search Expression |= Meaning | [[search:role:configuration]] | Zettel that contains some configuration data for the Zettelstore | [[search:ORDER REVERSE id LIMIT 40]] | 40 recently created zettel | [[search:ORDER REVERSE published LIMIT 40]] | 40 recently updated zettel | [[search:RANDOM LIMIT 40]] | 40 random zettel | [[search:dead:]] | Zettel with invalid / dead links | [[search:backward!: precursor!:]] | Zettel that are not referenced by other zettel | [[search:all-tags!:]] | Zettel without any tags | [[search:tags!:]] | Zettel without tags that are defined within metadata | [[search:content-tags:]] | Zettel with tags within content |
Changes to docs/manual/00001008000000.zettel.
1 2 3 4 5 | id: 00001008000000 title: Other Markup Languages role: manual tags: #manual #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001008000000 title: Other Markup Languages role: manual tags: #manual #zettelstore syntax: zmk modified: 20220627192329 [[Zettelmarkup|00001007000000]] is not the only markup language you can use to define your content. Zettelstore is quite agnostic with respect to markup languages. Of course, Zettelmarkup plays an important role. However, with the exception of zettel titles, you can use any (markup) language that is supported: * CSS |
︙ | ︙ | |||
40 41 42 43 44 45 46 | ; [!mustache|''mustache''] : A [[Mustache template|https://mustache.github.io/]], used when rendering a zettel as HTML for the [[web user interface|00001014000000]]. ; [!none|''none''] : Only the metadata of a zettel is ""parsed"". Useful for displaying the full metadata. The [[runtime configuration zettel|00000000000100]] uses this syntax. The zettel content is ignored. | < < | | | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | ; [!mustache|''mustache''] : A [[Mustache template|https://mustache.github.io/]], used when rendering a zettel as HTML for the [[web user interface|00001014000000]]. ; [!none|''none''] : Only the metadata of a zettel is ""parsed"". Useful for displaying the full metadata. The [[runtime configuration zettel|00000000000100]] uses this syntax. The zettel content is ignored. ; [!svg|''svg''] : A [[Scalable Vector Graphics|https://www.w3.org/TR/SVG2/]]. ; [!text|''text''], [!plain|''plain''], [!txt|''txt''] : Just plain text that must not be interpreted further. ; [!zmk|''zmk''] : [[Zettelmarkup|00001007000000]]. The actual values are also listed in a zettel named [[Zettelstore Supported Parser|00000000000092]]. If you specify something else, your content will be interpreted as plain text. === Language for other elements of a zettel [[Zettelmarkup|00001007000000]] allows to specify [[evaluation blocks|00001007031300]], which also receive a syntax value. An evaluation blocks is typically interpreted by external software, for example [[Zettel Presenter|00001006055000#external-applications]]. However, some values are interpreted by Zettelstore during evaluation of a zettel: ; [!draw|''draw''] : A [[language|00001008050000]] to ""draw"" a graphic by using some simple Unicode characters. |
Changes to docs/manual/00001010070200.zettel.
1 2 3 4 5 | id: 00001010070200 title: Visibility rules for zettel role: manual tags: #authorization #configuration #manual #security #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001010070200 title: Visibility rules for zettel role: manual tags: #authorization #configuration #manual #security #zettelstore syntax: zmk modified: 20220808152359 For every zettel you can specify under which condition the zettel is visible to others. This is controlled with the metadata key [[''visibility''|00001006020000#visibility]]. The following values are supported: ; [!public|""public""] : The zettel is visible to everybody, even if the user is not authenticated. |
︙ | ︙ | |||
24 25 26 27 28 29 30 | This is for zettel with sensitive content, e.g. the [[configuration zettel|00001004020000]] or the various zettel that contains the templates for rendering zettel in HTML. ; [!expert|""expert""] : Only the owner of the Zettelstore can access the zettel, if runtime configuration [[''expert-mode''|00001004020000#expert-mode]] is set to a [[boolean true value|00001006030500]]. This is for zettel with sensitive content that might irritate the owner. Computed zettel with internal runtime information are examples for such a zettel. | | | | | | | | | 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 | This is for zettel with sensitive content, e.g. the [[configuration zettel|00001004020000]] or the various zettel that contains the templates for rendering zettel in HTML. ; [!expert|""expert""] : Only the owner of the Zettelstore can access the zettel, if runtime configuration [[''expert-mode''|00001004020000#expert-mode]] is set to a [[boolean true value|00001006030500]]. This is for zettel with sensitive content that might irritate the owner. Computed zettel with internal runtime information are examples for such a zettel. When you install a Zettelstore, only [[some zettel|//h?visibility=public]] have visibility ""public"". One is the zettel that contains [[CSS|00000000020001]] for displaying the [[web user interface|00001014000000]]. This is to ensure that the web interface looks nice even for not authenticated users. Another is the zettel containing the Zettelstore [[license|00000000000004]]. The [[default image|00000000040001]], used if an image reference is invalid, is also public visible. Please note: if [[authentication is not enabled|00001010040100]], every user has the same rights as the owner of a Zettelstore. This is also true, if the Zettelstore runs additionally in [[read-only mode|00001004010000#read-only-mode]]. In this case, the [[runtime configuration zettel|00001004020000]] is shown (its visibility is ""owner""). The [[startup configuration|00001004010000]] is not shown, because the associated computed zettel with identifier ''00000000000096'' is stored with the visibility ""expert"". If you want to show such a zettel, you must set ''expert-mode'' to true. === Examples Similar to the [[API|00001012051810]], you can easily create a zettel list based on the ''visibility'' metadata key: | public | [[search:visibility:public]] | login | [[search:visibility:login]] | creator | [[search:visibility:creator]] | owner | [[search:visibility:owner]] | expert | [[search:visibility:expert]][^Only if [[''expert-mode''|00001004020000#expert-mode]] is enabled, this list will show some zettel.] |
Changes to docs/manual/00001012000000.zettel.
1 2 3 4 5 | id: 00001012000000 title: API role: manual tags: #api #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 | id: 00001012000000 title: API role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220805144406 The API (short for ""**A**pplication **P**rogramming **I**nterface"") is the primary way to communicate with a running Zettelstore. Most integration with other systems and services is done through the API. The [[web user interface|00001014000000]] is just an alternative, secondary way of interacting with a Zettelstore. === Background The API is HTTP-based and uses plain text and JSON as its main encoding format for exchanging messages between a Zettelstore and its client software. There is an [[overview zettel|00001012920000]] that shows the structure of the endpoints used by the API and gives an indication about its use. === Authentication If [[authentication is enabled|00001010040100]], most API calls must include an [[access token|00001010040700]] that proves the identity of the caller. * [[Authenticate an user|00001012050200]] to obtain an access token * [[Renew an access token|00001012050400]] without costly re-authentication * [[Provide an access token|00001012050600]] when doing an API call === Zettel lists * [[List metadata of all zettel|00001012051200]] * [[Shape the list of zettel metadata|00001012051800]] ** [[Selection of zettel|00001012051810]] ** [[Limit the list length|00001012051830]] ** [[Search expressions|00001012051840]] (includes content search) ** [[Sort the list of zettel metadata|00001012052000]] * [[Map metadata values to lists of zettel identifier|00001012052400]] === Working with zettel * [[Create a new zettel|00001012053200]] * [[Retrieve metadata and content of an existing zettel|00001012053300]] * [[Retrieve metadata of an existing zettel|00001012053400]] * [[Retrieve evaluated metadata and content of an existing zettel in various encodings|00001012053500]] * [[Retrieve parsed metadata and content of an existing zettel in various encodings|00001012053600]] |
︙ | ︙ |
Changes to docs/manual/00001012051200.zettel.
1 2 3 4 5 | id: 00001012051200 title: API: List metadata of all zettel role: manual tags: #api #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 | id: 00001012051200 title: API: List metadata of all zettel role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220201180649 To list the metadata of all zettel just send a HTTP GET request to the [[endpoint|00001012920000]] ''/j''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. If successful, the output is a JSON object: ```sh # curl http://127.0.0.1:23123/j {"query":"","list":[{"id":"00001012051200","meta":{"title":"API: Renew an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"},"rights":62},{"id":"00001012050600","meta":{"title":"API: Provide an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"},"rights":62},{"id":"00001012050400","meta":{"title":"API: Renew an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"},"rights":62},{"id":"00001012050200","meta":{"title":"API: Authenticate a client","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"},"rights":62},{"id":"00001012000000","meta":{"title":"API","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"},"rights":62}]} ``` The JSON object contains a key ''"list"'' where its value is a list of zettel JSON objects. These zettel JSON objects themselves contains the keys ''"id"'' (value is a string containing the [[zettel identifier|00001006050000]]), ''"meta"'' (value as a JSON object), and ''"rights"'' (encodes the [[access rights|00001012921200]] for the given zettel). The value of key ''"meta"'' effectively contains all metadata of the identified zettel, where metadata keys are encoded as JSON object keys and metadata values encoded as JSON strings. Additionally, the JSON object contains a key ''"query"'' with a string value. It will contain a textual description of the underlying query if you [[select only some zettel|00001012051810]]. Without a selection, the value is the empty string. If you reformat the JSON output from the ''GET /j'' call, you'll see its structure better: ```json { "query": "", "list": [ { "id": "00001012051200", "meta": { "title": "API: List for all zettel some data", "tags": "#api #manual #zettelstore", "syntax": "zmk", |
︙ | ︙ |
Deleted docs/manual/00001012051400.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added docs/manual/00001012051800.zettel.
> > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | id: 00001012051800 title: API: Shape the list of zettel metadata role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220805144809 In most cases, it is not essential to list __all__ zettel. Typically, you are interested only in a subset of the zettel maintained by your Zettelstore. This is done by adding some query parameters to the general ''GET /j'' request. * [[Select|00001012051810]] just some zettel, based on metadata. * Only a specific amount of zettel will be selected by specifying [[a length and/or an offset|00001012051830]]. * [[Specifying a search expression|00001012051840]], e.g. searching for zettel content and/or metadata, is another way of selecting some zettel. * The resulting list can be [[sorted|00001012052000]] according to various criteria. |
Added docs/manual/00001012051810.zettel.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | id: 00001012051810 title: API: Select zettel based on their metadata role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220811141840 Every query parameter that does __not__ begin with the low line character (""_"", U+005F) is treated as the name of a [[metadata|00001006010000]] key. According to the [[type|00001006030000]] of a metadata key, zettel are possibly selected. All [[supported|00001006020000]] metadata keys have a well-defined type. User-defined keys have the type ''e'' (string, possibly empty). For example, if you want to retrieve all zettel that contain the string ""API"" in its title, your request will be: ```sh # curl 'http://127.0.0.1:23123/j?title=API' {"query":"title MATCH API","list":[{"id":"00001012921000","meta":{"title":"API: JSON structure of an access token","tags":"#api #manual #reference #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012920500","meta":{"title":"Formats available by the API","tags":"#api #manual #reference #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012920000","meta":{"title":"Endpoints used by the API","tags":"#api #manual #reference #zettelstore","syntax":"zmk","role":"manual"}}, ... ``` However, if you want all zettel that does not match a given value, you must prefix the value with the exclamation mark character (""!"", U+0021). For example, if you want to retrieve all zettel that do not contain the string ""API"" in their title, your request will be: ```sh # curl 'http://127.0.0.1:23123/j?title=!API' {"query":"title NOT MATCH API","list":[{"id":"00010000000000","meta":{"back":"00001003000000 00001005090000","backward":"00001003000000 00001005090000","copyright":"(c) 2020-2021 by Detlef Stern <ds@zettelstore.de>","forward":"00000000000001 00000000000003 00000000000096 00000000000100","lang":"en","license":"EUPL-1.2-or-later","role":"zettel","syntax":"zmk","title":"Home"}},{"id":"00001014000000","meta":{"back":"00001000000000 00001004020000 00001012920510","backward":"00001000000000 00001004020000 00001012000000 00001012920510","copyright":"(c) 2020-2021 by Detlef Stern <ds@zettelstore.de>","forward":"00001012000000","lang":"en","license":"EUPL-1.2-or-later","published":"00001014000000","role":"manual","syntax":"zmk","tags":"#manual #webui #zettelstore","title":"Web user interface"}}, ... ``` In both cases, an implicit precondition is that the zettel must contain the given metadata key. For a metadata key like [[''title''|00001006020000#title]], which has a default value, this precondition should always be true. But the situation is different for a key like [[''url''|00001006020000#url]]. Both ``curl 'http://localhost:23123/j?url='`` and ``curl 'http://localhost:23123/j?url=!'`` may result in an empty list. The empty query parameter values matches all zettel that contain the given metadata key. Similar, if you specify just the exclamation mark character as a query parameter value, only those zettel match that does not contain the given metadata key. This is in contrast to above rule that the metadata value must exist before a match is done. For example ``curl 'http://localhost:23123/j?back=!&backward='`` returns all zettel that are reachable via other zettel, but also references these zettel. As stated above, the exact rule for comparison depends on the [[type|00001006030000]] of the specified metadata key. By using a [[simple search syntax|00001012051890]], you are able to specify other comparison operations.[^One is the already mentioned exclamation mark character.] Above example shows that all sub-expressions of a select specification must be true so that no zettel is rejected from the final list. If you specify the query parameter ''_negate'', either with or without a value, the whole selection will be negated. Because of the precondition described above, ``curl 'http://127.0.0.1:23123/j?url=!com'`` and ``curl 'http://127.0.0.1:23123/j?url=com&_negate'`` may produce different lists. The first query produces a zettel list, where each zettel does have a ''url'' metadata value, which does not contain the characters ""com"". The second query produces a zettel list, that excludes any zettel containing a ''url'' metadata value that contains the characters ""com""; this also includes all zettel that do not contain the metadata key ''url''. Alternatively, you also can use the [[endpoint|00001012920000]] ''/z'' for a simpler result format. The first example translates to: ```sh # curl 'http://127.0.0.1:23123/z?title=API' 00001012921000 API: JSON structure of an access token 00001012920500 Formats available by the API 00001012920000 Endpoints used by the API ... ``` === Deprecation Comparisons via URL query parameter are deprecated since version 0.6.0. They will be removed in version 0.7.0 |
Added docs/manual/00001012051830.zettel.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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: 00001012051830 title: API: Shape the list of zettel metadata by limiting its length role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20211004124642 === Limit By using the query parameter ""''_limit''"", which must have an integer value, you specifying an upper limit of list elements: ```sh # curl 'http://127.0.0.1:23123/j?title=API&_sort=id&_limit=2' {"query":"title MATCH API LIMIT 2","list":[{"id":"00001012000000","meta":{"all-tags":"#api #manual #zettelstore","back":"00001000000000 00001004020000","backward":"00001000000000 00001004020000 00001012053200 00001012054000 00001014000000","box-number":"1","forward":"00001010040100 00001010040700 00001012050200 00001012050400 00001012050600 00001012051200 00001012051800 00001012051810 00001012051830 00001012051840 00001012052000 00001012052200 00001012052400 00001012052600 00001012053200 00001012053300 00001012053500 00001012053600 00001012053700 00001012053800 00001012054000 00001012054200 00001012054400 00001012054600 00001012920000 00001014000000","modified":"20210817160844","published":"20210817160844","role":"manual","syntax":"zmk","tags":"#api #manual #zettelstore","title":"API"}},{"id":"00001012050200","meta":{"all-tags":"#api #manual #zettelstore","back":"00001012000000 00001012050400 00001012050600 00001012051200 00001012053400 00001012053500 00001012053600","backward":"00001010040700 00001012000000 00001012050400 00001012050600 00001012051200 00001012053400 00001012053500 00001012053600 00001012920000 00001012921000","box-number":"1","forward":"00001004010000 00001010040200 00001010040700 00001012920000 00001012921000","modified":"20210726123709","published":"20210726123709","role":"manual","syntax":"zmk","tags":"#api #manual #zettelstore","title":"API: Authenticate a client"}}]} ``` ```sh # curl 'http://127.0.0.1:23123/z?title=API&_sort=id&_limit=2' 00001012000000 API 00001012050200 API: Authenticate a client ``` === Offset The query parameter ""''_offset''"" allows to list not only the first elements, but to begin at a specific element: ```sh # curl 'http://127.0.0.1:23123/j?title=API&_sort=id&_limit=2&_offset=1' {"query":"title MATCH API OFFSET 1 LIMIT 2","list":[{"id":"00001012050200","meta":{"all-tags":"#api #manual #zettelstore","back":"00001012000000 00001012050400 00001012050600 00001012051200 00001012053400 00001012053500 00001012053600","backward":"00001010040700 00001012000000 00001012050400 00001012050600 00001012051200 00001012053400 00001012053500 00001012053600 00001012920000 00001012921000","box-number":"1","forward":"00001004010000 00001010040200 00001010040700 00001012920000 00001012921000","modified":"20210726123709","published":"20210726123709","role":"manual","syntax":"zmk","tags":"#api #manual #zettelstore","title":"API: Authenticate a client"}},{"id":"00001012050400","meta":{"all-tags":"#api #manual #zettelstore","back":"00001010040700 00001012000000","backward":"00001010040700 00001012000000 00001012920000 00001012921000","box-number":"1","forward":"00001010040100 00001012050200 00001012920000 00001012921000","modified":"20210726123745","published":"20210726123745","role":"manual","syntax":"zmk","tags":"#api #manual #zettelstore","title":"API: Renew an access token"}}]} ``` ```sh # curl 'http://127.0.0.1:23123/z?title=API&_sort=id&_limit=2&_offset=1' 00001012050200 API: Authenticate a client 00001012050400 API: Renew an access token ``` |
Changes to docs/manual/00001012051840.zettel.
1 | id: 00001012051840 | | < | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001012051840 title: API: Shape the list of zettel metadata by specifying a search expression role: manual tags: #api #manual #search #zettelstore syntax: zmk modified: 20220805165619 The query parameter ""''_s''"" allows you to specify [[search expressions|00001007700000]] for a full-text search of all zettel content and/or restricting the search according to specific metadata. You are allowed to specify this query parameter more than once, as well as the other query parameters. All results will be intersected, i.e. a zettel will be included into the list if all of the provided values match. This parameter loosely resembles the search form of the [[web user interface|00001014000000]]. |
Added docs/manual/00001012051890.zettel.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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: 00001012051890 title: API: Comparison syntax (simple) role: manual tags: #api #manual #search #zettelstore syntax: zmk modified: 20220811141804 By using a simple syntax for comparing metadata values, you can modify the default comparison. Note, this syntax is intend-fully similar to the syntax of the more general [[search operators|00001007705000]], which are part of [[search expressions|00001007700000]]. If the search string starts with the exclamation mark character (""!"", U+0021), it will be removed and the query matches all values that **do not match** the search string. In the next step, the first character of the search string will be inspected. If it contains one of the characters ""'':''"", ""''=''"", ""''>''"", ""''<''"", or ""''~''"", this will modify how the search will be performed. The character will be removed from the start of the search string. For example, assume the search string is ""def"": ; The colon character (""'':''"", U+003A) (or none of these characters) : This is the __default__ comparison. The comparison chosen depends on the [[metadata key type|00001006030000]]. It you omit the the comparison character, the default comparison is also used. ; The tilde character (""''~''"", U+007E) : The inspected text[^Either all words of the zettel content and/or some metadata values] contains the search string. ""def"", ""defghi"", and ""abcdefghi"" are matching the search string. ; The equal sign character (""''=''"", U+003D) : The inspected text must contain a word that is equal to the search string. Only the word ""def"" matches the search string. ; The greater-than sign character (""''>''"", U+003E) : The inspected text must contain a word with the search string as a prefix. A word like ""def"" or ""defghi"" matches the search string. ; The less-than sign character (""''<''"", U+003C) : The inspected text must contain a word with the search string as a suffix. A word like ""def"" or ""abcdef"" matches the search string. If you want to include an initial ""''!''"" into the search string, you must prefix that with the escape character ""''\\''"". For example ""\\!abc"" will search for the string ""!abc"". A similar rule applies to the characters that specify the way how the search will be done. For example, ""!\\=abc"" will search for content that does not contains the string ""=abc"". === Deprecation Comparisons via URL query parameter are deprecated since version 0.6.0. They will be removed in version 0.7.0 |
Added docs/manual/00001012052000.zettel.
> > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | id: 00001012052000 title: API: Sort the list of zettel metadata role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220218131937 If not specified, the list of zettel is sorted descending by the value of the [[zettel identifier|00001006050000]]. The highest zettel identifier, which is a number, comes first. You change that with the ""''_sort''"" query parameter. Alternatively, you can also use the ""''_order''"" query parameter. It is an alias. Its value is the name of a metadata key, optionally prefixed with a hyphen-minus character (""''-''"", U+002D). According to the [[type|00001006030000]] of a metadata key, the list of zettel is sorted. If hyphen-minus is given, the order is descending, else ascending. If you want a random list of zettel, specify the value ""_random"" in place of the metadata key. ""''_sort=_random''"" (or ""''_order=_random''"") is the query parameter in this case. If can be combined with ""[[''_limit=1''|00001012051830]]"" to obtain just one random zettel. Currently, only the first occurrence of ''_sort'' is recognized. In the future it will be possible to specify a combined sort key. |
Changes to docs/manual/00001012052400.zettel.
1 2 3 4 5 | id: 00001012052400 title: API: Map metadata values to list of zettel identifier role: manual tags: #api #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 | id: 00001012052400 title: API: Map metadata values to list of zettel identifier role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220627183323 The [[endpoint|00001012920000]] ''/m'' allows to retrieve a map of metadata values (of a specific key) to the list of zettel identifier, which reference zettel containing this value under the given metadata key. Currently, two keys are supported: * [[''role''|00001006020100]] * [[''tags''|00001006020000#tags]] To list all roles used in the Zettelstore, send a HTTP GET request to the endpoint ''/m?_key=role''. If successful, the output is a JSON object: ```sh # curl http://127.0.0.1:23123/m?_key=role {"map":{"configuration":["00000000090002","00000000090000", ... ,"00000000000001"],"manual":["00001014000000", ... ,"00001000000000"],"zettel":["00010000000000", ... ,"00001012070500","00000000090001"]}} ``` The JSON object only contains the key ''"map"'' with the value of another object. This second object contains all role names as keys and the list of identifier of those zettel with this specific role as a value. Similar, to list all tags used in the Zettelstore, send a HTTP GET request to the endpoint ''/m?_key=tags''. If successful, the output is a JSON object: ```sh # curl http://127.0.0.1:23123/m?_key=tags {"map":{"#api":[:["00001012921000","00001012920800","00001012920522",...],"#authorization":["00001010040700","00001010040400",...],...,"#zettelstore":["00010000000000","00001014000000",...,"00001001000000"]}} ``` The JSON object only contains the key ''"map"'' with the value of another object. This second object contains all tags as keys and the list of identifier of those zettel with this tag as a value. Please note that this structure will likely change in the future to be more compliant with other API calls. |
Changes to docs/manual/00001012053300.zettel.
1 2 3 4 5 | id: 00001012053300 title: API: Retrieve metadata and content of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001012053300 title: API: Retrieve metadata and content of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220724163741 The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/j/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/j/00001012053300''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. If successful, the output is a JSON object: ```sh # curl http://127.0.0.1:23123/j/00001012053300 |
︙ | ︙ | |||
54 55 56 57 58 59 60 | ; ''"rights"'' : An integer number that describes the [[access rights|00001012921200]] for the zettel. === Plain zettel [!plain]Additionally, you can retrieve the plain zettel, without using JSON. Just change the [[endpoint|00001012920000]] to ''/z/{ID}'' Optionally, you may provide which parts of the zettel you are requesting. | | | | | | 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 | ; ''"rights"'' : An integer number that describes the [[access rights|00001012921200]] for the zettel. === Plain zettel [!plain]Additionally, you can retrieve the plain zettel, without using JSON. Just change the [[endpoint|00001012920000]] to ''/z/{ID}'' Optionally, you may provide which parts of the zettel you are requesting. In this case, add an additional query parameter ''_part=[[PART|00001012920800]]''. Valid values are ""zettel"", ""[[meta|00001012053400]]"", and ""content"" (the default value). ````sh # curl 'http://127.0.0.1:23123/z/00001012053300' The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/j/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/j/00001012053300''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. If successful, the output is a JSON object: ```sh ... ```` ````sh # curl 'http://127.0.0.1:23123/z/00001012053300?_part=zettel' title: API: Retrieve metadata and content of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/j/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ... ```` === HTTP Status codes ; ''200'' : Retrieval was successful, the body contains an appropriate JSON object / plain zettel data. ; ''204'' : Request was valid, but there is no data to be returned. Most likely, you specified the query parameter ''_part=content'', but the zettel does not contain any content. ; ''400'' : Request was not valid. There are several reasons for this. Maybe the [[zettel identifier|00001006050000]] did not consists of exactly 14 digits. ; ''403'' : You are not allowed to retrieve data of the given zettel. ; ''404'' : Zettel not found. You probably used a zettel identifier that is not used in the Zettelstore. |
Changes to docs/manual/00001012053400.zettel.
1 2 3 4 5 | id: 00001012053400 title: API: Retrieve metadata of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001012053400 title: API: Retrieve metadata of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220202112048 The [[endpoint|00001012920000]] to work with metadata of a specific zettel is ''/m/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/j/00001012053400''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. If successful, the output is a JSON object: ```sh # curl http://127.0.0.1:23123/m/00001012053400 |
︙ | ︙ | |||
39 40 41 42 43 44 45 | : References an embedded JSON object with only string values. The name/value pairs of this objects are interpreted as the metadata of the new zettel. Please consider the [[list of supported metadata keys|00001006020000]] (and their value types). ; ''"rights"'' : An integer number that describes the [[access rights|00001012921200]] for the zettel. [!plain]Additionally, you can retrieve the plain metadata of a zettel, without using JSON. | | | | 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | : References an embedded JSON object with only string values. The name/value pairs of this objects are interpreted as the metadata of the new zettel. Please consider the [[list of supported metadata keys|00001006020000]] (and their value types). ; ''"rights"'' : An integer number that describes the [[access rights|00001012921200]] for the zettel. [!plain]Additionally, you can retrieve the plain metadata of a zettel, without using JSON. Just change the [[endpoint|00001012920000]] to ''/z/{ID}?_part=meta'' ````sh # curl 'http://127.0.0.1:23123/z/00001012053400?_part=meta' title: API: Retrieve metadata of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk ```` === HTTP Status codes |
︙ | ︙ |
Changes to docs/manual/00001012053500.zettel.
1 2 3 4 5 | id: 00001012053500 title: API: Retrieve evaluated metadata and content of an existing zettel in various encodings role: manual tags: #api #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 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 | id: 00001012053500 title: API: Retrieve evaluated metadata and content of an existing zettel in various encodings role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220410153546 The [[endpoint|00001012920000]] to work with evaluated metadata and content of a specific zettel is ''/v/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. For example, to retrieve some evaluated data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/v/00001012053500''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. If successful, the output is a JSON object: ```sh # curl http://127.0.0.1:23123/v/00001012053500 {"meta":{"title":[{"t":"Text","s":"API:"},{"t":"Space"},{"t":"Text","s":"Retrieve"},{"t":"Space"},{"t":"Text","s":"evaluated"},{"t":"Space"},{"t":"Text","s":"metadata"},{"t":"Space"},{"t":"Text","s":"and"},{"t":"Space"},{"t":"Text","s":"content"},{"t":"Space"},{"t":"Text","s":"of"},{"t":"Space"},{"t":"Text","s":"an"},{"t":"Space"},{"t":"Text","s":"existing"},{"t":"Space"},{"t":"Text","s":"zettel"},{"t":"Space"},{"t":"Text","s":"in"},{"t":"Space"}, ... ``` To select another encoding, you can provide a query parameter ''_enc=[[ENCODING|00001012920500]]''. The default encoding is ""[[zjson|00001012920503]]"". Others are ""[[html|00001012920510]]"", ""[[text|00001012920519]]"", and some more. ```sh # curl 'http://127.0.0.1:23123/v/00001012053500?_enc=html' <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>API: Retrieve evaluated metadata and content of an existing zettel in various encodings</title> <meta name="zs-role" content="manual"> <meta name="keywords" content="api, manual, zettelstore"> <meta name="zs-syntax" content="zmk"> <meta name="zs-back" content="00001012000000"> <meta name="zs-backward" content="00001012000000"> <meta name="zs-box-number" content="1"> <meta name="copyright" content="(c) 2020-2021 by Detlef Stern <ds@zettelstore.de>"> <meta name="zs-forward" content="00001010040100 00001012050200 00001012920000 00001012920800"> <meta name="zs-published" content="00001012053500"> </head> <body> <p>The <a href="00001012920000">endpoint</a> to work with evaluated metadata and content of a specific zettel is <kbd>/v/{ID}</kbd>, where <kbd>{ID}</kbd> is a placeholder for the <a href="00001006050000">zettel identifier</a>.</p> ... ``` You also can use the query parameter ''_part=[[PART|00001012920800]]'' to specify which parts of a zettel must be encoded. In this case, its default value is ''content''. ```sh # curl 'http://127.0.0.1:23123/v/00001012053500?_enc=html&_part=meta' <meta name="zs-title" content="API: Retrieve evaluated metadata and content of an existing zettel in various encodings"> <meta name="zs-role" content="manual"> <meta name="keywords" content="api, manual, zettelstore"> <meta name="zs-syntax" content="zmk"> <meta name="zs-back" content="00001012000000"> <meta name="zs-backward" content="00001012000000"> <meta name="zs-box-number" content="1"> <meta name="copyright" content="(c) 2020-2021 by Detlef Stern <ds@zettelstore.de>"> <meta name="zs-forward" content="00001010040100 00001012050200 00001012920000 00001012920800"> <meta name="zs-lang" content="en"> <meta name="zs-published" content="00001012053500"> ``` === HTTP Status codes ; ''200'' : Retrieval was successful, the body contains an appropriate JSON object. ; ''400'' : Request was not valid. There are several reasons for this. Maybe the zettel identifier did not consist of exactly 14 digits or ''_enc'' / ''_part'' contained illegal values. ; ''403'' : You are not allowed to retrieve data of the given zettel. ; ''404'' : Zettel not found. You probably used a zettel identifier that is not used in the Zettelstore. |
Changes to docs/manual/00001012053600.zettel.
1 2 3 4 5 | id: 00001012053600 title: API: Retrieve parsed metadata and content of an existing zettel in various encodings role: manual tags: #api #manual #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001012053600 title: API: Retrieve parsed metadata and content of an existing zettel in various encodings role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20211124180746 The [[endpoint|00001012920000]] to work with parsed metadata and content of a specific zettel is ''/p/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. A __parsed__ zettel is basically an [[unevaluated|00001012053500]] zettel: the zettel is read and analyzed, but its content is not __evaluated__. By using this endpoint, you are able to retrieve the structure of a zettel before it is evaluated. For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/v/00001012053600''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. |
︙ | ︙ | |||
23 24 25 26 27 28 29 | === HTTP Status codes ; ''200'' : Retrieval was successful, the body contains an appropriate JSON object. ; ''400'' : Request was not valid. There are several reasons for this. | | | 22 23 24 25 26 27 28 29 30 31 32 33 34 | === HTTP Status codes ; ''200'' : Retrieval was successful, the body contains an appropriate JSON object. ; ''400'' : Request was not valid. There are several reasons for this. Maybe the zettel identifier did not consist of exactly 14 digits or ''_enc'' / ''_part'' contained illegal values. ; ''403'' : You are not allowed to retrieve data of the given zettel. ; ''404'' : Zettel not found. You probably used a zettel identifier that is not used in the Zettelstore. |
Changes to docs/manual/00001012053900.zettel.
1 2 3 4 5 | id: 00001012053900 title: API: Retrieve unlinked references to an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001012053900 title: API: Retrieve unlinked references to an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220805144656 The value of a personal Zettelstore is determined in part by explicit connections between related zettel. If the number of zettel grow, some of these connections are missing. There are various reasons for this. Maybe, you forgot that a zettel exists. Or you add a zettel later, but forgot that previous zettel already mention its title. |
︙ | ︙ | |||
44 45 46 47 48 49 50 | This call searches within all zettel whether the title of the specified zettel occurs there. The other zettel must not link to the specified zettel. The title must not occur within a link (e.g. to another zettel), in a [[heading|00001007030300]], in a [[citation|00001007040340]], and must have a uniform formatting. The match must be exact, but is case-insensitive. If the title of the specified zettel contains some extra character that probably reduce the number of found unlinked references, | | > | | 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | This call searches within all zettel whether the title of the specified zettel occurs there. The other zettel must not link to the specified zettel. The title must not occur within a link (e.g. to another zettel), in a [[heading|00001007030300]], in a [[citation|00001007040340]], and must have a uniform formatting. The match must be exact, but is case-insensitive. If the title of the specified zettel contains some extra character that probably reduce the number of found unlinked references, you can specify the title phase to be searched for as a query parameter ''_phrase'': ```` # curl 'http://127.0.0.1:23123/u/00001007000000?phrase=markdown' {"id": "00001007000000","meta": {...},"list": [{"id": "00001008010000","meta": {...},"rights":62},{"id": "00001004020000","meta": {...},"rights":62}]} ```` In addition, you are allowed to specify all query parameter to [[select zettel based on their metadata|00001012051810]], to [[limit the length of the returned list|00001012051830]], and to [[sort the returned list|00001012052000]]. You are allowed to limit the search by a [[search expression|00001012051840]], which may search for zettel content. === Keys The following top-level JSON keys are returned: ; ''id'' : The [[zettel identifier|00001006050000]] for which the unlinked references were requested. ; ''meta'': : The metadata of the zettel, encoded as a JSON object. |
︙ | ︙ |
Changes to docs/manual/00001012080100.zettel.
1 2 3 4 5 | id: 00001012080100 title: API: Execute commands role: manual tags: #api #manual #zettelstore syntax: zmk | < | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | id: 00001012080100 title: API: Execute commands role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220805174227 The [[endpoint|00001012920000]] ''/x'' allows you to execute some (administrative) commands. To differentiate between the possible commands, you have to set the query parameter ''_cmd'' to a specific value: ; ''authenticated'' : [[Check for authentication|00001012080200]] ; ''refresh'' : [[Refresh internal data|00001012080500]] Other commands will be defined in the future. |
Changes to docs/manual/00001012080200.zettel.
1 2 3 4 5 | id: 00001012080200 title: API: Check for authentication role: manual tags: #api #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 | id: 00001012080200 title: API: Check for authentication role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220805174236 API clients typically wants to know, whether [[authentication is enabled|00001010040100]] or not. If authentication is enabled, they present some form of user interface to get user name and password for the actual authentication. Then they try to [[obtain an access token|00001012050200]]. If authentication is disabled, these steps are not needed. To check for enabled authentication, you must send a HTTP POST request to the [[endpoint|00001012920000]] ''/x'' and you must specify the query parameter ''_cmd=authenticated''. ```sh # curl -X POST 'http://127.0.0.1:23123/x?_cmd=authenticated' ``` If authentication is not enabled, you will get a HTTP status code 200 (OK) with an empty HTTP body. Otherwise, authentication is enabled. If you provide a valid access token, you will receive a HTTP status code 204 (No Content) with an empty HTTP body. If you did not provide a valid access token (with is the typical case), you will get a HTTP status code 401 (Unauthorized), again with an empty HTTP body. === HTTP Status codes ; ''200'' : Authentication is disabled. ; ''204'' : Authentication is enabled and a valid access token was provided. ; ''400'' : Request was not valid. There are several reasons for this. Most likely, no query parameter ''_cmd'' was given, or it did not contain the value ""authenticate"". ; ''401'' : Authentication is enabled and not valid access token was provided. |
Changes to docs/manual/00001012080500.zettel.
1 2 3 4 5 | id: 00001012080500 title: API: Refresh internal data role: manual tags: #api #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 | id: 00001012080500 title: API: Refresh internal data role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220805174246 Zettelstore maintains some internal data to allow faster operations. One example is the [[content search|00001012051840]] for a term: Zettelstore does not need to scan all zettel to find all occurrences for the term. Instead, all word are stored internally, with a list of zettel where they occur. Another example is the way to determine which zettel are stored in a [[ZIP file|00001004011200]]. Scanning a ZIP file is a lengthy operation, therefore Zettelstore maintains a directory of zettel for each ZIP file. All these internal data may become stale. This should not happen, but when it comes e.g. to file handling, every operating systems behaves differently in very subtle ways. To avoid stopping and re-starting Zettelstore, you can use the API to force Zettelstore to refresh its internal data if you think it is needed. To do this, you must send a HTTP POST request to the [[endpoint|00001012920000]] ''/x'' and you must specify the query parameter ''_cmd=refresh''. ```sh # curl -X POST 'http://127.0.0.1:23123/x?_cmd=refresh' ``` If successful, you will get a HTTP status code 204 (No Content) with an empty HTTP body. The request will be successful if either: * [[Authentication is enabled|00001010040100]] and you [[provide a valid access token|00001012050600]], * Authentication is not enabled and you started Zettelstore with the [[run-simple|00001004051100]] command or [[expert-mode|00001004020000#expert-mode]] is set to ""true"". === HTTP Status codes ; ''204'' : Operation was successful, the body is empty. ; ''400'' : Request was not valid. There are several reasons for this. Most likely, no query parameter ''_cmd'' was given, or it did not contain the value ""refresh"". ; ''403'' : You are not allowed to perform this operation. |
Changes to docs/manual/00001012920000.zettel.
1 2 3 4 5 | id: 00001012920000 title: Endpoints used by the API role: manual tags: #api #manual #reference #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: 00001012920000 title: Endpoints used by the API role: manual tags: #api #manual #reference #zettelstore syntax: zmk modified: 20220627183408 All API endpoints conform to the pattern ''[PREFIX]LETTER[/ZETTEL-ID]'', where: ; ''PREFIX'' : is the URL prefix (default: ""/""), configured via the ''url-prefix'' [[startup configuration|00001004010000]], ; ''LETTER'' : is a single letter that specifies the resource type, ; ''ZETTEL-ID'' : is an optional 14 digits string that uniquely [[identify a zettel|00001006050000]]. The following letters are currently in use: |= Letter:| Without zettel identifier | With [[zettel identifier|00001006050000]] | Mnemonic | ''a'' | POST: [[client authentication|00001012050200]] | | **A**uthenticate | | PUT: [[renew access token|00001012050400]] | | ''j'' | GET: [[list zettel AS JSON|00001012051200]] | GET: [[retrieve zettel AS JSON|00001012053300]] | **J**SON | | POST: [[create new zettel|00001012053200]] | PUT: [[update a zettel|00001012054200]] | | | DELETE: [[delete the zettel|00001012054600]] | | | MOVE: [[rename the zettel|00001012054400]] | ''m'' | GET: [[map metadata values|00001012052400]] | GET: [[retrieve metadata|00001012053400]] | **M**etadata | ''o'' | | GET: [[list zettel order|00001012054000]] | **O**rder | ''p'' | | GET: [[retrieve parsed zettel|00001012053600]]| **P**arsed | ''u'' | | GET [[unlinked references|00001012053900]] | **U**nlinked | ''v'' | | GET: [[retrieve evaluated zettel|00001012053500]] | E**v**aluated | ''x'' | GET: [[retrieve administrative data|00001012070500]] | GET: [[list zettel context|00001012053800]] | Conte**x**t | | POST: [[execute command|00001012080100]] | ''z'' | GET: [[list zettel|00001012051200#plain]] | GET: [[retrieve zettel|00001012053300#plain]] | **Z**ettel | | POST: [[create new zettel|00001012053200#plain]] | PUT: [[update a zettel|00001012054200#plain]] | | | DELETE: [[delete zettel|00001012054600#plain]] | | | MOVE: [[rename zettel|00001012054400#plain]] The full URL will contain either the ""http"" oder ""https"" scheme, a host name, and an optional port number. The API examples will assume the ""http"" schema, the local host ""127.0.0.1"", the default port ""23123"", and the default empty ''PREFIX'' ""/"". Therefore, all URLs in the API documentation will begin with ""http://127.0.0.1:23123/"". |
Changes to docs/manual/00001012920503.zettel.
1 2 3 4 5 | id: 00001012920503 title: ZJSON Encoding role: manual tags: #api #manual #reference #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: 00001012920503 title: ZJSON Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk modified: 20220422191748 A zettel representation that allows to process the syntactic structure of a zettel. It is a JSON-based encoding format, but different to the structures returned by [[endpoint|00001012920000]] ''/j/{ID}''. For an example, take a look at the ZJSON encoding of this page, which is available via the ""Info"" sub-page of this zettel: * [[//v/00001012920503?_enc=zjson&_part=zettel]], * [[//v/00001012920503?_enc=zjson&_part=meta]], * [[//v/00001012920503?_enc=zjson&_part=content]]. If transferred via HTTP, the content type will be ''application/json''. A full zettel encoding results in a JSON object with two keys: ''"meta"'' and ''"content"''. Both values are the same as if you have requested just the appropriate [[part|00001012920800]]. === Encoding of metadata |
︙ | ︙ |
Changes to docs/manual/00001012920516.zettel.
1 2 3 4 5 | id: 00001012920516 title: Sexpr Encoding role: manual tags: #api #manual #reference #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 | id: 00001012920516 title: Sexpr Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk modified: 20220724170637 A zettel representation that is a [[s-expression|https://en.wikipedia.org/wiki/S-expression]] (also known as symbolic expression). It is an alternative to the [[ZJSON encoding|00001012920503]]. Both encodings are (relatively) easy to parse and contain all relevant information of a zettel, metadata and content. For example, take a look at the Sexpr encoding of this page, which is available via the ""Info"" sub-page of this zettel: * [[//v/00001012920516?_enc=sexpr&_part=zettel]], * [[//v/00001012920516?_enc=sexpr&_part=meta]], * [[//v/00001012920516?_enc=sexpr&_part=content]]. If transferred via HTTP, the content type will be ''text/plain''. === Syntax of s-expressions There are only two types of elements: atoms and lists. A list always starts with the left parenthesis (""''(''"", U+0028) and ends with a right parenthesis (""'')''"", U+0029). |
︙ | ︙ |
Changes to docs/manual/00001017000000.zettel.
1 2 3 4 5 | id: 00001017000000 title: Tips and Tricks 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 | id: 00001017000000 title: Tips and Tricks role: manual tags: #manual #zettelstore syntax: zmk modified: 20220805174255 === Welcome Zettel * **Problem:** You want to put your Zettelstore into the public and need a starting zettel for your users. In addition, you still want a ""home zettel"", with all your references to internal, non-public zettel. Zettelstore only allows to specify one [[''home-zettel''|00001004020000#home-zettel]]. * **Solution:** *# Create a new zettel with all your references to internal, non-public zettel. Let's assume this zettel receives the zettel identifier ''20220803182600''. *# Create the zettel that should serve as the starting zettel for your users. It must have syntax [[Zettelmarkup|00001008000000#zmk]], i.e. the syntax metadata must be set to ''zmk''. If needed, set the runtime configuration [[''home-zettel|00001004020000#home-zettel]] to the value of the identifier of this zettel. *# At the beginning of the start zettel, add the following [[Zettelmarkup|00001007000000]] text in a separate paragraph: ``{{{20220803182600}}}`` (you have to adapt to the actual value of the zettel identifier for your non-public home zettel). * **Discussion:** As stated in the description for a [[transclusion|00001007031100]], a transclusion will be ignored, if the transcluded zettel is not visible to the current user. In effect, the transclusion statement (above paragraph that contained ''{{{...}}}'') is ignored when rendering the zettel. |
Changes to docs/manual/00001018000000.zettel.
1 2 3 4 5 | id: 00001018000000 title: Troubleshooting 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 24 25 26 | id: 00001018000000 title: Troubleshooting role: manual tags: #manual #zettelstore syntax: zmk modified: 20220805174305 This page lists some problems and their solutions that may occur when using your Zettelstore. === Installation * **Problem:** When you double-click on the Zettelstore executable icon, macOS complains that Zettelstore is an application from an unknown developer. Therefore, it will not start Zettelstore. ** **Solution:** Press the ''Ctrl'' key while opening the context menu of the Zettelstore executable with a right-click. A dialog is then opened where you can acknowledge that you understand the possible risks when you start Zettelstore. This dialog is only resented once for a given Zettelstore executable. * **Problem:** When you double-click on the Zettelstore executable icon, Windows complains that Zettelstore is an application from an unknown developer. ** **Solution:** Windows displays a dialog where you can acknowledge possible risks and allows to start Zettelstore. === Authentication * **Problem:** [[Authentication is enabled|00001010040100]] for a local running Zettelstore and there is a valid [[user zettel|00001010040200]] for the owner. But entering user name and password at the [[web user interface|00001014000000]] seems to be ignored, while entering a wrong password will result in an error message. ** **Explanation:** A local running Zettelstore typically means, that you are accessing the Zettelstore using an URL with schema ''http://'', and not ''https://'', for example ''http://localhost:23123''. The difference between these two is the missing encryption of user name / password and for the answer of the Zettelstore if you use the ''http://'' schema. To be secure by default, the Zettelstore will not work in an insecure environment. ** **Solution 1:** If you are sure that your communication medium is safe, even if you use the ''http:/\/'' schema (for example, you are running the Zettelstore on the same computer you are working on, or if the Zettelstore is running on a computer in your protected local network), then you could add the entry ''insecure-cookie: true'' in you [[startup configuration|00001004010000#insecure-cookie]] file. ** **Solution 2:** If you are not sure about the security of your communication medium (for example, if unknown persons might use your local network), then you should run an [[external server|00001010090100]] in front of your Zettelstore to enable the use of the ''https://'' schema. |
Changes to domain/id/id.go.
︙ | ︙ | |||
27 28 29 30 31 32 33 | // Some important ZettelIDs. const ( Invalid = Zid(0) // Invalid is a Zid that will never be valid ) // ZettelIDs that are used as Zid more than once. | < | | > > | 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 | // Some important ZettelIDs. const ( Invalid = Zid(0) // Invalid is a Zid that will never be valid ) // ZettelIDs that are used as Zid more than once. // Note: if you change some values, ensure that you also change them in the // constant box. They are mentioned there literally, because these // constants are not available there. var ( ConfigurationZid = MustParse(api.ZidConfiguration) BaseTemplateZid = MustParse(api.ZidBaseTemplate) LoginTemplateZid = MustParse(api.ZidLoginTemplate) ListTemplateZid = MustParse(api.ZidListTemplate) ZettelTemplateZid = MustParse(api.ZidZettelTemplate) InfoTemplateZid = MustParse(api.ZidInfoTemplate) FormTemplateZid = MustParse(api.ZidFormTemplate) RenameTemplateZid = MustParse(api.ZidRenameTemplate) DeleteTemplateZid = MustParse(api.ZidDeleteTemplate) ContextTemplateZid = MustParse(api.ZidContextTemplate) RolesTemplateZid = MustParse(api.ZidRolesTemplate) TagsTemplateZid = MustParse(api.ZidTagsTemplate) ErrorTemplateZid = MustParse(api.ZidErrorTemplate) RoleCSSMapZid = MustParse(api.ZidRoleCSSMap) EmojiZid = MustParse(api.ZidEmoji) TOCNewTemplateZid = MustParse(api.ZidTOCNewTemplate) DefaultHomeZid = MustParse(api.ZidDefaultHome) ) |
︙ | ︙ | |||
139 140 141 142 143 144 145 | result[12] = byte(second/10) + '0' result[13] = byte(second%10) + '0' } // IsValid determines if zettel id is a valid one, e.g. consists of max. 14 digits. func (zid Zid) IsValid() bool { return 0 < zid && zid <= maxZid } | < < < | | | 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | result[12] = byte(second/10) + '0' result[13] = byte(second%10) + '0' } // IsValid determines if zettel id is a valid one, e.g. consists of max. 14 digits. func (zid Zid) IsValid() bool { return 0 < zid && zid <= maxZid } // New returns a new zettel id based on the current time. func New(withSeconds bool) Zid { now := time.Now() var s string if withSeconds { s = now.Format("20060102150405") } else { s = now.Format("20060102150400") } res, err := Parse(s) if err != nil { panic(err) } return res } |
Changes to domain/meta/meta.go.
︙ | ︙ | |||
45 46 47 48 49 50 51 | // IsComputed returns true, if metadata is computed and not set by the user. func (kd *DescriptionKey) IsComputed() bool { return kd.usage >= usageComputed } // IsProperty returns true, if metadata is a computed property. func (kd *DescriptionKey) IsProperty() bool { return kd.usage >= usageProperty } | < < < | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | // IsComputed returns true, if metadata is computed and not set by the user. func (kd *DescriptionKey) IsComputed() bool { return kd.usage >= usageComputed } // IsProperty returns true, if metadata is a computed property. func (kd *DescriptionKey) IsProperty() bool { return kd.usage >= usageProperty } var registeredKeys = make(map[string]*DescriptionKey) func registerKey(name string, t *DescriptionType, usage keyUsage, inverse string) { if _, ok := registeredKeys[name]; ok { panic("Key '" + name + "' already defined") } if inverse != "" { |
︙ | ︙ | |||
88 89 90 91 92 93 94 | func IsProperty(name string) bool { if kd, ok := registeredKeys[name]; ok { return kd.IsProperty() } return false } | < < < < < < < < | 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | func IsProperty(name string) bool { if kd, ok := registeredKeys[name]; ok { return kd.IsProperty() } return false } // Inverse returns the name of the inverse key. func Inverse(name string) string { if kd, ok := registeredKeys[name]; ok { return kd.Inverse } return "" } |
︙ | ︙ | |||
131 132 133 134 135 136 137 | registerKey(api.KeyID, TypeID, usageComputed, "") registerKey(api.KeyTitle, TypeZettelmarkup, usageUser, "") registerKey(api.KeyRole, TypeWord, usageUser, "") registerKey(api.KeyTags, TypeTagSet, usageUser, "") registerKey(api.KeySyntax, TypeWord, usageUser, "") registerKey(api.KeyAllTags, TypeTagSet, usageProperty, "") | < < | 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | registerKey(api.KeyID, TypeID, usageComputed, "") registerKey(api.KeyTitle, TypeZettelmarkup, usageUser, "") registerKey(api.KeyRole, TypeWord, usageUser, "") registerKey(api.KeyTags, TypeTagSet, usageUser, "") registerKey(api.KeySyntax, TypeWord, usageUser, "") registerKey(api.KeyAllTags, TypeTagSet, usageProperty, "") registerKey(api.KeyBack, TypeIDSet, usageProperty, "") registerKey(api.KeyBackward, TypeIDSet, usageProperty, "") registerKey(api.KeyBoxNumber, TypeNumber, usageProperty, "") registerKey(api.KeyContentTags, TypeTagSet, usageProperty, "") registerKey(api.KeyCopyright, TypeString, usageUser, "") registerKey(api.KeyCredential, TypeCredential, usageUser, "") registerKey(api.KeyDead, TypeIDSet, usageProperty, "") registerKey(api.KeyFolge, TypeIDSet, usageProperty, "") registerKey(api.KeyForward, TypeIDSet, usageProperty, "") registerKey(api.KeyLang, TypeWord, usageUser, "") registerKey(api.KeyLicense, TypeEmpty, usageUser, "") registerKey(api.KeyModified, TypeTimestamp, usageComputed, "") |
︙ | ︙ |
Changes to domain/meta/parse.go.
︙ | ︙ | |||
150 151 152 153 154 155 156 | case "", api.KeyID: // Empty key and 'id' key will be ignored return } switch Type(key) { case TypeTagSet: | | | 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | case "", api.KeyID: // Empty key and 'id' key will be ignored return } switch Type(key) { case TypeTagSet: addSet(m, key, strings.ToLower(v), func(s string) bool { return s[0] == '#' }) case TypeWord: m.Set(key, strings.ToLower(v)) case TypeWordSet: addSet(m, key, strings.ToLower(v), func(s string) bool { return true }) case TypeID: if _, err := id.Parse(v); err == nil { m.Set(key, v) |
︙ | ︙ |
Changes to domain/meta/parse_test.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 | // under this license. //----------------------------------------------------------------------------- // Package meta_test provides tests for the domain specific type 'meta'. package meta_test import ( | < | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // under this license. //----------------------------------------------------------------------------- // Package meta_test provides tests for the domain specific type 'meta'. package meta_test import ( "testing" "zettelstore.de/c/api" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" ) |
︙ | ︙ | |||
55 56 57 58 59 60 61 | if got, ok := m.Get(api.KeyTitle); !ok || got != tc.e { t.Log(m) t.Errorf("TC=%d: expected %q, got %q", i, tc.e, got) } } } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | if got, ok := m.Get(api.KeyTitle); !ok || got != tc.e { t.Log(m) t.Errorf("TC=%d: expected %q, got %q", i, tc.e, got) } } } func TestNewFromInput(t *testing.T) { t.Parallel() testcases := []struct { input string exp []meta.Pair }{ {"", []meta.Pair{}}, |
︙ | ︙ |
Changes to domain/meta/type.go.
︙ | ︙ | |||
14 15 16 17 18 19 20 | import ( "strconv" "strings" "sync" "time" "zettelstore.de/c/api" | < | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import ( "strconv" "strings" "sync" "time" "zettelstore.de/c/api" ) // DescriptionType is a description of a specific key type. type DescriptionType struct { Name string IsSet bool } |
︙ | ︙ | |||
109 110 111 112 113 114 115 | } m.pairs[key] = strings.Join(values, " ") } } // SetNow stores the current timestamp under the given key. func (m *Meta) SetNow(key string) { | | | 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | } m.pairs[key] = strings.Join(values, " ") } } // SetNow stores the current timestamp under the given key. func (m *Meta) SetNow(key string) { m.Set(key, time.Now().Format("20060102150405")) } // BoolValue returns the value interpreted as a bool. func BoolValue(value string) bool { if len(value) > 0 { switch value[0] { case '0', 'f', 'F', 'n', 'N': |
︙ | ︙ | |||
133 134 135 136 137 138 139 | return BoolValue(value) } return false } // TimeValue returns the time value of the given value. func TimeValue(value string) (time.Time, bool) { | | | 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | return BoolValue(value) } return false } // TimeValue returns the time value of the given value. func TimeValue(value string) (time.Time, bool) { if t, err := time.Parse("20060102150405", value); err == nil { return t, true } return time.Time{}, false } // GetTime returns the time value of the given key. func (m *Meta) GetTime(key string) (time.Time, bool) { |
︙ | ︙ |
Changes to encoder/encoder_block_test.go.
︙ | ︙ | |||
274 275 276 277 278 279 280 | descr: "Simple Endnotes", zmk: `Text[^Footnote]`, expect: expectMap{ encoderZJSON: `[{"":"Para","i":[{"":"Text","s":"Text"},{"":"Footnote","i":[{"":"Text","s":"Footnote"}]}]}]`, encoderHTML: `<p>Text<sup id="fnref:1"><a class="zs-noteref" href="#fn:1" role="doc-noteref">1</a></sup></p><ol class="zs-endnotes"><li class="zs-endnote" id="fn:1" role="doc-endnote" value="1">Footnote <a class="zs-endnote-backref" href="#fnref:1" role="doc-backlink">↩︎</a></li></ol>`, encoderSexpr: `((PARA (TEXT "Text") (FOOTNOTE () (TEXT "Footnote"))))`, encoderText: "Text Footnote", | < < < < < < < < < < < | 274 275 276 277 278 279 280 281 282 283 284 285 286 287 | descr: "Simple Endnotes", zmk: `Text[^Footnote]`, expect: expectMap{ encoderZJSON: `[{"":"Para","i":[{"":"Text","s":"Text"},{"":"Footnote","i":[{"":"Text","s":"Footnote"}]}]}]`, encoderHTML: `<p>Text<sup id="fnref:1"><a class="zs-noteref" href="#fn:1" role="doc-noteref">1</a></sup></p><ol class="zs-endnotes"><li class="zs-endnote" id="fn:1" role="doc-endnote" value="1">Footnote <a class="zs-endnote-backref" href="#fnref:1" role="doc-backlink">↩︎</a></li></ol>`, encoderSexpr: `((PARA (TEXT "Text") (FOOTNOTE () (TEXT "Footnote"))))`, encoderText: "Text Footnote", encoderZmk: useZmk, }, }, { descr: "", zmk: ``, expect: expectMap{ |
︙ | ︙ |
Changes to encoder/encoder_inline_test.go.
︙ | ︙ | |||
437 438 439 440 441 442 443 | encoderHTML: `<a href="../relative">R</a>`, encoderSexpr: `((LINK-HOSTED () "../relative" (TEXT "R")))`, encoderText: `R`, encoderZmk: useZmk, }, }, { | | | | | | | | | | | | < < < < < < < < < < < | 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 | encoderHTML: `<a href="../relative">R</a>`, encoderSexpr: `((LINK-HOSTED () "../relative" (TEXT "R")))`, encoderText: `R`, encoderZmk: useZmk, }, }, { descr: "Search link w/o text", zmk: `[[search:title:syntax]]`, expect: expectMap{ encoderZJSON: `[{"":"Link","q":"search","s":"title:syntax"}]`, encoderHTML: `<a href="?_s=title%3Asyntax">title:syntax</a>`, encoderSexpr: `((LINK-SEARCH () "title:syntax"))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Search link with text", zmk: `[[S|search:title:syntax]]`, expect: expectMap{ encoderZJSON: `[{"":"Link","q":"search","s":"title:syntax","i":[{"":"Text","s":"S"}]}]`, encoderHTML: `<a href="?_s=title%3Asyntax">S</a>`, encoderSexpr: `((LINK-SEARCH () "title:syntax" (TEXT "S")))`, encoderText: `S`, encoderZmk: useZmk, }, }, { descr: "Dummy Embed", zmk: `{{abc}}`, expect: expectMap{ encoderZJSON: `[{"":"Embed","s":"abc"}]`, encoderHTML: `<img src="abc">`, encoderSexpr: `((EMBED () (EXTERNAL "abc") ""))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "", zmk: ``, expect: expectMap{ |
︙ | ︙ |
Changes to encoder/sexprenc/transform.go.
︙ | ︙ | |||
74 75 76 77 78 79 80 | case *ast.NestedListNode: return t.getNestedList(n) case *ast.DescriptionListNode: return t.getDescriptionList(n) case *ast.TableNode: return t.getTable(n) case *ast.TranscludeNode: | | | 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | case *ast.NestedListNode: return t.getNestedList(n) case *ast.DescriptionListNode: return t.getDescriptionList(n) case *ast.TableNode: return t.getTable(n) case *ast.TranscludeNode: return sxpf.NewPairFromValues(sexpr.SymTransclude, getReference(n.Ref)) case *ast.BLOBNode: return getBLOB(n) case *ast.TextNode: return sxpf.NewPairFromValues(sexpr.SymText, sxpf.NewString(n.Text)) case *ast.TagNode: return sxpf.NewPairFromValues(sexpr.SymTag, sxpf.NewString(n.Tag)) case *ast.SpaceNode: |
︙ | ︙ | |||
303 304 305 306 307 308 309 | ast.RefStateInvalid: sexpr.SymLinkInvalid, ast.RefStateZettel: sexpr.SymLinkZettel, ast.RefStateSelf: sexpr.SymLinkSelf, ast.RefStateFound: sexpr.SymLinkFound, ast.RefStateBroken: sexpr.SymLinkBroken, ast.RefStateHosted: sexpr.SymLinkHosted, ast.RefStateBased: sexpr.SymLinkBased, | | | 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | ast.RefStateInvalid: sexpr.SymLinkInvalid, ast.RefStateZettel: sexpr.SymLinkZettel, ast.RefStateSelf: sexpr.SymLinkSelf, ast.RefStateFound: sexpr.SymLinkFound, ast.RefStateBroken: sexpr.SymLinkBroken, ast.RefStateHosted: sexpr.SymLinkHosted, ast.RefStateBased: sexpr.SymLinkBased, ast.RefStateSearch: sexpr.SymLinkSearch, ast.RefStateExternal: sexpr.SymLinkExternal, } func (t *transformer) getLink(ln *ast.LinkNode) *sxpf.Pair { return sxpf.NewPair( mapGetS(mapRefStateLink, ln.Ref.State), sxpf.NewPair( |
︙ | ︙ | |||
395 396 397 398 399 400 401 | ast.RefStateInvalid: sexpr.SymRefStateInvalid, ast.RefStateZettel: sexpr.SymRefStateZettel, ast.RefStateSelf: sexpr.SymRefStateSelf, ast.RefStateFound: sexpr.SymRefStateFound, ast.RefStateBroken: sexpr.SymRefStateBroken, ast.RefStateHosted: sexpr.SymRefStateHosted, ast.RefStateBased: sexpr.SymRefStateBased, | | | 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 | ast.RefStateInvalid: sexpr.SymRefStateInvalid, ast.RefStateZettel: sexpr.SymRefStateZettel, ast.RefStateSelf: sexpr.SymRefStateSelf, ast.RefStateFound: sexpr.SymRefStateFound, ast.RefStateBroken: sexpr.SymRefStateBroken, ast.RefStateHosted: sexpr.SymRefStateHosted, ast.RefStateBased: sexpr.SymRefStateBased, ast.RefStateSearch: sexpr.SymRefStateSearch, ast.RefStateExternal: sexpr.SymRefStateExternal, } func getReference(ref *ast.Reference) *sxpf.Pair { return sxpf.NewPair( mapGetS(mapRefStateS, ref.State), sxpf.NewPair( |
︙ | ︙ |
Changes to encoder/zjsonenc/zjsonenc.go.
︙ | ︙ | |||
109 110 111 112 113 114 115 | v.visitNestedList(n) case *ast.DescriptionListNode: v.visitDescriptionList(n) case *ast.TableNode: v.visitTable(n) case *ast.TranscludeNode: v.writeNodeStart(zjson.TypeTransclude) | < | 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | v.visitNestedList(n) case *ast.DescriptionListNode: v.visitDescriptionList(n) case *ast.TableNode: v.visitTable(n) case *ast.TranscludeNode: v.writeNodeStart(zjson.TypeTransclude) v.writeContentStart(zjson.NameString2) writeEscaped(&v.b, mapRefState[n.Ref.State]) v.writeContentStart(zjson.NameString) writeEscaped(&v.b, n.Ref.String()) case *ast.BLOBNode: v.visitBLOB(n) case *ast.TextNode: |
︙ | ︙ | |||
354 355 356 357 358 359 360 | ast.RefStateInvalid: zjson.RefStateInvalid, ast.RefStateZettel: zjson.RefStateZettel, ast.RefStateSelf: zjson.RefStateSelf, ast.RefStateFound: zjson.RefStateFound, ast.RefStateBroken: zjson.RefStateBroken, ast.RefStateHosted: zjson.RefStateHosted, ast.RefStateBased: zjson.RefStateBased, | | | | 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 | ast.RefStateInvalid: zjson.RefStateInvalid, ast.RefStateZettel: zjson.RefStateZettel, ast.RefStateSelf: zjson.RefStateSelf, ast.RefStateFound: zjson.RefStateFound, ast.RefStateBroken: zjson.RefStateBroken, ast.RefStateHosted: zjson.RefStateHosted, ast.RefStateBased: zjson.RefStateBased, ast.RefStateSearch: zjson.RefStateSearch, ast.RefStateExternal: zjson.RefStateExternal, } func (v *visitor) visitLink(ln *ast.LinkNode) { v.writeNodeStart(zjson.TypeLink) v.visitAttributes(ln.Attrs) v.writeContentStart(zjson.NameString2) writeEscaped(&v.b, mapRefState[ln.Ref.State]) v.writeContentStart(zjson.NameString) if ln.Ref.State == ast.RefStateSearch { writeEscaped(&v.b, ln.Ref.Value) } else { writeEscaped(&v.b, ln.Ref.String()) } if len(ln.Inlines) > 0 { v.writeContentStart(zjson.NameInline) ast.Walk(v, &ln.Inlines) |
︙ | ︙ |
Changes to encoder/zmkenc/zmkenc.go.
︙ | ︙ | |||
122 123 124 125 126 127 128 | v.visitNestedList(n) case *ast.DescriptionListNode: v.visitDescriptionList(n) case *ast.TableNode: v.visitTable(n) case *ast.TranscludeNode: v.b.WriteStrings("{{{", n.Ref.String(), "}}}") | < | 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | v.visitNestedList(n) case *ast.DescriptionListNode: v.visitDescriptionList(n) case *ast.TableNode: v.visitTable(n) case *ast.TranscludeNode: v.b.WriteStrings("{{{", n.Ref.String(), "}}}") case *ast.BLOBNode: v.visitBLOB(n) case *ast.TextNode: v.visitText(n) case *ast.TagNode: v.b.WriteStrings("#", n.Tag) case *ast.SpaceNode: |
︙ | ︙ | |||
473 474 475 476 477 478 479 | v.b.WriteByte(' ') } v.b.WriteString("%%") v.visitAttributes(ln.Attrs) v.b.WriteByte(' ') v.b.Write(ln.Content) case ast.LiteralHTML: | | | 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 | v.b.WriteByte(' ') } v.b.WriteString("%%") v.visitAttributes(ln.Attrs) v.b.WriteByte(' ') v.b.Write(ln.Content) case ast.LiteralHTML: v.writeLiteral('x', syntaxToHTML(ln.Attrs), ln.Content) default: panic(fmt.Sprintf("Unknown literal kind %v", ln.Kind)) } } func (v *visitor) writeLiteral(code byte, a attrs.Attributes, content []byte) { v.b.WriteBytes(code, code) |
︙ | ︙ |
Deleted encoding/rss/rss.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to evaluator/evaluator.go.
︙ | ︙ | |||
26 27 28 29 30 31 32 | "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" "zettelstore.de/z/parser" "zettelstore.de/z/parser/cleaner" "zettelstore.de/z/parser/draw" | | | < < < < < < | | | 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 | "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" "zettelstore.de/z/parser" "zettelstore.de/z/parser/cleaner" "zettelstore.de/z/parser/draw" "zettelstore.de/z/search" ) // Port contains all methods to retrieve zettel (or part of it) to evaluate a zettel. type Port interface { GetMeta(context.Context, id.Zid) (*meta.Meta, error) GetZettel(context.Context, id.Zid) (domain.Zettel, error) SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) } // EvaluateZettel evaluates the given zettel in the given context, with the // given ports, and the given environment. func EvaluateZettel(ctx context.Context, port Port, rtConfig config.Config, zn *ast.ZettelNode) { if zn.Syntax == api.ValueSyntaxNone { // AST is empty, evaluate to a description list of metadata. zn.Ast = evaluateMetadata(zn.Meta) return } evaluateNode(ctx, port, rtConfig, &zn.Ast) cleaner.CleanBlockSlice(&zn.Ast) } // EvaluateInline evaluates the given inline list in the given context, with // the given ports, and the given environment. func EvaluateInline(ctx context.Context, port Port, rtConfig config.Config, is *ast.InlineSlice) { evaluateNode(ctx, port, rtConfig, is) cleaner.CleanInlineSlice(is) |
︙ | ︙ | |||
201 202 203 204 205 206 207 | e.transcludeCount++ return makeBlockNode(createInlineErrorText(ref, "Invalid", "or", "broken", "transclusion", "reference")) case ast.RefStateSelf: e.transcludeCount++ return makeBlockNode(createInlineErrorText(ref, "Self", "transclusion", "reference")) case ast.RefStateFound, ast.RefStateHosted, ast.RefStateBased, ast.RefStateExternal: return tn | | | | 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | e.transcludeCount++ return makeBlockNode(createInlineErrorText(ref, "Invalid", "or", "broken", "transclusion", "reference")) case ast.RefStateSelf: e.transcludeCount++ return makeBlockNode(createInlineErrorText(ref, "Self", "transclusion", "reference")) case ast.RefStateFound, ast.RefStateHosted, ast.RefStateBased, ast.RefStateExternal: return tn case ast.RefStateSearch: e.transcludeCount++ return e.evalSearchTransclusion(tn.Ref.Value) default: return makeBlockNode(createInlineErrorText(ref, "Illegal", "block", "state", strconv.Itoa(int(ref.State)))) } zid, err := id.Parse(ref.URL.Path) if err != nil { panic(err) |
︙ | ︙ | |||
228 229 230 231 232 233 234 | if err1 != nil { if errors.Is(err1, &box.ErrNotAllowed{}) { return nil } e.transcludeCount++ return makeBlockNode(createInlineErrorText(ref, "Unable", "to", "get", "zettel")) } | < | < | > > > > | > > > > > > | > > > > | > > > > < < < < < < < < | 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 | if err1 != nil { if errors.Is(err1, &box.ErrNotAllowed{}) { return nil } e.transcludeCount++ return makeBlockNode(createInlineErrorText(ref, "Unable", "to", "get", "zettel")) } ec := e.transcludeCount e.costMap[zid] = transcludeCost{zn: e.marker, ec: ec} zn = e.evaluateEmbeddedZettel(zettel) e.costMap[zid] = transcludeCost{zn: zn, ec: e.transcludeCount - ec} e.transcludeCount = 0 // No stack needed, because embedding is done left-recursive, depth-first. } e.transcludeCount++ if ec := cost.ec; ec > 0 { e.transcludeCount += cost.ec } return &zn.Ast } func (e *evaluator) evalSearchTransclusion(expr string) ast.BlockNode { ml, err := e.port.SelectMeta(e.ctx, search.Parse(expr)) if err != nil { if errors.Is(err, &box.ErrNotAllowed{}) { return nil } return makeBlockNode(createInlineErrorText(nil, "Unable", "to", "search", "zettel")) } if len(ml) == 0 { return nil } items := make([]ast.ItemSlice, 0, len(ml)) for _, m := range ml { zid := m.Zid.String() title, found := m.Get(api.KeyTitle) if !found { title = zid } items = append(items, ast.ItemSlice{ast.CreateParaNode(&ast.LinkNode{ Attrs: nil, Ref: ast.ParseReference(zid), Inlines: parser.ParseMetadataNoLink(title), })}) } result := &ast.NestedListNode{ Kind: ast.NestedListUnordered, Items: items, Attrs: nil, } ast.Walk(e, result) return result } func (e *evaluator) checkMaxTransclusions(ref *ast.Reference) ast.InlineNode { if maxTrans := e.transcludeMax; e.transcludeCount > maxTrans { e.transcludeCount = maxTrans + 1 return createInlineErrorText(ref, "Too", "many", "transclusions", "(must", "be", "at", "most", strconv.Itoa(maxTrans)+",", "see", "runtime", "configuration", "key", "max-transclusions)") } return nil } func makeBlockNode(in ast.InlineNode) ast.BlockNode { return ast.CreateParaNode(in) } func (e *evaluator) visitInlineSlice(is *ast.InlineSlice) { for i := 0; i < len(*is); i++ { in := (*is)[i] ast.Walk(e, in) switch n := in.(type) { case *ast.LinkNode: (*is)[i] = e.evalLinkNode(n) |
︙ | ︙ | |||
491 492 493 494 495 496 497 | func (e *evaluator) evaluateEmbeddedInline(content []byte, syntax string) ast.InlineSlice { is := parser.ParseInlines(input.NewInput(content), syntax) ast.Walk(e, &is) return is } func (e *evaluator) evaluateEmbeddedZettel(zettel domain.Zettel) *ast.ZettelNode { | | | 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 | func (e *evaluator) evaluateEmbeddedInline(content []byte, syntax string) ast.InlineSlice { is := parser.ParseInlines(input.NewInput(content), syntax) ast.Walk(e, &is) return is } func (e *evaluator) evaluateEmbeddedZettel(zettel domain.Zettel) *ast.ZettelNode { zn := parser.ParseZettel(zettel, zettel.Meta.GetDefault(api.KeySyntax, ""), e.rtConfig) ast.Walk(e, &zn.Ast) return zn } func findInlineSlice(bs *ast.BlockSlice, fragment string) ast.InlineSlice { if fragment == "" { return firstInlinesToEmbed(*bs) |
︙ | ︙ |
Deleted evaluator/list.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to go.mod.
1 2 3 4 5 6 7 8 | module zettelstore.de/z go 1.19 require ( codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0 github.com/fsnotify/fsnotify v1.5.4 github.com/pascaldekloe/jwt v1.12.0 | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | module zettelstore.de/z go 1.19 require ( codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0 github.com/fsnotify/fsnotify v1.5.4 github.com/pascaldekloe/jwt v1.12.0 github.com/yuin/goldmark v1.4.13 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 golang.org/x/text v0.3.7 zettelstore.de/c v0.6.0 ) require golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 // indirect |
Changes to go.sum.
1 2 3 4 5 6 | codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0 h1:viya/OgeF16+i8caBPJmcLQhGpZodPh+/nxtJzSSO1s= codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0/go.mod h1:4fAHEF3VH+ofbZkF6NzqiItTNy2X11tVCnZX99jXouA= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/pascaldekloe/jwt v1.12.0 h1:imQSkPOtAIBAXoKKjL9ZVJuF/rVqJ+ntiLGpLyeqMUQ= github.com/pascaldekloe/jwt v1.12.0/go.mod h1:LiIl7EwaglmH1hWThd/AmydNCnHf/mmfluBlNqHbk8U= | | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0 h1:viya/OgeF16+i8caBPJmcLQhGpZodPh+/nxtJzSSO1s= codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0/go.mod h1:4fAHEF3VH+ofbZkF6NzqiItTNy2X11tVCnZX99jXouA= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/pascaldekloe/jwt v1.12.0 h1:imQSkPOtAIBAXoKKjL9ZVJuF/rVqJ+ntiLGpLyeqMUQ= github.com/pascaldekloe/jwt v1.12.0/go.mod h1:LiIl7EwaglmH1hWThd/AmydNCnHf/mmfluBlNqHbk8U= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 h1:9vYwv7OjYaky/tlAeD7C4oC9EsPTlaFl1H2jS++V+ME= golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= zettelstore.de/c v0.6.0 h1:5EXEgIpDxFG0zBrq0qBmLzAmbye57oDro1Wy3Zxmw6U= zettelstore.de/c v0.6.0/go.mod h1:+SoneUhKQ81A2Id/bC6FdDYYQAHYfVryh7wHFnnklew= |
Changes to kernel/impl/box.go.
︙ | ︙ | |||
73 74 75 76 77 78 79 | if u == nil { break } boxURIs = append(boxURIs, u.(*url.URL)) } ps.mxService.Lock() defer ps.mxService.Unlock() | | | 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | if u == nil { break } boxURIs = append(boxURIs, u.(*url.URL)) } ps.mxService.Lock() defer ps.mxService.Unlock() mgr, err := ps.createManager(boxURIs, kern.auth.manager, kern.cfg.rtConfig) if err != nil { ps.logger.Fatal().Err(err).Msg("Unable to create manager") return err } ps.logger.Info().Str("location", mgr.Location()).Msg("Start Manager") if err = mgr.Start(context.Background()); err != nil { ps.logger.Fatal().Err(err).Msg("Unable to start manager") |
︙ | ︙ |
Changes to kernel/impl/cfg.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 | // under this license. //----------------------------------------------------------------------------- package impl import ( "context" | < < < < | > > > > | | | < | | | | | > | | | | | < | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > | | | | < | | | | | | < | | < < < | | < | < < < < < < < < < < < < | < < < < < < < | < | < < < < < < | < < < < < < < | | | > | > > | | < | | < < | < > | > | > > > > | | | | > > | | < < | > > | | | | | | | | | > > > > > | | > | > | | | > > | | > > | > > > > | | | | | | | | | | | | 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 | // under this license. //----------------------------------------------------------------------------- package impl import ( "context" "strings" "sync" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" ) type configService struct { srvConfig mxService sync.RWMutex rtConfig *myConfig } // Predefined Metadata keys for runtime configuration // See: https://zettelstore.de/manual/h/00001004020000 const ( keyDefaultCopyright = "default-copyright" keyDefaultLang = "default-lang" keyDefaultLicense = "default-license" keyDefaultVisibility = "default-visibility" keyExpertMode = "expert-mode" keyFooterHTML = "footer-html" keyHomeZettel = "home-zettel" keyMarkerExternal = "marker-external" keyMaxTransclusions = "max-transclusions" keySiteName = "site-name" keyYAMLHeader = "yaml-header" keyZettelFileSyntax = "zettel-file-syntax" ) func (cs *configService) Initialize(logger *logger.Logger) { cs.logger = logger cs.descr = descriptionMap{ keyDefaultCopyright: {"Default copyright", parseString, true}, keyDefaultLang: {"Default language", parseString, true}, keyDefaultLicense: {"Default license", parseString, true}, keyDefaultVisibility: { "Default zettel visibility", func(val string) interface{} { vis := meta.GetVisibility(val) if vis == meta.VisibilityUnknown { return nil } return vis }, true, }, keyExpertMode: {"Expert mode", parseBool, true}, keyFooterHTML: {"Footer HTML", parseString, true}, keyHomeZettel: {"Home zettel", parseZid, true}, keyMarkerExternal: {"Marker external URL", parseString, true}, keyMaxTransclusions: {"Maximum transclusions", parseInt64, true}, keySiteName: {"Site name", parseString, true}, keyYAMLHeader: {"YAML header", parseBool, true}, keyZettelFileSyntax: { "Zettel file syntax", func(val string) interface{} { return strings.Fields(val) }, true, }, kernel.ConfigSimpleMode: {"Simple mode", cs.noFrozen(parseBool), true}, } cs.next = interfaceMap{ keyDefaultCopyright: "", keyDefaultLang: api.ValueLangEN, keyDefaultLicense: "", keyDefaultVisibility: meta.VisibilityLogin, keyExpertMode: false, keyFooterHTML: "", keyHomeZettel: id.DefaultHomeZid, keyMarkerExternal: "➚", keyMaxTransclusions: int64(1024), keySiteName: "Zettelstore", keyYAMLHeader: false, keyZettelFileSyntax: nil, kernel.ConfigSimpleMode: false, } } func (cs *configService) GetLogger() *logger.Logger { return cs.logger } func (cs *configService) Start(*myKernel) error { cs.logger.Info().Msg("Start Service") data := meta.New(id.ConfigurationZid) for _, kv := range cs.GetNextConfigList() { data.Set(kv.Key, kv.Value) } cs.mxService.Lock() cs.rtConfig = newConfig(cs.logger, data) cs.mxService.Unlock() return nil } func (cs *configService) IsStarted() bool { cs.mxService.RLock() defer cs.mxService.RUnlock() return cs.rtConfig != nil } func (cs *configService) Stop(*myKernel) { cs.logger.Info().Msg("Stop Service") cs.mxService.Lock() cs.rtConfig = nil cs.mxService.Unlock() } func (*configService) GetStatistics() []kernel.KeyValue { return nil } func (cs *configService) setBox(mgr box.Manager) { cs.rtConfig.setBox(mgr) } // myConfig contains all runtime configuration data relevant for the software. type myConfig struct { log *logger.Logger mx sync.RWMutex orig *meta.Meta data *meta.Meta } // New creates a new Config value. func newConfig(logger *logger.Logger, orig *meta.Meta) *myConfig { cfg := myConfig{ log: logger, orig: orig, data: orig.Clone(), } return &cfg } func (cfg *myConfig) setBox(mgr box.Manager) { mgr.RegisterObserver(cfg.observe) cfg.doUpdate(mgr) } func (cfg *myConfig) doUpdate(p box.Box) error { m, err := p.GetMeta(context.Background(), cfg.data.Zid) if err != nil { return err } cfg.mx.Lock() for _, pair := range cfg.data.Pairs() { key := pair.Key if val, ok := m.Get(key); ok { cfg.data.Set(key, val) } else if defVal, defFound := cfg.orig.Get(key); defFound { cfg.data.Set(key, defVal) } } cfg.mx.Unlock() return nil } func (cfg *myConfig) observe(ci box.UpdateInfo) { if ci.Reason == box.OnReload || ci.Zid == id.ConfigurationZid { cfg.log.Debug().Uint("reason", uint64(ci.Reason)).Zid(ci.Zid).Msg("observe") go func() { cfg.doUpdate(ci.Box) }() } } var defaultKeys = map[string]string{ api.KeyCopyright: keyDefaultCopyright, api.KeyLang: keyDefaultLang, api.KeyLicense: keyDefaultLicense, api.KeyVisibility: keyDefaultVisibility, } // AddDefaultValues enriches the given meta data with its default values. func (cfg *myConfig) AddDefaultValues(m *meta.Meta) *meta.Meta { if cfg == nil { return m } result := m cfg.mx.RLock() for k, d := range defaultKeys { if _, ok := result.Get(k); !ok { if val, ok2 := cfg.data.Get(d); ok2 && val != "" { if result == m { result = m.Clone() } result.Set(k, val) } } } cfg.mx.RUnlock() return result } func (cfg *myConfig) getString(key string) string { cfg.mx.RLock() val, _ := cfg.data.Get(key) cfg.mx.RUnlock() return val } func (cfg *myConfig) getBool(key string) bool { cfg.mx.RLock() val := cfg.data.GetBool(key) cfg.mx.RUnlock() return val } // GetDefaultLang returns the current value of the "default-lang" key. func (cfg *myConfig) GetDefaultLang() string { return cfg.getString(keyDefaultLang) } // GetSiteName returns the current value of the "site-name" key. func (cfg *myConfig) GetSiteName() string { return cfg.getString(keySiteName) } // GetHomeZettel returns the value of the "home-zettel" key. func (cfg *myConfig) GetHomeZettel() id.Zid { val := cfg.getString(keyHomeZettel) if homeZid, err := id.Parse(val); err == nil { return homeZid } cfg.mx.RLock() val, _ = cfg.orig.Get(keyHomeZettel) homeZid, _ := id.Parse(val) cfg.mx.RUnlock() return homeZid } // GetMaxTransclusions return the maximum number of indirect transclusions. func (cfg *myConfig) GetMaxTransclusions() int { const defaultValue = 1024 cfg.mx.RLock() val := cfg.data.GetNumber(keyMaxTransclusions, defaultValue) cfg.mx.RUnlock() if 0 < val && val < 100000000 { return int(val) } return defaultValue } // GetYAMLHeader returns the current value of the "yaml-header" key. func (cfg *myConfig) GetYAMLHeader() bool { return cfg.getBool(keyYAMLHeader) } // GetMarkerExternal returns the current value of the "marker-external" key. func (cfg *myConfig) GetMarkerExternal() string { return cfg.getString(keyMarkerExternal) } // GetFooterHTML returns HTML code that should be embedded into the footer // of each WebUI page. func (cfg *myConfig) GetFooterHTML() string { return cfg.getString(keyFooterHTML) } // GetZettelFileSyntax returns the current value of the "zettel-file-syntax" key. func (cfg *myConfig) GetZettelFileSyntax() []string { cfg.mx.RLock() defer cfg.mx.RUnlock() return cfg.data.GetListOrNil(keyZettelFileSyntax) } // --- AuthConfig // GetSimpleMode returns true if system tuns in simple-mode. func (cfg *myConfig) GetSimpleMode() bool { return cfg.getBool(kernel.ConfigSimpleMode) } // GetExpertMode returns the current value of the "expert-mode" key. func (cfg *myConfig) GetExpertMode() bool { return cfg.getBool(keyExpertMode) } // GetVisibility returns the visibility value, or "login" if none is given. func (cfg *myConfig) GetVisibility(m *meta.Meta) meta.Visibility { if val, ok := m.Get(api.KeyVisibility); ok { if vis := meta.GetVisibility(val); vis != meta.VisibilityUnknown { return vis } } val := cfg.getString(keyDefaultVisibility) if vis := meta.GetVisibility(val); vis != meta.VisibilityUnknown { return vis } cfg.mx.RLock() val, _ = cfg.orig.Get(keyDefaultVisibility) vis := meta.GetVisibility(val) cfg.mx.RUnlock() return vis } |
Changes to kernel/impl/core.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 | "net" "os" "runtime" "sync" "time" "zettelstore.de/c/maps" | < | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | "net" "os" "runtime" "sync" "time" "zettelstore.de/c/maps" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/strfun" ) type coreService struct { srvConfig |
︙ | ︙ | |||
56 57 58 59 60 61 62 | return nil } return port }), true, }, kernel.CoreProgname: {"Program name", nil, false}, | < < < | 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 | return nil } return port }), true, }, kernel.CoreProgname: {"Program name", nil, false}, kernel.CoreVerbose: {"Verbose output", parseBool, true}, kernel.CoreVersion: { "Version", cs.noFrozen(func(val string) interface{} { if val == "" { return kernel.CoreDefaultVersion } return val }), false, }, } cs.next = interfaceMap{ kernel.CoreDebug: false, kernel.CoreGoArch: runtime.GOARCH, kernel.CoreGoOS: runtime.GOOS, kernel.CoreGoVersion: runtime.Version(), kernel.CoreHostname: "*unknown host*", kernel.CorePort: 0, kernel.CoreVerbose: false, } if hn, err := os.Hostname(); err == nil { cs.next[kernel.CoreHostname] = hn } } |
︙ | ︙ | |||
143 144 145 146 147 148 149 | ) } func (cs *coreService) updateRecoverInfo(name string, recoverInfo interface{}, stack []byte) { cs.mxRecover.Lock() ri := cs.mapRecover[name] ri.count++ | | | 139 140 141 142 143 144 145 146 147 148 149 150 151 | ) } func (cs *coreService) updateRecoverInfo(name string, recoverInfo interface{}, stack []byte) { cs.mxRecover.Lock() ri := cs.mapRecover[name] ri.count++ ri.ts = time.Now() ri.info = recoverInfo ri.stack = stack cs.mapRecover[name] = ri cs.mxRecover.Unlock() } |
Changes to kernel/impl/impl.go.
1 | //----------------------------------------------------------------------------- | | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // Copyright (c) 2021 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package impl provides the kernel implementation. |
︙ | ︙ | |||
21 22 23 24 25 26 27 | "runtime" "runtime/debug" "runtime/pprof" "strconv" "strings" "sync" "syscall" | < < | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | "runtime" "runtime/debug" "runtime/pprof" "strconv" "strings" "sync" "syscall" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" ) // myKernel is the main internal kernel. type myKernel struct { logWriter *kernelLogWriter |
︙ | ︙ | |||
114 115 116 117 118 119 120 | for _, dep := range deps { kern.depStop[dep] = append(kern.depStop[dep], srv) } } return kern } | < < < < < < | 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | for _, dep := range deps { kern.depStop[dep] = append(kern.depStop[dep], srv) } } return kern } func (kern *myKernel) Start(headline, lineServer bool) { for _, srvD := range kern.srvs { srvD.srv.Freeze() } if kern.cfg.GetConfig(kernel.ConfigSimpleMode).(bool) { kern.SetGlobalLogLevel(defaultSimpleLogLevel) } |
︙ | ︙ | |||
216 217 218 219 220 221 222 | } } func (kern *myKernel) RetrieveLogEntries() []kernel.LogEntry { return kern.logWriter.retrieveLogEntries() } | < < < < | 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | } } func (kern *myKernel) RetrieveLogEntries() []kernel.LogEntry { return kern.logWriter.retrieveLogEntries() } // LogRecover outputs some information about the previous panic. func (kern *myKernel) LogRecover(name string, recoverInfo interface{}) bool { return kern.doLogRecover(name, recoverInfo) } func (kern *myKernel) doLogRecover(name string, recoverInfo interface{}) bool { stack := debug.Stack() kern.logger.Fatal().Str("recovered_from", fmt.Sprint(recoverInfo)).Bytes("stack", stack).Msg(name) |
︙ | ︙ |
Changes to kernel/impl/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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | //----------------------------------------------------------------------------- // Copyright (c) 2021 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package impl import ( "os" "sync" "time" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" ) // kernelLogWriter adapts an io.Writer to a LogWriter type kernelLogWriter struct { mx sync.RWMutex // protects buf, serializes w.Write and retrieveLogEntries buf []byte writePos int data []logEntry full bool } // newKernelLogWriter creates a new LogWriter for kernel logging. func newKernelLogWriter(capacity int) *kernelLogWriter { if capacity < 1 { capacity = 1 } return &kernelLogWriter{ buf: make([]byte, 0, 500), data: make([]logEntry, capacity), } } func (klw *kernelLogWriter) WriteMessage(level logger.Level, ts time.Time, prefix, msg string, details []byte) error { klw.mx.Lock() if level > logger.DebugLevel { klw.data[klw.writePos] = logEntry{ level: level, ts: ts, prefix: prefix, msg: msg, details: append([]byte(nil), details...), } |
︙ | ︙ | |||
140 141 142 143 144 145 146 | for j := 0; j < klw.writePos; j++ { copyE2E(&result[pos], &klw.data[j]) pos++ } return result } | < < < < < < | 137 138 139 140 141 142 143 144 145 146 147 148 149 | for j := 0; j < klw.writePos; j++ { copyE2E(&result[pos], &klw.data[j]) pos++ } return result } func copyE2E(result *kernel.LogEntry, origin *logEntry) { result.Level = origin.level result.TS = origin.ts result.Prefix = origin.prefix result.Message = origin.msg + string(origin.details) } |
Changes to kernel/impl/web.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 | //----------------------------------------------------------------------------- // 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 import ( | < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //----------------------------------------------------------------------------- // 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 import ( "net" "strconv" "strings" "sync" "time" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" |
︙ | ︙ | |||
33 34 35 36 37 38 39 | srvw server.Server setupServer kernel.SetupWebServerFunc } func (ws *webService) Initialize(logger *logger.Logger) { ws.logger = logger ws.descr = descriptionMap{ | < < < < < < < < < < < < < < < < < < < < < | 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | srvw server.Server setupServer kernel.SetupWebServerFunc } func (ws *webService) Initialize(logger *logger.Logger) { ws.logger = logger ws.descr = descriptionMap{ kernel.WebListenAddress: { "Listen address", func(val string) interface{} { host, port, err := net.SplitHostPort(val) if err != nil { return nil } |
︙ | ︙ | |||
92 93 94 95 96 97 98 | } return nil }, true, }, } ws.next = interfaceMap{ | < < | 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | } return nil }, true, }, } ws.next = interfaceMap{ kernel.WebListenAddress: "127.0.0.1:23123", kernel.WebMaxRequestSize: int64(16 * 1024 * 1024), kernel.WebPersistentCookie: false, kernel.WebSecureCookie: true, kernel.WebTokenLifetimeAPI: 1 * time.Hour, kernel.WebTokenLifetimeHTML: 10 * time.Minute, kernel.WebURLPrefix: "/", |
︙ | ︙ | |||
120 121 122 123 124 125 126 | } return secs } return defDur } } | < < < < < < < < < | | | | 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 | } return secs } return defDur } } func (ws *webService) GetLogger() *logger.Logger { return ws.logger } func (ws *webService) Start(kern *myKernel) error { listenAddr := ws.GetNextConfig(kernel.WebListenAddress).(string) urlPrefix := ws.GetNextConfig(kernel.WebURLPrefix).(string) persistentCookie := ws.GetNextConfig(kernel.WebPersistentCookie).(bool) secureCookie := ws.GetNextConfig(kernel.WebSecureCookie).(bool) maxRequestSize := ws.GetNextConfig(kernel.WebMaxRequestSize).(int64) if maxRequestSize < 1024 { maxRequestSize = 1024 } srvw := impl.New(ws.logger, listenAddr, urlPrefix, persistentCookie, secureCookie, maxRequestSize, kern.auth.manager) err := kern.web.setupServer(srvw, kern.box.manager, kern.auth.manager, kern.cfg.rtConfig) if err != nil { ws.logger.Fatal().Err(err).Msg("Unable to create") return err } if kern.core.GetNextConfig(kernel.CoreDebug).(bool) { srvw.SetDebug() } if err = srvw.Run(); err != nil { ws.logger.Fatal().Err(err).Msg("Unable to start") return err } ws.logger.Info().Str("listen", listenAddr).Msg("Start Service") ws.mxService.Lock() ws.srvw = srvw ws.mxService.Unlock() if kern.cfg.GetConfig(kernel.ConfigSimpleMode).(bool) { listenAddr := ws.GetNextConfig(kernel.WebListenAddress).(string) if idx := strings.LastIndexByte(listenAddr, ':'); idx >= 0 { |
︙ | ︙ |
Changes to kernel/kernel.go.
︙ | ︙ | |||
22 23 24 25 26 27 28 | "zettelstore.de/z/domain/id" "zettelstore.de/z/logger" "zettelstore.de/z/web/server" ) // Kernel is the main internal service. type Kernel interface { | < < < < | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | "zettelstore.de/z/domain/id" "zettelstore.de/z/logger" "zettelstore.de/z/web/server" ) // Kernel is the main internal service. type Kernel interface { // Start the service. Start(headline bool, lineServer bool) // WaitForShutdown blocks the call until Shutdown is called. WaitForShutdown() // Shutdown the service. Waits for all concurrent activities to stop. |
︙ | ︙ | |||
75 76 77 78 79 80 81 | // SetLevel sets the logging level for the given service. SetLevel(Service, logger.Level) // RetrieveLogEntries returns all buffered log entries. RetrieveLogEntries() []LogEntry | < < < | 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | // SetLevel sets the logging level for the given service. SetLevel(Service, logger.Level) // RetrieveLogEntries returns all buffered log entries. RetrieveLogEntries() []LogEntry // StartService start the given service. StartService(Service) error // RestartService stops and restarts the given service, while maintaining service dependencies. RestartService(Service) error // StopService stop the given service. |
︙ | ︙ | |||
134 135 136 137 138 139 140 | CoreDebug = "debug" CoreGoArch = "go-arch" CoreGoOS = "go-os" CoreGoVersion = "go-version" CoreHostname = "hostname" CorePort = "port" CoreProgname = "progname" | < < | 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | CoreDebug = "debug" CoreGoArch = "go-arch" CoreGoOS = "go-os" CoreGoVersion = "go-version" CoreHostname = "hostname" CorePort = "port" CoreProgname = "progname" CoreVerbose = "verbose" CoreVersion = "version" ) // Defined values for core service. const ( CoreDefaultVersion = "unknown" ) |
︙ | ︙ | |||
170 171 172 173 174 175 176 | const ( BoxDirTypeNotify = "notify" BoxDirTypeSimple = "simple" ) // Constants for web service keys. const ( | < < | 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | const ( BoxDirTypeNotify = "notify" BoxDirTypeSimple = "simple" ) // Constants for web service keys. const ( WebListenAddress = "listen" WebPersistentCookie = "persistent" WebMaxRequestSize = "max-request-size" WebSecureCookie = "secure" WebTokenLifetimeAPI = "api-lifetime" WebTokenLifetimeHTML = "html-lifetime" WebURLPrefix = "prefix" |
︙ | ︙ |
Changes to logger/logger.go.
1 | //----------------------------------------------------------------------------- | | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // Copyright (c) 2021 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package logger implements a logging package for use in the Zettelstore. |
︙ | ︙ | |||
224 225 226 227 228 229 230 | context: l.context, topParent: l.topParent, uProvider: up, } } func (l *Logger) writeMessage(level Level, msg string, details []byte) error { | | | 224 225 226 227 228 229 230 231 232 | context: l.context, topParent: l.topParent, uProvider: up, } } func (l *Logger) writeMessage(level Level, msg string, details []byte) error { return l.topParent.lw.WriteMessage(level, time.Now(), l.prefix, msg, details) } |
Changes to parser/parser.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 | // under this license. //----------------------------------------------------------------------------- // Package parser provides a generic interface to a range of different parsers. package parser import ( | < | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // under this license. //----------------------------------------------------------------------------- // Package parser provides a generic interface to a range of different parsers. package parser import ( "fmt" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/config" "zettelstore.de/z/domain" "zettelstore.de/z/domain/meta" |
︙ | ︙ | |||
116 117 118 119 120 121 122 | func ParseMetadataNoLink(value string) ast.InlineSlice { in := ParseMetadata(value) cleaner.CleanInlineLinks(&in) return in } // ParseZettel parses the zettel based on the syntax. | | | | 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | func ParseMetadataNoLink(value string) ast.InlineSlice { in := ParseMetadata(value) cleaner.CleanInlineLinks(&in) return in } // ParseZettel parses the zettel based on the syntax. func ParseZettel(zettel domain.Zettel, syntax string, rtConfig config.Config) *ast.ZettelNode { m := zettel.Meta inhMeta := m if rtConfig != nil { inhMeta = rtConfig.AddDefaultValues(inhMeta) } if syntax == "" { syntax, _ = inhMeta.Get(api.KeySyntax) } parseMeta := inhMeta if syntax == api.ValueSyntaxNone { parseMeta = m |
︙ | ︙ |
Deleted parser/pikchr/internal/ORIG_LICENSE.
|
| < < < < < < < < < < < < < < < < < < < < < |
Deleted parser/pikchr/internal/README.txt.
|
| < < < < < < < < < < < < < < < < < < < < < |
Deleted parser/pikchr/internal/pikchr.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted parser/pikchr/internal/pikchr.y.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted parser/pikchr/pikchr.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to parser/zettelmark/block.go.
︙ | ︙ | |||
639 640 641 642 643 644 645 | posA, posE := inp.Pos, 0 loop: for { switch inp.Ch { case input.EOS: return nil, false case '\n', '\r', ' ', '\t': | | | 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 | posA, posE := inp.Pos, 0 loop: for { switch inp.Ch { case input.EOS: return nil, false case '\n', '\r', ' ', '\t': if !hasSearchPrefix(inp.Src[posA:]) { return nil, false } case '\\': inp.Next() switch inp.Ch { case input.EOS, '\n', '\r': return nil, false |
︙ | ︙ | |||
665 666 667 668 669 670 671 | if inp.Ch != '}' { continue } break loop } inp.Next() } | < < | | 665 666 667 668 669 670 671 672 673 674 675 676 | if inp.Ch != '}' { continue } break loop } inp.Next() } inp.SkipToEOL() refText := string(inp.Src[posA:posE]) ref := ast.ParseReference(refText) return &ast.TranscludeNode{Ref: ref}, true } |
Changes to parser/zettelmark/inline.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 | package zettelmark import ( "bytes" "fmt" "strings" | < < | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package zettelmark import ( "bytes" "fmt" "strings" "zettelstore.de/z/ast" "zettelstore.de/z/input" ) // parseInlineSlice parses a sequence of Inlines until EOS. func (cp *zmkP) parseInlineSlice() (ins ast.InlineSlice) { inp := cp.inp |
︙ | ︙ | |||
164 165 166 167 168 169 170 | Attrs: attrs, }, true } } return nil, false } | | | | < | | | | | | | | | | | | | | | | | | | | | | | < | 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 | Attrs: attrs, }, true } } return nil, false } func hasSearchPrefix(src []byte) bool { return len(src) > len(ast.SearchPrefix) && string(src[:len(ast.SearchPrefix)]) == ast.SearchPrefix } func (cp *zmkP) parseReference(closeCh rune) (ref string, is ast.InlineSlice, ok bool) { inp := cp.inp inp.Next() cp.skipSpace() pos := inp.Pos hasSpace, ok := cp.readReferenceToSep(closeCh) if !ok { return "", nil, false } if inp.Ch == '|' { // First part must be inline text if pos == inp.Pos { // [[| or {{| return "", nil, false } cp.inp = input.NewInput(inp.Src[pos:inp.Pos]) for { in := cp.parseInline() if in == nil { break } is = append(is, in) } cp.inp = inp inp.Next() } else { if hasSpace && !hasSearchPrefix(inp.Src[pos:]) { return "", nil, false } inp.SetPos(pos) } cp.skipSpace() pos = inp.Pos if !cp.readReferenceToClose(closeCh) { return "", nil, false } |
︙ | ︙ | |||
261 262 263 264 265 266 267 | inp := cp.inp pos := inp.Pos for { switch inp.Ch { case input.EOS: return false case '\t', '\r', '\n', ' ': | | | 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 | inp := cp.inp pos := inp.Pos for { switch inp.Ch { case input.EOS: return false case '\t', '\r', '\n', ' ': if !hasSearchPrefix(inp.Src[pos:]) { return false } case '\\': inp.Next() switch inp.Ch { case input.EOS, '\n', '\r': return false |
︙ | ︙ | |||
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 | if !ok { panic(fmt.Sprintf("%q is not a formatting char", fch)) } inp.Next() // read 2nd formatting character if inp.Ch != fch { return nil, false } inp.Next() var buf bytes.Buffer for { if inp.Ch == input.EOS { return nil, false } if inp.Ch == fch { if inp.Peek() == fch { inp.Next() inp.Next() | > | > > < < < < < < < < < < < < < < | 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 | if !ok { panic(fmt.Sprintf("%q is not a formatting char", fch)) } inp.Next() // read 2nd formatting character if inp.Ch != fch { return nil, false } litn := &ast.LiteralNode{Kind: kind} inp.Next() var buf bytes.Buffer for { if inp.Ch == input.EOS { return nil, false } if inp.Ch == fch { if inp.Peek() == fch { inp.Next() inp.Next() litn.Attrs = cp.parseInlineAttributes() litn.Content = buf.Bytes() return litn, true } buf.WriteRune(fch) inp.Next() } else { tn := cp.parseText() buf.WriteString(tn.Text) } } } func (cp *zmkP) parseLiteralMath() (res ast.InlineNode, success bool) { inp := cp.inp inp.Next() // read 2nd formatting character if inp.Ch != '$' { return nil, false } inp.Next() |
︙ | ︙ |
Changes to parser/zettelmark/zettelmark_test.go.
︙ | ︙ | |||
160 161 162 163 164 165 166 | {"[[\\]]]", "(PARA (LINK %5C%5D))"}, {"[[\\]|a]]", "(PARA (LINK a ]))"}, {"[[b\\]|a]]", "(PARA (LINK a b]))"}, {"[[\\]\\||a]]", "(PARA (LINK a ]|))"}, {"[[http://a]]", "(PARA (LINK http://a))"}, {"[[http://a|http://a]]", "(PARA (LINK http://a http://a))"}, {"[[[[a]]]]", "(PARA (LINK [[a) ]])"}, | | | < | | < | 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | {"[[\\]]]", "(PARA (LINK %5C%5D))"}, {"[[\\]|a]]", "(PARA (LINK a ]))"}, {"[[b\\]|a]]", "(PARA (LINK a b]))"}, {"[[\\]\\||a]]", "(PARA (LINK a ]|))"}, {"[[http://a]]", "(PARA (LINK http://a))"}, {"[[http://a|http://a]]", "(PARA (LINK http://a http://a))"}, {"[[[[a]]]]", "(PARA (LINK [[a) ]])"}, {"[[search:title]]", "(PARA (LINK search:title))"}, {"[[search:title syntax]]", "(PARA (LINK search:title syntax))"}, {"[[Text|search:title]]", "(PARA (LINK search:title Text))"}, {"[[Text|search:title syntax]]", "(PARA (LINK search:title syntax Text))"}, }) } func TestCite(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"[@", "(PARA [@)"}, |
︙ | ︙ | |||
677 678 679 680 681 682 683 | {"|a|b\n|c|d", "(TAB (TR (TD a)(TD b))(TR (TD c)(TD d)))"}, {"|%", ""}, {"|a|b\n|%---\n|c|d", "(TAB (TR (TD a)(TD b))(TR (TD c)(TD d)))"}, {"|a|b\n|c", "(TAB (TR (TD a)(TD b))(TR (TD c)(TD)))"}, }) } | | | | < | 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 | {"|a|b\n|c|d", "(TAB (TR (TD a)(TD b))(TR (TD c)(TD d)))"}, {"|%", ""}, {"|a|b\n|%---\n|c|d", "(TAB (TR (TD a)(TD b))(TR (TD c)(TD d)))"}, {"|a|b\n|c", "(TAB (TR (TD a)(TD b))(TR (TD c)(TD)))"}, }) } func TestBlockEmbed(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"{{{a}}}", "(TRANSCLUDE a)"}, {"{{{a}}}b", "(TRANSCLUDE a)"}, {"{{{a}}}}", "(TRANSCLUDE a)"}, {"{{{a\\}}}}", "(TRANSCLUDE a%5C%7D)"}, {"{{{a\\}}}}b", "(TRANSCLUDE a%5C%7D)"}, {"{{{a}}", "(PARA (EMBED %7Ba))"}, }) } func TestBlockAttr(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {":::go\n:::", "(SPAN)[ATTR =go]"}, |
︙ | ︙ | |||
876 877 878 879 880 881 882 | } tv.buf.WriteString(")") } } tv.buf.WriteString(")") case *ast.TranscludeNode: fmt.Fprintf(&tv.buf, "(TRANSCLUDE %v)", n.Ref) | < | 873 874 875 876 877 878 879 880 881 882 883 884 885 886 | } tv.buf.WriteString(")") } } tv.buf.WriteString(")") case *ast.TranscludeNode: fmt.Fprintf(&tv.buf, "(TRANSCLUDE %v)", n.Ref) case *ast.BLOBNode: tv.buf.WriteString("(BLOB ") tv.buf.WriteString(n.Syntax) tv.buf.WriteString(")") case *ast.TextNode: tv.buf.WriteString(n.Text) case *ast.TagNode: |
︙ | ︙ |
Deleted query/parser.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted query/parser_test.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted query/print.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted query/query.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted query/retrieve.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted query/select.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted query/select_test.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted query/sorter.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added search/parser.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 | //----------------------------------------------------------------------------- // 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 search import ( "strconv" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" ) // Parse the search specification and return a Search object. func Parse(spec string) *Search { state := parserState{ inp: input.NewInput([]byte(spec)), } return state.parse() } type parserState struct { inp *input.Input } func (ps *parserState) mustStop() bool { return ps.inp.Ch == input.EOS } func (ps *parserState) acceptSingleKw(s string) bool { return ps.inp.Accept(s) && (ps.isSpace() || ps.mustStop()) } func (ps *parserState) acceptKwArgs(s string) bool { if ps.inp.Accept(s) && ps.isSpace() { ps.skipSpace() return true } return false } const ( kwLimit = "LIMIT" kwNegate = "NEGATE" kwOffset = "OFFSET" kwOrder = "ORDER" kwRandom = "RANDOM" kwReverse = "REVERSE" ) func (ps *parserState) parse() *Search { inp := ps.inp var result *Search for { ps.skipSpace() if ps.mustStop() { break } pos := inp.Pos if ps.acceptSingleKw(kwNegate) { result = createIfNeeded(result) result.negate = !result.negate continue } if ps.acceptSingleKw(kwRandom) { result = createIfNeeded(result) if len(result.order) == 0 { result.order = []sortOrder{{"", false}} } continue } if ps.acceptKwArgs(kwOrder) { if s, ok := ps.parseOrder(result); ok { result = s continue } } if ps.acceptKwArgs(kwOffset) { if s, ok := ps.parseOffset(result); ok { result = s continue } } if ps.acceptKwArgs(kwLimit) { if s, ok := ps.parseLimit(result); ok { result = s continue } } inp.SetPos(pos) result = ps.parseText(result) } return result } func (ps *parserState) parseOrder(s *Search) (*Search, bool) { reverse := false if ps.acceptKwArgs(kwReverse) { reverse = true } word := ps.scanWord() if len(word) == 0 { return s, false } if sWord := string(word); meta.KeyIsValid(sWord) { s = createIfNeeded(s) if len(s.order) == 1 && s.order[0].isRandom() { s.order = nil } s.order = append(s.order, sortOrder{sWord, reverse}) return s, true } return s, false } func (ps *parserState) parseOffset(s *Search) (*Search, bool) { num, ok := ps.scanPosInt() if !ok { return s, false } s = createIfNeeded(s) if s.offset <= num { s.offset = num } return s, true } func (ps *parserState) parseLimit(s *Search) (*Search, bool) { num, ok := ps.scanPosInt() if !ok { return s, false } s = createIfNeeded(s) if s.limit == 0 || s.limit >= num { s.limit = num } return s, true } func (ps *parserState) parseText(s *Search) *Search { hasOp, cmpOp, cmpNegate := ps.scanSearchOp() text, key := ps.scanSearchTextOrKey(hasOp) if key != nil { // Assert: hasOp == false hasOp, cmpOp, cmpNegate = ps.scanSearchOp() // Assert hasOp == true text = ps.scanWord() } else if text == nil { // Only an empty search operation is found -> ignore it return s } s = createIfNeeded(s) if hasOp { s.addExpValue(string(key), expValue{string(text), cmpOp, cmpNegate}) } else { // Assert key == nil s.addExpValue("", expValue{string(text), cmpDefault, false}) } return s } func (ps *parserState) scanSearchTextOrKey(hasOp bool) ([]byte, []byte) { inp := ps.inp pos := inp.Pos allowKey := !hasOp for !ps.isSpace() && !ps.mustStop() { if allowKey { switch inp.Ch { case '!', ':', '=', '>', '<', '~': allowKey = false if key := inp.Src[pos:inp.Pos]; meta.KeyIsValid(string(key)) { return nil, key } } } inp.Next() } return inp.Src[pos:inp.Pos], nil } func (ps *parserState) scanWord() []byte { inp := ps.inp pos := inp.Pos for !ps.isSpace() && !ps.mustStop() { inp.Next() } return inp.Src[pos:inp.Pos] } func (ps *parserState) scanPosInt() (int, bool) { inp := ps.inp ch := inp.Ch if ch == '0' { ch = inp.Next() if isSpace(ch) || ps.mustStop() { return 0, true } return 0, false } word := ps.scanWord() if len(word) == 0 { return 0, false } uval, err := strconv.ParseUint(string(word), 10, 63) if err != nil { return 0, false } return int(uval), true } func (ps *parserState) scanSearchOp() (bool, compareOp, bool) { inp := ps.inp ch := inp.Ch negate := false if ch == '!' { ch = inp.Next() negate = true } switch ch { case ':': inp.Next() return true, cmpDefault, negate case '=': inp.Next() return true, cmpEqual, negate case '<': inp.Next() return true, cmpSuffix, negate case '>': inp.Next() return true, cmpPrefix, negate case '~': inp.Next() return true, cmpContains, negate } if negate { return true, cmpDefault, true } return false, cmpUnknown, false } func (ps *parserState) isSpace() bool { return isSpace(ps.inp.Ch) } func isSpace(ch rune) bool { switch ch { case input.EOS: return false case ' ', '\t', '\n', '\r': return true } return input.IsSpace(ch) } func (ps *parserState) skipSpace() { for ps.isSpace() { ps.inp.Next() } } |
Added search/parser_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 | //----------------------------------------------------------------------------- // 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 search_test import ( "testing" "zettelstore.de/z/search" ) func TestParser(t *testing.T) { t.Parallel() testcases := []struct { spec string exp string }{ {"", ""}, {`a`, `a`}, {`!a`, `!a`}, {`:a`, `a`}, {`!:a`, `!a`}, {`=a`, `=a`}, {`!=a`, `!=a`}, {`>a`, `>a`}, {`!>a`, `!>a`}, {`<a`, `<a`}, {`!<a`, `!<a`}, {`~a`, `a`}, {`!~a`, `!a`}, {`key:`, `key:`}, {`key!:`, `key!:`}, {`key=`, `key=`}, {`key!=`, `key!=`}, {`key>`, `key>`}, {`key!>`, `key!>`}, {`key<`, `key<`}, {`key!<`, `key!<`}, {`key~`, `key~`}, {`key!~`, `key!~`}, {`key:a`, `key:a`}, {`key!:a`, `key!:a`}, {`key=a`, `key=a`}, {`key!=a`, `key!=a`}, {`key>a`, `key>a`}, {`key!>a`, `key!>a`}, {`key<a`, `key<a`}, {`key!<a`, `key!<a`}, {`key~a`, `key~a`}, {`key!~a`, `key!~a`}, {`key1:a key2:b`, `key1:a key2:b`}, {`key1: key2:b`, `key1: key2:b`}, {`NEGATE`, `NEGATE`}, {`NEGATE a`, `NEGATE a`}, {`a NEGATE`, `NEGATE a`}, {`NEGATE NEGATE a`, `a`}, {`NEGATENEGATE a`, `NEGATENEGATE a`}, {`RANDOM`, `RANDOM`}, {`RANDOM a`, `a RANDOM`}, {`a RANDOM`, `a RANDOM`}, {`RANDOM RANDOM a`, `a RANDOM`}, {`RANDOMRANDOM a`, `RANDOMRANDOM a`}, {`a RANDOMRANDOM`, `a RANDOMRANDOM`}, {`ORDER`, `ORDER`}, {"ORDER a b", "b ORDER a"}, {"a ORDER", "a ORDER"}, {"ORDER ?", "ORDER ?"}, {"ORDER a ?", "? ORDER a"}, {"ORDER REVERSE", "ORDER REVERSE"}, {"ORDER REVERSE a b", "b ORDER REVERSE a"}, {"a RANDOM ORDER b", "a ORDER b"}, {"a ORDER b RANDOM", "a ORDER b"}, {"OFFSET", "OFFSET"}, {"OFFSET a", "OFFSET a"}, {"OFFSET 10 a", "a OFFSET 10"}, {"OFFSET 01 a", "OFFSET 01 a"}, {"OFFSET 0 a", "a"}, {"a OFFSET 0", "a"}, {"OFFSET 4 OFFSET 8", "OFFSET 8"}, {"OFFSET 8 OFFSET 4", "OFFSET 8"}, {"LIMIT", "LIMIT"}, {"LIMIT a", "LIMIT a"}, {"LIMIT 10 a", "a LIMIT 10"}, {"LIMIT 01 a", "LIMIT 01 a"}, {"LIMIT 0 a", "a"}, {"a LIMIT 0", "a"}, {"LIMIT 4 LIMIT 8", "LIMIT 4"}, {"LIMIT 8 LIMIT 4", "LIMIT 4"}, } for i, tc := range testcases { got := search.Parse(tc.spec).String() if tc.exp != got { t.Errorf("%d: Parse(%q) does not yield %q, but got %q", i, tc.spec, tc.exp, got) continue } gotReparse := search.Parse(got).String() if gotReparse != got { t.Errorf("%d: Parse(%q) does not yield itself, but %q", i, got, gotReparse) } } } |
Added search/print.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 | //----------------------------------------------------------------------------- // 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 search provides a zettel search. package search import ( "io" "strconv" "strings" "zettelstore.de/c/api" "zettelstore.de/c/maps" ) func (s *Search) String() string { var sb strings.Builder s.Print(&sb) return sb.String() } // Print the search in a parseable form. func (s *Search) Print(w io.Writer) { if s == nil { return } env := printEnv{w: w} if s.negate { io.WriteString(w, kwNegate) env.space = true } if len(s.search) > 0 { env.printExprValues("", s.search) } for _, name := range maps.Keys(s.mvals) { env.printExprValues(name, s.mvals[name]) } env.printOrder(s.order) env.printPosInt(kwOffset, s.offset) env.printPosInt(kwLimit, s.limit) } type printEnv struct { w io.Writer space bool } var bsSpace = []byte{' '} func (pe *printEnv) printSpace() { if pe.space { pe.w.Write(bsSpace) return } pe.space = true } func (pe *printEnv) writeString(s string) { io.WriteString(pe.w, s) } func (pe *printEnv) printExprValues(key string, values []expValue) { for _, val := range values { pe.printSpace() pe.writeString(key) if val.negate { pe.writeString("!") } switch val.op { case cmpDefault: pe.writeString(":") case cmpEqual: pe.writeString("=") case cmpPrefix: pe.writeString(">") case cmpSuffix: pe.writeString("<") case cmpContains: // An empty key signals a full-text search. Since "~" is the default op in this case, // it can be ignored. Therefore, print only "~" if there is a key. if key != "" { pe.writeString("~") } } if s := val.value; s != "" { pe.writeString(s) } } } func (s *Search) Human() string { var sb strings.Builder s.PrintHuman(&sb) return sb.String() } // PrintHuman the search to a writer in a human readable form. func (s *Search) PrintHuman(w io.Writer) { if s == nil { return } env := printEnv{w: w} if s.negate { env.writeString("NOT (") } if len(s.search) > 0 { env.writeString("ANY") env.printHumanSelectExprValues(s.search) env.space = true } for _, name := range maps.Keys(s.mvals) { if env.space { env.writeString(" AND ") } env.writeString(name) env.printHumanSelectExprValues(s.mvals[name]) env.space = true } if s.negate { env.writeString(")") env.space = true } env.printOrder(s.order) env.printPosInt(kwOffset, s.offset) env.printPosInt(kwLimit, s.limit) } func (pe *printEnv) printHumanSelectExprValues(values []expValue) { if len(values) == 0 { pe.writeString(" MATCH ANY") return } for j, val := range values { if j > 0 { pe.writeString(" AND") } if val.negate { pe.writeString(" NOT") } switch val.op { case cmpDefault: pe.writeString(" MATCH ") case cmpEqual: pe.writeString(" EQUAL ") case cmpPrefix: pe.writeString(" PREFIX ") case cmpSuffix: pe.writeString(" SUFFIX ") case cmpContains: pe.writeString(" CONTAINS ") default: pe.writeString(" MaTcH ") } if val.value == "" { pe.writeString("ANY") } else { pe.writeString(val.value) } } } func (pe *printEnv) printOrder(order []sortOrder) { for _, o := range order { if o.isRandom() { pe.printSpace() pe.writeString(kwRandom) continue } else if o.key == api.KeyID && o.descending { continue } pe.printSpace() pe.writeString(kwOrder) if o.descending { pe.printSpace() pe.writeString(kwReverse) } pe.printSpace() pe.writeString(o.key) } } func (pe *printEnv) printPosInt(key string, val int) { if val > 0 { pe.printSpace() pe.writeString(key) pe.writeString(" ") pe.writeString(strconv.Itoa(val)) } } |
Added search/retrieve.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 | //----------------------------------------------------------------------------- // 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 search // This file contains helper functions to search within the index. import ( "fmt" "strings" "zettelstore.de/z/domain/id" "zettelstore.de/z/strfun" ) type searchOp struct { s string op compareOp } type searchFunc func(string) id.Set type searchCallMap map[searchOp]searchFunc var cmpPred = map[compareOp]func(string, string) bool{ cmpEqual: func(s, t string) bool { return s == t }, cmpPrefix: strings.HasPrefix, cmpSuffix: strings.HasSuffix, cmpContains: strings.Contains, } func (scm searchCallMap) addSearch(s string, op compareOp, sf searchFunc) { pred := cmpPred[op] for k := range scm { if op == cmpContains { if strings.Contains(k.s, s) { return } if strings.Contains(s, k.s) { delete(scm, k) break } } if k.op != op { continue } if pred(k.s, s) { return } if pred(s, k.s) { delete(scm, k) } } scm[searchOp{s: s, op: op}] = sf } func alwaysIncluded(id.Zid) bool { return true } func neverIncluded(id.Zid) bool { return false } func prepareRetrieveCalls(searcher Searcher, search []expValue) (normCalls, plainCalls, negCalls searchCallMap) { normCalls = make(searchCallMap, len(search)) negCalls = make(searchCallMap, len(search)) for _, val := range search { for _, word := range strfun.NormalizeWords(val.value) { sf := getSearchFunc(searcher, val.op) if val.negate { negCalls.addSearch(word, val.op, sf) } else { normCalls.addSearch(word, val.op, sf) } } } plainCalls = make(searchCallMap, len(search)) for _, val := range search { word := strings.ToLower(strings.TrimSpace(val.value)) sf := getSearchFunc(searcher, val.op) if val.negate { negCalls.addSearch(word, val.op, sf) } else { plainCalls.addSearch(word, val.op, sf) } } return normCalls, plainCalls, negCalls } func hasConflictingCalls(normCalls, plainCalls, negCalls searchCallMap) bool { for val := range negCalls { if _, found := normCalls[val]; found { return true } if _, found := plainCalls[val]; found { return true } } return false } func retrievePositives(normCalls, plainCalls searchCallMap) id.Set { if isSuperset(normCalls, plainCalls) { var normResult id.Set for c, sf := range normCalls { normResult = normResult.IntersectOrSet(sf(c.s)) } return normResult } type searchResults map[searchOp]id.Set var cache searchResults var plainResult id.Set for c, sf := range plainCalls { result := sf(c.s) if _, found := normCalls[c]; found { if cache == nil { cache = make(searchResults) } cache[c] = result } plainResult = plainResult.IntersectOrSet(result) } var normResult id.Set for c, sf := range normCalls { if cache != nil { if result, found := cache[c]; found { normResult = normResult.IntersectOrSet(result) continue } } normResult = normResult.IntersectOrSet(sf(c.s)) } return normResult.Add(plainResult) } func isSuperset(normCalls, plainCalls searchCallMap) bool { for c := range plainCalls { if _, found := normCalls[c]; !found { return false } } return true } func retrieveNegatives(negCalls searchCallMap) id.Set { var negatives id.Set for val, sf := range negCalls { negatives = negatives.Add(sf(val.s)) } return negatives } func getSearchFunc(searcher Searcher, op compareOp) searchFunc { switch op { case cmpEqual: return searcher.SearchEqual case cmpPrefix: return searcher.SearchPrefix case cmpSuffix: return searcher.SearchSuffix case cmpContains: return searcher.SearchContains default: panic(fmt.Sprintf("Unexpected value of comparison operation: %v", op)) } } |
Added search/search.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 | //----------------------------------------------------------------------------- // 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 search provides a zettel search. package search import ( "fmt" "math/rand" "sort" "strings" "sync" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) // Searcher is used to select zettel identifier based on search criteria. type Searcher interface { // Select all zettel that contains the given exact word. // The word must be normalized through Unicode NKFD, trimmed and not empty. SearchEqual(word string) id.Set // Select all zettel that have a word with the given prefix. // The prefix must be normalized through Unicode NKFD, trimmed and not empty. SearchPrefix(prefix string) id.Set // Select all zettel that have a word with the given suffix. // The suffix must be normalized through Unicode NKFD, trimmed and not empty. SearchSuffix(suffix string) id.Set // Select all zettel that contains the given string. // The string must be normalized through Unicode NKFD, trimmed and not empty. SearchContains(s string) id.Set } // MetaMatchFunc is a function determine whethe some metadata should be selected or not. type MetaMatchFunc func(*meta.Meta) bool // RetrieveFunc retrieves the index based on a Search. type RetrieveFunc func() id.Set // RetrievePredicate returns true, if the given Zid is contained in the (full-text) search. type RetrievePredicate func(id.Zid) bool // Search specifies a mechanism for selecting zettel. type Search struct { mx sync.RWMutex // Protects other attributes // Fields to be used for selecting preMatch MetaMatchFunc // Match that must be true mvals expMetaValues // Expected values for a meta datum search []expValue // Search string negate bool // Negate the result of the whole selecting process // Fields to be used for sorting order []sortOrder offset int // <= 0: no offset limit int // <= 0: no limit } type sortOrder struct { key string descending bool } func (so *sortOrder) isRandom() bool { return so.key == "" } type expMetaValues map[string][]expValue func createIfNeeded(s *Search) *Search { if s == nil { return new(Search) } return s } // Clone the search value. func (s *Search) Clone() *Search { if s == nil { return nil } c := new(Search) c.preMatch = s.preMatch c.mvals = make(expMetaValues, len(s.mvals)) for k, v := range s.mvals { c.mvals[k] = v } c.search = append([]expValue{}, s.search...) c.negate = s.negate c.order = append([]sortOrder{}, s.order...) c.offset = s.offset c.limit = s.limit return c } // RandomOrder is a pseudo metadata key that selects a random order. const RandomOrder = "_random" type compareOp uint8 const ( cmpUnknown compareOp = iota cmpDefault cmpNotDefault cmpEqual cmpNotEqual cmpPrefix cmpNoPrefix cmpSuffix cmpNoSuffix cmpContains cmpNotContains ) var negateMap = map[compareOp]compareOp{ cmpUnknown: cmpUnknown, cmpDefault: cmpNotDefault, cmpNotDefault: cmpDefault, cmpEqual: cmpNotEqual, cmpNotEqual: cmpEqual, cmpPrefix: cmpNoPrefix, cmpNoPrefix: cmpPrefix, cmpSuffix: cmpNoSuffix, cmpNoSuffix: cmpSuffix, cmpContains: cmpNotContains, cmpNotContains: cmpContains, } func (op compareOp) negate() compareOp { return negateMap[op] } type expValue struct { value string op compareOp negate bool } // AddExpr adds a match expression to the search. func (s *Search) AddExpr(key, value string) *Search { val := parseOp(strings.TrimSpace(value)) if s == nil { s = new(Search) } s.mx.Lock() defer s.mx.Unlock() s.addExpValue(key, val) return s } func (s *Search) addExpValue(key string, val expValue) { if key == "" { s.addSearch(val) } else if s.mvals == nil { s.mvals = expMetaValues{key: {val}} } else { s.mvals[key] = append(s.mvals[key], val) } } func (s *Search) addSearch(val expValue) { if val.negate { val.op = val.op.negate() val.negate = false } switch val.op { case cmpDefault: val.op = cmpContains case cmpNotDefault: val.op = cmpContains val.negate = true case cmpNotEqual, cmpNoPrefix, cmpNoSuffix, cmpNotContains: val.op = val.op.negate() val.negate = true } s.search = append(s.search, val) } func parseOp(s string) expValue { if s == "" { return expValue{value: s, op: cmpDefault, negate: false} } if s[0] == '\\' { return expValue{value: s[1:], op: cmpDefault, negate: false} } negate := false if s[0] == '!' { negate = true s = s[1:] } if s == "" { return expValue{value: s, op: cmpDefault, negate: negate} } if s[0] == '\\' { return expValue{value: s[1:], op: cmpDefault, negate: negate} } switch s[0] { case ':': return expValue{value: s[1:], op: cmpDefault, negate: negate} case '=': return expValue{value: s[1:], op: cmpEqual, negate: negate} case '>': return expValue{value: s[1:], op: cmpPrefix, negate: negate} case '<': return expValue{value: s[1:], op: cmpSuffix, negate: negate} case '~': return expValue{value: s[1:], op: cmpContains, negate: negate} } return expValue{value: s, op: cmpDefault, negate: negate} } // SetNegate changes the search to reverse its selection. func (s *Search) SetNegate() *Search { s = createIfNeeded(s) s.mx.Lock() defer s.mx.Unlock() s.negate = true return s } // AddPreMatch adds the pre-selection predicate. func (s *Search) AddPreMatch(preMatch MetaMatchFunc) *Search { s = createIfNeeded(s) s.mx.Lock() defer s.mx.Unlock() if pre := s.preMatch; pre == nil { s.preMatch = preMatch } else { s.preMatch = func(m *meta.Meta) bool { return preMatch(m) && pre(m) } } return s } // AddOrder adds the given order to the search object. func (s *Search) AddOrder(key string, descending bool) *Search { s = createIfNeeded(s) s.mx.Lock() defer s.mx.Unlock() if len(s.order) > 0 { panic("order field already set: " + fmt.Sprintf("%v", s.order)) } if key == RandomOrder { s.order = []sortOrder{{"", false}} } else { s.order = []sortOrder{{key, descending}} } return s } // SetOffset sets the given offset of the search object. func (s *Search) SetOffset(offset int) *Search { s = createIfNeeded(s) s.mx.Lock() defer s.mx.Unlock() if offset < 0 { offset = 0 } s.offset = offset return s } // GetOffset returns the current offset value. func (s *Search) GetOffset() int { if s == nil { return 0 } s.mx.RLock() defer s.mx.RUnlock() return s.offset } // SetLimit sets the given limit of the search object. func (s *Search) SetLimit(limit int) *Search { s = createIfNeeded(s) s.mx.Lock() defer s.mx.Unlock() if limit < 0 { limit = 0 } s.limit = limit return s } // GetLimit returns the current offset value. func (s *Search) GetLimit() int { if s == nil { return 0 } s.mx.RLock() defer s.mx.RUnlock() return s.limit } // EnrichNeeded returns true, if the search references a metadata key that // is calculated via metadata enrichments. func (s *Search) EnrichNeeded() bool { if s == nil { return false } s.mx.RLock() defer s.mx.RUnlock() for key := range s.mvals { if meta.IsComputed(key) { return true } } for _, o := range s.order { if meta.IsComputed(o.key) { return true } } return false } // RetrieveAndCompileMatch queries the search index and returns a predicate // for its results and returns a matching predicate. func (s *Search) RetrieveAndCompileMatch(searcher Searcher) (RetrievePredicate, MetaMatchFunc) { if s == nil { return alwaysIncluded, matchAlways } s = s.Clone() match := s.compileMatch() // Match might add some searches var pred RetrievePredicate if searcher != nil { pred = s.retrieveIndex(searcher) } if pred == nil { if match == nil { if s.negate { return neverIncluded, matchNever } return alwaysIncluded, matchAlways } return alwaysIncluded, match } if match == nil { return pred, matchAlways } return pred, match } // retrieveIndex and return a predicate to ask for results. func (s *Search) retrieveIndex(searcher Searcher) RetrievePredicate { if len(s.search) == 0 { return nil } normCalls, plainCalls, negCalls := prepareRetrieveCalls(searcher, s.search) if hasConflictingCalls(normCalls, plainCalls, negCalls) { return s.neverWithNegate() } negate := s.negate positives := retrievePositives(normCalls, plainCalls) if positives == nil { // No positive search for words, must contain only words for a negative search. // Otherwise len(search) == 0 (see above) negatives := retrieveNegatives(negCalls) return func(zid id.Zid) bool { return negatives.Contains(zid) == negate } } if len(positives) == 0 { // Positive search didn't found anything. We can omit the negative search. return s.neverWithNegate() } if len(negCalls) == 0 { // Positive search found something, but there is no negative search. return func(zid id.Zid) bool { return positives.Contains(zid) != negate } } negatives := retrieveNegatives(negCalls) return func(zid id.Zid) bool { return (positives.Contains(zid) && !negatives.Contains(zid)) != negate } } func (s *Search) neverWithNegate() RetrievePredicate { if s.negate { return alwaysIncluded } return neverIncluded } // compileMatch returns a function to match metadata based on select specification. func (s *Search) compileMatch() MetaMatchFunc { compMeta := s.compileMeta() preMatch := s.preMatch if compMeta == nil { if preMatch == nil { return nil } return preMatch } if s.negate { if preMatch == nil { return func(m *meta.Meta) bool { return !compMeta(m) } } return func(m *meta.Meta) bool { return preMatch(m) && !compMeta(m) } } if preMatch == nil { return compMeta } return func(m *meta.Meta) bool { return preMatch(m) && compMeta(m) } } func matchAlways(*meta.Meta) bool { return true } func matchNever(*meta.Meta) bool { return false } // Sort applies the sorter to the slice of meta data. func (s *Search) Sort(metaList []*meta.Meta) []*meta.Meta { if len(metaList) == 0 { return metaList } if s == nil { sort.Slice(metaList, func(i, j int) bool { return metaList[i].Zid > metaList[j].Zid }) return metaList } if len(s.order) == 0 { sort.Slice(metaList, createSortFunc(api.KeyID, true, metaList)) } else if s.order[0].isRandom() { rand.Shuffle(len(metaList), func(i, j int) { metaList[i], metaList[j] = metaList[j], metaList[i] }) } else { sort.Slice(metaList, createSortFunc(s.order[0].key, s.order[0].descending, metaList)) } if s.offset > 0 { if s.offset > len(metaList) { return nil } metaList = metaList[s.offset:] } return s.Limit(metaList) } // Limit returns only s.GetLimit() elements of the given list. func (s *Search) Limit(metaList []*meta.Meta) []*meta.Meta { if s == nil { return metaList } if s.limit > 0 && s.limit < len(metaList) { return metaList[:s.limit] } return metaList } |
Added search/select.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 | //----------------------------------------------------------------------------- // 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 search import ( "fmt" "strings" "zettelstore.de/z/domain/meta" ) type matchValueFunc func(value string) bool func matchValueNever(string) bool { return false } func matchValueAlways(string) bool { return true } type matchSpec struct { key string match matchValueFunc } // compileMeta calculates a selection func based on the given select criteria. func (s *Search) compileMeta() MetaMatchFunc { posSpecs, negSpecs, nomatch := s.createSelectSpecs() if len(posSpecs) > 0 || len(negSpecs) > 0 || len(nomatch) > 0 { return makeSearchMetaMatchFunc(posSpecs, negSpecs, nomatch) } return nil } func (s *Search) createSelectSpecs() (posSpecs, negSpecs []matchSpec, nomatch []string) { posSpecs = make([]matchSpec, 0, len(s.mvals)) negSpecs = make([]matchSpec, 0, len(s.mvals)) for key, values := range s.mvals { if !meta.KeyIsValid(key) { continue } if always, never := countEmptyValues(values); always+never > 0 { if never == 0 { posSpecs = append(posSpecs, matchSpec{key, matchValueAlways}) continue } if always == 0 { negSpecs = append(negSpecs, matchSpec{key, nil}) continue } // value must match always AND never, at the same time. This results in a no-match. nomatch = append(nomatch, key) continue } posMatch, negMatch := createPosNegMatchFunc( key, values, func(val string, op compareOp) { s.addSearch(expValue{value: val, op: op, negate: false}) }) if posMatch != nil { posSpecs = append(posSpecs, matchSpec{key, posMatch}) } if negMatch != nil { negSpecs = append(negSpecs, matchSpec{key, negMatch}) } } return posSpecs, negSpecs, nomatch } func countEmptyValues(values []expValue) (always, never int) { for _, v := range values { if v.value == "" { if v.negate { never++ } else { always++ } } } return always, never } type addSearchFunc func(val string, op compareOp) func createPosNegMatchFunc(key string, values []expValue, addSearch addSearchFunc) (posMatch, negMatch matchValueFunc) { posValues := make([]opValue, 0, len(values)) negValues := make([]opValue, 0, len(values)) for _, val := range values { if val.negate { negValues = append(negValues, opValue{value: val.value, op: val.op.negate()}) } else { posValues = append(posValues, opValue{value: val.value, op: val.op}) } } return createMatchFunc(key, posValues, addSearch), createMatchFunc(key, negValues, addSearch) } // opValue is an expValue, but w/o the field "negate" type opValue struct { value string op compareOp } func createMatchFunc(key string, values []opValue, addSearch addSearchFunc) matchValueFunc { if len(values) == 0 { return nil } switch meta.Type(key) { case meta.TypeCredential: return matchValueNever case meta.TypeID, meta.TypeTimestamp: // ID and timestamp use the same layout return createMatchIDFunc(values, addSearch) case meta.TypeIDSet: return createMatchIDSetFunc(values, addSearch) case meta.TypeTagSet: return createMatchTagSetFunc(values, addSearch) case meta.TypeWord: return createMatchWordFunc(values, addSearch) case meta.TypeWordSet: return createMatchWordSetFunc(values, addSearch) } return createMatchStringFunc(values, addSearch) } func createMatchIDFunc(values []opValue, addSearch addSearchFunc) matchValueFunc { preds := valuesToStringPredicates(values, cmpPrefix, addSearch) return func(value string) bool { for _, pred := range preds { if !pred(value) { return false } } return true } } func createMatchIDSetFunc(values []opValue, addSearch addSearchFunc) matchValueFunc { predList := valuesToStringSetPredicates(preprocessSet(values), cmpPrefix, addSearch) return func(value string) bool { ids := meta.ListFromValue(value) for _, preds := range predList { for _, pred := range preds { if !pred(ids) { return false } } } return true } } func createMatchTagSetFunc(values []opValue, addSearch addSearchFunc) matchValueFunc { predList := valuesToStringSetPredicates(processTagSet(preprocessSet(sliceToLower(values))), cmpEqual, addSearch) return func(value string) bool { tags := meta.ListFromValue(value) // Remove leading '#' from each tag for i, tag := range tags { tags[i] = meta.CleanTag(tag) } for _, preds := range predList { for _, pred := range preds { if !pred(tags) { return false } } } return true } } func processTagSet(valueSet [][]opValue) [][]opValue { result := make([][]opValue, len(valueSet)) for i, values := range valueSet { tags := make([]opValue, len(values)) for j, val := range values { if tval := val.value; tval != "" && tval[0] == '#' { tval = meta.CleanTag(tval) tags[j] = opValue{value: tval, op: resolveDefaultOp(val.op, cmpEqual)} } else { tags[j] = opValue{value: tval, op: resolveDefaultOp(val.op, cmpPrefix)} } } result[i] = tags } return result } func createMatchWordFunc(values []opValue, addSearch addSearchFunc) matchValueFunc { preds := valuesToStringPredicates(sliceToLower(values), cmpEqual, addSearch) return func(value string) bool { value = strings.ToLower(value) for _, pred := range preds { if !pred(value) { return false } } return true } } func createMatchWordSetFunc(values []opValue, addSearch addSearchFunc) matchValueFunc { predsList := valuesToStringSetPredicates(preprocessSet(sliceToLower(values)), cmpEqual, addSearch) return func(value string) bool { words := meta.ListFromValue(value) for _, preds := range predsList { for _, pred := range preds { if !pred(words) { return false } } } return true } } func createMatchStringFunc(values []opValue, addSearch addSearchFunc) matchValueFunc { preds := valuesToStringPredicates(sliceToLower(values), cmpContains, addSearch) return func(value string) bool { value = strings.ToLower(value) for _, pred := range preds { if !pred(value) { return false } } return true } } func sliceToLower(sl []opValue) []opValue { result := make([]opValue, 0, len(sl)) for _, s := range sl { result = append(result, opValue{ value: strings.ToLower(s.value), op: s.op, }) } return result } func preprocessSet(set []opValue) [][]opValue { result := make([][]opValue, 0, len(set)) for _, elem := range set { splitElems := strings.Split(elem.value, ",") valueElems := make([]opValue, 0, len(splitElems)) for _, se := range splitElems { e := strings.TrimSpace(se) if len(e) > 0 { valueElems = append(valueElems, opValue{value: e, op: elem.op}) } } if len(valueElems) > 0 { result = append(result, valueElems) } } return result } type stringPredicate func(string) bool func valuesToStringPredicates(values []opValue, defOp compareOp, addSearch addSearchFunc) []stringPredicate { result := make([]stringPredicate, len(values)) for i, v := range values { opVal := v.value // loop variable is used in closure --> save needed value op := resolveDefaultOp(v.op, defOp) switch op { case cmpEqual: addSearch(opVal, op) // addSearch only for positive selections result[i] = func(metaVal string) bool { return metaVal == opVal } case cmpNotEqual: result[i] = func(metaVal string) bool { return metaVal != opVal } case cmpPrefix: addSearch(opVal, op) result[i] = func(metaVal string) bool { return strings.HasPrefix(metaVal, opVal) } case cmpNoPrefix: result[i] = func(metaVal string) bool { return !strings.HasPrefix(metaVal, opVal) } case cmpSuffix: addSearch(opVal, op) result[i] = func(metaVal string) bool { return strings.HasSuffix(metaVal, opVal) } case cmpNoSuffix: result[i] = func(metaVal string) bool { return !strings.HasSuffix(metaVal, opVal) } case cmpContains: addSearch(opVal, op) result[i] = func(metaVal string) bool { return strings.Contains(metaVal, opVal) } case cmpNotContains: result[i] = func(metaVal string) bool { return !strings.Contains(metaVal, opVal) } default: panic(fmt.Sprintf("Unknown compare operation %d/%d with value %q", op, v.op, opVal)) } } return result } type stringSetPredicate func(value []string) bool func valuesToStringSetPredicates(values [][]opValue, defOp compareOp, addSearch addSearchFunc) [][]stringSetPredicate { result := make([][]stringSetPredicate, len(values)) for i, val := range values { elemPreds := make([]stringSetPredicate, len(val)) for j, v := range val { opVal := v.value // loop variable is used in closure --> save needed value op := resolveDefaultOp(v.op, defOp) switch op { case cmpEqual: addSearch(opVal, op) // addSearch only for positive selections elemPreds[j] = makeStringSetPredicate(opVal, stringEqual, true) case cmpNotEqual: elemPreds[j] = makeStringSetPredicate(opVal, stringEqual, false) case cmpPrefix: addSearch(opVal, op) elemPreds[j] = makeStringSetPredicate(opVal, strings.HasPrefix, true) case cmpNoPrefix: elemPreds[j] = makeStringSetPredicate(opVal, strings.HasPrefix, false) case cmpSuffix: addSearch(opVal, op) elemPreds[j] = makeStringSetPredicate(opVal, strings.HasSuffix, true) case cmpNoSuffix: elemPreds[j] = makeStringSetPredicate(opVal, strings.HasSuffix, false) case cmpContains: addSearch(opVal, op) elemPreds[j] = makeStringSetPredicate(opVal, strings.Contains, true) case cmpNotContains: elemPreds[j] = makeStringSetPredicate(opVal, strings.Contains, false) default: panic(fmt.Sprintf("Unknown compare operation %d/%d with value %q", op, v.op, opVal)) } } result[i] = elemPreds } return result } func stringEqual(val1, val2 string) bool { return val1 == val2 } type compareStringFunc func(val1, val2 string) bool func makeStringSetPredicate(neededValue string, compare compareStringFunc, foundResult bool) stringSetPredicate { return func(metaVals []string) bool { for _, metaVal := range metaVals { if compare(metaVal, neededValue) { return foundResult } } return !foundResult } } func resolveDefaultOp(op, defOp compareOp) compareOp { if op == cmpDefault { return defOp } if op == cmpNotDefault { return defOp.negate() } return op } func makeSearchMetaMatchFunc(posSpecs, negSpecs []matchSpec, nomatch []string) MetaMatchFunc { if len(nomatch) == 0 { // Optimize for simple cases: only negative or only positive matching if len(posSpecs) == 0 { return func(m *meta.Meta) bool { return matchMetaNegSpecs(m, negSpecs) } } if len(negSpecs) == 0 { return func(m *meta.Meta) bool { return matchMetaPosSpecs(m, posSpecs) } } } return func(m *meta.Meta) bool { return matchMetaNoMatch(m, nomatch) && matchMetaPosSpecs(m, posSpecs) && matchMetaNegSpecs(m, negSpecs) } } func matchMetaNoMatch(m *meta.Meta, nomatch []string) bool { for _, key := range nomatch { if _, ok := m.Get(key); ok { return false } } return true } func matchMetaPosSpecs(m *meta.Meta, posSpecs []matchSpec) bool { for _, s := range posSpecs { if value, ok := m.Get(s.key); !ok || !s.match(value) { return false } } return true } func matchMetaNegSpecs(m *meta.Meta, negSpecs []matchSpec) bool { for _, s := range negSpecs { if s.match == nil { if value, ok := m.Get(s.key); ok && matchValueAlways(value) { return false } } else if value, ok := m.Get(s.key); !ok || !s.match(value) { return false } } return true } |
Added search/select_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 | //----------------------------------------------------------------------------- // Copyright (c) 2021 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package search_test import ( "testing" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/search" ) func TestMatchZidNegate(t *testing.T) { var s *search.Search s = s.AddExpr(api.KeyID, "!="+string(api.ZidVersion)) s = s.AddExpr(api.KeyID, "!="+string(api.ZidLicense)) _, matchFunc := s.RetrieveAndCompileMatch(nil) testCases := []struct { zid api.ZettelID exp bool }{ {api.ZidVersion, false}, {api.ZidLicense, false}, {api.ZidAuthors, true}, } for i, tc := range testCases { m := meta.New(id.MustParse(tc.zid)) if matchFunc(m) != tc.exp { if tc.exp { t.Errorf("%d: meta %v must match %q", i, m.Zid, s) } else { t.Errorf("%d: meta %v must not match %q", i, m.Zid, s) } } } } |
Added search/sorter.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 | //----------------------------------------------------------------------------- // 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 search provides a zettel search. package search import ( "strconv" "zettelstore.de/c/api" "zettelstore.de/z/domain/meta" ) type sortFunc func(i, j int) bool func createSortFunc(key string, descending bool, ml []*meta.Meta) sortFunc { keyType := meta.Type(key) if key == api.KeyID || keyType == meta.TypeCredential { if descending { return func(i, j int) bool { return ml[i].Zid > ml[j].Zid } } return func(i, j int) bool { return ml[i].Zid < ml[j].Zid } } if keyType == meta.TypeNumber { return createSortNumberFunc(ml, key, descending) } return createSortStringFunc(ml, key, descending) } func createSortNumberFunc(ml []*meta.Meta, key string, descending bool) sortFunc { if descending { return func(i, j int) bool { iVal, iOk := getNum(ml[i], key) jVal, jOk := getNum(ml[j], key) return (iOk && (!jOk || iVal > jVal)) || !jOk } } return func(i, j int) bool { iVal, iOk := getNum(ml[i], key) jVal, jOk := getNum(ml[j], key) return (iOk && (!jOk || iVal < jVal)) || !jOk } } func createSortStringFunc(ml []*meta.Meta, key string, descending bool) sortFunc { if descending { return func(i, j int) bool { iVal, iOk := ml[i].Get(key) jVal, jOk := ml[j].Get(key) return (iOk && (!jOk || iVal > jVal)) || !jOk } } return func(i, j int) bool { iVal, iOk := ml[i].Get(key) jVal, jOk := ml[j].Get(key) return (iOk && (!jOk || iVal < jVal)) || !jOk } } func getNum(m *meta.Meta, key string) (int64, bool) { if s, ok := m.Get(key); ok { if i, err := strconv.ParseInt(s, 10, 64); err == nil { return i, true } } return 0, false } |
Changes to strfun/slugify.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) 2020-2021 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package strfun provides some string functions. package strfun import ( "strings" "unicode" "golang.org/x/text/unicode/norm" ) // NormalizeWords produces a word list that is normalized for better searching. func NormalizeWords(s string) []string { result := make([]string, 0, 1) word := make([]rune, 0, len(s)) for _, r := range norm.NFKD.String(s) { if unicode.Is(unicode.Diacritic, r) { continue } if unicode.In(r, unicode.Letter, unicode.Number) { word = append(word, unicode.ToLower(r)) |
︙ | ︙ |
Deleted testdata/naughty/LICENSE.
|
| < < < < < < < < < < < < < < < < < < < < < < |
Deleted testdata/naughty/README.md.
|
| < < < < < < |
Deleted testdata/naughty/blns.txt.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to tests/client/client_test.go.
︙ | ︙ | |||
45 46 47 48 49 50 51 | } } } func TestListZettel(t *testing.T) { const ( | | | | | > | | | | | 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 | } } } func TestListZettel(t *testing.T) { const ( ownerZettel = 49 configRoleZettel = 31 writerZettel = ownerZettel - 25 readerZettel = ownerZettel - 25 creatorZettel = 7 publicZettel = 4 ) testdata := []struct { user string exp int }{ {"", publicZettel}, {"creator", creatorZettel}, {"reader", readerZettel}, {"writer", writerZettel}, {"owner", ownerZettel}, } t.Parallel() c := getClient() query := url.Values{api.QueryKeyEncoding: {api.EncodingHTML}} // Client must remove "html" for i, tc := range testdata { t.Run(fmt.Sprintf("User %d/%q", i, tc.user), func(tt *testing.T) { c.SetAuth(tc.user, tc.user) q, h, l, err := c.ListZettelJSON(context.Background(), query) if err != nil { tt.Error(err) return } if q != "" { tt.Errorf("Query should be empty, but is %q", q) } if h != "" { tt.Errorf("Human should be empty, but is %q", q) } got := len(l) if got != tc.exp { tt.Errorf("List of length %d expected, but got %d\n%v", tc.exp, got, l) } }) } q, h, l, err := c.ListZettelJSON(context.Background(), url.Values{api.KeyRole: {api.ValueRoleConfiguration}}) if err != nil { t.Error(err) return } expQ := "role:configuration" if q != expQ { t.Errorf("Query should be %q, but is %q", expQ, q) } expH := "role MATCH configuration" if h != expH { t.Errorf("Human should be %q, but is %q", expH, h) } got := len(l) if got != configRoleZettel { t.Errorf("List of length %d expected, but got %d\n%v", configRoleZettel, got, l) } pl, err := c.ListZettel(context.Background(), url.Values{api.KeyRole: {api.ValueRoleConfiguration}}) if err != nil { t.Error(err) return } compareZettelList(t, pl, l) } |
︙ | ︙ | |||
335 336 337 338 339 340 341 | } } func TestListTags(t *testing.T) { t.Parallel() c := getClient() c.SetAuth("owner", "owner") | | | 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 | } } func TestListTags(t *testing.T) { t.Parallel() c := getClient() c.SetAuth("owner", "owner") tm, err := c.ListMapMeta(context.Background(), api.KeyTags) if err != nil { t.Error(err) return } tags := []struct { key string size int |
︙ | ︙ | |||
369 370 371 372 373 374 375 | } } func TestListRoles(t *testing.T) { t.Parallel() c := getClient() c.SetAuth("owner", "owner") | | | 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 | } } func TestListRoles(t *testing.T) { t.Parallel() c := getClient() c.SetAuth("owner", "owner") rl, err := c.ListMapMeta(context.Background(), api.KeyRole) if err != nil { t.Error(err) return } exp := []string{"configuration", "user", "zettel"} if len(rl) != len(exp) { t.Errorf("Expected %d different tags, but got only %d (%v)", len(exp), len(rl), rl) |
︙ | ︙ |
Changes to tests/markdown_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //----------------------------------------------------------------------------- // 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 tests import ( "bytes" "encoding/json" "fmt" "os" | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 tests provides some higher-level tests. package tests import ( "bytes" "encoding/json" "fmt" "os" |
︙ | ︙ | |||
63 64 65 66 67 68 69 | } var testcases []markdownTestCase if err = json.Unmarshal(content, &testcases); err != nil { panic(err) } for _, tc := range testcases { | | < < < < | 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | } var testcases []markdownTestCase if err = json.Unmarshal(content, &testcases); err != nil { panic(err) } for _, tc := range testcases { ast := parser.ParseBlocks(input.NewInput([]byte(tc.Markdown)), nil, "markdown") testAllEncodings(t, tc, &ast) testZmkEncoding(t, tc, &ast) } } func testAllEncodings(t *testing.T, tc markdownTestCase, ast *ast.BlockSlice) { var buf bytes.Buffer testID := tc.Example*100 + 1 for _, enc := range encodings { t.Run(fmt.Sprintf("Encode %v %v", enc, testID), func(st *testing.T) { encoder.Create(enc).WriteBlocks(&buf, ast) buf.Reset() |
︙ | ︙ | |||
114 115 116 117 118 119 120 | gotThird := buf.String() if gotSecond != gotThird { st.Errorf("\n1st: %q\n2nd: %q", gotSecond, gotThird) } }) } | < < < < < < < < < < < < < < < < < < < < | 111 112 113 114 115 116 117 | gotThird := buf.String() if gotSecond != gotThird { st.Errorf("\n1st: %q\n2nd: %q", gotSecond, gotThird) } }) } |
Deleted tests/naughtystrings_test.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to tests/regression_test.go.
︙ | ︙ | |||
141 142 143 144 145 146 147 | panic(err) } for _, meta := range metaList { zettel, err2 := p.GetZettel(context.Background(), meta.Zid) if err2 != nil { panic(err2) } | | < | | < > | | > | | | | 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 | panic(err) } for _, meta := range metaList { zettel, err2 := p.GetZettel(context.Background(), meta.Zid) if err2 != nil { panic(err2) } z := parser.ParseZettel(zettel, "", testConfig) for _, enc := range encodings { t.Run(fmt.Sprintf("%s::%d(%s)", p.Location(), meta.Zid, enc), func(st *testing.T) { resultName := filepath.Join(wd, "result", "meta", boxName, z.Zid.String()+"."+enc.String()) checkMetaFile(st, resultName, z, enc) }) } } ss.Stop(context.Background()) } type myConfig struct{} func (*myConfig) AddDefaultValues(m *meta.Meta) *meta.Meta { return m } func (*myConfig) GetDefaultLang() string { return "" } func (*myConfig) GetFooterHTML() string { return "" } func (*myConfig) GetHomeZettel() id.Zid { return id.Invalid } func (*myConfig) GetListPageSize() int { return 0 } func (*myConfig) GetMarkerExternal() string { return "" } func (*myConfig) GetSiteName() string { return "" } func (*myConfig) GetYAMLHeader() bool { return false } func (*myConfig) GetZettelFileSyntax() []string { return nil } func (*myConfig) GetSimpleMode() bool { return false } func (*myConfig) GetExpertMode() bool { return false } func (*myConfig) GetVisibility(*meta.Meta) meta.Visibility { return meta.VisibilityPublic } func (*myConfig) GetMaxTransclusions() int { return 1024 } var testConfig = &myConfig{} |
︙ | ︙ |
Changes to tools/build.go.
︙ | ︙ | |||
381 382 383 384 385 386 387 | func getReleaseVersionData() string { if fossil := getFossilDirty(); fossil != "" { fmt.Fprintln(os.Stderr, "Warning: releasing a dirty version") } base := getVersion() if strings.HasSuffix(base, "dev") { | | | 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 | func getReleaseVersionData() string { if fossil := getFossilDirty(); fossil != "" { fmt.Fprintln(os.Stderr, "Warning: releasing a dirty version") } base := getVersion() if strings.HasSuffix(base, "dev") { return base[:len(base)-3] + "preview-" + time.Now().Format("20060102") } return base } func cmdRelease() error { if err := cmdCheck(true); err != nil { return err |
︙ | ︙ |
Changes to usecase/authenticate.go.
︙ | ︙ | |||
18 19 20 21 22 23 24 | "zettelstore.de/c/api" "zettelstore.de/z/auth" "zettelstore.de/z/auth/cred" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/logger" | | | | | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | "zettelstore.de/c/api" "zettelstore.de/z/auth" "zettelstore.de/z/auth/cred" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/logger" "zettelstore.de/z/search" ) // AuthenticatePort is the interface used by this use case. type AuthenticatePort interface { GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) } // Authenticate is the data for this use case. type Authenticate struct { log *logger.Logger token auth.TokenManager port AuthenticatePort |
︙ | ︙ |
Changes to usecase/create_zettel.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 | // under this license. //----------------------------------------------------------------------------- package usecase import ( "context" | < | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // under this license. //----------------------------------------------------------------------------- package usecase import ( "context" "zettelstore.de/c/api" "zettelstore.de/z/config" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/logger" |
︙ | ︙ | |||
111 112 113 114 115 116 117 | // Run executes the use case. func (uc *CreateZettel) Run(ctx context.Context, zettel domain.Zettel) (id.Zid, error) { m := zettel.Meta if m.Zid.IsValid() { return m.Zid, nil // TODO: new error: already exists } | < | 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | // Run executes the use case. func (uc *CreateZettel) Run(ctx context.Context, zettel domain.Zettel) (id.Zid, error) { m := zettel.Meta if m.Zid.IsValid() { return m.Zid, nil // TODO: new error: already exists } m.Delete(api.KeyModified) m.YamlSep = uc.rtConfig.GetYAMLHeader() zettel.Content.TrimSpace() zid, err := uc.port.CreateZettel(ctx, zettel) uc.log.Info().User(ctx).Zid(zid).Err(err).Msg("Create zettel") return zid, err } |
Changes to usecase/evaluate.go.
︙ | ︙ | |||
16 17 18 19 20 21 22 | "zettelstore.de/z/ast" "zettelstore.de/z/config" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/evaluator" "zettelstore.de/z/parser" | | | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | "zettelstore.de/z/ast" "zettelstore.de/z/config" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/evaluator" "zettelstore.de/z/parser" "zettelstore.de/z/search" ) // Evaluate is the data for this use case. type Evaluate struct { rtConfig config.Config getZettel GetZettel getMeta GetMeta |
︙ | ︙ | |||
43 44 45 46 47 48 49 | // Run executes the use case. func (uc *Evaluate) Run(ctx context.Context, zid id.Zid, syntax string) (*ast.ZettelNode, error) { zettel, err := uc.getZettel.Run(ctx, zid) if err != nil { return nil, err } | | < < < < < < < < < < | 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | // Run executes the use case. func (uc *Evaluate) Run(ctx context.Context, zid id.Zid, syntax string) (*ast.ZettelNode, error) { zettel, err := uc.getZettel.Run(ctx, zid) if err != nil { return nil, err } zn, err := parser.ParseZettel(zettel, syntax, uc.rtConfig), nil if err != nil { return nil, err } evaluator.EvaluateZettel(ctx, uc, uc.rtConfig, zn) return zn, nil } // RunMetadata executes the use case for a metadata value. func (uc *Evaluate) RunMetadata(ctx context.Context, value string) ast.InlineSlice { is := parser.ParseMetadata(value) evaluator.EvaluateInline(ctx, uc, uc.rtConfig, &is) return is } |
︙ | ︙ | |||
87 88 89 90 91 92 93 | // GetZettel retrieves the full zettel of a given zettel identifier. func (uc *Evaluate) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) { return uc.getZettel.Run(ctx, zid) } // SelectMeta returns a list of metadata that comply to the given selection criteria. | | | | 77 78 79 80 81 82 83 84 85 86 | // GetZettel retrieves the full zettel of a given zettel identifier. func (uc *Evaluate) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) { return uc.getZettel.Run(ctx, zid) } // SelectMeta returns a list of metadata that comply to the given selection criteria. func (uc *Evaluate) SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) { return uc.listMeta.Run(ctx, s) } |
Changes to usecase/get_user.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-2021 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package usecase import ( "context" "zettelstore.de/c/api" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/search" ) // Use case: return user identified by meta key ident. // --------------------------------------------------- // GetUserPort is the interface used by this use case. type GetUserPort interface { GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) } // GetUser is the data for this use case. type GetUser struct { authz auth.AuthzManager port GetUserPort } |
︙ | ︙ | |||
49 50 51 52 53 54 55 | // could give herself the same ''ident''. Second, in most cases the owner // will authenticate. identMeta, err := uc.port.GetMeta(ctx, uc.authz.Owner()) if err == nil && identMeta.GetDefault(api.KeyUserID, "") == ident { return identMeta, nil } // Owner was not found or has another ident. Try via list search. | > > | | | 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | // could give herself the same ''ident''. Second, in most cases the owner // will authenticate. identMeta, err := uc.port.GetMeta(ctx, uc.authz.Owner()) if err == nil && identMeta.GetDefault(api.KeyUserID, "") == ident { return identMeta, nil } // Owner was not found or has another ident. Try via list search. var s *search.Search s = s.AddExpr("", "="+ident) s = s.AddExpr(api.KeyUserID, ident) metaList, err := uc.port.SelectMeta(ctx, s) if err != nil { return nil, err } if len(metaList) < 1 { return nil, nil } return metaList[len(metaList)-1], nil |
︙ | ︙ |
Changes to usecase/lists.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 | import ( "context" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/domain/meta" "zettelstore.de/z/parser" | | | | | | > | | | | > | | | > | | | 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 | import ( "context" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/domain/meta" "zettelstore.de/z/parser" "zettelstore.de/z/search" ) // ListMetaPort is the interface used by this use case. type ListMetaPort interface { // SelectMeta returns all zettel metadata that match the selection criteria. SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) } // ListMeta is the data for this use case. type ListMeta struct { port ListMetaPort } // NewListMeta creates a new use case. func NewListMeta(port ListMetaPort) ListMeta { return ListMeta{port: port} } // Run executes the use case. func (uc ListMeta) Run(ctx context.Context, s *search.Search) ([]*meta.Meta, error) { return uc.port.SelectMeta(ctx, s) } // -------- List roles ------------------------------------------------------- // ListSyntaxPort is the interface used by this use case. type ListSyntaxPort interface { // SelectMeta returns all zettel metadata that match the selection criteria. SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) } // ListSyntax is the data for this use case. type ListSyntax struct { port ListSyntaxPort } // NewListSyntax creates a new use case. func NewListSyntax(port ListSyntaxPort) ListSyntax { return ListSyntax{port: port} } // Run executes the use case. func (uc ListSyntax) Run(ctx context.Context) (meta.Arrangement, error) { var s *search.Search s = s.AddExpr(api.KeySyntax, "") // We look for all metadata with a syntax key metas, err := uc.port.SelectMeta(box.NoEnrichContext(ctx), s) if err != nil { return nil, err } result := meta.CreateArrangement(metas, api.KeySyntax) for _, syn := range parser.GetSyntaxes() { if _, found := result[syn]; !found { result[syn] = nil } } return result, nil } // -------- List roles ------------------------------------------------------- // ListRolesPort is the interface used by this use case. type ListRolesPort interface { // SelectMeta returns all zettel metadata that match the selection criteria. SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) } // ListRoles is the data for this use case. type ListRoles struct { port ListRolesPort } // NewListRoles creates a new use case. func NewListRoles(port ListRolesPort) ListRoles { return ListRoles{port: port} } // Run executes the use case. func (uc ListRoles) Run(ctx context.Context) (meta.Arrangement, error) { var s *search.Search s = s.AddExpr(api.KeyRole, "") // We look for all metadata with a role key metas, err := uc.port.SelectMeta(box.NoEnrichContext(ctx), s) if err != nil { return nil, err } return meta.CreateArrangement(metas, api.KeyRole), nil } // -------- List tags -------------------------------------------------------- // ListTagsPort is the interface used by this use case. type ListTagsPort interface { // SelectMeta returns all zettel metadata that match the selection criteria. SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) } // ListTags is the data for this use case. type ListTags struct { port ListTagsPort } // NewListTags creates a new use case. func NewListTags(port ListTagsPort) ListTags { return ListTags{port: port} } // Run executes the use case. func (uc ListTags) Run(ctx context.Context, minCount int) (meta.Arrangement, error) { var s *search.Search s = s.AddExpr(api.KeyTags, "") // We look for all metadata with a tag metas, err := uc.port.SelectMeta(ctx, s) if err != nil { return nil, err } result := meta.CreateArrangement(metas, api.KeyAllTags) if minCount > 1 { for t, ms := range result { if len(ms) < minCount { delete(result, t) } } } return result, nil } |
Changes to usecase/parse_zettel.go.
︙ | ︙ | |||
33 34 35 36 37 38 39 | // Run executes the use case. func (uc ParseZettel) Run(ctx context.Context, zid id.Zid, syntax string) (*ast.ZettelNode, error) { zettel, err := uc.getZettel.Run(ctx, zid) if err != nil { return nil, err } | | | 33 34 35 36 37 38 39 40 41 | // Run executes the use case. func (uc ParseZettel) Run(ctx context.Context, zid id.Zid, syntax string) (*ast.ZettelNode, error) { zettel, err := uc.getZettel.Run(ctx, zid) if err != nil { return nil, err } return parser.ParseZettel(zettel, syntax, uc.rtConfig), nil } |
Changes to usecase/unlinked_refs.go.
︙ | ︙ | |||
20 21 22 23 24 25 26 | "zettelstore.de/z/config" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/evaluator" "zettelstore.de/z/parser" | | | | | < < | < | | | | | | 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 | "zettelstore.de/z/config" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/evaluator" "zettelstore.de/z/parser" "zettelstore.de/z/search" ) // UnlinkedReferencesPort is the interface used by this use case. type UnlinkedReferencesPort interface { GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) } // UnlinkedReferences is the data for this use case. type UnlinkedReferences struct { port UnlinkedReferencesPort rtConfig config.Config encText *textenc.Encoder } // NewUnlinkedReferences creates a new use case. func NewUnlinkedReferences(port UnlinkedReferencesPort, rtConfig config.Config) UnlinkedReferences { return UnlinkedReferences{ port: port, rtConfig: rtConfig, encText: textenc.Create(), } } // Run executes the usecase with already evaluated title value. func (uc *UnlinkedReferences) Run(ctx context.Context, title string, s *search.Search) ([]*meta.Meta, error) { words := makeWords(title) if len(words) == 0 { return nil, nil } for _, word := range words { s = s.AddExpr("", "="+word) } // Limit applies to the filtering process, not to SelectMeta limit := s.GetLimit() s = s.SetLimit(0) candidates, err := uc.port.SelectMeta(ctx, s) if err != nil { return nil, err } s = s.SetLimit(limit) // Restore limit return s.Limit(uc.filterCandidates(ctx, candidates, words)), nil } func makeWords(text string) []string { return strings.FieldsFunc(text, func(r rune) bool { return unicode.In(r, unicode.C, unicode.P, unicode.Z) }) } |
︙ | ︙ | |||
108 109 110 111 112 113 114 | } } syntax := zettel.Meta.GetDefault(api.KeySyntax, "") if !parser.IsTextParser(syntax) { continue } | | | 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | } } syntax := zettel.Meta.GetDefault(api.KeySyntax, "") if !parser.IsTextParser(syntax) { continue } zn, err := parser.ParseZettel(zettel, syntax, nil), nil if err != nil { continue } evaluator.EvaluateZettel(ctx, uc.port, uc.rtConfig, zn) ast.Walk(&v, &zn.Ast) if v.found { result = append(result, cand) |
︙ | ︙ |
Changes to usecase/update_zettel.go.
1 | //----------------------------------------------------------------------------- | | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2021 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package usecase |
︙ | ︙ | |||
46 47 48 49 50 51 52 | oldZettel, err := uc.port.GetZettel(box.NoEnrichContext(ctx), m.Zid) if err != nil { return err } if zettel.Equal(oldZettel, false) { return nil } | < < < < < < < < < | 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | oldZettel, err := uc.port.GetZettel(box.NoEnrichContext(ctx), m.Zid) if err != nil { return err } if zettel.Equal(oldZettel, false) { return nil } m.SetNow(api.KeyModified) m.YamlSep = oldZettel.Meta.YamlSep if m.Zid == id.ConfigurationZid { m.Set(api.KeySyntax, api.ValueSyntaxNone) } if !hasContent { zettel.Content = oldZettel.Content } zettel.Content.TrimSpace() err = uc.port.UpdateZettel(ctx, zettel) uc.log.Sense().User(ctx).Zid(m.Zid).Err(err).Msg("Update zettel") return err } |
Changes to web/adapter/api/api.go.
︙ | ︙ | |||
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | // API holds all data and methods for delivering API call results. type API struct { log *logger.Logger b server.Builder authz auth.AuthzManager token auth.TokenManager rtConfig config.Config policy auth.Policy tokenLifetime time.Duration } // New creates a new API object. func New(log *logger.Logger, b server.Builder, authz auth.AuthzManager, token auth.TokenManager, | > | > | | 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 | // API holds all data and methods for delivering API call results. type API struct { log *logger.Logger b server.Builder authz auth.AuthzManager token auth.TokenManager auth server.Auth rtConfig config.Config policy auth.Policy tokenLifetime time.Duration } // New creates a new API object. func New(log *logger.Logger, b server.Builder, authz auth.AuthzManager, token auth.TokenManager, auth server.Auth, rtConfig config.Config, pol auth.Policy) *API { a := &API{ log: log, b: b, authz: authz, token: token, auth: auth, rtConfig: rtConfig, policy: pol, tokenLifetime: kernel.Main.GetConfig(kernel.WebService, kernel.WebTokenLifetimeAPI).(time.Duration), } return a } // GetURLPrefix returns the configured URL prefix of the web server. func (a *API) GetURLPrefix() string { return a.b.GetURLPrefix() } // NewURLBuilder creates a new URL builder object with the given key. func (a *API) NewURLBuilder(key byte) *api.URLBuilder { return a.b.NewURLBuilder(key) } func (a *API) getAuthData(ctx context.Context) *server.AuthData { return a.auth.GetAuthData(ctx) } func (a *API) withAuth() bool { return a.authz.WithAuth() } func (a *API) getToken(ident *meta.Meta) ([]byte, error) { return a.token.GetToken(ident, a.tokenLifetime, auth.KindJSON) } func (a *API) reportUsecaseError(w http.ResponseWriter, err error) { |
︙ | ︙ | |||
89 90 91 92 93 94 95 | w.WriteHeader(http.StatusOK) _, err := w.Write(buf.Bytes()) return err } func (a *API) getRights(ctx context.Context, m *meta.Meta) (result api.ZettelRights) { pol := a.policy | | | 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | w.WriteHeader(http.StatusOK) _, err := w.Write(buf.Bytes()) return err } func (a *API) getRights(ctx context.Context, m *meta.Meta) (result api.ZettelRights) { pol := a.policy user := a.auth.GetUser(ctx) if pol.CanCreate(user, m) { result |= api.ZettelCanCreate } if pol.CanRead(user, m) { result |= api.ZettelCanRead } if pol.CanWrite(user, m, m) { |
︙ | ︙ |
Changes to web/adapter/api/command.go.
︙ | ︙ | |||
23 24 25 26 27 28 29 | ucIsAuth *usecase.IsAuthenticated, ucRefresh *usecase.Refresh, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() q := r.URL.Query() cmd := q.Get(api.QueryKeyCommand) | < < < | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | ucIsAuth *usecase.IsAuthenticated, ucRefresh *usecase.Refresh, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() q := r.URL.Query() cmd := q.Get(api.QueryKeyCommand) switch api.Command(cmd) { case api.CommandAuthenticated: handleIsAuthenticated(ctx, w, ucIsAuth) return case api.CommandRefresh: err := ucRefresh.Run(ctx) if err != nil { |
︙ | ︙ |
Changes to web/adapter/api/get_lists.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 | //----------------------------------------------------------------------------- // 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 api import ( "bytes" "net/http" "strconv" "zettelstore.de/c/api" "zettelstore.de/z/domain/meta" "zettelstore.de/z/usecase" ) // MakeListMapMetaHandler creates a new HTTP handler to retrieve mappings of // metadata values of a specific key to the list of zettel IDs, which contain // this value. func (a *API) MakeListMapMetaHandler(listRole usecase.ListRoles, listTags usecase.ListTags) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { | > < | < < < < < | > < < < < < | 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 | //----------------------------------------------------------------------------- // 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 api provides api handlers for web requests. package api import ( "bytes" "net/http" "strconv" "zettelstore.de/c/api" "zettelstore.de/z/domain/meta" "zettelstore.de/z/usecase" ) // MakeListMapMetaHandler creates a new HTTP handler to retrieve mappings of // metadata values of a specific key to the list of zettel IDs, which contain // this value. func (a *API) MakeListMapMetaHandler(listRole usecase.ListRoles, listTags usecase.ListTags) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var ar meta.Arrangement query := r.URL.Query() iMinCount, err := strconv.Atoi(query.Get(api.QueryKeyMin)) if err != nil || iMinCount < 0 { iMinCount = 0 } ctx := r.Context() key := query.Get(api.QueryKeyKey) switch key { case api.KeyRole: ar, err = listRole.Run(ctx) case api.KeyTags: ar, err = listTags.Run(ctx, iMinCount) default: a.log.Info().Str("key", key).Msg("illegal key for retrieving meta map") |
︙ | ︙ | |||
63 64 65 66 67 68 69 | zidList := make([]api.ZettelID, 0, len(metaList)) for _, m := range metaList { zidList = append(zidList, api.ZettelID(m.Zid.String())) } mm[tag] = zidList } | | | 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | zidList := make([]api.ZettelID, 0, len(metaList)) for _, m := range metaList { zidList = append(zidList, api.ZettelID(m.Zid.String())) } mm[tag] = zidList } var buf bytes.Buffer err = encodeJSONData(&buf, api.MapListJSON{Map: mm}) if err != nil { a.log.Fatal().Err(err).Msg("Unable to store map list in buffer") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } err = writeBuffer(w, &buf, ctJSON) a.log.IfErr(err).Str("key", key).Msg("write meta map") } } |
Changes to web/adapter/api/get_unlinked_refs.go.
︙ | ︙ | |||
37 38 39 40 41 42 43 | ctx := r.Context() zm, err := getMeta.Run(ctx, zid) if err != nil { a.reportUsecaseError(w, err) return } | | | < < < | | 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 | ctx := r.Context() zm, err := getMeta.Run(ctx, zid) if err != nil { a.reportUsecaseError(w, err) return } q := r.URL.Query() phrase := q.Get(api.QueryKeyPhrase) if phrase == "" { if zmkTitle, found := zm.Get(api.KeyTitle); found { isTitle := evaluate.RunMetadata(ctx, zmkTitle) encdr := textenc.Create() var b strings.Builder _, err = encdr.WriteInlines(&b, &isTitle) if err == nil { phrase = b.String() } } } metaList, err := unlinkedRefs.Run( ctx, phrase, adapter.AddUnlinkedRefsToSearch(adapter.GetSearch(q), zm)) if err != nil { a.reportUsecaseError(w, err) return } result := api.ZidMetaRelatedList{ ID: api.ZettelID(zid.String()), |
︙ | ︙ |
Changes to web/adapter/api/get_zettel_context.go.
︙ | ︙ | |||
25 26 27 28 29 30 31 | return func(w http.ResponseWriter, r *http.Request) { zid, err := id.Parse(r.URL.Path[1:]) if err != nil { http.NotFound(w, r) return } q := r.URL.Query() | < < < < | < < < < < < | 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | return func(w http.ResponseWriter, r *http.Request) { zid, err := id.Parse(r.URL.Path[1:]) if err != nil { http.NotFound(w, r) return } q := r.URL.Query() dir := adapter.GetZCDirection(q.Get(api.QueryKeyDir)) depth, ok := adapter.GetInteger(q, api.QueryKeyDepth) if !ok || depth < 0 { depth = 5 } limit, ok := adapter.GetInteger(q, api.QueryKeyLimit) if !ok || limit < 0 { limit = 200 } ctx := r.Context() metaList, err := getContext.Run(ctx, zid, dir, depth, limit) if err != nil { a.reportUsecaseError(w, err) |
︙ | ︙ |
Changes to web/adapter/api/get_zettel_list.go.
︙ | ︙ | |||
21 22 23 24 25 26 27 | "zettelstore.de/z/web/adapter" ) // MakeListMetaHandler creates a new HTTP handler for the use case "list some zettel". func (a *API) MakeListMetaHandler(listMeta usecase.ListMeta) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() | > | | | | > > | | 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 | "zettelstore.de/z/web/adapter" ) // MakeListMetaHandler creates a new HTTP handler for the use case "list some zettel". func (a *API) MakeListMetaHandler(listMeta usecase.ListMeta) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() q := r.URL.Query() s := adapter.GetSearch(q) metaList, err := listMeta.Run(ctx, s) if err != nil { a.reportUsecaseError(w, err) return } result := make([]api.ZidMetaJSON, 0, len(metaList)) for _, m := range metaList { result = append(result, api.ZidMetaJSON{ ID: api.ZettelID(m.Zid.String()), Meta: m.Map(), Rights: a.getRights(ctx, m), }) } var buf bytes.Buffer err = encodeJSONData(&buf, api.ZettelListJSON{ Query: s.String(), Human: s.Human(), List: result, }) if err != nil { a.log.Fatal().Err(err).Msg("Unable to store meta list in buffer") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } err = writeBuffer(w, &buf, ctJSON) a.log.IfErr(err).Msg("Write JSON List") } } // MakeListPlainHandler creates a new HTTP handler for the use case "list some zettel". func (a *API) MakeListPlainHandler(listMeta usecase.ListMeta) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() q := r.URL.Query() s := adapter.GetSearch(q) metaList, err := listMeta.Run(ctx, s) if err != nil { a.reportUsecaseError(w, err) return } var buf bytes.Buffer for _, m := range metaList { |
︙ | ︙ |
Deleted web/adapter/api/query.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to web/adapter/api/request.go.
︙ | ︙ | |||
22 23 24 25 26 27 28 | "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" ) // getEncoding returns the data encoding selected by the caller. func getEncoding(r *http.Request, q url.Values, defEncoding api.EncodingEnum) (api.EncodingEnum, string) { encoding := q.Get(api.QueryKeyEncoding) | < | < < | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" ) // getEncoding returns the data encoding selected by the caller. func getEncoding(r *http.Request, q url.Values, defEncoding api.EncodingEnum) (api.EncodingEnum, string) { encoding := q.Get(api.QueryKeyEncoding) if len(encoding) > 0 { return api.Encoder(encoding), encoding } if enc, ok := getOneEncoding(r, api.HeaderAccept); ok { return api.Encoder(enc), enc } if enc, ok := getOneEncoding(r, api.HeaderContentType); ok { return api.Encoder(enc), enc |
︙ | ︙ | |||
78 79 80 81 82 83 84 | api.PartZettel: partZettel, } func getPart(q url.Values, defPart partType) partType { if part, ok := partMap[q.Get(api.QueryKeyPart)]; ok { return part } | < < < | 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | api.PartZettel: partZettel, } func getPart(q url.Values, defPart partType) partType { if part, ok := partMap[q.Get(api.QueryKeyPart)]; ok { return part } return defPart } func (p partType) String() string { switch p { case partMeta: return "meta" |
︙ | ︙ |
Changes to web/adapter/request.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 | "net/url" "strconv" "strings" "zettelstore.de/c/api" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" | | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | "net/url" "strconv" "strings" "zettelstore.de/c/api" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" "zettelstore.de/z/search" "zettelstore.de/z/usecase" ) // GetCredentialsViaForm retrieves the authentication credentions from a form. func GetCredentialsViaForm(r *http.Request) (ident, cred string, ok bool) { err := r.ParseForm() if err != nil { |
︙ | ︙ | |||
46 47 48 49 50 51 52 | if val, err := strconv.Atoi(s); err == nil { return val, true } } return 0, false } | | | | | > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > | | > > > > > > > | > > > > > > > > > > > > > > > > | | < < < | < < < | < | < < | | 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 | if val, err := strconv.Atoi(s); err == nil { return val, true } } return 0, false } // GetSearch retrieves the specified search and sorting options from a query. func GetSearch(q url.Values) (s *search.Search) { if exprs, found := q[api.QueryKeySearch]; found { s = search.Parse(strings.Join(exprs, " ")) } for key, values := range q { switch key { case api.QueryKeySort, api.QueryKeyOrder: s = extractOrderFromQuery(values, s) case api.QueryKeyOffset: s = extractOffsetFromQuery(values, s) case api.QueryKeyLimit: s = extractLimitFromQuery(values, s) case api.QueryKeyNegate: s = s.SetNegate() case api.QueryKeySearch: // Ignore, already processed to top of method. default: if meta.KeyIsValid(key) { s = setCleanedQueryValues(s, key, values) } } } return s } func extractOrderFromQuery(values []string, s *search.Search) *search.Search { if len(values) > 0 { descending := false sortkey := values[0] if strings.HasPrefix(sortkey, "-") { descending = true sortkey = sortkey[1:] } if meta.KeyIsValid(sortkey) || sortkey == search.RandomOrder { s = s.AddOrder(sortkey, descending) } } return s } func extractOffsetFromQuery(values []string, s *search.Search) *search.Search { if len(values) > 0 { if offset, err := strconv.Atoi(values[0]); err == nil && offset > 0 { s = s.SetOffset(offset) } } return s } func extractLimitFromQuery(values []string, s *search.Search) *search.Search { if len(values) > 0 { if limit, err := strconv.Atoi(values[0]); err == nil && limit > 0 { s = s.SetLimit(limit) } } return s } func setCleanedQueryValues(s *search.Search, key string, values []string) *search.Search { for _, val := range values { s = s.AddExpr(key, val) } return s } // GetZCDirection returns a direction value for a given string. func GetZCDirection(s string) usecase.ZettelContextDirection { switch s { case api.DirBackward: return usecase.ZettelContextBackward case api.DirForward: return usecase.ZettelContextForward } return usecase.ZettelContextBoth } // AddUnlinkedRefsToSearch inspects metadata and enhances the given search to ignore // some zettel identifier. func AddUnlinkedRefsToSearch(s *search.Search, m *meta.Meta) *search.Search { s = s.AddExpr(api.KeyID, "!="+m.Zid.String()) for _, pair := range m.ComputedPairsRest() { switch meta.Type(pair.Key) { case meta.TypeID: s = s.AddExpr(api.KeyID, "!="+pair.Value) case meta.TypeIDSet: for _, value := range meta.ListFromValue(pair.Value) { s = s.AddExpr(api.KeyID, "!="+value) } } } return s } |
Changes to web/adapter/response.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //----------------------------------------------------------------------------- // 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 adapter import ( "errors" "fmt" "net/http" | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 adapter provides handlers for web requests. package adapter import ( "errors" "fmt" "net/http" |
︙ | ︙ |
Changes to web/adapter/webui/create_zettel.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 19 20 21 22 23 24 | import ( "context" "net/http" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/parser" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" | > < | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | import ( "context" "net/http" "zettelstore.de/c/api" "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/parser" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeGetCreateZettelHandler creates a new HTTP handler to display the // HTML edit view for the various zettel creation methods. func (wui *WebUI) MakeGetCreateZettelHandler( getZettel usecase.GetZettel, createZettel *usecase.CreateZettel, ucListRoles usecase.ListRoles, ucListSyntax usecase.ListSyntax) http.HandlerFunc { |
︙ | ︙ | |||
88 89 90 91 92 93 94 | ctx context.Context, w http.ResponseWriter, zettel domain.Zettel, title, heading string, roleData []string, syntaxData []string, ) { | | | | 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | ctx context.Context, w http.ResponseWriter, zettel domain.Zettel, title, heading string, roleData []string, syntaxData []string, ) { user := wui.getUser(ctx) m := zettel.Meta var base baseData wui.makeBaseData(ctx, config.GetLang(m, wui.rtConfig), title, "", user, &base) wui.renderTemplate(ctx, w, id.FormTemplateZid, &base, formZettelData{ Heading: heading, MetaTitle: m.GetDefault(api.KeyTitle, ""), MetaTags: m.GetDefault(api.KeyTags, ""), MetaRole: m.GetDefault(api.KeyRole, ""), HasRoleData: len(roleData) > 0, RoleData: roleData, |
︙ | ︙ |
Changes to web/adapter/webui/delete_zettel.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 19 20 21 22 | import ( "net/http" "zettelstore.de/c/api" "zettelstore.de/c/maps" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/strfun" "zettelstore.de/z/usecase" | > < | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import ( "net/http" "zettelstore.de/c/api" "zettelstore.de/c/maps" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/strfun" "zettelstore.de/z/usecase" ) // MakeGetDeleteZettelHandler creates a new HTTP handler to display the // HTML delete view of a zettel. func (wui *WebUI) MakeGetDeleteZettelHandler( getMeta usecase.GetMeta, getAllMeta usecase.GetAllMeta, |
︙ | ︙ | |||
46 47 48 49 50 51 52 | m := ms[0] var shadowedBox string var incomingLinks []simpleLink if len(ms) > 1 { shadowedBox = ms[1].GetDefault(api.KeyBoxNumber, "???") } else { | | | | | 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | m := ms[0] var shadowedBox string var incomingLinks []simpleLink if len(ms) > 1 { shadowedBox = ms[1].GetDefault(api.KeyBoxNumber, "???") } else { getTextTitle := wui.makeGetTextTitle(ctx, getMeta, evaluate) incomingLinks = wui.encodeIncoming(m, getTextTitle) } uselessFiles := retrieveUselessFiles(m) user := wui.getUser(ctx) var base baseData wui.makeBaseData(ctx, config.GetLang(m, wui.rtConfig), "Delete Zettel "+m.Zid.String(), "", user, &base) wui.renderTemplate(ctx, w, id.DeleteTemplateZid, &base, struct { Zid string MetaPairs []meta.Pair HasShadows bool ShadowedBox string HasIncoming bool Incoming []simpleLink |
︙ | ︙ |
Changes to web/adapter/webui/get_info.go.
︙ | ︙ | |||
17 18 19 20 21 22 23 | "sort" "strings" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/collect" | | | | < | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | "sort" "strings" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/collect" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/encoder" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) type metaDataInfo struct { Key string Value string } |
︙ | ︙ | |||
59 60 61 62 63 64 65 | zn, err := parseZettel.Run(ctx, zid, q.Get(api.KeySyntax)) if err != nil { wui.reportError(ctx, w, err) return } | < | | | | | | | | | < < < < < | | | | | | | | | | | | | | | | | | 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 | zn, err := parseZettel.Run(ctx, zid, q.Get(api.KeySyntax)) if err != nil { wui.reportError(ctx, w, err) return } enc := wui.getSimpleHTMLEncoder() pairs := zn.Meta.ComputedPairs() metaData := make([]metaDataInfo, len(pairs)) getTextTitle := wui.makeGetTextTitle(ctx, getMeta, evaluate) for i, p := range pairs { var buf bytes.Buffer wui.writeHTMLMetaValue( &buf, p.Key, p.Value, getTextTitle, func(val string) ast.InlineSlice { return evaluate.RunMetadata(ctx, val) }, enc) metaData[i] = metaDataInfo{p.Key, buf.String()} } summary := collect.References(zn) locLinks, searchQuery, extLinks := splitLocSeaExtLinks(append(summary.Links, summary.Embeds...)) searchLinks := make([]simpleLink, len(searchQuery)) for i, sq := range searchQuery { searchLinks[i].Text = sq searchLinks[i].URL = wui.NewURLBuilder('h').AppendSearch(sq).String() } textTitle := wui.encodeTitleAsText(ctx, zn.InhMeta, evaluate) phrase := q.Get(api.QueryKeyPhrase) if phrase == "" { phrase = textTitle } phrase = strings.TrimSpace(phrase) unlinkedMeta, err := unlinkedRefs.Run( ctx, phrase, adapter.AddUnlinkedRefsToSearch(nil, zn.InhMeta)) if err != nil { wui.reportError(ctx, w, err) return } unLinks := wui.buildHTMLMetaList(unlinkedMeta, func(val string) ast.InlineSlice { return evaluate.RunMetadata(ctx, val) }) shadowLinks := getShadowLinks(ctx, zid, getAllMeta) endnotes, err := enc.BlocksString(&ast.BlockSlice{}) if err != nil { endnotes = "" } user := wui.getUser(ctx) canCreate := wui.canCreate(ctx, user) apiZid := api.ZettelID(zid.String()) var base baseData wui.makeBaseData(ctx, config.GetLang(zn.InhMeta, wui.rtConfig), textTitle, "", user, &base) wui.renderTemplate(ctx, w, id.InfoTemplateZid, &base, struct { Zid string WebURL string ContextURL string CanWrite bool EditURL string CanFolge bool FolgeURL string CanCopy bool CopyURL string CanRename bool RenameURL string CanDelete bool DeleteURL string MetaData []metaDataInfo HasLocLinks bool LocLinks []localLink HasSearchLinks bool SearchLinks []simpleLink HasExtLinks bool ExtLinks []string ExtNewWindow string UnLinks []simpleLink UnLinksPhrase string QueryKeyPhrase string EvalMatrix []matrixLine ParseMatrix []matrixLine HasShadowLinks bool ShadowLinks []string Endnotes string }{ Zid: zid.String(), WebURL: wui.NewURLBuilder('h').SetZid(apiZid).String(), ContextURL: wui.NewURLBuilder('k').SetZid(apiZid).String(), CanWrite: wui.canWrite(ctx, user, zn.Meta, zn.Content), EditURL: wui.NewURLBuilder('e').SetZid(apiZid).String(), CanFolge: canCreate, FolgeURL: wui.NewURLBuilder('c').SetZid(apiZid).AppendQuery(queryKeyAction, valueActionFolge).String(), CanCopy: canCreate && !zn.Content.IsBinary(), CopyURL: wui.NewURLBuilder('c').SetZid(apiZid).AppendQuery(queryKeyAction, valueActionCopy).String(), CanRename: wui.canRename(ctx, user, zn.Meta), RenameURL: wui.NewURLBuilder('b').SetZid(apiZid).String(), CanDelete: wui.canDelete(ctx, user, zn.Meta), DeleteURL: wui.NewURLBuilder('d').SetZid(apiZid).String(), MetaData: metaData, HasLocLinks: len(locLinks) > 0, LocLinks: locLinks, HasSearchLinks: len(searchQuery) > 0, SearchLinks: searchLinks, HasExtLinks: len(extLinks) > 0, ExtLinks: extLinks, ExtNewWindow: htmlAttrNewWindow(len(extLinks) > 0), UnLinks: unLinks, UnLinksPhrase: phrase, QueryKeyPhrase: api.QueryKeyPhrase, EvalMatrix: wui.infoAPIMatrix('v', zid), ParseMatrix: wui.infoAPIMatrixPlain('p', zid), HasShadowLinks: len(shadowLinks) > 0, ShadowLinks: shadowLinks, Endnotes: endnotes, }) } } type localLink struct { Valid bool Zid string } func splitLocSeaExtLinks(links []*ast.Reference) (locLinks []localLink, searchQuery, extLinks []string) { if len(links) == 0 { return nil, nil, nil } for _, ref := range links { if ref.State == ast.RefStateSelf || ref.IsZettel() { continue } if ref.State == ast.RefStateSearch { searchQuery = append(searchQuery, ref.Value) continue } if ref.IsExternal() { extLinks = append(extLinks, ref.String()) continue } locLinks = append(locLinks, localLink{ref.IsValid(), ref.String()}) } return locLinks, searchQuery, extLinks } func (wui *WebUI) infoAPIMatrix(key byte, zid id.Zid) []matrixLine { encodings := encoder.GetEncodings() encTexts := make([]string, 0, len(encodings)) for _, f := range encodings { encTexts = append(encTexts, f.String()) } sort.Strings(encTexts) defEncoding := encoder.GetDefaultEncoding().String() parts := getParts() matrix := make([]matrixLine, 0, len(parts)) u := wui.NewURLBuilder(key).SetZid(api.ZettelID(zid.String())) for _, part := range parts { row := make([]simpleLink, len(encTexts)) for j, enc := range encTexts { u.AppendQuery(api.QueryKeyPart, part) if enc != defEncoding { u.AppendQuery(api.QueryKeyEncoding, enc) } row[j] = simpleLink{enc, u.String()} u.ClearQuery() } matrix = append(matrix, matrixLine{part, row}) } return matrix } func (wui *WebUI) infoAPIMatrixPlain(key byte, zid id.Zid) []matrixLine { matrix := wui.infoAPIMatrix(key, zid) apiZid := api.ZettelID(zid.String()) // Append plain and JSON format u := wui.NewURLBuilder('z').SetZid(apiZid) for i, part := range getParts() { u.AppendQuery(api.QueryKeyPart, part) matrix[i].Elements = append(matrix[i].Elements, simpleLink{"plain", u.String()}) u.ClearQuery() } u = wui.NewURLBuilder('j').SetZid(apiZid) matrix[0].Elements = append(matrix[0].Elements, simpleLink{"json", u.String()}) u = wui.NewURLBuilder('m').SetZid(apiZid) matrix[1].Elements = append(matrix[1].Elements, simpleLink{"json", u.String()}) |
︙ | ︙ |
Changes to web/adapter/webui/get_zettel.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 20 21 22 23 | import ( "bytes" "net/http" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/usecase" | > < | | > > | > | | | | | | < | | | < | 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 | import ( "bytes" "net/http" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/usecase" ) // MakeGetHTMLZettelHandler creates a new HTTP handler for the use case "get zettel". func (wui *WebUI) MakeGetHTMLZettelHandler(evaluate *usecase.Evaluate, getMeta usecase.GetMeta) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() zid, err := id.Parse(r.URL.Path[1:]) if err != nil { wui.reportError(ctx, w, box.ErrNotFound) return } q := r.URL.Query() zn, err := evaluate.Run(ctx, zid, q.Get(api.KeySyntax)) if err != nil { wui.reportError(ctx, w, err) return } enc := wui.createZettelEncoder() evalMetadata := func(value string) ast.InlineSlice { return evaluate.RunMetadata(ctx, value) } metaHeader := enc.MetaString(zn.InhMeta, evalMetadata) textTitle := wui.encodeTitleAsText(ctx, zn.InhMeta, evaluate) htmlTitle := encodeZmkMetadata(zn.InhMeta.GetTitle(), evalMetadata, enc) htmlContent, err := enc.BlocksString(&zn.Ast) if err != nil { wui.reportError(ctx, w, err) return } var roleCSSURL string cssZid, err := wui.retrieveCSSZidFromRole(ctx, *zn.InhMeta) if err != nil { wui.reportError(ctx, w, err) return } if cssZid != id.Invalid { roleCSSURL = wui.NewURLBuilder('z').SetZid(api.ZettelID(cssZid.String())).String() } user := wui.getUser(ctx) roleText := zn.Meta.GetDefault(api.KeyRole, "*") tags := wui.buildTagInfos(zn.Meta) canCreate := wui.canCreate(ctx, user) getTextTitle := wui.makeGetTextTitle(ctx, getMeta, evaluate) extURL, hasExtURL := zn.Meta.Get(api.KeyURL) folgeLinks := wui.encodeZettelLinks(zn.InhMeta, api.KeyFolge, getTextTitle) backLinks := wui.encodeZettelLinks(zn.InhMeta, api.KeyBack, getTextTitle) apiZid := api.ZettelID(zid.String()) var base baseData wui.makeBaseData(ctx, config.GetLang(zn.InhMeta, wui.rtConfig), textTitle, roleCSSURL, user, &base) base.MetaHeader = metaHeader wui.renderTemplate(ctx, w, id.ZettelTemplateZid, &base, struct { HTMLTitle string RoleCSS string CanWrite bool EditURL string Zid string InfoURL string RoleText string RoleURL string HasTags bool Tags []simpleLink CanCopy bool CopyURL string CanFolge bool FolgeURL string PrecursorRefs string HasExtURL bool ExtURL string ExtNewWindow string Content string HasFolgeLinks bool FolgeLinks []simpleLink HasBackLinks bool BackLinks []simpleLink }{ HTMLTitle: htmlTitle, RoleCSS: roleCSSURL, CanWrite: wui.canWrite(ctx, user, zn.Meta, zn.Content), EditURL: wui.NewURLBuilder('e').SetZid(apiZid).String(), Zid: zid.String(), InfoURL: wui.NewURLBuilder('i').SetZid(apiZid).String(), RoleText: roleText, RoleURL: wui.NewURLBuilder('h').AppendQuery("role", roleText).String(), HasTags: len(tags) > 0, Tags: tags, CanCopy: canCreate && !zn.Content.IsBinary(), CopyURL: wui.NewURLBuilder('c').SetZid(apiZid).AppendQuery(queryKeyAction, valueActionCopy).String(), CanFolge: canCreate, FolgeURL: wui.NewURLBuilder('c').SetZid(apiZid).AppendQuery(queryKeyAction, valueActionFolge).String(), PrecursorRefs: wui.encodeIdentifierSet(zn.InhMeta, api.KeyPrecursor, getTextTitle), ExtURL: extURL, HasExtURL: hasExtURL, ExtNewWindow: htmlAttrNewWindow(hasExtURL), Content: htmlContent, HasFolgeLinks: len(folgeLinks) > 0, FolgeLinks: folgeLinks, HasBackLinks: len(backLinks) > 0, BackLinks: backLinks, }) } |
︙ | ︙ | |||
141 142 143 144 145 146 147 | func (wui *WebUI) buildTagInfos(m *meta.Meta) []simpleLink { var tagInfos []simpleLink if tags, ok := m.GetList(api.KeyTags); ok { ub := wui.NewURLBuilder('h') tagInfos = make([]simpleLink, len(tags)) for i, tag := range tags { | | | 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | func (wui *WebUI) buildTagInfos(m *meta.Meta) []simpleLink { var tagInfos []simpleLink if tags, ok := m.GetList(api.KeyTags); ok { ub := wui.NewURLBuilder('h') tagInfos = make([]simpleLink, len(tags)) for i, tag := range tags { tagInfos[i] = simpleLink{Text: tag, URL: ub.AppendQuery(api.KeyAllTags, tag).String()} ub.ClearQuery() } } return tagInfos } func (wui *WebUI) encodeIdentifierSet(m *meta.Meta, key string, getTextTitle getTextTitleFunc) string { |
︙ | ︙ |
Changes to web/adapter/webui/home.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-2021 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package webui import ( "context" "errors" "net/http" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) type getRootStore interface { // GetMeta retrieves just the meta data of a specific zettel. GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) } |
︙ | ︙ | |||
45 46 47 48 49 50 51 | homeZid = id.DefaultHomeZid } _, err := s.GetMeta(ctx, homeZid) if err == nil { wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(apiHomeZid)) return } | | | 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | homeZid = id.DefaultHomeZid } _, err := s.GetMeta(ctx, homeZid) if err == nil { wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(apiHomeZid)) return } if errors.Is(err, &box.ErrNotAllowed{}) && wui.authz.WithAuth() && wui.getUser(ctx) == nil { wui.redirectFound(w, r, wui.NewURLBuilder('i')) return } wui.redirectFound(w, r, wui.NewURLBuilder('h')) } } |
Changes to web/adapter/webui/htmlgen.go.
︙ | ︙ | |||
19 20 21 22 23 24 25 | "zettelstore.de/c/html" "zettelstore.de/c/sexpr" "zettelstore.de/z/ast" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" "zettelstore.de/z/encoder/sexprenc" "zettelstore.de/z/encoder/textenc" | | > | > | | 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 | "zettelstore.de/c/html" "zettelstore.de/c/sexpr" "zettelstore.de/z/ast" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" "zettelstore.de/z/encoder/sexprenc" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/search" "zettelstore.de/z/strfun" ) // Builder allows to build new URLs for the web service. type urlBuilder interface { GetURLPrefix() string NewURLBuilder(key byte) *api.URLBuilder } type htmlGenerator struct { builder urlBuilder textEnc *textenc.Encoder extMarker string newWindow bool env *html.EncEnvironment } func createGenerator(builder urlBuilder, extMarker string, newWindow bool) *htmlGenerator { env := html.NewEncEnvironment(nil, 1) gen := &htmlGenerator{ builder: builder, textEnc: textenc.Create(), extMarker: extMarker, newWindow: newWindow, env: env, } env.Builtins.Set(sexpr.SymTag, sxpf.NewBuiltin("tag", true, 0, -1, gen.generateTag)) env.Builtins.Set(sexpr.SymLinkZettel, sxpf.NewBuiltin("linkZ", true, 2, -1, gen.generateLinkZettel)) env.Builtins.Set(sexpr.SymLinkFound, sxpf.NewBuiltin("linkZ", true, 2, -1, gen.generateLinkZettel)) env.Builtins.Set(sexpr.SymLinkBased, sxpf.NewBuiltin("linkB", true, 2, -1, gen.generateLinkBased)) env.Builtins.Set(sexpr.SymLinkSearch, sxpf.NewBuiltin("linkS", true, 2, -1, gen.generateLinkSearch)) env.Builtins.Set(sexpr.SymLinkExternal, sxpf.NewBuiltin("linkE", true, 2, -1, gen.generateLinkExternal)) f, err := env.Builtins.LookupForm(sexpr.SymEmbed) if err != nil { panic(err) } b := f.(*sxpf.Builtin) |
︙ | ︙ | |||
142 143 144 145 146 147 148 | func (g *htmlGenerator) generateTag(senv sxpf.Environment, args *sxpf.Pair, _ int) (sxpf.Value, error) { if !sxpf.IsNil(args) { env := senv.(*html.EncEnvironment) s := env.GetString(args) if env.IgnoreLinks() { env.WriteEscaped(s) } else { | | | 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | func (g *htmlGenerator) generateTag(senv sxpf.Environment, args *sxpf.Pair, _ int) (sxpf.Value, error) { if !sxpf.IsNil(args) { env := senv.(*html.EncEnvironment) s := env.GetString(args) if env.IgnoreLinks() { env.WriteEscaped(s) } else { u := g.builder.NewURLBuilder('h').AppendQuery(api.KeyAllTags, "#"+strings.ToLower(s)) env.WriteStrings(`<a href="`, u.String(), `">#`) env.WriteEscaped(s) env.WriteString("</a>") } } return nil, nil } |
︙ | ︙ | |||
173 174 175 176 177 178 179 | if a, refValue, ok := html.PrepareLink(env, args); ok { u := g.builder.NewURLBuilder('/').SetRawLocal(refValue) html.WriteLink(env, args, a.Set("href", u.String()), refValue, "") } return nil, nil } | | | | | 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | if a, refValue, ok := html.PrepareLink(env, args); ok { u := g.builder.NewURLBuilder('/').SetRawLocal(refValue) html.WriteLink(env, args, a.Set("href", u.String()), refValue, "") } return nil, nil } func (g *htmlGenerator) generateLinkSearch(senv sxpf.Environment, args *sxpf.Pair, _ int) (sxpf.Value, error) { env := senv.(*html.EncEnvironment) if a, refValue, ok := html.PrepareLink(env, args); ok { searchExpr := search.Parse(refValue).String() u := g.builder.NewURLBuilder('h').AppendSearch(searchExpr) html.WriteLink(env, args, a.Set("href", u.String()), refValue, "") } return nil, nil } func (g *htmlGenerator) generateLinkExternal(senv sxpf.Environment, args *sxpf.Pair, _ int) (sxpf.Value, error) { env := senv.(*html.EncEnvironment) |
︙ | ︙ |
Changes to web/adapter/webui/htmlmeta.go.
︙ | ︙ | |||
25 26 27 28 29 30 31 32 33 34 35 36 37 38 | "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/usecase" ) var space = []byte{' '} func (wui *WebUI) writeHTMLMetaValue( w io.Writer, key, value string, getTextTitle getTextTitleFunc, evalMetadata evalMetadataFunc, gen *htmlGenerator, ) { | > > | 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/usecase" ) var space = []byte{' '} type evalMetadataFunc = func(string) ast.InlineSlice func (wui *WebUI) writeHTMLMetaValue( w io.Writer, key, value string, getTextTitle getTextTitleFunc, evalMetadata evalMetadataFunc, gen *htmlGenerator, ) { |
︙ | ︙ | |||
143 144 145 146 147 148 149 | w.Write(space) } wui.writeWord(w, key, word) } } func (wui *WebUI) writeLink(w io.Writer, key, value, text string) { | | | < < < | < | | < < < | < < | > > > | > > > > > > | 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 | w.Write(space) } wui.writeWord(w, key, word) } } func (wui *WebUI) writeLink(w io.Writer, key, value, text string) { fmt.Fprintf(w, "<a href=\"%v?%v=%v\">", wui.NewURLBuilder('h'), url.QueryEscape(key), url.QueryEscape(value)) html.Escape(w, text) io.WriteString(w, "</a>") } type getTextTitleFunc func(id.Zid) (string, int) func (wui *WebUI) makeGetTextTitle( ctx context.Context, getMeta usecase.GetMeta, evaluate *usecase.Evaluate, ) getTextTitleFunc { return func(zid id.Zid) (string, int) { m, err := getMeta.Run(box.NoEnrichContext(ctx), zid) if err != nil { if errors.Is(err, &box.ErrNotAllowed{}) { return "", -1 } return "", 0 } return wui.encodeTitleAsText(ctx, m, evaluate), 1 } } func (wui *WebUI) encodeTitleAsText(ctx context.Context, m *meta.Meta, evaluate *usecase.Evaluate) string { is := evaluate.RunMetadata(ctx, m.GetTitle()) result, err := encodeInlinesText(&is, wui.gentext) if err != nil { return err.Error() } return result } func encodeZmkMetadata(value string, evalMetadata evalMetadataFunc, gen *htmlGenerator) string { is := evalMetadata(value) result, err := gen.InlinesString(&is) if err != nil { return err.Error() } return result } |
Changes to web/adapter/webui/lists.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 | // under this license. //----------------------------------------------------------------------------- package webui import ( "bytes" | < < < > | > | < < < | > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | < < < < < | < < < < < < > | | | | | | | | | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > | < < < > > > > > > > > > > | > | | | < > > > > | < > | > | | < < > > > | < < < < < < < > | | | | | | > | > | | | > > > > > > > > > > > > > | 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 | // under this license. //----------------------------------------------------------------------------- package webui import ( "bytes" "net/http" "net/url" "sort" "strconv" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/search" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeListHTMLMetaHandler creates a HTTP handler for rendering the list of // zettel as HTML. func (wui *WebUI) MakeListHTMLMetaHandler( listMeta usecase.ListMeta, listRole usecase.ListRoles, listTags usecase.ListTags, evaluate *usecase.Evaluate, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() switch query.Get("_l") { case "r": wui.renderRolesList(w, r, listRole) case "t": wui.renderTagsList(w, r, listTags) default: wui.renderZettelList(w, r, listMeta, evaluate) } } } func (wui *WebUI) renderZettelList( w http.ResponseWriter, r *http.Request, listMeta usecase.ListMeta, evaluate *usecase.Evaluate, ) { query := r.URL.Query() s := adapter.GetSearch(query) ctx := r.Context() if !s.EnrichNeeded() { ctx = box.NoEnrichContext(ctx) } metaList, err := listMeta.Run(ctx, s) if err != nil { wui.reportError(ctx, w, err) return } user := wui.getUser(ctx) metas := wui.buildHTMLMetaList(metaList, func(val string) ast.InlineSlice { return evaluate.RunMetadataNoLink(ctx, val) }) var base baseData wui.makeBaseData(ctx, wui.rtConfig.GetDefaultLang(), wui.rtConfig.GetSiteName(), "", user, &base) wui.renderTemplate(ctx, w, id.ListTemplateZid, &base, struct { Title string SearchURL string SearchValue string QueryKeySearch string Metas []simpleLink }{ Title: wui.listTitleSearch(s), SearchURL: base.SearchURL, SearchValue: s.String(), QueryKeySearch: base.QueryKeySearch, Metas: metas, }) } type roleInfo struct { Text string URL string } func (wui *WebUI) renderRolesList(w http.ResponseWriter, r *http.Request, listRole usecase.ListRoles) { ctx := r.Context() roleArrangement, err := listRole.Run(ctx) if err != nil { wui.reportError(ctx, w, err) return } roleList := roleArrangement.Counted() roleList.SortByName() roleInfos := make([]roleInfo, len(roleList)) for i, role := range roleList { roleInfos[i] = roleInfo{role.Name, wui.NewURLBuilder('h').AppendQuery("role", role.Name).String()} } user := wui.getUser(ctx) var base baseData wui.makeBaseData(ctx, wui.rtConfig.GetDefaultLang(), wui.rtConfig.GetSiteName(), "", user, &base) wui.renderTemplate(ctx, w, id.RolesTemplateZid, &base, struct { Roles []roleInfo }{ Roles: roleInfos, }) } type countInfo struct { Count string URL string } type tagInfo struct { Name string URL string iCount int Count string Size string } const fontSizes = 6 // Must be the number of CSS classes zs-font-size-* in base.css func (wui *WebUI) renderTagsList(w http.ResponseWriter, r *http.Request, listTags usecase.ListTags) { ctx := r.Context() iMinCount, err := strconv.Atoi(r.URL.Query().Get("min")) if err != nil || iMinCount < 0 { iMinCount = 0 } tagData, err := listTags.Run(ctx, iMinCount) if err != nil { wui.reportError(ctx, w, err) return } user := wui.getUser(ctx) tagsList := make([]tagInfo, 0, len(tagData)) countMap := make(map[int]int) baseTagListURL := wui.NewURLBuilder('h') for tag, ml := range tagData { count := len(ml) countMap[count]++ tagsList = append( tagsList, tagInfo{tag, baseTagListURL.AppendQuery(api.KeyAllTags, tag).String(), count, "", ""}) baseTagListURL.ClearQuery() } sort.Slice(tagsList, func(i, j int) bool { return tagsList[i].Name < tagsList[j].Name }) countList := make([]int, 0, len(countMap)) for count := range countMap { countList = append(countList, count) } sort.Ints(countList) for pos, count := range countList { countMap[count] = (pos * fontSizes) / len(countList) } for i := 0; i < len(tagsList); i++ { count := tagsList[i].iCount tagsList[i].Count = strconv.Itoa(count) tagsList[i].Size = strconv.Itoa(countMap[count]) } var base baseData wui.makeBaseData(ctx, wui.rtConfig.GetDefaultLang(), wui.rtConfig.GetSiteName(), "", user, &base) minCounts := make([]countInfo, 0, len(countList)) for _, c := range countList { sCount := strconv.Itoa(c) minCounts = append(minCounts, countInfo{sCount, base.ListTagsURL + "&min=" + sCount}) } wui.renderTemplate(ctx, w, id.TagsTemplateZid, &base, struct { ListTagsURL string MinCounts []countInfo Tags []tagInfo }{ ListTagsURL: base.ListTagsURL, MinCounts: minCounts, Tags: tagsList, }) } // MakeZettelContextHandler creates a new HTTP handler for the use case "zettel context". func (wui *WebUI) MakeZettelContextHandler(getContext usecase.ZettelContext, evaluate *usecase.Evaluate) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() zid, err := id.Parse(r.URL.Path[1:]) if err != nil { wui.reportError(ctx, w, box.ErrNotFound) return } q := r.URL.Query() dir := adapter.GetZCDirection(q.Get(api.QueryKeyDir)) depth := getIntParameter(q, api.QueryKeyDepth, 5) limit := getIntParameter(q, api.QueryKeyLimit, 200) metaList, err := getContext.Run(ctx, zid, dir, depth, limit) if err != nil { wui.reportError(ctx, w, err) return } apiZid := api.ZettelID(zid.String()) metaLinks := wui.buildHTMLMetaList(metaList, func(val string) ast.InlineSlice { return evaluate.RunMetadataNoLink(ctx, val) }) depths := []string{"2", "3", "4", "5", "6", "7", "8", "9", "10"} depthLinks := make([]simpleLink, len(depths)) depthURL := wui.NewURLBuilder('k').SetZid(apiZid) for i, depth := range depths { depthURL.ClearQuery() switch dir { case usecase.ZettelContextBackward: depthURL.AppendQuery(api.QueryKeyDir, api.DirBackward) case usecase.ZettelContextForward: depthURL.AppendQuery(api.QueryKeyDir, api.DirForward) } depthURL.AppendQuery(api.QueryKeyDepth, depth) depthLinks[i].Text = depth depthLinks[i].URL = depthURL.String() } var base baseData user := wui.getUser(ctx) wui.makeBaseData(ctx, wui.rtConfig.GetDefaultLang(), wui.rtConfig.GetSiteName(), "", user, &base) wui.renderTemplate(ctx, w, id.ContextTemplateZid, &base, struct { Title string InfoURL string Depths []simpleLink Start simpleLink Metas []simpleLink }{ Title: "Zettel Context", InfoURL: wui.NewURLBuilder('i').SetZid(apiZid).String(), Depths: depthLinks, Start: metaLinks[0], Metas: metaLinks[1:], }) } } func getIntParameter(q url.Values, key string, minValue int) int { val, ok := adapter.GetInteger(q, key) if !ok || val < 0 { return minValue } return val } func (wui *WebUI) listTitleSearch(s *search.Search) string { if s == nil { return wui.rtConfig.GetSiteName() } var buf bytes.Buffer s.PrintHuman(&buf) return buf.String() } // buildHTMLMetaList builds a zettel list based on a meta list for HTML rendering. func (wui *WebUI) buildHTMLMetaList(metaList []*meta.Meta, evalMetadata evalMetadataFunc) []simpleLink { metas := make([]simpleLink, 0, len(metaList)) encHTML := wui.getSimpleHTMLEncoder() for _, m := range metaList { metas = append(metas, simpleLink{ Text: encodeZmkMetadata(m.GetTitle(), evalMetadata, encHTML), URL: wui.NewURLBuilder('h').SetZid(api.ZettelID(m.Zid.String())).String(), }) } return metas } |
Changes to web/adapter/webui/login.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 | package webui import ( "context" "net/http" | < | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package webui import ( "context" "net/http" "zettelstore.de/z/auth" "zettelstore.de/z/domain/id" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeGetLoginOutHandler creates a new HTTP handler to display the HTML login view, |
︙ | ︙ | |||
33 34 35 36 37 38 39 | } wui.renderLoginForm(wui.clearToken(r.Context(), w), w, false) } } func (wui *WebUI) renderLoginForm(ctx context.Context, w http.ResponseWriter, retry bool) { var base baseData | | | 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | } wui.renderLoginForm(wui.clearToken(r.Context(), w), w, false) } } func (wui *WebUI) renderLoginForm(ctx context.Context, w http.ResponseWriter, retry bool) { var base baseData wui.makeBaseData(ctx, wui.rtConfig.GetDefaultLang(), "Login", "", nil, &base) wui.renderTemplate(ctx, w, id.LoginTemplateZid, &base, struct { Title string Retry bool }{ Title: base.Title, Retry: retry, }) |
︙ | ︙ |
Changes to web/adapter/webui/rename_zettel.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 20 21 22 23 | import ( "fmt" "net/http" "strings" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" | > < | | | | 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 | import ( "fmt" "net/http" "strings" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeGetRenameZettelHandler creates a new HTTP handler to display the // HTML rename view of a zettel. func (wui *WebUI) MakeGetRenameZettelHandler(getMeta usecase.GetMeta, evaluate *usecase.Evaluate) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() zid, err := id.Parse(r.URL.Path[1:]) if err != nil { wui.reportError(ctx, w, box.ErrNotFound) return } m, err := getMeta.Run(ctx, zid) if err != nil { wui.reportError(ctx, w, err) return } getTextTitle := wui.makeGetTextTitle(ctx, getMeta, evaluate) incomingLinks := wui.encodeIncoming(m, getTextTitle) uselessFiles := retrieveUselessFiles(m) user := wui.getUser(ctx) var base baseData wui.makeBaseData(ctx, config.GetLang(m, wui.rtConfig), "Rename Zettel "+zid.String(), "", user, &base) wui.renderTemplate(ctx, w, id.RenameTemplateZid, &base, struct { Zid string MetaPairs []meta.Pair HasIncoming bool Incoming []simpleLink HasUselessFiles bool UselessFiles []string |
︙ | ︙ |
Deleted web/adapter/webui/titleenc.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to web/adapter/webui/webui.go.
︙ | ︙ | |||
95 96 97 98 99 100 101 | gentext: textenc.Create(), tokenLifetime: kernel.Main.GetConfig(kernel.WebService, kernel.WebTokenLifetimeHTML).(time.Duration), cssBaseURL: ab.NewURLBuilder('z').SetZid(api.ZidBaseCSS).String(), cssUserURL: ab.NewURLBuilder('z').SetZid(api.ZidUserCSS).String(), homeURL: ab.NewURLBuilder('/').String(), listZettelURL: ab.NewURLBuilder('h').String(), | | | | | | 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | gentext: textenc.Create(), tokenLifetime: kernel.Main.GetConfig(kernel.WebService, kernel.WebTokenLifetimeHTML).(time.Duration), cssBaseURL: ab.NewURLBuilder('z').SetZid(api.ZidBaseCSS).String(), cssUserURL: ab.NewURLBuilder('z').SetZid(api.ZidUserCSS).String(), homeURL: ab.NewURLBuilder('/').String(), listZettelURL: ab.NewURLBuilder('h').String(), listRolesURL: ab.NewURLBuilder('h').AppendQuery("_l", "r").String(), listTagsURL: ab.NewURLBuilder('h').AppendQuery("_l", "t").String(), refreshURL: ab.NewURLBuilder('g').AppendQuery("_c", "r").String(), withAuth: authz.WithAuth(), loginURL: loginoutBase.String(), logoutURL: loginoutBase.AppendQuery("logout", "").String(), searchURL: ab.NewURLBuilder('h').String(), } wui.observe(box.UpdateInfo{Box: mgr, Reason: box.OnReload, Zid: id.Invalid}) mgr.RegisterObserver(wui.observe) return wui } |
︙ | ︙ | |||
250 251 252 253 254 255 256 | ListRolesURL string ListTagsURL string CanRefresh bool RefreshURL string HasNewZettelLinks bool NewZettelLinks []simpleLink SearchURL string | | | 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 | ListRolesURL string ListTagsURL string CanRefresh bool RefreshURL string HasNewZettelLinks bool NewZettelLinks []simpleLink SearchURL string QueryKeySearch string Content string FooterHTML string DebugMode bool } func (wui *WebUI) makeBaseData(ctx context.Context, lang, title, roleCSSURL string, user *meta.Meta, data *baseData) { var userZettelURL string |
︙ | ︙ | |||
288 289 290 291 292 293 294 | data.ListRolesURL = wui.listRolesURL data.ListTagsURL = wui.listTagsURL data.CanRefresh = wui.canRefresh(user) data.RefreshURL = wui.refreshURL data.HasNewZettelLinks = len(newZettelLinks) > 0 data.NewZettelLinks = newZettelLinks data.SearchURL = wui.searchURL | | | | > > | | | | 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 | data.ListRolesURL = wui.listRolesURL data.ListTagsURL = wui.listTagsURL data.CanRefresh = wui.canRefresh(user) data.RefreshURL = wui.refreshURL data.HasNewZettelLinks = len(newZettelLinks) > 0 data.NewZettelLinks = newZettelLinks data.SearchURL = wui.searchURL data.QueryKeySearch = api.QueryKeySearch data.FooterHTML = wui.rtConfig.GetFooterHTML() data.DebugMode = wui.debug } func (wui *WebUI) getSimpleHTMLEncoder() *htmlGenerator { return createGenerator(wui, "", false) } func (wui *WebUI) createZettelEncoder() *htmlGenerator { return createGenerator(wui, wui.rtConfig.GetMarkerExternal(), true) } // htmlAttrNewWindow returns HTML attribute string for opening a link in a new window. // If hasURL is false an empty string is returned. func htmlAttrNewWindow(hasURL bool) string { if hasURL { return " target=\"_blank\" ref=\"noopener noreferrer\"" } return "" } func (wui *WebUI) fetchNewTemplates(ctx context.Context, user *meta.Meta) (result []simpleLink) { ctx = box.NoEnrichContext(ctx) if !wui.canCreate(ctx, user) { return nil } menu, err := wui.box.GetZettel(ctx, id.TOCNewTemplateZid) if err != nil { return nil } refs := collect.Order(parser.ParseZettel(menu, "", wui.rtConfig)) for _, ref := range refs { zid, err2 := id.Parse(ref.URL.Path) if err2 != nil { continue } m, err2 := wui.box.GetMeta(ctx, zid) if err2 != nil { |
︙ | ︙ | |||
341 342 343 344 345 346 347 | if err2 != nil { menuTitle = title } } result = append(result, simpleLink{ Text: menuTitle, URL: wui.NewURLBuilder('c').SetZid(api.ZettelID(m.Zid.String())). | | | | 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 | if err2 != nil { menuTitle = title } } result = append(result, simpleLink{ Text: menuTitle, URL: wui.NewURLBuilder('c').SetZid(api.ZettelID(m.Zid.String())). AppendQuery(queryKeyAction, valueActionNew).String(), }) } return result } func (wui *WebUI) renderTemplate( ctx context.Context, w http.ResponseWriter, templateID id.Zid, base *baseData, data interface{}) { wui.renderTemplateStatus(ctx, w, http.StatusOK, templateID, base, data) } func (wui *WebUI) reportError(ctx context.Context, w http.ResponseWriter, err error) { code, text := adapter.CodeMessageFromError(err) if code == http.StatusInternalServerError { wui.log.Error().Msg(err.Error()) } user := wui.getUser(ctx) var base baseData wui.makeBaseData(ctx, api.ValueLangEN, "Error", "", user, &base) wui.renderTemplateStatus(ctx, w, code, id.ErrorTemplateZid, &base, struct { ErrorTitle string ErrorText string }{ ErrorTitle: http.StatusText(code), |
︙ | ︙ | |||
393 394 395 396 397 398 399 | } t, err := wui.getTemplate(ctx, templateID) if err != nil { wui.log.IfErr(err).Zid(templateID).Msg("Unable to get template") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } | | > > | 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 | } t, err := wui.getTemplate(ctx, templateID) if err != nil { wui.log.IfErr(err).Zid(templateID).Msg("Unable to get template") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if user := wui.getUser(ctx); user != nil { if tok, err1 := wui.token.GetToken(user, wui.tokenLifetime, auth.KindHTML); err1 == nil { wui.setToken(w, tok) } } var content bytes.Buffer err = t.Render(&content, data) if err == nil { wui.prepareAndWriteHeader(w, code) base.Content = content.String() err = bt.Render(w, base) } if err != nil { wui.log.IfErr(err).Msg("Unable to write HTML via template") } } func (wui *WebUI) getUser(ctx context.Context) *meta.Meta { return wui.ab.GetUser(ctx) } // GetURLPrefix returns the configured URL prefix of the web server. func (wui *WebUI) GetURLPrefix() string { return wui.ab.GetURLPrefix() } // NewURLBuilder creates a new URL builder object with the given key. func (wui *WebUI) NewURLBuilder(key byte) *api.URLBuilder { return wui.ab.NewURLBuilder(key) } |
︙ | ︙ |
Changes to web/server/impl/impl.go.
︙ | ︙ | |||
21 22 23 24 25 26 27 | "zettelstore.de/z/domain/meta" "zettelstore.de/z/logger" "zettelstore.de/z/web/server" ) type myServer struct { log *logger.Logger | < | < > > > | < | | | | 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 | "zettelstore.de/z/domain/meta" "zettelstore.de/z/logger" "zettelstore.de/z/web/server" ) type myServer struct { log *logger.Logger server httpServer router httpRouter persistentCookie bool secureCookie bool } // New creates a new web server. func New(log *logger.Logger, listenAddr, urlPrefix string, persistentCookie, secureCookie bool, maxRequestSize int64, auth auth.TokenManager) server.Server { srv := myServer{ log: log, persistentCookie: persistentCookie, secureCookie: secureCookie, } srv.router.initializeRouter(log, urlPrefix, maxRequestSize, auth) srv.server.initializeHTTPServer(listenAddr, &srv.router) return &srv } func (srv *myServer) Handle(pattern string, handler http.Handler) { srv.router.Handle(pattern, handler) } func (srv *myServer) AddListRoute(key byte, method server.Method, handler http.Handler) { srv.router.addListRoute(key, method, handler) } func (srv *myServer) AddZettelRoute(key byte, method server.Method, handler http.Handler) { srv.router.addZettelRoute(key, method, handler) } func (srv *myServer) SetUserRetriever(ur server.UserRetriever) { srv.router.ur = ur } func (srv *myServer) GetUser(ctx context.Context) *meta.Meta { if data := srv.GetAuthData(ctx); data != nil { return data.User } return nil } func (srv *myServer) NewURLBuilder(key byte) *api.URLBuilder { return api.NewURLBuilder(srv.GetURLPrefix(), key) } func (srv *myServer) GetURLPrefix() string { return srv.router.urlPrefix } const sessionName = "zsession" func (srv *myServer) SetToken(w http.ResponseWriter, token []byte, d time.Duration) { cookie := http.Cookie{ Name: sessionName, |
︙ | ︙ | |||
88 89 90 91 92 93 94 | w.Header().Add("Cache-Control", `no-cache="Set-Cookie"`) w.Header().Add("Vary", "Cookie") } } // ClearToken invalidates the session cookie by sending an empty one. func (srv *myServer) ClearToken(ctx context.Context, w http.ResponseWriter) context.Context { | | > > > > > > > > > > > > > | | | 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 | w.Header().Add("Cache-Control", `no-cache="Set-Cookie"`) w.Header().Add("Vary", "Cookie") } } // ClearToken invalidates the session cookie by sending an empty one. func (srv *myServer) ClearToken(ctx context.Context, w http.ResponseWriter) context.Context { if authData := srv.GetAuthData(ctx); authData == nil { // No authentication data stored in session, nothing to do. return ctx } if w != nil { srv.SetToken(w, nil, 0) } return updateContext(ctx, nil, nil) } // GetAuthData returns the full authentication data from the context. func (*myServer) GetAuthData(ctx context.Context) *server.AuthData { data, ok := ctx.Value(ctxKeySession).(*server.AuthData) if ok { return data } return nil } type ctxKeyTypeSession struct{} var ctxKeySession ctxKeyTypeSession func updateContext(ctx context.Context, user *meta.Meta, data *auth.TokenData) context.Context { if data == nil { return context.WithValue(ctx, ctxKeySession, &server.AuthData{User: user}) } return context.WithValue( ctx, ctxKeySession, &server.AuthData{ User: user, Token: data.Token, Now: data.Now, Issued: data.Issued, Expires: data.Expires, }) } func (srv *myServer) SetDebug() { srv.server.SetDebug() } func (srv *myServer) Run() error { return srv.server.Run() } func (srv *myServer) Stop() { srv.server.Stop() } |
Changes to web/server/server.go.
1 | //----------------------------------------------------------------------------- | | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2021 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package server provides the Zettelstore web service. |
︙ | ︙ | |||
48 49 50 51 52 53 54 | SetUserRetriever(ur UserRetriever) } // Builder allows to build new URLs for the web service. type Builder interface { GetURLPrefix() string NewURLBuilder(key byte) *api.URLBuilder | < | > > > < < < < < < < < < < < < < < < < < < < < < < < < < | 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 | SetUserRetriever(ur UserRetriever) } // Builder allows to build new URLs for the web service. type Builder interface { GetURLPrefix() string NewURLBuilder(key byte) *api.URLBuilder } // Auth is the authencation interface. type Auth interface { GetUser(context.Context) *meta.Meta SetToken(w http.ResponseWriter, token []byte, d time.Duration) // ClearToken invalidates the session cookie by sending an empty one. ClearToken(ctx context.Context, w http.ResponseWriter) context.Context // GetAuthData returns the full authentication data from the context. GetAuthData(ctx context.Context) *AuthData } // AuthData stores all relevant authentication data for a context. type AuthData struct { User *meta.Meta Token []byte Now time.Time Issued time.Time Expires time.Time } // AuthBuilder is a Builder that also allows to execute authentication functions. type AuthBuilder interface { Auth Builder } // Server is the main web server for accessing Zettelstore via HTTP. |
︙ | ︙ |
Changes to www/changes.wiki.
1 2 | <title>Change Log</title> | < < < < < < < < < < < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 | <title>Change Log</title> <a name="0_7"></a> <h2>Changes for Version 0.7.0 (pending)</h2> <a name="0_6"></a> <h2>Changes for Version 0.6.0 (2022-08-11)</h2> * Translating of "..." into horizontal ellipsis is no longer supported. Use &hellip; instead. (breaking: zettelmarkup) * Allow to specify search expressions, which allow to specify search criterias by using a simple syntax. Can be specified in WebUI's search box and via the API by using query parameter "_s". |
︙ | ︙ | |||
124 125 126 127 128 129 130 | (minor: manual) * Using URL query parameter to search for metdata values, specify an ordering, an offset, and a limit for the resulting list, will be removed in version 0.7. Replace these with the more useable search expressions. Please be aware that the = search operator is also deprecated. It was only introduced to help the migration. (deprecated: api, webui) | | | 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | (minor: manual) * Using URL query parameter to search for metdata values, specify an ordering, an offset, and a limit for the resulting list, will be removed in version 0.7. Replace these with the more useable search expressions. Please be aware that the = search operator is also deprecated. It was only introduced to help the migration. (deprecated: api, webui) * Some smaller bug fixes and inprovements, to the software and to the documentation. <a name="0_5_0"></a> <h2>Changes for Version 0.5.1 (2022-08-02)</h2> * Log missing authentication tokens in debug level (was: sense level) (major) * Allow to use empty metadata values of string and zmk types. |
︙ | ︙ | |||
189 190 191 192 193 194 195 | (minor) * Log authentication issues in level "sense"; add caller IP address to some web server log messages. (minor: web server) * New startup configuration key <kbd>max-request-size</kbd> to limit a web request body to prevent client sending too large requests. (minor: web server) | | | 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | (minor) * Log authentication issues in level "sense"; add caller IP address to some web server log messages. (minor: web server) * New startup configuration key <kbd>max-request-size</kbd> to limit a web request body to prevent client sending too large requests. (minor: web server) * Many smaller bug fixes and inprovements, to the software and to the documentation. <a name="0_4"></a> <h2>Changes for Version 0.4 (2022-03-08)</h2> * Encoding “djson” renamed to “zjson” (<em>zettel json</em>). (breaking: api; minor: webui) |
︙ | ︙ | |||
245 246 247 248 249 250 251 | (minor: api) * Limit the amount of zettel and bytes to be stored in a memory box. Allows to use it with public access. (minor: box) * Disallow to cache the authentication cookie. Will remove most unexpected log-outs when using a mobile device. (minor: webui) | | | 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | (minor: api) * Limit the amount of zettel and bytes to be stored in a memory box. Allows to use it with public access. (minor: box) * Disallow to cache the authentication cookie. Will remove most unexpected log-outs when using a mobile device. (minor: webui) * Many smaller bug fixes and inprovements, to the software and to the documentation. <a name="0_3"></a> <h2>Changes for Version 0.3 (2022-02-09)</h2> * Zettel files with extension <tt>.meta</tt> are now treated as content files. Previoulsy, they were interpreted as metadata files. The interpretation as metadata files was deprecated in version 0.2. |
︙ | ︙ | |||
267 268 269 270 271 272 273 | (major: Zettelmarkup) * Metadata-returning API calls additionally return an indication about access rights for the given zettel. (minor: api) * A previously duplicate file that is now useful (because another file was deleted) is now logged as such. (minor: directory and file/zip box) | | | 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | (major: Zettelmarkup) * Metadata-returning API calls additionally return an indication about access rights for the given zettel. (minor: api) * A previously duplicate file that is now useful (because another file was deleted) is now logged as such. (minor: directory and file/zip box) * Many smaller bug fixes and inprovements, to the software and to the documentation. <a name="0_2"></a> <h2>Changes for Version 0.2 (2022-01-19)</h2> * v0.2.1 (2021-02-01) updates the license year in some documents * Remove support for <tt>;;small text;;</tt> Zettelmarkup. (breaking: Zettelmarkup) |
︙ | ︙ | |||
333 334 335 336 337 338 339 | <tt>Content-Length: 0</tt>. (minor: api) * Metadata key <tt>duplicates</tt> stores the duplicate file names, instead of just a boolean value that there were duplicate file names. (minor) * Document autostarting Zettelstore on Windows, macOS, and Linux. (minor) | | | 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 | <tt>Content-Length: 0</tt>. (minor: api) * Metadata key <tt>duplicates</tt> stores the duplicate file names, instead of just a boolean value that there were duplicate file names. (minor) * Document autostarting Zettelstore on Windows, macOS, and Linux. (minor) * Many smaller bug fixes and inprovements, to the software and to the documentation. <a name="0_1"></a><a name="0_1_0"></a> <h2>Changes for Version 0.1 (2021-11-11)</h2> * v0.1.3 (2021-12-15) fixes a bug where the modification date could be set when a new zettel is created. * v0.1.2 (2021-11-18) fixes a bug when selecting zettel from a list when |
︙ | ︙ | |||
398 399 400 401 402 403 404 | * Fix: when a very long word (longer than width of browser window) is given, still allow to scroll horizontally. (minor: webui) * Separate repository for [https://zettelstore.de/contrib/|contributed] software. First entry is a software for creating a presentation by using zettel. (info) | | | 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | * Fix: when a very long word (longer than width of browser window) is given, still allow to scroll horizontally. (minor: webui) * Separate repository for [https://zettelstore.de/contrib/|contributed] software. First entry is a software for creating a presentation by using zettel. (info) * Many smaller bug fixes and inprovements, to the software and to the documentation. <a name="0_0_15"></a> <h2>Changes for Version 0.0.15 (2021-09-17)</h2> * Move again endpoint characters for authentication to make room for future features. WebUI authentication moves from <tt>/a</tt> to <tt>/i</tt> (login) and <tt>/i?logout</tt> (logout). API authentication moves from |
︙ | ︙ | |||
453 454 455 456 457 458 459 | * Folgezettel are now displayed with full title at the bottom of a page. (minor: webui) * Add API endpoint <tt>/p/{ID}</tt> to retrieve a parsed, but not evaluated zettel in various encodings. (minor: api) * Fix: do not list a shadowed zettel that matches the select criteria. (minor) | | | 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 | * Folgezettel are now displayed with full title at the bottom of a page. (minor: webui) * Add API endpoint <tt>/p/{ID}</tt> to retrieve a parsed, but not evaluated zettel in various encodings. (minor: api) * Fix: do not list a shadowed zettel that matches the select criteria. (minor) * Many smaller bug fixes and inprovements, to the software and to the documentation. <a name="0_0_14"></a> <h2>Changes for Version 0.0.14 (2021-07-23)</h2> * Rename “place” into “box”. This also affects the configuration keys to specify boxes <tt>box-uri<em>X</em></tt> (previously <tt>place-uri-<em>X</em></tt>. Older changes documented here are renamed |
︙ | ︙ | |||
502 503 504 505 506 507 508 | (minor: webui) * When a user moves a zettel file with additional characters into the box directory, these characters are preserved when zettel is updated. (bug) * The phase “filtering a zettel list” is more precise “selecting zettel” (documentation) | | | 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 | (minor: webui) * When a user moves a zettel file with additional characters into the box directory, these characters are preserved when zettel is updated. (bug) * The phase “filtering a zettel list” is more precise “selecting zettel” (documentation) * Many smaller bug fixes and inprovements, to the software and to the documentation. <a name="0_0_13"></a> <h2>Changes for Version 0.0.13 (2021-06-01)</h2> * Startup configuration <tt>box-<em>X</em>-uri</tt> (where <em>X</em> is a number greater than zero) has been renamed to <tt>box-uri-<em>X</em></tt>. |
︙ | ︙ | |||
546 547 548 549 550 551 552 | substituted with the new default image, a spinning emoji. See [/file?name=box/constbox/emoji_spin.gif]. (minor: webui) * Add zettelmarkup syntax for a table row that should be ignored: <tt>|%</tt>. This allows to paste output of the administrator console into a zettel. (minor: zmk) | | | 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 | substituted with the new default image, a spinning emoji. See [/file?name=box/constbox/emoji_spin.gif]. (minor: webui) * Add zettelmarkup syntax for a table row that should be ignored: <tt>|%</tt>. This allows to paste output of the administrator console into a zettel. (minor: zmk) * Many smaller bug fixes and inprovements, to the software and to the documentation. <a name="0_0_12"></a> <h2>Changes for Version 0.0.12 (2021-04-16)</h2> * Raise the per-process limit of open files on macOS to 1.048.576. This allows most macOS users to use at least 500.000 zettel. That should be enough for the near future. |
︙ | ︙ | |||
600 601 602 603 604 605 606 | into erratic, non-deterministic context lists. (minor: api, webui) * Selecting zettel depending on tag values can be both by comparing only the prefix or the whole string. If a search value begins with '#', only zettel with the exact tag will be returned. Otherwise a zettel will be returned if the search string just matches the prefix of only one of its tags. (minor: api, webui) | | | 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 | into erratic, non-deterministic context lists. (minor: api, webui) * Selecting zettel depending on tag values can be both by comparing only the prefix or the whole string. If a search value begins with '#', only zettel with the exact tag will be returned. Otherwise a zettel will be returned if the search string just matches the prefix of only one of its tags. (minor: api, webui) * Many smaller bug fixes and inprovements, to the software and to the documentation. A note for users of macOS: in the current release and with macOS's default values, a zettel directory must not contain more than approx. 250 files. There are three options to mitigate this limitation temporarily: # You update the per-process limit of open files on macOS. # You setup a virtualization environment to run Zettelstore on Linux or Windows. # You wait for version 0.0.12 which addresses this issue. |
︙ | ︙ | |||
651 652 653 654 655 656 657 | “&#10138;” (“➚”). It is more beautiful than the previous “&#8599;&#xfe0e;” (“↗︎”), which also needed the additional “&#xfe0e;” to disable the conversion to an emoji on iPadOS. (minor: webui) * A pre-build binary for macOS ARM64 (also known as Apple silicon) is available. (minor: infrastructure) | | | 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 | “&#10138;” (“➚”). It is more beautiful than the previous “&#8599;&#xfe0e;” (“↗︎”), which also needed the additional “&#xfe0e;” to disable the conversion to an emoji on iPadOS. (minor: webui) * A pre-build binary for macOS ARM64 (also known as Apple silicon) is available. (minor: infrastructure) * Many smaller bug fixes and inprovements, to the software and to the documentation. <a name="0_0_9"></a> <h2>Changes for Version 0.0.9 (2021-01-29)</h2> This is the first version that is managed by [https://fossil-scm.org|Fossil] instead of GitHub. To access older versions, use the Git repository under [https://github.com/zettelstore/zettelstore-github|zettelstore-github]. |
︙ | ︙ |
Changes to www/download.wiki.
1 2 3 4 5 6 7 8 9 10 11 | <title>Download</title> <h1>Download of Zettelstore Software</h1> <h2>Foreword</h2> * Zettelstore is free/libre open source software, licensed under EUPL-1.2-or-later. * The software is provided as-is. * There is no guarantee that it will not damage your system. * However, it is in use by the main developer since March 2020 without any damage. * It may be useful for you. It is useful for me. * Take a look at the [https://zettelstore.de/manual/|manual] to know how to start and use it. <h2>ZIP-ped Executables</h2> | | | | | | | | | 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 | <title>Download</title> <h1>Download of Zettelstore Software</h1> <h2>Foreword</h2> * Zettelstore is free/libre open source software, licensed under EUPL-1.2-or-later. * The software is provided as-is. * There is no guarantee that it will not damage your system. * However, it is in use by the main developer since March 2020 without any damage. * It may be useful for you. It is useful for me. * Take a look at the [https://zettelstore.de/manual/|manual] to know how to start and use it. <h2>ZIP-ped Executables</h2> Build: <code>v0.6.0</code> (2022-08-11). * [/uv/zettelstore-0.6.0-linux-amd64.zip|Linux] (amd64) * [/uv/zettelstore-0.6.0-linux-arm.zip|Linux] (arm6, e.g. Raspberry Pi) * [/uv/zettelstore-0.6.0-windows-amd64.zip|Windows] (amd64) * [/uv/zettelstore-0.6.0-darwin-amd64.zip|macOS] (amd64) * [/uv/zettelstore-0.6.0-darwin-arm64.zip|macOS] (arm64, aka Apple silicon) Unzip the appropriate file, install and execute Zettelstore according to the manual. <h2>Zettel for the manual</h2> As a starter, you can download the zettel for the manual [/uv/manual-0.6.0.zip|here]. Just unzip the contained files and put them into your zettel folder or configure a file box to read the zettel directly from the ZIP file. |
Changes to www/index.wiki.
︙ | ︙ | |||
20 21 22 23 24 25 26 | access Zettelstore via its API more easily, [https://zettelstore.de/contrib|Zettelstore Contrib] contains contributed software, which often connects to Zettelstore via its API. Some of the software packages may be experimental. [https://twitter.com/zettelstore|Stay tuned] … <hr> | | | | | | | | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | access Zettelstore via its API more easily, [https://zettelstore.de/contrib|Zettelstore Contrib] contains contributed software, which often connects to Zettelstore via its API. Some of the software packages may be experimental. [https://twitter.com/zettelstore|Stay tuned] … <hr> <h3>Latest Release: 0.6.0 (2022-08-11)</h3> * [./download.wiki|Download] * [./changes.wiki#0_6|Change summary] * [/timeline?p=v0.6.0&bt=v0.5.0&y=ci|Check-ins for version 0.6.0], [/vdiff?to=v0.6.0&from=v0.5.0|content diff] * [/timeline?df=v0.6.0&y=ci|Check-ins derived from the 0.6.0 release], [/vdiff?from=v0.6.0&to=trunk|content diff] * [./plan.wiki|Limitations and planned improvements] * [/timeline?t=release|Timeline of all past releases] <hr> <h2>Build instructions</h2> Just install [https://golang.org/dl/|Go] and some Go-based tools. Please read the [./build.md|instructions] for details. |
︙ | ︙ |