Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From v0.18.0 To v0.19.0
2024-12-13
| ||
14:01 | Increase version to 0.20.0-dev to begin next development cycle ... (check-in: 8d481c1e92 user: stern tags: trunk) | |
13:26 | Version 0.19.0 ... (check-in: bb751329ab user: stern tags: trunk, release, v0.19.0) | |
12:36 | Update to latest zettelstore client ... (check-in: 5ed6d8e8e3 user: stern tags: trunk) | |
2024-07-11
| ||
15:34 | Increase version to 0.19.0-dev to begin next development cycle ... (check-in: 1d1cd5e637 user: stern tags: trunk) | |
14:43 | Version 0.18.0 ... (check-in: b94ede10d4 user: stern tags: trunk, release, v0.18.0) | |
14:14 | Add KEYS aggregate action to API manual ... (check-in: a6d7c963a1 user: stern tags: trunk) | |
Changes to VERSION.
|
| | | 1 | 0.19.0 |
Changes to auth/auth.go.
︙ | ︙ | |||
91 92 93 94 95 96 97 | // User is allowed to read zettel CanRead(user, m *meta.Meta) bool // User is allowed to write zettel. CanWrite(user, oldMeta, newMeta *meta.Meta) bool | < < < | 91 92 93 94 95 96 97 98 99 100 101 102 103 | // User is allowed to read zettel CanRead(user, m *meta.Meta) bool // User is allowed to write zettel. CanWrite(user, oldMeta, newMeta *meta.Meta) bool // User is allowed to delete zettel. CanDelete(user, m *meta.Meta) bool // User is allowed to refresh box data. CanRefresh(user *meta.Meta) bool } |
Changes to auth/policy/anon.go.
︙ | ︙ | |||
32 33 34 35 36 37 38 | return ap.pre.CanRead(user, m) && ap.checkVisibility(m) } func (ap *anonPolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool { return ap.pre.CanWrite(user, oldMeta, newMeta) && ap.checkVisibility(oldMeta) } | < < < < | 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | return ap.pre.CanRead(user, m) && ap.checkVisibility(m) } func (ap *anonPolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool { return ap.pre.CanWrite(user, oldMeta, newMeta) && ap.checkVisibility(oldMeta) } func (ap *anonPolicy) CanDelete(user, m *meta.Meta) bool { return ap.pre.CanDelete(user, m) && ap.checkVisibility(m) } func (ap *anonPolicy) CanRefresh(user *meta.Meta) bool { if ap.authConfig.GetExpertMode() || ap.authConfig.GetSimpleMode() { return true |
︙ | ︙ |
Changes to auth/policy/box.go.
︙ | ︙ | |||
118 119 120 121 122 123 124 | } if pp.policy.CanWrite(user, oldZettel.Meta, zettel.Meta) { return pp.box.UpdateZettel(ctx, zettel) } return box.NewErrNotAllowed("Write", user, zid) } | < < < < < < < < < < < < < < < < | 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | } if pp.policy.CanWrite(user, oldZettel.Meta, zettel.Meta) { return pp.box.UpdateZettel(ctx, zettel) } return box.NewErrNotAllowed("Write", user, zid) } 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 { z, err := pp.box.GetZettel(ctx, zid) if err != nil { |
︙ | ︙ |
Changes to auth/policy/default.go.
︙ | ︙ | |||
24 25 26 27 28 29 30 | } func (*defaultPolicy) CanCreate(_, _ *meta.Meta) bool { return true } func (*defaultPolicy) CanRead(_, _ *meta.Meta) bool { return true } func (d *defaultPolicy) CanWrite(user, oldMeta, _ *meta.Meta) bool { return d.canChange(user, oldMeta) } | < | 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | } func (*defaultPolicy) CanCreate(_, _ *meta.Meta) bool { return true } func (*defaultPolicy) CanRead(_, _ *meta.Meta) bool { return true } func (d *defaultPolicy) CanWrite(user, oldMeta, _ *meta.Meta) bool { return d.canChange(user, oldMeta) } func (d *defaultPolicy) CanDelete(user, m *meta.Meta) bool { return d.canChange(user, m) } func (*defaultPolicy) CanRefresh(user *meta.Meta) bool { return user != nil } func (d *defaultPolicy) canChange(user, m *meta.Meta) bool { metaRo, ok := m.Get(api.KeyReadOnly) if !ok { |
︙ | ︙ |
Changes to auth/policy/owner.go.
︙ | ︙ | |||
111 112 113 114 115 116 117 | switch userRole := o.manager.GetUserRole(user); userRole { case meta.UserRoleReader, meta.UserRoleCreator: return false } return o.userCanCreate(user, newMeta) } | < < < < < < < < < < | 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | switch userRole := o.manager.GetUserRole(user); userRole { case meta.UserRoleReader, meta.UserRoleCreator: return false } return o.userCanCreate(user, newMeta) } func (o *ownerPolicy) CanDelete(user, m *meta.Meta) bool { if user == nil || !o.pre.CanDelete(user, m) { return false } if res, ok := o.checkVisibility(user, o.authConfig.GetVisibility(m)); ok { return res } |
︙ | ︙ |
Changes to auth/policy/policy.go.
︙ | ︙ | |||
56 57 58 59 60 61 62 | } func (p *prePolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool { return oldMeta != nil && newMeta != nil && oldMeta.Zid == newMeta.Zid && p.post.CanWrite(user, oldMeta, newMeta) } | < < < < | 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | } func (p *prePolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool { return oldMeta != nil && newMeta != nil && oldMeta.Zid == newMeta.Zid && p.post.CanWrite(user, oldMeta, newMeta) } func (p *prePolicy) CanDelete(user, m *meta.Meta) bool { return m != nil && p.post.CanDelete(user, m) } func (p *prePolicy) CanRefresh(user *meta.Meta) bool { return p.post.CanRefresh(user) } |
Changes to auth/policy/policy_test.go.
︙ | ︙ | |||
55 56 57 58 59 60 61 | ) name := fmt.Sprintf("readonly=%v/withauth=%v/expert=%v/simple=%v", ts.readonly, ts.withAuth, ts.expert, ts.simple) t.Run(name, func(tt *testing.T) { testCreate(tt, pol, ts.withAuth, ts.readonly) testRead(tt, pol, ts.withAuth, ts.expert) testWrite(tt, pol, ts.withAuth, ts.readonly, ts.expert) | < | 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | ) name := fmt.Sprintf("readonly=%v/withauth=%v/expert=%v/simple=%v", ts.readonly, ts.withAuth, ts.expert, ts.simple) t.Run(name, func(tt *testing.T) { testCreate(tt, pol, ts.withAuth, ts.readonly) testRead(tt, pol, ts.withAuth, ts.expert) testWrite(tt, pol, ts.withAuth, ts.readonly, ts.expert) testDelete(tt, pol, ts.withAuth, ts.readonly, ts.expert) testRefresh(tt, pol, ts.withAuth, ts.expert, ts.simple) }) } } type testAuthzManager struct { |
︙ | ︙ | |||
391 392 393 394 395 396 397 | {owner, roTrue, roTrue, false}, {owner2, roTrue, roTrue, false}, } for _, tc := range testCases { t.Run("Write", func(tt *testing.T) { got := pol.CanWrite(tc.user, tc.old, tc.new) if tc.exp != got { | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 390 391 392 393 394 395 396 397 398 399 400 401 402 403 | {owner, roTrue, roTrue, false}, {owner2, roTrue, roTrue, false}, } for _, tc := range testCases { t.Run("Write", func(tt *testing.T) { got := pol.CanWrite(tc.user, tc.old, tc.new) if tc.exp != got { tt.Errorf("exp=%v, but got=%v", tc.exp, got) } }) } } func testDelete(t *testing.T, pol auth.Policy, withAuth, readonly, expert bool) { |
︙ | ︙ |
Changes to auth/policy/readonly.go.
︙ | ︙ | |||
16 17 18 19 20 21 22 | import "zettelstore.de/z/zettel/meta" type roPolicy struct{} func (*roPolicy) CanCreate(_, _ *meta.Meta) bool { return false } func (*roPolicy) CanRead(_, _ *meta.Meta) bool { return true } func (*roPolicy) CanWrite(_, _, _ *meta.Meta) bool { return false } | < | 16 17 18 19 20 21 22 23 24 | import "zettelstore.de/z/zettel/meta" type roPolicy struct{} func (*roPolicy) CanCreate(_, _ *meta.Meta) bool { return false } func (*roPolicy) CanRead(_, _ *meta.Meta) bool { return true } func (*roPolicy) CanWrite(_, _, _ *meta.Meta) bool { return false } func (*roPolicy) CanDelete(_, _ *meta.Meta) bool { return false } func (*roPolicy) CanRefresh(user *meta.Meta) bool { return user != nil } |
Changes to box/box.go.
︙ | ︙ | |||
33 34 35 36 37 38 39 | // Location returns some information where the box is located. // Format is dependent of the box. Location() string // GetZettel retrieves a specific zettel. GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) | < < < < < < | 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | // Location returns some information where the box is located. // Format is dependent of the box. Location() string // GetZettel retrieves a specific zettel. GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) // CanDeleteZettel returns true, if box could possibly delete the given zettel. CanDeleteZettel(ctx context.Context, zid id.Zid) bool // DeleteZettel removes the zettel from the box. DeleteZettel(ctx context.Context, zid id.Zid) error } |
︙ | ︙ | |||
208 209 210 211 212 213 214 | type UpdateReason uint8 // Values for Reason const ( _ UpdateReason = iota OnReady // Box is started and fully operational OnReload // Box was reloaded | | > > > > | 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 | type UpdateReason uint8 // Values for Reason const ( _ UpdateReason = iota OnReady // Box is started and fully operational OnReload // Box was reloaded OnZettel // Something with an existing zettel happened OnDelete // A zettel was deleted ) // UpdateInfo contains all the data about a changed zettel. type UpdateInfo struct { Box BaseBox Reason UpdateReason Zid id.Zid } // UpdateFunc is a function to be called when a change is detected. type UpdateFunc func(UpdateInfo) // UpdateNotifier is an UpdateFunc, but with separate values. type UpdateNotifier func(BaseBox, id.Zid, UpdateReason) // Subject is a box that notifies observers about changes. type Subject interface { // RegisterObserver registers an observer that will be notified // if one or all zettel are found to be changed. RegisterObserver(UpdateFunc) } |
︙ | ︙ |
Changes to box/compbox/compbox.go.
︙ | ︙ | |||
28 29 30 31 32 33 34 | "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func init() { manager.Register( " comp", | | | < | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func init() { manager.Register( " comp", func(_ *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { return getCompBox(cdata.Number, cdata.Enricher), nil }) } type compBox struct { log *logger.Logger number int enricher box.Enricher } var myConfig *meta.Meta var myZettel = map[id.Zid]struct { meta func(id.Zid) *meta.Meta content func(context.Context, *compBox) []byte }{ |
︙ | ︙ | |||
61 62 63 64 65 66 67 | // id.MustParse(api.ZidConsole): {genConsoleM, genConsoleC}, id.MustParse(api.ZidBoxManager): {genManagerM, genManagerC}, // id.MustParse(api.ZidIndex): {genIndexM, genIndexC}, // id.MustParse(api.ZidQuery): {genQueryM, genQueryC}, id.MustParse(api.ZidMetadataKey): {genKeysM, genKeysC}, id.MustParse(api.ZidParser): {genParserM, genParserC}, id.MustParse(api.ZidStartupConfiguration): {genConfigZettelM, genConfigZettelC}, | < < | < | 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | // id.MustParse(api.ZidConsole): {genConsoleM, genConsoleC}, id.MustParse(api.ZidBoxManager): {genManagerM, genManagerC}, // id.MustParse(api.ZidIndex): {genIndexM, genIndexC}, // id.MustParse(api.ZidQuery): {genQueryM, genQueryC}, id.MustParse(api.ZidMetadataKey): {genKeysM, genKeysC}, id.MustParse(api.ZidParser): {genParserM, genParserC}, id.MustParse(api.ZidStartupConfiguration): {genConfigZettelM, genConfigZettelC}, } // Get returns the one program box. func getCompBox(boxNumber int, mf box.Enricher) *compBox { return &compBox{ log: kernel.Main.GetLogger(kernel.BoxService).Clone(). Str("box", "comp").Int("boxnum", int64(boxNumber)).Child(), number: boxNumber, enricher: mf, } } // Setup remembers important values. func Setup(cfg *meta.Meta) { myConfig = cfg.Clone() } func (*compBox) Location() string { return "" } |
︙ | ︙ | |||
138 139 140 141 142 143 144 | handle(m) } } } return nil } | < < < < < < < < < < < < < < < | 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | handle(m) } } } return nil } func (*compBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } func (cb *compBox) DeleteZettel(_ context.Context, zid id.Zid) (err error) { if _, ok := myZettel[zid]; ok { err = box.ErrReadOnly } else { err = box.ErrZettelNotFound{Zid: zid} |
︙ | ︙ |
Deleted box/compbox/mapping.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to box/compbox/memory.go.
︙ | ︙ | |||
38 39 40 41 42 43 44 45 46 47 48 49 50 51 | var buf bytes.Buffer buf.WriteString("|=Name|=Value>\n") fmt.Fprintf(&buf, "|Page Size|%d\n", pageSize) fmt.Fprintf(&buf, "|Pages|%d\n", m.HeapSys/uint64(pageSize)) fmt.Fprintf(&buf, "|Heap Objects|%d\n", m.HeapObjects) fmt.Fprintf(&buf, "|Heap Sys (KiB)|%d\n", m.HeapSys/1024) fmt.Fprintf(&buf, "|Heap Inuse (KiB)|%d\n", m.HeapInuse/1024) debug := kernel.Main.GetConfig(kernel.CoreService, kernel.CoreDebug).(bool) if debug { for i, bysize := range m.BySize { fmt.Fprintf(&buf, "|Size %2d: %d|%d - %d → %d\n", i, bysize.Size, bysize.Mallocs, bysize.Frees, bysize.Mallocs-bysize.Frees) } } | > > | 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | var buf bytes.Buffer buf.WriteString("|=Name|=Value>\n") fmt.Fprintf(&buf, "|Page Size|%d\n", pageSize) fmt.Fprintf(&buf, "|Pages|%d\n", m.HeapSys/uint64(pageSize)) fmt.Fprintf(&buf, "|Heap Objects|%d\n", m.HeapObjects) fmt.Fprintf(&buf, "|Heap Sys (KiB)|%d\n", m.HeapSys/1024) fmt.Fprintf(&buf, "|Heap Inuse (KiB)|%d\n", m.HeapInuse/1024) fmt.Fprintf(&buf, "|CPUs|%d\n", runtime.NumCPU()) fmt.Fprintf(&buf, "|Threads|%d\n", runtime.NumGoroutine()) debug := kernel.Main.GetConfig(kernel.CoreService, kernel.CoreDebug).(bool) if debug { for i, bysize := range m.BySize { fmt.Fprintf(&buf, "|Size %2d: %d|%d - %d → %d\n", i, bysize.Size, bysize.Mallocs, bysize.Frees, bysize.Mallocs-bysize.Frees) } } |
︙ | ︙ |
Deleted box/compbox/warnings.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to box/constbox/base.css.
︙ | ︙ | |||
12 13 14 15 16 17 18 | *----------------------------------------------------------------------------- */ *,*::before,*::after { box-sizing: border-box; } html { | < | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | *----------------------------------------------------------------------------- */ *,*::before,*::after { box-sizing: border-box; } html { font-family: serif; scroll-behavior: smooth; height: 100%; } body { margin: 0; min-height: 100vh; |
︙ | ︙ | |||
85 86 87 88 89 90 91 | .zs-dropdown:hover > .zs-dropdown-content { display: block } main { padding: 0 1rem } article > * + * { margin-top: .5rem } article header { padding: 0; margin: 0; } | | | | | | | | | | | | | | | | < | < > | | < < < | < | 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 | .zs-dropdown:hover > .zs-dropdown-content { display: block } main { padding: 0 1rem } article > * + * { margin-top: .5rem } article header { padding: 0; margin: 0; } h1,h2,h3,h4,h5,h6 { font-family:sans-serif; font-weight:normal; margin:.4em 0 } h1 { font-size:1.5em } h2 { font-size:1.25em } h3 { font-size:1.15em } h4 { font-size:1.05em; font-weight: bold } h5 { font-size:1.05em } h6 { font-size:1.05em; font-weight: lighter } p { margin: .5em 0 0 0 } p.zs-meta-zettel { margin-top: .5em; margin-left: .5em } li,figure,figcaption,dl { margin: 0 } dt { margin: .5em 0 0 0 } dt+dd { margin-top: 0 } dd { margin: .5em 0 0 2em } dd > p:first-child { margin: 0 0 0 0 } blockquote { border-left: .5em solid lightgray; padding-left: 1em; margin-left: 1em; margin-right: 2em; } blockquote p { margin-bottom: .5em } table { border-collapse: collapse; border-spacing: 0; max-width: 100%; } td, th {text-align: left; padding: .25em .5em;} th { font-weight: bold } thead th { border-bottom: 2px solid hsl(0, 0%, 70%) } td { border-bottom: 1px solid hsl(0, 0%, 85%) } main form { padding: 0 .5em; margin: .5em 0 0 0; } main form:after { content: "."; display: block; |
︙ | ︙ | |||
155 156 157 158 159 160 161 | input.zs-secondary { float:left } input.zs-upload { padding-left: 1em; padding-right: 1em; } a:not([class]) { text-decoration-skip-ink: auto } a.broken { text-decoration: line-through } | | | | | | | | | | | | | | | | | | | | | | | 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 | input.zs-secondary { float:left } input.zs-upload { padding-left: 1em; padding-right: 1em; } a:not([class]) { text-decoration-skip-ink: auto } a.broken { text-decoration: line-through } a[rel~="external"]::after { content: "➚"; display: inline-block } img { max-width: 100% } img.right { float: right } ol.zs-endnotes { padding-top: .5em; border-top: 1px solid; } kbd { font-family:monospace } code,pre { font-family: monospace; font-size: 85%; } code { padding: .1em .2em; background: #f0f0f0; border: 1px solid #ccc; border-radius: .25em; } pre { padding: .5em .7em; max-width: 100%; overflow: auto; border: 1px solid #ccc; border-radius: .5em; background: #f0f0f0; } pre code { font-size: 95%; position: relative; padding: 0; border: none; } div.zs-indication { padding: .5em .7em; max-width: 100%; border-radius: .5em; border: 1px solid black; } div.zs-indication p:first-child { margin-top: 0 } span.zs-indication { border: 1px solid black; border-radius: .25em; padding: .1rem .2em; font-size: 95%; } .zs-info { background-color: lightblue; padding: .5em 1em; } .zs-warning { background-color: lightyellow; padding: .5em 1em; } .zs-error { background-color: lightpink; border-style: none !important; font-weight: bold; } td.left, th.left { text-align:left } td.center, th.center { text-align:center } td.right, th.right { text-align:right } .zs-font-size-0 { font-size:75% } .zs-font-size-1 { font-size:83% } .zs-font-size-2 { font-size:100% } .zs-font-size-3 { font-size:117% } .zs-font-size-4 { font-size:150% } .zs-font-size-5 { font-size:200% } .zs-deprecated { border-style: dashed; padding: .2em } .zs-meta { font-size:.75rem; color:#444; margin-bottom:1em; } .zs-meta a { color:#444 } h1+.zs-meta { margin-top:-1em } nav > details { margin-top:1em } details > summary { width: 100%; background-color: #eee; font-family:sans-serif; } details > ul { margin-top:0; padding-left:2em; background-color: #eee; } footer { padding: 0 1em } @media (prefers-reduced-motion: reduce) { * { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } |
Changes to box/constbox/constbox.go.
︙ | ︙ | |||
29 30 31 32 33 34 35 | "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func init() { manager.Register( " const", | | | 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func init() { manager.Register( " const", func(_ *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { return &constBox{ log: kernel.Main.GetLogger(kernel.BoxService).Clone(). Str("box", "const").Int("boxnum", int64(cdata.Number)).Child(), number: cdata.Number, zettel: constZettelMap, enricher: cdata.Enricher, }, nil |
︙ | ︙ | |||
93 94 95 96 97 98 99 | cb.enricher.Enrich(ctx, m, cb.number) handle(m) } } return nil } | < < < < < < < < < < < < < < < | 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | cb.enricher.Enrich(ctx, m, cb.number) handle(m) } } return nil } func (*constBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } func (cb *constBox) DeleteZettel(_ context.Context, zid id.Zid) (err error) { if _, ok := cb.zettel[zid]; ok { err = box.ErrReadOnly } else { err = box.ErrZettelNotFound{Zid: zid} |
︙ | ︙ | |||
197 198 199 200 201 202 203 | zettel.NewContent(contentLoginSxn)}, id.ZettelTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230510155300", | | | < < < < < < < < < < | | 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 | zettel.NewContent(contentLoginSxn)}, id.ZettelTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230510155300", api.KeyModified: "20241127170400", api.KeyVisibility: api.ValueVisibilityExpert, }, zettel.NewContent(contentZettelSxn)}, id.InfoTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Info HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20200804111624", api.KeyModified: "20241127170500", api.KeyVisibility: api.ValueVisibilityExpert, }, zettel.NewContent(contentInfoSxn)}, id.FormTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Form HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20200804111624", api.KeyModified: "20240219145200", api.KeyVisibility: api.ValueVisibilityExpert, }, zettel.NewContent(contentFormSxn)}, id.DeleteTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Delete HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20200804111624", api.KeyModified: "20241127170530", api.KeyVisibility: api.ValueVisibilityExpert, }, zettel.NewContent(contentDeleteSxn)}, id.ListTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore List Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, |
︙ | ︙ | |||
278 279 280 281 282 283 284 | zettel.NewContent(contentStartCodeSxn)}, id.BaseSxnZid: { constHeader{ api.KeyTitle: "Zettelstore Sxn Base Code", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230619132800", | | | 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 | zettel.NewContent(contentStartCodeSxn)}, id.BaseSxnZid: { constHeader{ api.KeyTitle: "Zettelstore Sxn Base Code", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230619132800", api.KeyModified: "20241118173500", api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityExpert, api.KeyPrecursor: string(api.ZidSxnPrelude), }, zettel.NewContent(contentBaseCodeSxn)}, id.PreludeSxnZid: { constHeader{ |
︙ | ︙ | |||
301 302 303 304 305 306 307 | zettel.NewContent(contentPreludeSxn)}, id.MustParse(api.ZidBaseCSS): { constHeader{ api.KeyTitle: "Zettelstore Base CSS", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxCSS, api.KeyCreated: "20200804111624", | | | 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | zettel.NewContent(contentPreludeSxn)}, id.MustParse(api.ZidBaseCSS): { constHeader{ api.KeyTitle: "Zettelstore Base CSS", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxCSS, api.KeyCreated: "20200804111624", api.KeyModified: "20240827143500", api.KeyVisibility: api.ValueVisibilityPublic, }, zettel.NewContent(contentBaseCSS)}, id.MustParse(api.ZidUserCSS): { constHeader{ api.KeyTitle: "Zettelstore User CSS", api.KeyRole: api.ValueRoleConfiguration, |
︙ | ︙ | |||
395 396 397 398 399 400 401 | }, zettel.NewContent(contentRoleZettel)}, id.MustParse(api.ZidRoleConfigurationZettel): { constHeader{ api.KeyTitle: api.ValueRoleConfiguration, api.KeyRole: api.ValueRoleRole, api.KeySyntax: meta.SyntaxZmk, | | | 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 | }, zettel.NewContent(contentRoleZettel)}, id.MustParse(api.ZidRoleConfigurationZettel): { constHeader{ api.KeyTitle: api.ValueRoleConfiguration, api.KeyRole: api.ValueRoleRole, api.KeySyntax: meta.SyntaxZmk, api.KeyCreated: "20241213103100", api.KeyLang: api.ValueLangEN, api.KeyVisibility: api.ValueVisibilityLogin, }, zettel.NewContent(contentRoleConfiguration)}, id.MustParse(api.ZidRoleRoleZettel): { constHeader{ api.KeyTitle: api.ValueRoleRole, |
︙ | ︙ | |||
465 466 467 468 469 470 471 | //go:embed info.sxn var contentInfoSxn []byte //go:embed form.sxn var contentFormSxn []byte | < < < | 440 441 442 443 444 445 446 447 448 449 450 451 452 453 | //go:embed info.sxn var contentInfoSxn []byte //go:embed form.sxn var contentFormSxn []byte //go:embed delete.sxn var contentDeleteSxn []byte //go:embed listzettel.sxn var contentListZettelSxn []byte //go:embed error.sxn |
︙ | ︙ |
Changes to box/constbox/info.sxn.
︙ | ︙ | |||
16 17 18 19 20 21 22 | (p (a (@ (href ,web-url)) "Web") (@H " · ") (a (@ (href ,context-url)) "Context") (@H " / ") (a (@ (href ,context-full-url)) "Full") ,@(if (bound? 'edit-url) `((@H " · ") (a (@ (href ,edit-url)) "Edit"))) ,@(ROLE-DEFAULT-actions (current-binding)) ,@(if (bound? 'reindex-url) `((@H " · ") (a (@ (href ,reindex-url)) "Reindex"))) | < | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | (p (a (@ (href ,web-url)) "Web") (@H " · ") (a (@ (href ,context-url)) "Context") (@H " / ") (a (@ (href ,context-full-url)) "Full") ,@(if (bound? 'edit-url) `((@H " · ") (a (@ (href ,edit-url)) "Edit"))) ,@(ROLE-DEFAULT-actions (current-binding)) ,@(if (bound? 'reindex-url) `((@H " · ") (a (@ (href ,reindex-url)) "Reindex"))) ,@(if (bound? 'delete-url) `((@H " · ") (a (@ (href ,delete-url)) "Delete"))) ) ) (h2 "Interpreted Metadata") (table ,@(map wui-info-meta-table-row metadata)) (h2 "References") ,@(if local-links `((h3 "Local") (ul ,@(map wui-local-link local-links)))) |
︙ | ︙ |
Deleted box/constbox/rename.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to box/constbox/roleconfiguration.zettel.
1 2 | Zettel with role ""configuration"" are used within Zettelstore to manage and to show the current configuration of the software. | | > > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | Zettel with role ""configuration"" are used within Zettelstore to manage and to show the current configuration of the software. Typically, there are some public zettel that show the license of this software, its dependencies. There is some CSS code to make the default web user interface a litte bit nicer. The default image to signal a broken image can be configured too. Other zettel are only visible if an user has authenticated itself, or if there is no authentication enabled. In this case, one additional configuration zettel is the zettel containing the version number of this software. Other zettel are showing the supported metadata keys and supported syntax values. Zettel that allow to configure the menu of template to create new zettel are also using the role ""configuration"". Most important is the zettel that contains the runtime configuration. You may change its metadata value to change the behaviour of the software. One configuration is the ""expert mode"". If enabled, and if you are authorized to see them, you will discover some more zettel. For example, HTML templates to customize the default web user interface, to show the application log, to see statistics about zettel boxes, to show the host name and it operating system, and many more. You are allowed to add your own configuration zettel, for example if you want to customize the look and feel of zettel by placing relevant data into your own zettel. By default, user zettel (for authentification) use also the role ""configuration"". However, you are allowed to change this. |
Changes to box/constbox/wuicode.sxn.
︙ | ︙ | |||
34 35 36 37 38 39 40 | ;; wui-tdata-link taks a pair (text . url) and returns a HTML link inside ;; a table data item. (defun wui-tdata-link (q) `(td ,(wui-link q))) ;; wui-item-popup-link is like 'wui-item-link, but the HTML link will open ;; a new tab / window. (defun wui-item-popup-link (e) | | | 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | ;; wui-tdata-link taks a pair (text . url) and returns a HTML link inside ;; a table data item. (defun wui-tdata-link (q) `(td ,(wui-link q))) ;; wui-item-popup-link is like 'wui-item-link, but the HTML link will open ;; a new tab / window. (defun wui-item-popup-link (e) `(li (a (@ (href ,e) (target "_blank") (rel "external noreferrer")) ,e))) ;; wui-option-value returns a value for an HTML option element. (defun wui-option-value (v) `(option (@ (value ,v)))) ;; wui-datalist returns a HTML datalist with the given HTML identifier and a ;; list of values. (defun wui-datalist (id lst) |
︙ | ︙ | |||
88 89 90 91 92 93 94 | ;; ROLE-DEFAULT-actions returns the default text for actions. (defun ROLE-DEFAULT-actions (binding) `(,@(let ((copy-url (binding-lookup 'copy-url binding))) (if (defined? copy-url) `((@H " · ") (a (@ (href ,copy-url)) "Copy")))) ,@(let ((version-url (binding-lookup 'version-url binding))) (if (defined? version-url) `((@H " · ") (a (@ (href ,version-url)) "Version")))) | | | | 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | ;; ROLE-DEFAULT-actions returns the default text for actions. (defun ROLE-DEFAULT-actions (binding) `(,@(let ((copy-url (binding-lookup 'copy-url binding))) (if (defined? copy-url) `((@H " · ") (a (@ (href ,copy-url)) "Copy")))) ,@(let ((version-url (binding-lookup 'version-url binding))) (if (defined? version-url) `((@H " · ") (a (@ (href ,version-url)) "Version")))) ,@(let ((sequel-url (binding-lookup 'sequel-url binding))) (if (defined? sequel-url) `((@H " · ") (a (@ (href ,sequel-url)) "Sequel")))) ,@(let ((folge-url (binding-lookup 'folge-url binding))) (if (defined? folge-url) `((@H " · ") (a (@ (href ,folge-url)) "Folge")))) ) ) ;; ROLE-tag-actions returns an additional action "Zettel" for zettel with role "tag". (defun ROLE-tag-actions (binding) |
︙ | ︙ |
Changes to box/constbox/zettel.sxn.
︙ | ︙ | |||
22 23 24 25 26 27 28 | ,@(if (and (bound? 'folge-role-url) (bound? 'meta-folge-role)) `((@H " → ") (a (@ (href ,folge-role-url)) ,meta-folge-role))) ")" ,@(if tag-refs `((@H " · ") ,@tag-refs)) ,@(ROLE-DEFAULT-actions (current-binding)) ,@(if predecessor-refs `((br) "Predecessor: " ,predecessor-refs)) ,@(if precursor-refs `((br) "Precursor: " ,precursor-refs)) | | | | | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | ,@(if (and (bound? 'folge-role-url) (bound? 'meta-folge-role)) `((@H " → ") (a (@ (href ,folge-role-url)) ,meta-folge-role))) ")" ,@(if tag-refs `((@H " · ") ,@tag-refs)) ,@(ROLE-DEFAULT-actions (current-binding)) ,@(if predecessor-refs `((br) "Predecessor: " ,predecessor-refs)) ,@(if precursor-refs `((br) "Precursor: " ,precursor-refs)) ,@(if prequel-refs `((br) "Prequel: " ,prequel-refs)) ,@(ROLE-DEFAULT-heading (current-binding)) ) ) ,@content ,endnotes ,@(if (or folge-links sequel-links back-links successor-links) `((nav ,@(if folge-links `((details (@ (,folge-open)) (summary "Folgezettel") (ul ,@(map wui-item-link folge-links))))) ,@(if sequel-links `((details (@ (,sequel-open)) (summary "Sequel") (ul ,@(map wui-item-link sequel-links))))) ,@(if back-links `((details (@ (,back-open)) (summary "Incoming") (ul ,@(map wui-item-link back-links))))) ,@(if successor-links `((details (@ (,successor-open)) (summary "Successors") (ul ,@(map wui-item-link successor-links))))) )) ) ) |
Changes to box/dirbox/dirbox.go.
︙ | ︙ | |||
199 200 201 202 203 204 205 | func (dp *dirBox) stopFileServices() { for _, c := range dp.fCmds { close(c) } } | | | | | | 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | func (dp *dirBox) stopFileServices() { for _, c := range dp.fCmds { close(c) } } func (dp *dirBox) notifyChanged(zid id.Zid, reason box.UpdateReason) { if notify := dp.cdata.Notify; notify != nil { dp.log.Trace().Zid(zid).Uint("reason", uint64(reason)).Msg("notifyChanged") notify(dp, zid, reason) } } func (dp *dirBox) getFileChan(zid id.Zid) chan fileCmd { // Based on https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function sum := 2166136261 ^ uint32(zid) sum *= 16777619 |
︙ | ︙ | |||
240 241 242 243 244 245 246 | 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) } | | | 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 | entry := notify.DirEntry{Zid: newZid} dp.updateEntryFromMetaContent(&entry, meta, zettel.Content) err = dp.srvSetZettel(ctx, &entry, zettel) if err == nil { err = dp.dirSrv.UpdateDirEntry(&entry) } dp.notifyChanged(meta.Zid, box.OnZettel) dp.log.Trace().Err(err).Zid(meta.Zid).Msg("CreateZettel") return meta.Zid, err } func (dp *dirBox) GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) { entry := dp.dirSrv.GetDirEntry(zid) if !entry.IsValid() { |
︙ | ︙ | |||
312 313 314 315 316 317 318 | // 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 { | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 | // Existing zettel, but new in this box. entry = ¬ify.DirEntry{Zid: zid} } dp.updateEntryFromMetaContent(entry, meta, zettel.Content) dp.dirSrv.UpdateDirEntry(entry) err := dp.srvSetZettel(ctx, entry, zettel) if err == nil { dp.notifyChanged(zid, box.OnZettel) } dp.log.Trace().Zid(zid).Err(err).Msg("UpdateZettel") return err } func (dp *dirBox) updateEntryFromMetaContent(entry *notify.DirEntry, m *meta.Meta, content zettel.Content) { entry.SetupFromMetaContent(m, content, dp.cdata.Config.GetZettelFileSyntax) } func (dp *dirBox) CanDeleteZettel(_ context.Context, zid id.Zid) bool { if dp.readonly { return false } entry := dp.dirSrv.GetDirEntry(zid) return entry.IsValid() } |
︙ | ︙ | |||
391 392 393 394 395 396 397 | } err := dp.dirSrv.DeleteDirEntry(zid) if err != nil { return nil } err = dp.srvDeleteZettel(ctx, entry, zid) if err == nil { | | | 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | } err := dp.dirSrv.DeleteDirEntry(zid) if err != nil { return nil } err = dp.srvDeleteZettel(ctx, entry, zid) if err == nil { dp.notifyChanged(zid, box.OnDelete) } 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.
︙ | ︙ | |||
31 32 33 34 35 36 37 | ) type zipBox struct { log *logger.Logger number int name string enricher box.Enricher | | | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | ) type zipBox struct { log *logger.Logger number int name string enricher box.Enricher notify box.UpdateNotifier dirSrv *notify.DirService } func (zb *zipBox) Location() string { if strings.HasPrefix(zb.name, "/") { return "file://" + zb.name } |
︙ | ︙ | |||
168 169 170 171 172 173 174 | } zb.enricher.Enrich(ctx, m, zb.number) handle(m) } return nil } | < < < < < < < < < < < < < < < < < < | 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | } zb.enricher.Enrich(ctx, m, zb.number) handle(m) } return nil } func (*zipBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } func (zb *zipBox) DeleteZettel(_ context.Context, zid id.Zid) error { err := box.ErrReadOnly entry := zb.dirSrv.GetDirEntry(zid) if !entry.IsValid() { err = box.ErrZettelNotFound{Zid: zid} |
︙ | ︙ |
Changes to box/helper.go.
︙ | ︙ | |||
43 44 45 46 47 48 49 | // GetQueryBool is a helper function to extract bool values from a box URI. func GetQueryBool(u *url.URL, key string) bool { _, ok := u.Query()[key] return ok } // GetQueryInt is a helper function to extract int values of a specified range from a box URI. | | | | | | | | | 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | // GetQueryBool is a helper function to extract bool values from a box URI. func GetQueryBool(u *url.URL, key string) bool { _, ok := u.Query()[key] return ok } // GetQueryInt is a helper function to extract int values of a specified range from a box URI. func GetQueryInt(u *url.URL, key string, minVal, defVal, maxVal int) int { sVal := u.Query().Get(key) if sVal == "" { return defVal } iVal, err := strconv.Atoi(sVal) if err != nil { return defVal } if iVal < minVal { return minVal } if iVal > maxVal { return maxVal } return iVal } |
Changes to box/manager/box.go.
︙ | ︙ | |||
52 53 54 55 56 57 58 | if box, isWriteBox := mgr.boxes[0].(box.WriteBox); isWriteBox { return box.CanCreateZettel(ctx) } return false } // CreateZettel creates a new zettel. | | | | | | > > > | 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 | if box, isWriteBox := mgr.boxes[0].(box.WriteBox); isWriteBox { return box.CanCreateZettel(ctx) } return false } // CreateZettel creates a new zettel. func (mgr *Manager) CreateZettel(ctx context.Context, ztl zettel.Zettel) (id.Zid, error) { mgr.mgrLog.Debug().Msg("CreateZettel") if err := mgr.checkContinue(ctx); err != nil { return id.Invalid, err } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if box, isWriteBox := mgr.boxes[0].(box.WriteBox); isWriteBox { ztl.Meta = mgr.cleanMetaProperties(ztl.Meta) zidO, err := box.CreateZettel(ctx, ztl) if err == nil { mgr.idxUpdateZettel(ctx, ztl) } return zidO, err } return id.Invalid, box.ErrReadOnly } // GetZettel retrieves a specific zettel. func (mgr *Manager) GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) { mgr.mgrLog.Debug().Zid(zid).Msg("GetZettel") if err := mgr.checkContinue(ctx); err != nil { return zettel.Zettel{}, err } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() return mgr.getZettel(ctx, zid) } func (mgr *Manager) getZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) { for i, p := range mgr.boxes { var errZNF box.ErrZettelNotFound if z, err := p.GetZettel(ctx, zid); !errors.As(err, &errZNF) { if err == nil { mgr.Enrich(ctx, z.Meta, i+1) } return z, err |
︙ | ︙ | |||
135 136 137 138 139 140 141 | if err != nil { return nil, err } } return result, nil } | > > > > > > | > > | 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 | if err != nil { return nil, err } } return result, nil } // FetchZidsO returns the set of all old-style zettel identifer managed by the box. func (mgr *Manager) FetchZidsO(ctx context.Context) (*id.Set, error) { mgr.mgrLog.Debug().Msg("FetchZidsO") return mgr.fetchZids(ctx) } func (mgr *Manager) hasZettel(ctx context.Context, zid id.Zid) bool { mgr.mgrLog.Debug().Zid(zid).Msg("HasZettel") if err := mgr.checkContinue(ctx); err != nil { return false } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() for _, bx := range mgr.boxes { if bx.HasZettel(ctx, zid) { return true } } return false } // GetMeta returns just the metadata of the zettel with the given identifier. func (mgr *Manager) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { mgr.mgrLog.Debug().Zid(zid).Msg("GetMeta") if err := mgr.checkContinue(ctx); err != nil { return nil, err } m, err := mgr.idxStore.GetMeta(ctx, zid) if err != nil { // TODO: Call GetZettel and return just metadata, in case the index is not complete. return nil, err } mgr.Enrich(ctx, m, 0) return m, nil } // SelectMeta returns all zettel meta data that match the selection |
︙ | ︙ | |||
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 | // UpdateZettel updates an existing zettel. func (mgr *Manager) UpdateZettel(ctx context.Context, zettel zettel.Zettel) error { mgr.mgrLog.Debug().Zid(zettel.Meta.Zid).Msg("UpdateZettel") if err := mgr.checkContinue(ctx); err != nil { return err } if box, isWriteBox := mgr.boxes[0].(box.WriteBox); isWriteBox { zettel.Meta = mgr.cleanMetaProperties(zettel.Meta) if err := box.UpdateZettel(ctx, zettel); err != nil { return err } mgr.idxUpdateZettel(ctx, zettel) return nil } return box.ErrReadOnly } | > > > < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | | | | | 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 | // UpdateZettel updates an existing zettel. func (mgr *Manager) UpdateZettel(ctx context.Context, zettel zettel.Zettel) error { mgr.mgrLog.Debug().Zid(zettel.Meta.Zid).Msg("UpdateZettel") if err := mgr.checkContinue(ctx); err != nil { return err } return mgr.updateZettel(ctx, zettel) } func (mgr *Manager) updateZettel(ctx context.Context, zettel zettel.Zettel) error { if box, isWriteBox := mgr.boxes[0].(box.WriteBox); isWriteBox { zettel.Meta = mgr.cleanMetaProperties(zettel.Meta) if err := box.UpdateZettel(ctx, zettel); err != nil { return err } mgr.idxUpdateZettel(ctx, zettel) return nil } return box.ErrReadOnly } // CanDeleteZettel returns true, if box could possibly delete the given zettel. func (mgr *Manager) CanDeleteZettel(ctx context.Context, zid id.Zid) bool { if err := mgr.checkContinue(ctx); err != nil { return false } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() for _, p := range mgr.boxes { if p.CanDeleteZettel(ctx, zid) { return true } } return false } // DeleteZettel removes the zettel from the box. func (mgr *Manager) DeleteZettel(ctx context.Context, zidO id.Zid) error { mgr.mgrLog.Debug().Zid(zidO).Msg("DeleteZettel") if err := mgr.checkContinue(ctx); err != nil { return err } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() for _, p := range mgr.boxes { err := p.DeleteZettel(ctx, zidO) if err == nil { mgr.idxDeleteZettel(ctx, zidO) return err } var errZNF box.ErrZettelNotFound if !errors.As(err, &errZNF) && !errors.Is(err, box.ErrReadOnly) { return err } } return box.ErrZettelNotFound{Zid: zidO} } // Remove all (computed) properties from metadata before storing the zettel. func (mgr *Manager) cleanMetaProperties(m *meta.Meta) *meta.Meta { result := m.Clone() for _, p := range result.ComputedPairsRest() { if mgr.propertyKeys.Has(p.Key) { result.Delete(p.Key) } } return result } |
Changes to box/manager/enrich.go.
︙ | ︙ | |||
21 22 23 24 25 26 27 | "zettelstore.de/z/box" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // Enrich computes additional properties and updates the given metadata. func (mgr *Manager) Enrich(ctx context.Context, m *meta.Meta, boxNumber int) { | < | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | "zettelstore.de/z/box" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // Enrich computes additional properties and updates the given metadata. func (mgr *Manager) Enrich(ctx context.Context, m *meta.Meta, boxNumber int) { // Calculate computed, but stored values. _, hasCreated := m.Get(api.KeyCreated) if !hasCreated { m.Set(api.KeyCreated, computeCreated(m.Zid)) } if box.DoEnrich(ctx) { |
︙ | ︙ |
Changes to box/manager/indexer.go.
︙ | ︙ | |||
153 154 155 156 157 158 159 | } return true } func (mgr *Manager) idxUpdateZettel(ctx context.Context, zettel zettel.Zettel) { var cData collectData cData.initialize() | > | > > > > > | 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 | } return true } func (mgr *Manager) idxUpdateZettel(ctx context.Context, zettel zettel.Zettel) { var cData collectData cData.initialize() if mustIndexZettel(zettel.Meta) { collectZettelIndexData(parser.ParseZettel(ctx, zettel, "", mgr.rtConfig), &cData) } m := zettel.Meta zi := store.NewZettelIndex(m) mgr.idxCollectFromMeta(ctx, m, zi, &cData) mgr.idxProcessData(ctx, zi, &cData) toCheck := mgr.idxStore.UpdateReferences(ctx, zi) mgr.idxCheckZettel(toCheck) } func mustIndexZettel(m *meta.Meta) bool { return m.Zid >= id.Zid(999999900) } func (mgr *Manager) idxCollectFromMeta(ctx context.Context, m *meta.Meta, zi *store.ZettelIndex, cData *collectData) { for _, pair := range m.ComputedPairs() { descr := meta.GetDescription(pair.Key) if descr.IsProperty() { continue } |
︙ | ︙ | |||
207 208 209 210 211 212 213 | } else { stWords.Add(value) } } func (mgr *Manager) idxProcessData(ctx context.Context, zi *store.ZettelIndex, cData *collectData) { cData.refs.ForEach(func(ref id.Zid) { | | | < < < < < | 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 | } else { stWords.Add(value) } } func (mgr *Manager) idxProcessData(ctx context.Context, zi *store.ZettelIndex, cData *collectData) { cData.refs.ForEach(func(ref id.Zid) { if mgr.hasZettel(ctx, ref) { zi.AddBackRef(ref) } else { zi.AddDeadRef(ref) } }) zi.SetWords(cData.words) zi.SetUrls(cData.urls) } func (mgr *Manager) idxUpdateValue(ctx context.Context, inverseKey, value string, zi *store.ZettelIndex) { zid, err := id.Parse(value) if err != nil { return } if !mgr.hasZettel(ctx, zid) { zi.AddDeadRef(zid) return } if inverseKey == "" { zi.AddBackRef(zid) return } zi.AddInverseRef(inverseKey, zid) } func (mgr *Manager) idxDeleteZettel(ctx context.Context, zid id.Zid) { toCheck := mgr.idxStore.DeleteZettel(ctx, zid) mgr.idxCheckZettel(toCheck) } func (mgr *Manager) idxCheckZettel(s *id.Set) { s.ForEach(func(zid id.Zid) { mgr.idxAr.EnqueueZettel(zid) }) } |
Changes to box/manager/manager.go.
︙ | ︙ | |||
34 35 36 37 38 39 40 | ) // ConnectData contains all administration related values. type ConnectData struct { Number int // number of the box, starting with 1. Config config.Config Enricher box.Enricher | | < < < < < < < < | 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | ) // ConnectData contains all administration related values. type ConnectData struct { Number int // number of the box, starting with 1. Config config.Config Enricher box.Enricher Notify box.UpdateNotifier } // Connect returns a handle to the specified box. func Connect(u *url.URL, authManager auth.BaseManager, cdata *ConnectData) (box.ManagedBox, error) { if authManager.IsReadonly() { rawURL := u.String() // TODO: the following is wrong under some circumstances: |
︙ | ︙ | |||
98 99 100 101 102 103 104 | rtConfig config.Config boxes []box.ManagedBox observers []box.UpdateFunc mxObserver sync.RWMutex done chan struct{} infos chan box.UpdateInfo propertyKeys strfun.Set // Set of property key names | < > | 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 | rtConfig config.Config boxes []box.ManagedBox observers []box.UpdateFunc mxObserver sync.RWMutex done chan struct{} infos chan box.UpdateInfo propertyKeys strfun.Set // Set of property key names // Indexer data idxLog *logger.Logger idxStore store.Store idxAr *anteroomQueue idxReady chan struct{} // Signal a non-empty anteroom to background task // Indexer stats data idxMx sync.RWMutex idxLastReload time.Time idxDurReload time.Duration idxSinceReload uint64 } func (mgr *Manager) setState(newState box.StartState) { mgr.stateMx.Lock() mgr.state = newState mgr.stateMx.Unlock() } // State returns the box.StartState of the manager. func (mgr *Manager) State() box.StartState { mgr.stateMx.RLock() state := mgr.state mgr.stateMx.RUnlock() return state } |
︙ | ︙ | |||
147 148 149 150 151 152 153 | propertyKeys: propertyKeys, idxLog: boxLog.Clone().Str("box", "index").Child(), idxStore: createIdxStore(rtConfig), idxAr: newAnteroomQueue(1000), idxReady: make(chan struct{}, 1), } | < | | 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | propertyKeys: propertyKeys, idxLog: boxLog.Clone().Str("box", "index").Child(), idxStore: createIdxStore(rtConfig), idxAr: newAnteroomQueue(1000), idxReady: make(chan struct{}, 1), } cdata := ConnectData{Number: 1, Config: rtConfig, Enricher: mgr, Notify: mgr.notifyChanged} boxes := make([]box.ManagedBox, 0, len(boxURIs)+2) for _, uri := range boxURIs { p, err := Connect(uri, authManager, &cdata) if err != nil { return nil, err } if p != nil { |
︙ | ︙ | |||
220 221 222 223 224 225 226 227 228 229 230 | reason, zid := ci.Reason, ci.Zid mgr.mgrLog.Debug().Uint("reason", uint64(reason)).Zid(zid).Msg("notifier") if ignoreUpdate(cache, now, reason, zid) { mgr.mgrLog.Trace().Uint("reason", uint64(reason)).Zid(zid).Msg("notifier ignored") continue } mgr.idxEnqueue(reason, zid) if ci.Box == nil { ci.Box = mgr } | > | | 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 | reason, zid := ci.Reason, ci.Zid mgr.mgrLog.Debug().Uint("reason", uint64(reason)).Zid(zid).Msg("notifier") if ignoreUpdate(cache, now, reason, zid) { mgr.mgrLog.Trace().Uint("reason", uint64(reason)).Zid(zid).Msg("notifier ignored") continue } isStarted := mgr.State() == box.StartStateStarted mgr.idxEnqueue(reason, zid) if ci.Box == nil { ci.Box = mgr } if isStarted { mgr.notifyObserver(&ci) } } case <-mgr.done: return } } |
︙ | ︙ | |||
253 254 255 256 257 258 259 | cache[zid] = destutterData{ deadAt: now.Add(500 * time.Millisecond), reason: reason, } return false } | | | > > | | 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 | cache[zid] = destutterData{ deadAt: now.Add(500 * time.Millisecond), reason: reason, } return false } func (mgr *Manager) idxEnqueue(reason box.UpdateReason, zidO id.Zid) { switch reason { case box.OnReady: return case box.OnReload: mgr.idxAr.Reset() case box.OnZettel: mgr.idxAr.EnqueueZettel(zidO) case box.OnDelete: mgr.idxAr.EnqueueZettel(zidO) default: mgr.mgrLog.Error().Uint("reason", uint64(reason)).Zid(zidO).Msg("Unknown notification reason") return } select { case mgr.idxReady <- struct{}{}: default: } } |
︙ | ︙ | |||
313 314 315 316 317 318 319 320 321 322 323 324 325 326 | } mgr.idxAr.Reset() // Ensure an initial index run mgr.done = make(chan struct{}) go mgr.notifier() mgr.waitBoxesAreStarted() mgr.setState(box.StartStateStarted) mgr.notifyObserver(&box.UpdateInfo{Box: mgr, Reason: box.OnReady}) go mgr.idxIndexer() return nil } func (mgr *Manager) waitBoxesAreStarted() { | > | 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 | } mgr.idxAr.Reset() // Ensure an initial index run mgr.done = make(chan struct{}) go mgr.notifier() mgr.waitBoxesAreStarted() mgr.setState(box.StartStateStarted) mgr.notifyObserver(&box.UpdateInfo{Box: mgr, Reason: box.OnReady}) go mgr.idxIndexer() return nil } func (mgr *Manager) waitBoxesAreStarted() { |
︙ | ︙ | |||
383 384 385 386 387 388 389 | // ReIndex data of the given zettel. func (mgr *Manager) ReIndex(ctx context.Context, zid id.Zid) error { mgr.mgrLog.Debug().Msg("ReIndex") if err := mgr.checkContinue(ctx); err != nil { return err } | | | 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 | // ReIndex data of the given zettel. func (mgr *Manager) ReIndex(ctx context.Context, zid id.Zid) error { mgr.mgrLog.Debug().Msg("ReIndex") if err := mgr.checkContinue(ctx); err != nil { return err } mgr.infos <- box.UpdateInfo{Box: mgr, Reason: box.OnZettel, Zid: zid} return nil } // ReadStats populates st with box statistics. func (mgr *Manager) ReadStats(st *box.Stats) { mgr.mgrLog.Debug().Msg("ReadStats") mgr.mgrMx.RLock() |
︙ | ︙ | |||
433 434 435 436 437 438 439 | func (mgr *Manager) checkContinue(ctx context.Context) error { if mgr.State() != box.StartStateStarted { return box.ErrStopped } return ctx.Err() } | > > > > > > | 428 429 430 431 432 433 434 435 436 437 438 439 440 | func (mgr *Manager) checkContinue(ctx context.Context) error { if mgr.State() != box.StartStateStarted { return box.ErrStopped } return ctx.Err() } func (mgr *Manager) notifyChanged(bbox box.BaseBox, zid id.Zid, reason box.UpdateReason) { if infos := mgr.infos; infos != nil { mgr.infos <- box.UpdateInfo{Box: bbox, Reason: reason, Zid: zid} } } |
Changes to box/manager/mapstore/mapstore.go.
︙ | ︙ | |||
453 454 455 456 457 458 459 | return zi } zi := &zettelData{} ms.idx[zid] = zi return zi } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 453 454 455 456 457 458 459 460 461 462 463 464 465 466 | return zi } zi := &zettelData{} ms.idx[zid] = zi return zi } func (ms *mapStore) DeleteZettel(_ context.Context, zid id.Zid) *id.Set { ms.mx.Lock() defer ms.mx.Unlock() return ms.doDeleteZettel(zid) } func (ms *mapStore) doDeleteZettel(zid id.Zid) *id.Set { |
︙ | ︙ |
Changes to box/manager/store/store.go.
︙ | ︙ | |||
49 50 51 52 53 54 55 | // 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 | < < < < | 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | // Entrich metadata with data from store. Enrich(ctx context.Context, m *meta.Meta) // UpdateReferences for a specific zettel. // Returns set of zettel identifier that must also be checked for changes. UpdateReferences(context.Context, *ZettelIndex) *id.Set // DeleteZettel removes index data for given zettel. // Returns set of zettel identifier that must also be checked for changes. DeleteZettel(context.Context, id.Zid) *id.Set // Optimize removes unneeded space. Optimize() |
︙ | ︙ |
Deleted box/manager/zidmapper.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to box/membox/membox.go.
︙ | ︙ | |||
50 51 52 53 54 55 56 | maxZettel int maxBytes int mx sync.RWMutex // Protects the following fields zettel map[id.Zid]zettel.Zettel curBytes int } | | | | | 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | maxZettel int maxBytes int mx sync.RWMutex // Protects the following fields zettel map[id.Zid]zettel.Zettel curBytes int } func (mb *memBox) notifyChanged(zid id.Zid, reason box.UpdateReason) { if notify := mb.cdata.Notify; notify != nil { notify(mb, zid, reason) } } func (mb *memBox) Location() string { return mb.u.String() } |
︙ | ︙ | |||
112 113 114 115 116 117 118 | meta := zettel.Meta.Clone() meta.Zid = zid zettel.Meta = meta mb.zettel[zid] = zettel mb.curBytes = newBytes mb.mx.Unlock() | | | 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | meta := zettel.Meta.Clone() meta.Zid = zid zettel.Meta = meta mb.zettel[zid] = zettel mb.curBytes = newBytes mb.mx.Unlock() mb.notifyChanged(zid, box.OnZettel) mb.log.Trace().Zid(zid).Msg("CreateZettel") return zid, nil } func (mb *memBox) GetZettel(_ context.Context, zid id.Zid) (zettel.Zettel, error) { mb.mx.RLock() z, ok := mb.zettel[zid] |
︙ | ︙ | |||
197 198 199 200 201 202 203 | return box.ErrCapacity } zettel.Meta = m mb.zettel[m.Zid] = zettel mb.curBytes = newBytes mb.mx.Unlock() | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | 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 | return box.ErrCapacity } zettel.Meta = m mb.zettel[m.Zid] = zettel mb.curBytes = newBytes mb.mx.Unlock() mb.notifyChanged(m.Zid, box.OnZettel) mb.log.Trace().Msg("UpdateZettel") return nil } func (mb *memBox) CanDeleteZettel(_ context.Context, zid id.Zid) bool { mb.mx.RLock() _, ok := mb.zettel[zid] mb.mx.RUnlock() return ok } func (mb *memBox) DeleteZettel(_ context.Context, zid id.Zid) error { mb.mx.Lock() oldZettel, found := mb.zettel[zid] if !found { mb.mx.Unlock() return box.ErrZettelNotFound{Zid: zid} } delete(mb.zettel, zid) mb.curBytes -= oldZettel.Length() mb.mx.Unlock() mb.notifyChanged(zid, box.OnDelete) 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.
︙ | ︙ | |||
14 15 16 17 18 19 20 | package notify import ( "errors" "fmt" "path/filepath" "regexp" | < | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package notify import ( "errors" "fmt" "path/filepath" "regexp" "sync" "zettelstore.de/z/box" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/parser" "zettelstore.de/z/query" |
︙ | ︙ | |||
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | // dsCreated --Start--> dsStarting // dsStarting --last list notification--> dsWorking // dsWorking --directory missing--> dsMissing // dsMissing --last list notification--> dsWorking // --Stop--> dsStopping type DirServiceState uint8 const ( DsCreated DirServiceState = iota DsStarting // Reading inital scan DsWorking // Initial scan complete, fully operational DsMissing // Directory is missing DsStopping // Service is shut down ) // DirService specifies a directory service for file based zettel. type DirService struct { box box.ManagedBox log *logger.Logger dirPath string notifier Notifier | > | | | | 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 | // dsCreated --Start--> dsStarting // dsStarting --last list notification--> dsWorking // dsWorking --directory missing--> dsMissing // dsMissing --last list notification--> dsWorking // --Stop--> dsStopping type DirServiceState uint8 // Constants for DirServiceState const ( DsCreated DirServiceState = iota DsStarting // Reading inital scan DsWorking // Initial scan complete, fully operational DsMissing // Directory is missing DsStopping // Service is shut down ) // DirService specifies a directory service for file based zettel. type DirService struct { box box.ManagedBox log *logger.Logger dirPath string notifier Notifier infos box.UpdateNotifier mx sync.RWMutex // protects status, entries state DirServiceState entries entrySet } // ErrNoDirectory signals missing directory data. var ErrNoDirectory = errors.New("unable to retrieve zettel directory information") // NewDirService creates a new directory service. func NewDirService(box box.ManagedBox, log *logger.Logger, notifier Notifier, notify box.UpdateNotifier) *DirService { return &DirService{ box: box, log: log, notifier: notifier, infos: notify, state: DsCreated, } } // State the current service state. func (ds *DirService) State() DirServiceState { ds.mx.RLock() |
︙ | ︙ | |||
182 183 184 185 186 187 188 | if ds.entries == nil { return ds.logMissingEntry("update") } ds.entries[entry.Zid] = &entry return nil } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | if ds.entries == nil { return ds.logMissingEntry("update") } ds.entries[entry.Zid] = &entry return nil } // DeleteDirEntry removes a entry from the directory. func (ds *DirService) DeleteDirEntry(zid id.Zid) error { ds.mx.Lock() defer ds.mx.Unlock() if ds.entries == nil { return ds.logMissingEntry("delete") } |
︙ | ︙ | |||
287 288 289 290 291 292 293 | ds.log.Error().Str("path", ds.dirPath).Msg("Zettel directory missing") return nil, true case Update: ds.mx.Lock() zid := ds.onUpdateFileEvent(ds.entries, ev.Name) ds.mx.Unlock() if zid != id.Invalid { | | | | | | | 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 | ds.log.Error().Str("path", ds.dirPath).Msg("Zettel directory missing") return nil, true case Update: ds.mx.Lock() zid := ds.onUpdateFileEvent(ds.entries, ev.Name) ds.mx.Unlock() if zid != id.Invalid { ds.notifyChange(zid, box.OnZettel) } case Delete: ds.mx.Lock() zid := ds.onDeleteFileEvent(ds.entries, ev.Name) ds.mx.Unlock() if zid != id.Invalid { ds.notifyChange(zid, box.OnDelete) } default: ds.log.Error().Str("event", fmt.Sprintf("%v", ev)).Msg("Unknown zettel notification event") } return newEntries, true } func getNewZids(entries entrySet) id.Slice { zids := make(id.Slice, 0, len(entries)) for zid := range entries { zids = append(zids, zid) } return zids } func (ds *DirService) onCreateDirectory(zids id.Slice, prevEntries entrySet) { for _, zid := range zids { ds.notifyChange(zid, box.OnZettel) delete(prevEntries, zid) } // These were previously stored, by are not found now. // Notify system that these were deleted, e.g. for updating the index. for zid := range prevEntries { ds.notifyChange(zid, box.OnDelete) } } func (ds *DirService) onDestroyDirectory() { ds.mx.Lock() entries := ds.entries ds.entries = nil ds.state = DsMissing ds.mx.Unlock() for zid := range entries { ds.notifyChange(zid, box.OnDelete) } } var validFileName = regexp.MustCompile(`^(\d{14})`) func matchValidFileName(name string) []string { return validFileName.FindStringSubmatch(name) |
︙ | ︙ | |||
601 602 603 604 605 606 607 | newLen := len(newExt) if oldLen != newLen { return newLen < oldLen } return newExt < oldExt } | | | | | | 571 572 573 574 575 576 577 578 579 580 581 582 583 | newLen := len(newExt) if oldLen != newLen { return newLen < oldLen } return newExt < oldExt } func (ds *DirService) notifyChange(zid id.Zid, reason box.UpdateReason) { if notify := ds.infos; notify != nil { ds.log.Trace().Zid(zid).Uint("reason", uint64(reason)).Msg("notifyChange") notify(ds.box, zid, reason) } } |
Changes to cmd/cmd_run.go.
︙ | ︙ | |||
71 72 73 74 75 76 77 | ucQuery.SetEvaluate(&ucEvaluate) ucTagZettel := usecase.NewTagZettel(protectedBoxManager, &ucQuery) ucRoleZettel := usecase.NewRoleZettel(protectedBoxManager, &ucQuery) ucListSyntax := usecase.NewListSyntax(protectedBoxManager) ucListRoles := usecase.NewListRoles(protectedBoxManager) ucDelete := usecase.NewDeleteZettel(logUc, protectedBoxManager) ucUpdate := usecase.NewUpdateZettel(logUc, protectedBoxManager) | < < < | 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 | ucQuery.SetEvaluate(&ucEvaluate) ucTagZettel := usecase.NewTagZettel(protectedBoxManager, &ucQuery) ucRoleZettel := usecase.NewRoleZettel(protectedBoxManager, &ucQuery) ucListSyntax := usecase.NewListSyntax(protectedBoxManager) ucListRoles := usecase.NewListRoles(protectedBoxManager) ucDelete := usecase.NewDeleteZettel(logUc, protectedBoxManager) ucUpdate := usecase.NewUpdateZettel(logUc, protectedBoxManager) ucRefresh := usecase.NewRefresh(logUc, protectedBoxManager) ucReIndex := usecase.NewReIndex(logUc, protectedBoxManager) ucVersion := usecase.NewVersion(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) a := api.New( webLog.Clone().Str("adapter", "api").Child(), webSrv, authManager, authManager, rtConfig, authPolicy) wui := webui.New( webLog.Clone().Str("adapter", "wui").Child(), webSrv, authManager, rtConfig, authManager, boxManager, authPolicy, &ucEvaluate) webSrv.Handle("/", wui.MakeGetRootHandler(protectedBoxManager)) if assetDir := kern.GetConfig(kernel.WebService, kernel.WebAssetDir).(string); assetDir != "" { const assetPrefix = "/assets/" webSrv.Handle(assetPrefix, http.StripPrefix(assetPrefix, http.FileServer(http.Dir(assetDir)))) webSrv.Handle("/favicon.ico", wui.MakeFaviconHandler(assetDir)) } // Web user interface if !authManager.IsReadonly() { webSrv.AddListRoute('c', server.MethodGet, wui.MakeGetZettelFromListHandler(&ucQuery, &ucEvaluate, ucListRoles, ucListSyntax)) webSrv.AddListRoute('c', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel)) webSrv.AddZettelRoute('c', server.MethodGet, wui.MakeGetCreateZettelHandler( ucGetZettel, &ucCreateZettel, ucListRoles, ucListSyntax)) webSrv.AddZettelRoute('c', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel)) webSrv.AddZettelRoute('d', server.MethodGet, wui.MakeGetDeleteZettelHandler(ucGetZettel, ucGetAllZettel)) webSrv.AddZettelRoute('d', server.MethodPost, wui.MakePostDeleteZettelHandler(&ucDelete)) |
︙ | ︙ | |||
123 124 125 126 127 128 129 | webSrv.AddListRoute('x', server.MethodPost, a.MakePostCommandHandler(&ucIsAuth, &ucRefresh)) webSrv.AddListRoute('z', server.MethodGet, a.MakeQueryHandler(&ucQuery, &ucTagZettel, &ucRoleZettel, &ucReIndex)) webSrv.AddZettelRoute('z', server.MethodGet, a.MakeGetZettelHandler(ucGetZettel, ucParseZettel, ucEvaluate)) if !authManager.IsReadonly() { webSrv.AddListRoute('z', server.MethodPost, a.MakePostCreateZettelHandler(&ucCreateZettel)) webSrv.AddZettelRoute('z', server.MethodPut, a.MakeUpdateZettelHandler(&ucUpdate)) webSrv.AddZettelRoute('z', server.MethodDelete, a.MakeDeleteZettelHandler(&ucDelete)) | < | 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | webSrv.AddListRoute('x', server.MethodPost, a.MakePostCommandHandler(&ucIsAuth, &ucRefresh)) webSrv.AddListRoute('z', server.MethodGet, a.MakeQueryHandler(&ucQuery, &ucTagZettel, &ucRoleZettel, &ucReIndex)) webSrv.AddZettelRoute('z', server.MethodGet, a.MakeGetZettelHandler(ucGetZettel, ucParseZettel, ucEvaluate)) if !authManager.IsReadonly() { webSrv.AddListRoute('z', server.MethodPost, a.MakePostCreateZettelHandler(&ucCreateZettel)) webSrv.AddZettelRoute('z', server.MethodPut, a.MakeUpdateZettelHandler(&ucUpdate)) webSrv.AddZettelRoute('z', server.MethodDelete, a.MakeDeleteZettelHandler(&ucDelete)) } if authManager.WithAuth() { webSrv.SetUserRetriever(usecase.NewGetUserByZid(boxManager)) } } type getUserImpl struct{} func (*getUserImpl) GetUser(ctx context.Context) *meta.Meta { return server.GetUser(ctx) } |
Changes to cmd/main.go.
︙ | ︙ | |||
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | } } const ( keyAdminPort = "admin-port" keyAssetDir = "asset-dir" keyBaseURL = "base-url" keyDebug = "debug-mode" keyDefaultDirBoxType = "default-dir-box-type" keyInsecureCookie = "insecure-cookie" keyInsecureHTML = "insecure-html" keyListenAddr = "listen-addr" keyLogLevel = "log-level" keyMaxRequestSize = "max-request-size" keyOwner = "owner" keyPersistentCookie = "persistent-cookie" | > < > | 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 | } } const ( keyAdminPort = "admin-port" keyAssetDir = "asset-dir" keyBaseURL = "base-url" keyBoxOneURI = kernel.BoxURIs + "1" keyDebug = "debug-mode" keyDefaultDirBoxType = "default-dir-box-type" keyInsecureCookie = "insecure-cookie" keyInsecureHTML = "insecure-html" keyListenAddr = "listen-addr" keyLogLevel = "log-level" keyMaxRequestSize = "max-request-size" keyOwner = "owner" keyPersistentCookie = "persistent-cookie" keyReadOnly = "read-only-mode" keyRuntimeProfiling = "runtime-profiling" keyTokenLifetimeHTML = "token-lifetime-html" keyTokenLifetimeAPI = "token-lifetime-api" keyURLPrefix = "url-prefix" keyVerbose = "verbose-mode" ) func setServiceConfig(cfg *meta.Meta) bool { |
︙ | ︙ | |||
205 206 207 208 209 210 211 | val, found := cfg.Get(key) if !found { break } err = setConfigValue(err, kernel.BoxService, key, val) } | > | > | > | 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 | val, found := cfg.Get(key) if !found { break } err = setConfigValue(err, kernel.BoxService, key, val) } err = setConfigValue( err, kernel.ConfigService, kernel.ConfigInsecureHTML, cfg.GetDefault(keyInsecureHTML, kernel.ConfigSecureHTML)) err = setConfigValue( err, kernel.WebService, kernel.WebListenAddress, cfg.GetDefault(keyListenAddr, "127.0.0.1:23123")) if val, found := cfg.Get(keyBaseURL); found { err = setConfigValue(err, kernel.WebService, kernel.WebBaseURL, val) } if val, found := cfg.Get(keyURLPrefix); found { err = setConfigValue(err, kernel.WebService, kernel.WebURLPrefix, val) } err = setConfigValue(err, kernel.WebService, kernel.WebSecureCookie, !cfg.GetBool(keyInsecureCookie)) err = setConfigValue(err, kernel.WebService, kernel.WebPersistentCookie, cfg.GetBool(keyPersistentCookie)) if val, found := cfg.Get(keyMaxRequestSize); found { err = setConfigValue(err, kernel.WebService, kernel.WebMaxRequestSize, val) } err = setConfigValue( err, kernel.WebService, kernel.WebTokenLifetimeAPI, cfg.GetDefault(keyTokenLifetimeAPI, "")) err = setConfigValue( err, kernel.WebService, kernel.WebTokenLifetimeHTML, cfg.GetDefault(keyTokenLifetimeHTML, "")) err = setConfigValue(err, kernel.WebService, kernel.WebProfiling, debugMode || cfg.GetBool(keyRuntimeProfiling)) if val, found := cfg.Get(keyAssetDir); found { err = setConfigValue(err, kernel.WebService, kernel.WebAssetDir, val) } return err == nil } func setConfigValue(err error, subsys kernel.Service, key string, val any) error { |
︙ | ︙ |
Changes to cmd/zettelstore/main.go.
︙ | ︙ | |||
17 18 19 20 21 22 23 | import ( "os" "zettelstore.de/z/cmd" ) // Version variable. Will be filled by build process. | | | 17 18 19 20 21 22 23 24 25 26 27 28 29 | import ( "os" "zettelstore.de/z/cmd" ) // Version variable. Will be filled by build process. var version string func main() { exitCode := cmd.Main("Zettelstore", version) os.Exit(exitCode) } |
Changes to config/config.go.
︙ | ︙ | |||
18 19 20 21 22 23 24 | "context" "zettelstore.de/z/zettel/meta" ) // Key values that are supported by Config.Get const ( | | | | | | | | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | "context" "zettelstore.de/z/zettel/meta" ) // Key values that are supported by Config.Get const ( KeyFooterZettel = "footer-zettel" KeyHomeZettel = "home-zettel" KeyShowBackLinks = "show-back-links" KeyShowFolgeLinks = "show-folge-links" KeyShowSequelLinks = "show-sequel-links" KeyShowSuccessorLinks = "show-successor-links" // api.KeyLang ) // Config allows to retrieve all defined configuration values that can be changed during runtime. type Config interface { AuthConfig |
︙ | ︙ |
Changes to docs/development/20210916193200.zettel.
1 2 3 4 5 | id: 20210916193200 title: Required Software role: zettel syntax: zmk created: 20210916193200 | | | | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | id: 20210916193200 title: Required Software role: zettel syntax: zmk created: 20210916193200 modified: 20241213124936 The following software must be installed: * A current, supported [[release of Go|https://go.dev/doc/devel/release]], * [[Fossil|https://fossil-scm.org/]], * [[Git|https://git-scm.org/]] (most dependencies are accessible via Git only). Make sure that the software is in your path, e.g. via: ```sh export PATH=$PATH:/usr/local/go/bin export PATH=$PATH:$(go env GOPATH)/bin ``` The internal build tool needs the following software tools. They can be installed / updated via the build tool itself: ``go run tools/devtools/devtools.go``. Otherwise you can install the software by hand: * [[shadow|https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow]] via ``go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest``, * [[staticcheck|https://staticcheck.io/]] via ``go install honnef.co/go/tools/cmd/staticcheck@latest``, * [[unparam|https://mvdan.cc/unparam]][^[[GitHub|https://github.com/mvdan/unparam]]] via ``go install mvdan.cc/unparam@latest``, * [[revive|https://revive.run]] via ``go install github.com/mgechev/revive@vlatest``, * [[govulncheck|https://golang.org/x/vuln/cmd/govulncheck]] via ``go install golang.org/x/vuln/cmd/govulncheck@latest``, |
Changes to docs/development/20210916194900.zettel.
1 2 3 4 5 | id: 20210916194900 title: Checklist for Release role: zettel syntax: zmk created: 20210916194900 | | | | | | | | | | | | | | | | | | | | | 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 | id: 20210916194900 title: Checklist for Release role: zettel syntax: zmk created: 20210916194900 modified: 20241213125640 # Sync with the official repository: #* ``fossil sync -u`` # Make sure that there is no workspace defined: #* ``ls ..`` must not have a file ''go.work'', in no parent folder. # Make sure that all dependencies are up-to-date: #* ``cat go.mod`` # Clean up your Go workspace: #* ``go run tools/clean/clean.go`` (alternatively: ``make clean``) # All internal tests must succeed: #* ``go run tools/check/check.go -r`` (alternatively: ``make relcheck``) # The API tests must succeed on every development platform: #* ``go run tools/testapi/testapi.go`` (alternatively: ``make api``) # Run [[linkchecker|https://linkchecker.github.io/linkchecker/]] with the manual: #* ``go run -race cmd/zettelstore/main.go run -d docs/manual`` #* ``linkchecker http://127.0.0.1:23123 2>&1 | tee lc.txt`` #* Check all ""Error: 404 Not Found"" #* Check all ""Error: 403 Forbidden"": allowed for endpoint ''/z'' for those zettel that are accessible only in ''expert-mode'' #* Try to resolve other error messages and warnings #* Warnings about empty content can be ignored # On every development platform, the box with 10.000 zettel must run, with ''-race'' enabled: #* ``go run -race cmd/zettelstore/main.go run -d DIR`` # Create a development release: #* ``go run tools/build.go release`` (alternatively: ``make release``) # On every platform (esp. macOS), the box with 10.000 zettel must run properly: #* ``./zettelstore -d DIR`` # Update files in directory ''www'': #* ''index.wiki'' #* ''download.wiki'' #* ''changes.wiki'' #* ''plan.wiki'' # Set file ''VERSION'' to the new release version. It **must** consists of three numbers: ''MAJOR.MINOR.PATCH'', even if ''PATCH'' is zero. # Disable Fossil autosync mode: #* ``fossil setting autosync off`` # Commit the new release version: #* ``fossil commit --tag release --tag vVERSION -m "Version VERSION"`` #* **Important:** the tag must follow the given pattern, e.g. ''v0.0.15''. Otherwise client software will not be able to import ''zettelstore.de/z''. # Clean up your Go workspace: #* ``go run tools/clean/clean.go`` (alternatively: ``make clean``) # Create the release: #* ``go run tools/build/build.go release`` (alternatively: ``make release``) # Remove previous executables: #* ``fossil uv remove --glob '*-PREVVERSION*'`` # Add executables for release: #* ``cd releases`` #* ``fossil uv add *.zip`` #* ``cd ..`` #* Synchronize with main repository: |
︙ | ︙ |
Changes to docs/development/20231218181900.zettel.
︙ | ︙ | |||
67 68 69 70 71 72 73 | This list is used to check the generated HTML code (''ZID'' is the paceholder for the zettel identification): * Check all zettel HTML encodings, via the path ''/z/ZID?enc=html&part=zettel'' * Check all zettel web views, via the path ''/h/ZID'' * The info page of all zettel is checked, via path ''/i/ZID'' * A subset of max. 100 zettel will be checked for the validity of their edit page, via ''/e/ZID'' * 10 random zettel are checked for a valid create form, via ''/c/ZID'' | < | 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | This list is used to check the generated HTML code (''ZID'' is the paceholder for the zettel identification): * Check all zettel HTML encodings, via the path ''/z/ZID?enc=html&part=zettel'' * Check all zettel web views, via the path ''/h/ZID'' * The info page of all zettel is checked, via path ''/i/ZID'' * A subset of max. 100 zettel will be checked for the validity of their edit page, via ''/e/ZID'' * 10 random zettel are checked for a valid create form, via ''/c/ZID'' * A maximum of 200 random zettel are checked for a valid delete dialog, via ''/d/ZID'' Depending on the selected Zettelstore, the command might take a long time. You can shorten the time, if you disable any zettel query in the footer. === Build |
︙ | ︙ |
Changes to docs/manual/00001000000000.zettel.
1 2 3 4 5 6 | id: 00001000000000 title: Zettelstore Manual role: manual tags: #manual #zettelstore syntax: zmk created: 20210126175322 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | id: 00001000000000 title: Zettelstore Manual role: manual tags: #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20241128141924 show-back-links: false * [[Introduction|00001001000000]] * [[Design goals|00001002000000]] * [[Installation|00001003000000]] * [[Configuration|00001004000000]] * [[Structure of Zettelstore|00001005000000]] * [[Layout of a zettel|00001006000000]] * [[Zettelmarkup|00001007000000]] * [[Other markup languages|00001008000000]] * [[Security|00001010000000]] * [[API|00001012000000]] * [[Web user interface|00001014000000]] * [[Tips and Tricks|00001017000000]] * [[Troubleshooting|00001018000000]] * Frequently asked questions Version: {{00001000000001}} Licensed under the EUPL-1.2-or-later. |
Changes to docs/manual/00001003000000.zettel.
1 2 3 4 5 6 | id: 00001003000000 title: Installation of the Zettelstore software role: manual tags: #installation #manual #zettelstore syntax: zmk created: 20210126175322 | | | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | id: 00001003000000 title: Installation of the Zettelstore software role: manual tags: #installation #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20241213101917 === The curious user You just want to check out the Zettelstore software * Grab the appropriate executable and copy it into any directory * Start the Zettelstore software, e.g. with a double click[^On Windows and macOS, the operating system tries to protect you from possible malicious software. If you encounter a problem, please take a look on the [[Troubleshooting|00001018000000]] page.] * A sub-directory ""zettel"" will be created in the directory where you put the executable. It will contain your future zettel. * Open the URI [[http://localhost:23123]] with your web browser. It will present you a mostly empty Zettelstore. There will be a zettel titled ""[[Home|00010000000000]]"" that contains some helpful information. * Please read the instructions for the [[web-based user interface|00001014000000]] and learn about the various ways to write zettel. * If you restart your device, please make sure to start your Zettelstore again. |
︙ | ︙ |
Changes to docs/manual/00001003305000.zettel.
1 2 3 4 5 6 | id: 00001003305000 title: Enable Zettelstore to start automatically on Windows role: manual tags: #installation #manual #zettelstore syntax: zmk created: 20211125191727 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001003305000 title: Enable Zettelstore to start automatically on Windows role: manual tags: #installation #manual #zettelstore syntax: zmk created: 20211125191727 modified: 20241213103259 Windows is a complicated beast. There are several ways to automatically start Zettelstore. === Startup folder One way is to use the [[autostart folder|https://support.microsoft.com/en-us/windows/add-an-app-to-run-automatically-at-startup-in-windows-10-150da165-dcd9-7230-517b-cf3c295d89dd]]. Open the folder where you have placed in the Explorer. |
︙ | ︙ | |||
31 32 33 34 35 36 37 | === Task scheduler The Windows Task scheduler allows you to start Zettelstore as an background task. This is both an advantage and a disadvantage. | | | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | === Task scheduler The Windows Task scheduler allows you to start Zettelstore as an background task. This is both an advantage and a disadvantage. On the plus side, Zettelstore runs in the background, and it does not disturb you. All you have to do is to open your web browser, enter the appropriate URL, and there you go. On the negative side, you will not be notified when you enter the wrong data in the Task scheduler and Zettelstore fails to start. This can be mitigated by first using the command line prompt to start Zettelstore with the appropriate options. Once everything works, you can register Zettelstore to be automatically started by the task scheduler. There you should make sure that you have followed the first steps as described on the [[parent page|00001003300000]]. |
︙ | ︙ | |||
68 69 70 71 72 73 74 | Create a new action. {{00001003305112}} The next steps are the trickiest. | | | 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | Create a new action. {{00001003305112}} The next steps are the trickiest. If you did not create a startup configuration file, then create an action that starts a program. Enter the file path where you placed the Zettelstore executable. The ""Browse ..."" button helps you with that.[^I store my Zettelstore executable in the sub-directory ''bin'' of my home directory.] It is essential that you also enter a directory, which serves as the environment for your zettelstore. The (sub-) directory ''zettel'', which will contain your zettel, will be placed in this directory. If you leave the field ""Start in (optional)"" empty, the directory will be an internal Windows system directory (most likely: ''C:\\Windows\\System32''). |
︙ | ︙ |
Changes to docs/manual/00001004010000.zettel.
1 2 3 4 5 6 | id: 00001004010000 title: Zettelstore startup configuration role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001004010000 title: Zettelstore startup configuration role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20240926144803 The configuration file, specified by the ''-c CONFIGFILE'' [[command line option|00001004051000]], allows you to specify some startup options. These cannot be stored in a [[configuration zettel|00001004020000]] because they are needed before Zettelstore can start or because of security reasons. For example, Zettelstore needs to know in advance on which network address it 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. |
︙ | ︙ | |||
46 47 48 49 50 51 52 | : Specifies a [[box|00001004011200]] where zettel are stored. During startup, __X__ is incremented, starting with one, until no key is found. This allows to configuring than one box. If no ''box-uri-1'' key is given, the overall effect will be the same as if only ''box-uri-1'' was specified with the value ""dir://.zettel"". In this case, even a key ''box-uri-2'' will be ignored. ; [!debug-mode|''debug-mode''] | | > | 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | : Specifies a [[box|00001004011200]] where zettel are stored. During startup, __X__ is incremented, starting with one, until no key is found. This allows to configuring than one box. If no ''box-uri-1'' key is given, the overall effect will be the same as if only ''box-uri-1'' was specified with the value ""dir://.zettel"". In this case, even a key ''box-uri-2'' will be ignored. ; [!debug-mode|''debug-mode''] : If set to [[true|00001006030500]], allows to debug the Zettelstore software (mostly used by Zettelstore developers). Disables any timeout values of the internal web server and does not send some security-related data. Sets [[''log-level''|#log-level]] to ""debug"". Enables [[''runtime-profiling''|#runtime-profiling]]. Do not enable it for a production server. Default: ""false"" ; [!default-dir-box-type|''default-dir-box-type''] : Specifies the default value for the (sub-)type of [[directory boxes|00001004011400#type]], in which Zettel are typically stored. |
︙ | ︙ | |||
116 117 118 119 120 121 122 123 124 125 126 127 128 129 | Default: ""false"" ; [!read-only-mode|''read-only-mode''] : If set to a [[true value|00001006030500]] the Zettelstore service puts into a read-only mode. No changes are possible. Default: ""false"". ; [!secret|''secret''] : A string value to make the communication with external clients strong enough so that sessions of the [[web user interface|00001014000000]] or [[API access token|00001010040700]] cannot be altered by some external unfriendly party. The string must have a length of at least 16 bytes. This value is only needed to be set if [[authentication is enabled|00001010040100]] by setting the key [[''owner''|#owner]] to some user identification value. ; [!token-lifetime-api|''token-lifetime-api''], [!token-lifetime-html|''token-lifetime-html''] : Define lifetime of access tokens in minutes. | > > > > > | 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | Default: ""false"" ; [!read-only-mode|''read-only-mode''] : If set to a [[true value|00001006030500]] the Zettelstore service puts into a read-only mode. No changes are possible. Default: ""false"". ; [!runtime-profiling|''runtime-profiling''] : A boolean value that enables a web interface to obtain [[runtime profiling information|00001004010200]]. Default: ""false"", but it is set to ""true"" if [[''debug-mode''|#debug-mode]] is enabled. In this case, it cannot be disabled. ; [!secret|''secret''] : A string value to make the communication with external clients strong enough so that sessions of the [[web user interface|00001014000000]] or [[API access token|00001010040700]] cannot be altered by some external unfriendly party. The string must have a length of at least 16 bytes. This value is only needed to be set if [[authentication is enabled|00001010040100]] by setting the key [[''owner''|#owner]] to some user identification value. ; [!token-lifetime-api|''token-lifetime-api''], [!token-lifetime-html|''token-lifetime-html''] : Define lifetime of access tokens in minutes. |
︙ | ︙ |
Added docs/manual/00001004010200.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 | id: 00001004010200 title: Zettelstore runtime profiling role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20240926144556 modified: 20240926144951 For debugging purposes, you can enable runtime profiling by setting the startup configuration [[''runtime-profiling''|00001004010000#runtime-profiling]]. Typically, a Zettelstore developer will do this. In certain cases, a Zettelstore developer will ask you to enable runtime profiling, because you encountered a hard error. Runtime profiling will generate some data that can be retrieved through the builtin web server. The following URL paths are valid: |=Path|Description |''/rtp/''|Show an index page, where you can navigate to detailed information |''/rtp/allocs''|Show a sampling of all past memory allocations |''/rtp/block''|Show stack traces that led to internal blocking |''/rtp/cmdline''|Show the running Zettelstore command line, with arguments separated by NUL bytes |''/rtp/goroutine''|Show stack traces of all current internal activities |''/rtp/heap''|Show a sampling of memory allocations of live objects |''/rtp/mutex''|Show stack traces of holders of contended mutexes |''/rtp/profile''|Execute a CPU profile |''/rtp/symbol''|Shows function names for given program counter value |''/rtp/trace''|Show trace of execution of the current program |''/rtp/threadcreate''|Show stack traces that led to the creation of new OS threads See documentation for Go standard package [[''net/http/pprof''|https://pkg.go.dev/net/http/pprof]]. |
Changes to docs/manual/00001004020000.zettel.
1 2 3 4 5 6 | id: 00001004020000 title: Configure the running Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001004020000 title: Configure the running Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20241118175216 show-back-links: false You can configure a running Zettelstore by modifying the special zettel with the ID [[00000000000100]]. This zettel is called __configuration zettel__. The following metadata keys change the appearance / behavior of Zettelstore. Some of them can be overwritten in an [[user zettel|00001010040200]], a subset of those may be overwritten in zettel that is currently used. See the full list of [[metadata that may be overwritten|00001004020200]]. |
︙ | ︙ | |||
56 57 58 59 60 61 62 | Use values according to the language definition of [[RFC-5646|https://tools.ietf.org/html/rfc5646]]. ; [!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"". | | | | 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | Use values according to the language definition of [[RFC-5646|https://tools.ietf.org/html/rfc5646]]. ; [!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"". ; [!show-back-links|''show-back-links''], [!show-folge-links|''show-folge-links''], [!show-sequel-links|''show-sequel-links''], [!show-successor-links|''show-successor-links''] : When displaying a zettel in the web user interface, references to other zettel are normally shown below the content of the zettel. This affects the metadata keys [[''back''|00001006020000#back]], [[''folge''|00001006020000#folge]], [[''sequel''|00001006020000#sequel]], and [[''prequel''|00001006020000#prequel]]. These configuration keys may be used to show, not to show, or to close the list of referenced zettel. Allowed values are: ""false"" (will not show the list), ""close"" (will show the list closed), and ""open"" / """" (will show the list). Default: """". |
︙ | ︙ |
Changes to docs/manual/00001004020200.zettel.
1 2 3 4 5 6 | id: 00001004020200 title: Runtime configuration data that may be user specific or zettel specific role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20221205155521 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | id: 00001004020200 title: Runtime configuration data that may be user specific or zettel specific role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20221205155521 modified: 20241118175124 Some metadata of the [[runtime configuration|00001004020000]] may be overwritten in an [[user zettel|00001010040200]]. A subset of those may be overwritten in zettel that is currently used. This allows to specify user specific or zettel specific behavior. The following metadata keys are supported to provide a more specific behavior: |=Key|User:|Zettel:|Remarks |[[''footer-zettel''|00001004020000#footer-zettel]]|Y|N| |[[''home-zettel''|00001004020000#home-zettel]]|Y|N| |[[''lang''|00001004020000#lang]]|Y|Y|Making it user-specific could make zettel for other user less useful |[[''show-back-links''|00001004020000#show-back-links]]|Y|Y| |[[''show-folge-links''|00001004020000#show-folge-links]]|Y|Y| |[[''show-sequel-links''|00001004020000#show-sequel-links]]|Y|Y| |[[''show-successor-links''|00001004020000#show-successor-links]]|Y|Y| |
Changes to docs/manual/00001005000000.zettel.
1 2 3 4 5 6 | id: 00001005000000 title: Structure of Zettelstore role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210126175322 | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | id: 00001005000000 title: Structure of Zettelstore role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20241213101751 Zettelstore is a software that manages your zettel. Since every zettel must be readable without any special tool, most zettel has to be stored as ordinary files within specific directories. Typically, file names and file content must comply to specific rules so that Zettelstore can manage them. If you add, delete, or change zettel files with other tools, e.g. a text editor, Zettelstore will monitor these actions. Zettelstore provides additional services to the user. Via the builtin [[web user interface|00001014000000]] you can work with zettel in various ways. For example, you are able to list zettel, to create new zettel, to edit them, or to delete them. You can view zettel details and relations between zettel. In addition, Zettelstore provides an ""application programming interface"" ([[API|00001012000000]]) that allows other software to communicate with the Zettelstore. Zettelstore becomes extensible by external software. For example, a more sophisticated user interface could be built, or an application for your mobile device that allows you to send content to your Zettelstore as new zettel. === Where zettel are stored Your zettel are stored typically as files in a specific directory. If you have not explicitly specified the directory, a default directory will be used. The directory has to be specified at [[startup time|00001004010000]]. Nested directories are not supported (yet). Every file in this directory that should be monitored by Zettelstore must have a file name that begins with 14 digits (0-9), the [[zettel identifier|00001006050000]]. If you create a new zettel via the [[web user interface|00001014000000]] or via the [[API|00001012053200]], the zettel identifier will be the timestamp of the current date and time (format is ''YYYYMMDDhhmmss''). This allows zettel to be sorted naturally by creation time. Since the only restriction on zettel identifiers are the 14 digits, you are free to use other digit sequences. The [[configuration zettel|00001004020000]] is one prominent example, as well as these manual zettel. You can create these special zettel by manually renaming the underlying zettel files. It is allowed that the file name contains other characters after the 14 digits. These are ignored by Zettelstore. Two filename extensions are used by Zettelstore: # ''.zettel'' is a format that stores metadata and content together in one file, # the empty file extension is used, when the content must be stored in its own file, e.g. image data; in this case, the filename just the 14 digits of the zettel identifier, and optional characters except the period ''"."''. Other filename extensions are used to determine the ""syntax"" of a zettel. This allows to use other content within the Zettelstore, e.g. images or HTML templates. For example, you want to store an important figure in the Zettelstore that is encoded as a ''.png'' file. Since each zettel contains some metadata, e.g. the title of the figure, the question arises where these data should be stores. The solution is a meta-file with the same zettel identifier, but without a filename extension. Zettelstore recognizes this situation and reads in both files for the one zettel containing the figure. It maintains this relationship as long as these files exists. In case of some textual zettel content you do not want to store the metadata and the zettel content in two different files. Here the ''.zettel'' extension will signal that the metadata and the zettel content will be put in the same file, separated by an empty line or a line with three dashes (""''-\-\-''"", also known as ""YAML separator""). === Predefined zettel Zettelstore contains some [[predefined zettel|00001005090000]] to work properly. |
︙ | ︙ | |||
70 71 72 73 74 75 76 | To allow changing predefined zettel, both the file store and the internal zettel store are internally chained together. If you change a zettel, it will be always stored as a file. If a zettel is requested, Zettelstore will first try to read that zettel from a file. If such a file was not found, the internal zettel store is searched secondly. Therefore, the file store ""shadows"" the internal zettel store. | | | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | To allow changing predefined zettel, both the file store and the internal zettel store are internally chained together. If you change a zettel, it will be always stored as a file. If a zettel is requested, Zettelstore will first try to read that zettel from a file. If such a file was not found, the internal zettel store is searched secondly. Therefore, the file store ""shadows"" the internal zettel store. If you want to read the original zettel, you have to delete the zettel (which removes it from the file directory). Now we have two places where zettel are stored: in the specific directory and within the Zettelstore software. * [[List of predefined zettel|00001005090000]] === Boxes: alternative ways to store zettel As described above, a zettel may be stored as a file inside a directory or inside the Zettelstore software itself. Zettelstore allows other ways to store zettel by providing an abstraction called __box__.[^Formerly, zettel were stored physically in boxes, often made of wood.] |
︙ | ︙ |
Changes to docs/manual/00001005090000.zettel.
1 2 3 4 5 6 | id: 00001005090000 title: List of predefined zettel role: manual tags: #manual #reference #zettelstore syntax: zmk created: 20210126175322 | | | > > > > < < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | id: 00001005090000 title: List of predefined zettel role: manual tags: #manual #reference #zettelstore syntax: zmk created: 20210126175322 modified: 20241202102358 The following table lists all predefined zettel with their purpose. The content of most[^To be more exact: zettel with an identifier greater or equal ''00000999999900'' will have their content indexed.] of these zettel will not be indexed by Zettelstore. You will not find zettel when searched for some content, e.g. ""[[query:european]]"" will not find the [[Zettelstore License|00000000000004]]. However, metadata is always indexed, e.g. ""[[query:title:license]]"" will find the Zettelstore License zettel. |= 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 | [[00000000000008]] | Zettelstore Memory | Some statistics about main memory usage | [[00000000000009]] | Zettelstore Sx Engine | Statistics about the [[Sx|https://t73f.de/r/sx]] engine, which interprets symbolic expressions | [[00000000000020]] | Zettelstore Box Manager | Contains some statistics about zettel boxes and the the index process | [[00000000000090]] | Zettelstore Supported Metadata Keys | Contains all supported metadata keys, their [[types|00001006030000]], and more | [[00000000000092]] | Zettelstore Supported Parser | Lists all supported values for metadata [[syntax|00001006020000#syntax]] that are recognized by Zettelstore | [[00000000000096]] | Zettelstore Startup Configuration | Contains the effective values of the [[startup configuration|00001004010000]] | [[00000000000100]] | Zettelstore Runtime Configuration | Allows to [[configure Zettelstore at runtime|00001004020000]] | [[00000000010100]] | Zettelstore Base HTML Template | Contains the general layout of the HTML view | [[00000000010200]] | Zettelstore Login Form HTML Template | Layout of the login form, when authentication is [[enabled|00001010040100]] | [[00000000010300]] | Zettelstore List Zettel HTML Template | Used when displaying a list of zettel | [[00000000010401]] | Zettelstore Detail HTML Template | Layout for the HTML detail view of one zettel | [[00000000010402]] | Zettelstore Info HTML Template | Layout for the information view of a specific zettel | [[00000000010403]] | Zettelstore Form HTML Template | Form that is used to create a new or to change an existing zettel that contains text | [[00000000010405]] | Zettelstore Delete HTML Template | View to confirm the deletion of a zettel | [[00000000010700]] | Zettelstore Error HTML Template | View to show an error message | [[00000000019000]] | Zettelstore Sxn Start Code | Starting point of sxn functions to build the templates | [[00000000019990]] | Zettelstore Sxn Base Code | Base sxn functions to build the templates | [[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]] | [[00000000040001]] | Generic Emoji | Image that is shown if [[original image reference|00001007040322]] is invalid | [[00000000060010]] | zettel | [[Role zettel|00001012051800]] for the role ""[[zettel|00001006020100#zettel]]"" | [[00000000060020]] | confguration | [[Role zettel|00001012051800]] for the role ""[[confguration|00001006020100#confguration]]"" | [[00000000060030]] | role | [[Role zettel|00001012051800]] for the role ""[[role|00001006020100#role]]"" | [[00000000060040]] | tag | [[Role zettel|00001012051800]] for the role ""[[tag|00001006020100#tag]]"" | [[00000000090000]] | New Menu | Contains items that should be in the zettel template menu | [[00000000090001]] | New Zettel | Template for a new zettel with role ""[[zettel|00001006020100#zettel]]"" | [[00000000090002]] | New User | Template for a new [[user zettel|00001010040200]] | [[00000000090003]] | New Tag | Template for a new [[tag zettel|00001006020100#tag]] | [[00000000090004]] | New Role | Template for a new [[role zettel|00001006020100#role]] | [[00000999999999]] | Zettelstore Application Directory | Maps application name to application specific zettel | [[00010000000000]] | Home | Default home zettel, contains some welcome information If a zettel is not linked, it is not accessible for the current user. In most cases, you must at least enable [[''expert-mode''|00001004020000#expert-mode]]. **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 6 | id: 00001006020000 title: Supported Metadata Keys role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001006020000 title: Supported Metadata Keys role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 modified: 20241118175033 Although you are free to define your own metadata, by using any key (according to the [[syntax|00001006010000]]), some keys have a special meaning that is enforced by Zettelstore. See the [[computed list of supported metadata keys|00000000000090]] for details. Most keys conform to a [[type|00001006030000]]. ; [!author|''author''] |
︙ | ︙ | |||
85 86 87 88 89 90 91 92 93 94 95 96 97 98 | ; [!precursor|''precursor''] : References zettel for which this zettel is a ""Folgezettel"" / follow-up zettel. Basically the inverse of key [[''folge''|#folge]]. ; [!predecessor|''predecessor''] : References the zettel that contains a previous version of the content. In contrast to [[''precursor''|#precurso]] / [[''folge''|#folge]], this is a reference because of technical reasons, not because of content-related reasons. Basically the inverse of key [[''successors''|#successors]]. ; [!published|''published''] : This property contains the timestamp of the mast modification / creation of the zettel. If [[''modified''|#modified]] is set with a valid timestamp, it contains the its value. Otherwise, if [[''created''|#created]] is set with a valid timestamp, it contains the its value. Otherwise, if the zettel identifier contains a valid timestamp, the identifier is used. In all other cases, this property is not set. | > > > | 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | ; [!precursor|''precursor''] : References zettel for which this zettel is a ""Folgezettel"" / follow-up zettel. Basically the inverse of key [[''folge''|#folge]]. ; [!predecessor|''predecessor''] : References the zettel that contains a previous version of the content. In contrast to [[''precursor''|#precurso]] / [[''folge''|#folge]], this is a reference because of technical reasons, not because of content-related reasons. Basically the inverse of key [[''successors''|#successors]]. ; [!prequel|''prequel''] : Specifies a zettel that is conceptually a prequel zettel. This is a zettel that occured somehow before the current zettel. ; [!published|''published''] : This property contains the timestamp of the mast modification / creation of the zettel. If [[''modified''|#modified]] is set with a valid timestamp, it contains the its value. Otherwise, if [[''created''|#created]] is set with a valid timestamp, it contains the its value. Otherwise, if the zettel identifier contains a valid timestamp, the identifier is used. In all other cases, this property is not set. |
︙ | ︙ | |||
107 108 109 110 111 112 113 | : Marks a zettel as read-only. The interpretation of [[supported values|00001006020400]] for this key depends, whether authentication is [[enabled|00001010040100]] or not. ; [!role|''role''] : Defines the role of the zettel. Can be used for selecting zettel. See [[supported zettel roles|00001006020100]]. If not given, it is ignored. | | | < < < | | 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 | : Marks a zettel as read-only. The interpretation of [[supported values|00001006020400]] for this key depends, whether authentication is [[enabled|00001010040100]] or not. ; [!role|''role''] : Defines the role of the zettel. Can be used for selecting zettel. See [[supported zettel roles|00001006020100]]. If not given, it is ignored. ; [!sequel|''sequel''] : Is a property that contains identifier of all zettel that reference this zettel through the [[''prequel''|#prequel]] value. ; [!successors|''successors''] : Is a property that contains identifier of all zettel that reference this zettel through the [[''predecessor''|#predecessor]] value. Therefore, it references all zettel that contain a new version of the content and/or metadata. In contrast to [[''folge''|#folge]], these are references because of technical reasons, not because of content-related reasons. In most cases, zettel referencing the current zettel should be updated to reference a successor zettel. The [[query reference|00001007040310]] [[query:backward? successors?]] lists all such zettel. ; [!summary|''summary''] : Summarizes the content of the zettel. You may use all [[inline-structued elements|00001007040000]] of Zettelmarkup. ; [!syntax|''syntax''] : Specifies the syntax that should be used for interpreting the zettel. The zettel about [[other markup languages|00001008000000]] defines supported values. If it is not given, it defaults to ''plain''. ; [!tags|''tags''] : Contains a space separated list of tags to describe the zettel further. Each Tag must begin with the number sign character (""''#''"", U+0023). ; [!title|''title''] : Specifies the title of the zettel. If not given, the value of [[''id''|#id]] will be used. ; [!url|''url''] : Defines an URL / URI for this zettel that possibly references external material. One use case is to specify the document that the current zettel comments on. The URL will be rendered special in the [[web user interface|00001014000000]] if you use the default template. ; [!useless-files|''useless-files''] : Contains the file names that are rejected to serve the content of a zettel. Is used for [[directory boxes|00001004011400]] and [[file boxes|00001004011200#file]]. If a zettel is deleted, these files will also be deleted. ; [!user-id|''user-id''] : Provides some unique user identification for an [[user zettel|00001010040200]]. It is used as a user name for authentication. It is only used for zettel with a ''role'' value of ""user"". ; [!user-role|''user-role''] : Defines the basic privileges of an authenticated user, e.g. reading / changing zettel. |
︙ | ︙ |
Changes to docs/manual/00001006050000.zettel.
1 2 3 4 5 6 | id: 00001006050000 title: Zettel identifier role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210126175322 | | < < | < < < < < < < < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | id: 00001006050000 title: Zettel identifier role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20241128141443 Each zettel is given a unique identifier. To some degree, the zettel identifier is part of the metadata. Basically, the identifier is given by the [[Zettelstore|00001005000000]] software. Every zettel identifier consists of 14 digits. They resemble a timestamp: the first four digits could represent the year, the next two represent the month, following by day, hour, minute, and second. This allows to order zettel chronologically in a canonical way. In most cases the zettel identifier is the timestamp when the zettel was created. However, the Zettelstore software just checks for exactly 14 digits. Anybody is free to assign a ""non-timestamp"" identifier to a zettel, e.g. with a month part of ""35"" or with ""99"" as the last two digits. Some zettel identifier are [[reserved|00001006055000]] and should not be used otherwise. All identifiers of zettel initially provided by an empty Zettelstore begin with ""000000"", except the home zettel ''00010000000000''. Zettel identifier of this manual have be chosen to begin with ""000010"". A zettel can have any identifier that contains 14 digits and that is not in use by another zettel managed by the same Zettelstore. |
Deleted docs/manual/00001006050200.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to docs/manual/00001006055000.zettel.
1 2 3 4 5 6 | id: 00001006055000 title: Reserved zettel identifier role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210721105704 | | | < | | | | | | < < | | 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: 00001006055000 title: Reserved zettel identifier role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210721105704 modified: 20241202100917 [[Zettel identifier|00001006050000]] are typically created by examine the current date and time. By renaming the name of the underlying zettel file, you are able to provide any sequence of 14 digits. To make things easier, you must not use zettel identifier that begin with four zeroes (''0000''). All zettel provided by an empty zettelstore begin with six zeroes[^Exception: the predefined home zettel ''00010000000000''. But you can [[configure|00001004020000#home-zettel]] another zettel with another identifier as the new home zettel.]. Zettel identifier of this manual have be chosen to begin with ''000010''. However, some external applications may need at least one defined zettel identifier to work properly. Zettel [[Zettelstore Application Directory|00000999999999]] (''00000999999999'') can be used to associate a name to a zettel identifier. For example, if your application is named ""app"", you create a metadata key ''app-zid''. Its value is the zettel identifier of the zettel that configures your application. === Reserved Zettel Identifier |= From | To | Description | 00000000000000 | 00000000000000 | This is an invalid zettel identifier | 00000000000001 | 00000999999999 | [[Predefined zettel|00001005090000]] | 00001000000000 | 00001099999999 | This [[Zettelstore manual|00001000000000]] | 00001100000000 | 00008999999999 | Reserved, do not use | 00009000000000 | 00009999999999 | Reserved for applications ==== External Applications |= From | To | Description | 00009000001000 | 00009000001999 | [[Zettel Presenter|https://zettelstore.de/contrib]], an application to display zettel as a HTML-based slideshow |
Changes to docs/manual/00001007000000.zettel.
1 2 3 4 5 6 | id: 00001007000000 title: Zettelmarkup role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | id: 00001007000000 title: Zettelmarkup role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20241212152823 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. [[CommonMark|00001008010500]] is an attempt to make it simpler by providing a comprehensive specification, combined with an extra chapter to give hints for the implementation. Zettelmarkup follows some simple principles that anybody who knows how ho write software should be able understand to create an implementation. Zettelmarkup is a markup language on its own. This is in contrast to Markdown, which is basically a super-set of HTML: every HTML document is a valid Markdown document.[^To be precise: the content of the ``<body>`` of each HTML document is a valid Markdown document.] While HTML is a markup language that will probably last for a long time, it cannot be easily translated to other formats, such as PDF, JSON, or LaTeX. Additionally, it is allowed to embed other languages into HTML, such as CSS or even JavaScript. This could create problems with longevity as well as security problems. |
︙ | ︙ |
Changes to docs/manual/00001007010000.zettel.
1 2 3 4 5 6 | id: 00001007010000 title: Zettelmarkup: General Principles role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001007010000 title: Zettelmarkup: General Principles role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20241213101524 Any document can be thought as a sequence of paragraphs and other [[block-structured elements|00001007030000]] (""blocks""), such as [[headings|00001007030300]], [[lists|00001007030200]], quotations, and code blocks. Some of these blocks can contain other blocks, for example lists may contain other lists or paragraphs. Other blocks contain [[inline-structured elements|00001007040000]] (""inlines""), such as text, [[links|00001007040310]], emphasized text, and images. With the exception of lists and tables, the markup for blocks always begins at the first position of a line with three or more identical characters. List blocks also begin at the first position of a line, but may need one or more identical character, plus a space character. |
︙ | ︙ | |||
44 45 46 47 48 49 50 | Many block and inline elements can be refined by additional [[attributes|00001007050000]]. Attributes resemble roughly HTML attributes and are put near the corresponding elements by using the syntax ``{...}``{=zmk}. One example is to make space characters visible inside a inline literal element: ``1 + 2 = 3``{-} was specified by using the default attribute: ``\`\`1 + 2 = 3\`\`{-}``. To summarize: | | | 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | Many block and inline elements can be refined by additional [[attributes|00001007050000]]. Attributes resemble roughly HTML attributes and are put near the corresponding elements by using the syntax ``{...}``{=zmk}. One example is to make space characters visible inside a inline literal element: ``1 + 2 = 3``{-} was specified by using the default attribute: ``\`\`1 + 2 = 3\`\`{-}``. To summarize: * With some exceptions, block-structural elements begins at the for position of a line with three identical characters. * The most important exception to this rule is the specification of lists. * If no block element is found, a paragraph with inline elements is assumed. * With some exceptions, inline-structural elements begins with two characters, quite often the same two characters. * The most important exceptions are links. * The backslash character can help to resolve possible ambiguities. * Attributes refine some block and inline elements. * Block elements have a higher priority than inline elements. These principles makes automatic recognizing zettelmarkup an (relatively) easy task. By looking at the reference implementation, a moderately skilled software developer should be able to create a appropriate software in a different programming language. |
Changes to docs/manual/00001007030000.zettel.
1 2 3 4 5 6 | id: 00001007030000 title: Zettelmarkup: Block-Structured Elements role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001007030000 title: Zettelmarkup: Block-Structured Elements role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20241212153023 Every markup for blocks-structured elements (""blocks"") begins at the very first position of a line. There are five kinds of blocks: lists, one-line blocks, line-range blocks, tables, and paragraphs. === Lists |
︙ | ︙ | |||
62 63 64 65 66 67 68 | :::example = Heading Some text follows. ::: This is because headings need at least three equal sign character. A paragraph is essentially a sequence of [[inline-structured elements|00001007040000]]. | | | 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | :::example = Heading Some text follows. ::: This is because headings need at least three equal sign character. A paragraph is essentially a sequence of [[inline-structured elements|00001007040000]]. Inline-structured elements can span more than one line. Paragraphs are separated by empty lines. If you want to specify a second paragraph inside a list item, or if you want to continue a paragraph on a second and more line within a list item, you must begin the paragraph with a certain number of space characters. The number of space characters depends on the kind of a list and the relevant nesting level. A line that begins with a space character and which is outside of a list or does not contain the right number of space characters is considered to be part of a paragraph. |
Changes to docs/manual/00001007030200.zettel.
1 2 3 4 5 6 | id: 00001007030200 title: Zettelmarkup: Nested Lists role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | id: 00001007030200 title: Zettelmarkup: Nested Lists role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20241213121000 There are three kinds of lists that can be nested: ordered lists, unordered lists, and quotation lists. Ordered lists are specified with the number sign (""''#''"", U+0023), unordered lists use the asterisk (""''*''"", U+002A), and quotation lists are specified with the greater-than sign (""''>''"", U+003E). Let's call these three characters __list characters__. Any nested list item is specified by a non-empty sequence of list characters, followed by a space character and a sequence of [[inline elements|00001007040000]]. In case of a quotation list as the last list character, the space character followed by a sequence of inline elements is optional. The number / count of list characters gives the nesting of the lists. If the following lines should also be part of the list item, exactly the same number of spaces must be given at the beginning of each of the following lines as it is the lists are nested, plus one additional space character. In other words: the inline elements must begin at the same column as it was on the previous line. The resulting sequence of inline elements is merged into a paragraph. Appropriately indented paragraphs can be specified after the first one. Since each blocks-structured element has to be specified at the first position of a line, none of the nested list items may contain anything else than paragraphs. Some examples: ```zmk # One # Two # Three |
︙ | ︙ |
Changes to docs/manual/00001007031000.zettel.
1 2 3 4 5 6 | id: 00001007031000 title: Zettelmarkup: Tables role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | id: 00001007031000 title: Zettelmarkup: Tables role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20241212153641 Tables are used to show some data in a two-dimensional fashion. In zettelmarkup, tables are not specified explicitly, but by entering __table rows__. Therefore, a table can be seen as a sequence of table rows. A table row is nothing but a sequence of __table cells__. The length of a table is the number of table rows, the width of a table is the maximum length of its rows. The first cell of a row must begin with the vertical bar character (""''|''"", U+007C) at the first position of a line. The other cells of a row begin with the same vertical bar character at later positions in that line. A cell is delimited by the vertical bar character of the next cell or by the end of the current line. A vertical bar character as the last character of a line will not result in a table cell. It will be ignored. |
︙ | ︙ |
Changes to docs/manual/00001007040322.zettel.
1 2 3 4 5 6 | id: 00001007040322 title: Zettelmarkup: Image Embedding role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210811154251 | | | > | | 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: 00001007040322 title: Zettelmarkup: Image Embedding role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210811154251 modified: 20241202101206 Image content is assumed, if an URL is used or if the referenced zettel contains an image. Supported formats are: * Portable Network Graphics (""PNG""), as defined by [[RFC\ 2083|https://tools.ietf.org/html/rfc2083]]. * Graphics Interchange Format (""GIF"), as defined by [[https://www.w3.org/Graphics/GIF/spec-gif89a.txt]]. * JPEG / JPG, defined by the __Joint Photographic Experts Group__. * Scalable Vector Graphics (SVG), defined by [[https://www.w3.org/Graphics/SVG/]] * WebP, defined by [[Google|https://developers.google.com/speed/webp]] If the text is given, it will be interpreted as an alternative textual representation, to help persons with some visual disabilities. [[Attributes|00001007050000]] are supported. They must follow the last right curly bracket character immediately. One prominent example is to specify an explicit title attribute that is shown on certain web browsers when the zettel is rendered in HTML: Examples: * [!spin|``{{Spinning Emoji|00000000040001}}{title=Emoji width=30}``] is rendered as ::{{Spinning Emoji|00000000040001}}{title=Emoji width=30}::{=example}. * The above image is also the placeholder for a non-existing invalid zettel or for using an invalid zettel identifier: ** ``{{99999999999999}}`` will be rendered as ::{{99999999999999}}::{=example}. ** ``{{00000000000000}}`` will be rendered as ::{{00000000000000}}::{=example}. |
Changes to docs/manual/00001007720300.zettel.
1 2 3 4 5 6 | id: 00001007720300 title: Query: Context Directive role: manual tags: #manual #search #zettelstore syntax: zmk created: 20230707204706 | | | | | 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: 00001007720300 title: Query: Context Directive role: manual tags: #manual #search #zettelstore syntax: zmk created: 20230707204706 modified: 20241118174741 A context directive calculates the __context__ of a list of zettel identifier. It starts with the keyword ''CONTEXT''. Optionally you may specify some context details, after the keyword ''CONTEXT'', separated by space characters. These are: * ''FULL'': additionally search for zettel with the same tags, * ''BACKWARD'': search for context only though backward links, * ''FORWARD'': search for context only through forward links, * ''COST'': one or more space characters, and a positive integer: set the maximum __cost__ (default: 17), * ''MAX'': one or more space characters, and a positive integer: set the maximum number of context zettel (default: 200). If no ''BACKWARD'' and ''FORWARD'' is specified, a search for context zettel will be done though backward and forward links. The cost of a context zettel is calculated iteratively: * Each of the specified zettel hast a cost of one. * A zettel found as a single folge zettel or single precursor zettel has the cost of the originating zettel, plus 0.1. * A zettel found as a single sequel zettel or single prequel zettel has the cost of the originating zettel, plus 1.0. * A zettel found as a single successor zettel or single predecessor zettel has the cost of the originating zettel, plus seven. * A zettel found via another link without being part of a [[set of zettel identifier|00001006032500]], has the cost of the originating zettel, plus two. * A zettel which is part of a set of zettel identifier, has the cost of the originating zettel, plus one of the four choices above and multiplied with roughly a linear-logarithmic value based on the size of the set. * A zettel with the same tag, has the cost of the originating zettel, plus a linear-logarithmic number based on the number of zettel with this tag. If a zettel belongs to more than one tag compared with the current zettel, there is a discount of 90% per additional tag. This only applies if the ''FULL'' directive was specified. |
︙ | ︙ |
Changes to docs/manual/00001007800000.zettel.
1 2 3 4 5 6 | id: 00001007800000 title: Zettelmarkup: Summary of Formatting Characters role: manual tags: #manual #reference #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | id: 00001007800000 title: Zettelmarkup: Summary of Formatting Characters role: manual tags: #manual #reference #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20241125182149 The following table gives an overview about the use of all characters that begin a markup element. |= Character :|= [[Blocks|00001007030000]] <|= [[Inlines|00001007040000]] < | ''!'' | (free) | (free) | ''"'' | [[Verse block|00001007030700]] | [[Short inline quote|00001007040100]] | ''#'' | [[Ordered list|00001007030200]] | [[marked / highlighted text|00001007040100]] | ''$'' | (reserved) | (reserved) | ''%'' | [[Comment block|00001007030900]] | [[Comment|00001007040000]] | ''&'' | (free) | [[Entity|00001007040000]] | ''\''' | (free) | [[Computer input|00001007040200]] | ''('' | (free) | (free) | '')'' | (free) | (free) | ''*'' | [[Unordered list|00001007030200]] | [[strongly emphasized text|00001007040100]] | ''+'' | (reserved) | (reserved) | '','' | (free) | [[Sub-scripted text|00001007040100]] | ''-'' | [[Horizontal rule|00001007030400]] | ""[[en-dash|00001007040000]]"" | ''.'' | (free) | (free) | ''/'' | (free) | (free) | '':'' | [[Region block|00001007030800]] / [[description text|00001007030100]] | [[Inline region|00001007040100]] | '';'' | [[Description term|00001007030100]] | (free) | ''<'' | [[Quotation block|00001007030600]] | (free) |
︙ | ︙ |
Changes to docs/manual/00001010000000.zettel.
1 2 3 4 5 6 | id: 00001010000000 title: Security role: manual tags: #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | id: 00001010000000 title: Security role: manual tags: #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 modified: 20241213102811 Your zettel may contain sensitive content. You probably want to ensure that only authorized persons can read and/or modify them. Zettelstore ensures this in various ways. === Local first The Zettelstore is designed to run on your local computer. If you do not configure it in other ways, no person from another computer can connect to your Zettelstore. You must explicitly configure it to allow access from other computers. In the case that you own multiple computers, you do not have to access the Zettelstore remotely. You could install Zettelstore on each computer and set-up some software to synchronize your zettel. Since zettel are stored as ordinary files, this task could be done in various ways. === Read-only You can start the Zettelstore in a read-only mode. Nobody, not even you as the owner of the Zettelstore, can change something via its interfaces[^However, as an owner, you have access to the files that store the zettel. If you modify the files, these changes will be reflected via its interfaces.]. You enable read-only mode through the key ''readonly'' in the [[startup configuration zettel|00001004010000#readonly]] or with the ''-r'' option of the ``zettelstore run`` sub-command. === Authentication The Zettelstore can be configured that users must authenticate themselves to gain access to the content. * [[How to enable authentication|00001010040100]] * [[How to add a new user|00001010040200]] * [[How users are authenticated|00001010040400]] (some technical background) * [[Authenticated sessions|00001010040700]] === Authorization |
︙ | ︙ |
Changes to docs/manual/00001010040700.zettel.
1 2 3 4 5 6 | id: 00001010040700 title: Access token role: manual tags: #authentication #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | id: 00001010040700 title: Access token role: manual tags: #authentication #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 modified: 20241213101607 If an user is authenticated, an ""access token"" is created that must be sent with every request to prove the identity of the caller. Otherwise the user will not be recognized by Zettelstore. If the user was authenticated via the [[web user interface|00001014000000]], the access token is stored in a [[""session cookie""|https://en.wikipedia.org/wiki/HTTP_cookie#Session_cookie]]. When the web browser is closed, these cookies are not saved. If you want web browser to store the cookie as long as lifetime of that token, the owner must set ''persistent-cookie'' of the [[startup configuration|00001004010000]] to ''true''. If the web browser remains inactive for a period, the user will be automatically logged off, because each access token has a limited lifetime. The maximum length of this period is specified by the ''token-lifetime-html'' value of the startup configuration. Every time a web page is displayed, a fresh token is created and stored inside the cookie. If the user was authenticated via the API, the access token will be returned as the content of the response. Typically, the lifetime of this token is more short term, e.g. 10 minutes. It is specified by the ''token-lifetime-api'' value of the startup configuration. If you need more time, you can either [[re-authenticate|00001012050200]] the user or use an API call to [[renew the access token|00001012050400]]. If you remotely access your Zettelstore via HTTP (not via HTTPS, which allows encrypted communication), you must set the ''insecure-cookie'' value of the startup configuration to ''true''. In most cases, such a scenario is not recommended, because user name and password will be transferred as plain text. You could make use of such scenario if you know all parties that access the local network where you access the Zettelstore. |
Changes to docs/manual/00001010070600.zettel.
1 2 3 4 5 6 | id: 00001010070600 title: Access rules role: manual tags: #authorization #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001010070600 title: Access rules role: manual tags: #authorization #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 modified: 20240711183714 Whether an operation of the Zettelstore is allowed or rejected, depends on various factors. The following rules are checked first, in this order: # In read-only mode, every operation except the ""Read"" operation is rejected. # If there is no owner, authentication is disabled and every operation is allowed for everybody. |
︙ | ︙ | |||
39 40 41 42 43 44 45 | ** If the zettel is the [[user zettel|00001010040200]] of the authenticated user, proceed as follows: *** If some sensitive meta values are changed (e.g. user identifier, zettel role, user role, but not hashed password), reject the access *** Since the user just updates some uncritical values, grant the access In other words: a user is allowed to change its user zettel, even if s/he has no writer privilege and if only uncritical data is changed. ** If the ''user-role'' of the user is ""reader"", reject the access. ** If the user is not allowed to create a new zettel, reject the access. ** Otherwise grant the access. | < < < | 39 40 41 42 43 44 45 46 47 48 49 | ** If the zettel is the [[user zettel|00001010040200]] of the authenticated user, proceed as follows: *** If some sensitive meta values are changed (e.g. user identifier, zettel role, user role, but not hashed password), reject the access *** Since the user just updates some uncritical values, grant the access In other words: a user is allowed to change its user zettel, even if s/he has no writer privilege and if only uncritical data is changed. ** If the ''user-role'' of the user is ""reader"", reject the access. ** If the user is not allowed to create a new zettel, reject the access. ** Otherwise grant the access. * Delete a zettel ** Reject the access. Only the owner of the Zettelstore is allowed to delete a zettel. This may change in the future. |
Changes to docs/manual/00001012000000.zettel.
1 2 3 4 5 6 | id: 00001012000000 title: API role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001012000000 title: API role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20240711183736 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 [[symbolic expressions|00001012930000]] as its main encoding formats for exchanging messages between a Zettelstore and its client software. |
︙ | ︙ | |||
30 31 32 33 34 35 36 | === 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]] * [[Update metadata and content of a zettel|00001012054200]] | < | 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | === 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]] * [[Update metadata and content of a zettel|00001012054200]] * [[Delete a zettel|00001012054600]] === Various helper methods * [[Retrieve administrative data|00001012070500]] * [[Execute some commands|00001012080100]] ** [[Check for authentication|00001012080200]] ** [[Refresh internal data|00001012080500]] |
Deleted docs/manual/00001012054400.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to docs/manual/00001012920000.zettel.
1 2 3 4 5 6 | id: 00001012920000 title: Endpoints used by the API role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20210126175322 | | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | id: 00001012920000 title: Endpoints used by the API role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20210126175322 modified: 20240711183819 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]] | | ''x'' | GET: [[retrieve administrative data|00001012070500]] | | E**x**ecute | | POST: [[execute command|00001012080100]] | ''z'' | GET: [[list zettel|00001012051200]]/[[query zettel|00001012051400]] | GET: [[retrieve zettel|00001012053300]] | **Z**ettel | | POST: [[create new zettel|00001012053200]] | PUT: [[update zettel|00001012054200]] | | | DELETE: [[delete zettel|00001012054600]] 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/00001012921200.zettel.
1 2 3 4 5 6 | id: 00001012921200 title: API: Encoding of Zettel Access Rights role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20220201173115 | | | | 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: 00001012921200 title: API: Encoding of Zettel Access Rights role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20220201173115 modified: 20240711183931 Various API calls return a symbolic expression list ''(rights N)'', with ''N'' as a number, that encodes the access rights the user currently has. ''N'' is an integer number between 0 and 62.[^Not all values in this range are used.] The value ""0"" signals that something went wrong internally while determining the access rights. A value of ""1"" says, that the current user has no access right for the given zettel. In most cases, this value will not occur, because only zettel are presented, which are at least readable by the current user. Values ""2"" to ""62"" are binary encoded values, where each bit signals a special right. |=Bit number:|Bit value:|Meaning | 1 | 2 | User is allowed to create a new zettel | 2 | 4 | User is allowed to read the zettel | 3 | 8 | User is allowed to update the zettel | 4 | 16 | (not in use; was assigned to an operation) | 5 | 32 | User is allowed to delete the zettel The algorithm to calculate the actual access rights from the value is relatively simple: # Search for the biggest bit value that is less than the rights value. This is an access right for the current user. # Subtract the bit value from the rights value. Remember the difference as the new rights value. |
︙ | ︙ |
Changes to docs/manual/00001018000000.zettel.
1 2 3 4 5 6 | id: 00001018000000 title: Troubleshooting role: manual tags: #manual #zettelstore syntax: zmk created: 20211027105921 | | | | 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: 00001018000000 title: Troubleshooting role: manual tags: #manual #zettelstore syntax: zmk created: 20211027105921 modified: 20241212153148 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 allow 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. |
︙ | ︙ | |||
36 37 38 39 40 41 42 | ** **Solution 3:** If you have an enabled [[Administrator Console|00001004100000]] you can use the command [[''refresh''|00001004101000#refresh]] to make your changes visible. ** **Solution 4:** You configure the zettel box as [[""simple""|00001004011400]]. === HTML content is not shown * **Problem:** You have entered some HTML code as content for your Zettelstore, but this content is not shown on the Web User Interface. You may have entered a Zettel with syntax [[""html""|00001008000000#html]], or you have used an [[inline-zettel block|00001007031200]] with syntax ""html"", or you entered a Zettel with syntax [[""markdown""|00001008000000#markdown]] (or ""md"") and used some HTML code fragments. | | | > > > > > > | 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 | ** **Solution 3:** If you have an enabled [[Administrator Console|00001004100000]] you can use the command [[''refresh''|00001004101000#refresh]] to make your changes visible. ** **Solution 4:** You configure the zettel box as [[""simple""|00001004011400]]. === HTML content is not shown * **Problem:** You have entered some HTML code as content for your Zettelstore, but this content is not shown on the Web User Interface. You may have entered a Zettel with syntax [[""html""|00001008000000#html]], or you have used an [[inline-zettel block|00001007031200]] with syntax ""html"", or you entered a Zettel with syntax [[""markdown""|00001008000000#markdown]] (or ""md"") and used some HTML code fragments. ** **Explanation:** Working with HTML code from unknown sources may lead to severe security problems. The HTML code may force web browsers to load more content from external server, it may contain malicious JavaScript code, it may reference to CSS artifacts that itself load from external servers and may contain malicious software. Zettelstore tries to do its best to ignore problematic HTML code, but it may fail. Either because of unknown bugs or because of yet unknown changes in the future. Zettelstore sets a restrictive [[Content Security Policy|https://www.w3.org/TR/CSP/]], but this depends on web browsers to implement them correctly and on users to not disable it. Zettelstore will not display any HTML code, which contains a ``<script>>`` or an ``<iframe>`` tag. But attackers may find other ways to deploy their malicious code. Therefore, Zettelstore disallows any HTML content as a default. If you know what you are doing, e.g. because you will never copy HTML code you do not understand, you can relax this default. ** **Solution 1:** If you want zettel with syntax ""html"" not to be ignored, you set the startup configuration key [[''insecure-html''|00001004010000#insecure-html]] to the value ""html"". ** **Solution 2:** If you want zettel with syntax ""html"" not to be ignored, **and** want to allow HTML in Markdown, you set the startup configuration key [[''insecure-html''|00001004010000#insecure-html]] to the value ""markdown"". ** **Solution 3:** If you want zettel with syntax ""html"" not to be ignored, **and** want to allow HTML in Markdown, **and** want to use HTML code within Zettelmarkup, you set the startup configuration key [[''insecure-html''|00001004010000#insecure-html]] to the value ""zettelmarkup"". === Search for specific content * **Problem:** If you are searching for zettel with zettel content ""EUPL"", the zettel with Zettelstore's [[License|00000000000004]] is not shown, but it does contain the character sequence ""EUPL"". ** **Solution:** The content of zettel with a zettel identifier less or equal ''00009999999999'' is not searched. These zettel are predefined zettel, sometimes computed zettel, with some content not related to your research. For these zettel, only the metadata can be searched. |
Changes to encoder/encoder_block_test.go.
︙ | ︙ | |||
183 184 185 186 187 188 189 | }, }, { descr: "Quote Block with multiple paragraphs", zmk: "<<<\nToBeOr\n\nNotToBe\n<<< Romeo", expect: expectMap{ encoderHTML: "<blockquote><p>ToBeOr</p><p>NotToBe</p><cite>Romeo</cite></blockquote>", | | | 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | }, }, { descr: "Quote Block with multiple paragraphs", zmk: "<<<\nToBeOr\n\nNotToBe\n<<< Romeo", expect: expectMap{ encoderHTML: "<blockquote><p>ToBeOr</p><p>NotToBe</p><cite>Romeo</cite></blockquote>", encoderMD: "> ToBeOr\n>\n> NotToBe", encoderSz: `(BLOCK (REGION-QUOTE () ((PARA (TEXT "ToBeOr")) (PARA (TEXT "NotToBe"))) (TEXT "Romeo")))`, encoderSHTML: `((blockquote (p "ToBeOr") (p "NotToBe") (cite "Romeo")))`, encoderText: "ToBeOr\nNotToBe\nRomeo", encoderZmk: useZmk, }, }, { |
︙ | ︙ | |||
329 330 331 332 333 334 335 | { descr: "Table with alignment and comment", zmk: `|h1>|=h2|h3:| |%--+---+---+ |<c1|c2|:c3| |f1|f2|=f3`, expect: expectMap{ | | | | 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 | { descr: "Table with alignment and comment", zmk: `|h1>|=h2|h3:| |%--+---+---+ |<c1|c2|:c3| |f1|f2|=f3`, expect: expectMap{ encoderHTML: `<table><thead><tr><th class="right">h1</th><th>h2</th><th class="center">h3</th></tr></thead><tbody><tr><td class="left">c1</td><td>c2</td><td class="center">c3</td></tr><tr><td class="right">f1</td><td>f2</td><td class="center">=f3</td></tr></tbody></table>`, encoderMD: "", encoderSz: `(BLOCK (TABLE ((CELL-RIGHT (TEXT "h1")) (CELL (TEXT "h2")) (CELL-CENTER (TEXT "h3"))) ((CELL-LEFT (TEXT "c1")) (CELL (TEXT "c2")) (CELL-CENTER (TEXT "c3"))) ((CELL-RIGHT (TEXT "f1")) (CELL (TEXT "f2")) (CELL-CENTER (TEXT "=f3")))))`, encoderSHTML: `((table (thead (tr (th (@ (class . "right")) "h1") (th "h2") (th (@ (class . "center")) "h3"))) (tbody (tr (td (@ (class . "left")) "c1") (td "c2") (td (@ (class . "center")) "c3")) (tr (td (@ (class . "right")) "f1") (td "f2") (td (@ (class . "center")) "=f3")))))`, encoderText: "h1 h2 h3\nc1 c2 c3\nf1 f2 =f3", encoderZmk: `|=h1>|=h2|=h3: |<c1|c2|c3 |f1|f2|=f3`, }, }, { |
︙ | ︙ |
Changes to encoder/encoder_inline_test.go.
︙ | ︙ | |||
147 148 149 150 151 152 153 | }, }, { descr: "Quotes formatting", zmk: `""quotes""`, expect: expectMap{ encoderHTML: "“quotes”", | | | | | | | | 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 | }, }, { descr: "Quotes formatting", zmk: `""quotes""`, expect: expectMap{ encoderHTML: "“quotes”", encoderMD: "“quotes”", encoderSz: `(INLINE (FORMAT-QUOTE () (TEXT "quotes")))`, encoderSHTML: `((@L (@H "“") "quotes" (@H "”")))`, encoderText: `quotes`, encoderZmk: useZmk, }, }, { descr: "Quotes formatting (german)", zmk: `""quotes""{lang=de}`, expect: expectMap{ encoderHTML: `<span lang="de">„quotes“</span>`, encoderMD: "„quotes“", encoderSz: `(INLINE (FORMAT-QUOTE (("lang" . "de")) (TEXT "quotes")))`, encoderSHTML: `((span (@ (lang . "de")) (@H "„") "quotes" (@H "“")))`, encoderText: `quotes`, encoderZmk: `""quotes""{lang="de"}`, }, }, { descr: "Empty quotes (default)", zmk: `""""`, expect: expectMap{ encoderHTML: `“”`, encoderMD: "“”", encoderSz: `(INLINE (FORMAT-QUOTE ()))`, encoderSHTML: `((@L (@H "“" "”")))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Empty quotes (unknown)", zmk: `""""{lang=unknown}`, expect: expectMap{ encoderHTML: `<span lang="unknown">""</span>`, encoderMD: """", encoderSz: `(INLINE (FORMAT-QUOTE (("lang" . "unknown"))))`, encoderSHTML: `((span (@ (lang . "unknown")) (@H """ """)))`, encoderText: ``, encoderZmk: `""""{lang="unknown"}`, }, }, { descr: "Nested quotes (default)", zmk: `""say: ::""yes, ::""or?""::""::""`, expect: expectMap{ encoderHTML: `“say: <span>‘yes, <span>“or?”</span>’</span>”`, encoderMD: `“say: ‘yes, “or?”’”`, encoderSz: `(INLINE (FORMAT-QUOTE () (TEXT "say: ") (FORMAT-SPAN () (FORMAT-QUOTE () (TEXT "yes, ") (FORMAT-SPAN () (FORMAT-QUOTE () (TEXT "or?")))))))`, encoderSHTML: `((@L (@H "“") "say: " (span (@L (@H "‘") "yes, " (span (@L (@H "“") "or?" (@H "”"))) (@H "’"))) (@H "”")))`, encoderText: `say: yes, or?`, encoderZmk: useZmk, }, }, { descr: "Two quotes", zmk: `""yes"" or ""no""`, expect: expectMap{ encoderHTML: `“yes” or “no”`, encoderMD: `“yes” or “no”`, encoderSz: `(INLINE (FORMAT-QUOTE () (TEXT "yes")) (TEXT " or ") (FORMAT-QUOTE () (TEXT "no")))`, encoderSHTML: `((@L (@H "“") "yes" (@H "”")) " or " (@L (@H "“") "no" (@H "”")))`, encoderText: `yes or no`, encoderZmk: useZmk, }, }, { |
︙ | ︙ | |||
279 280 281 282 283 284 285 | }, }, { descr: "Input formatting", zmk: `''input''`, expect: expectMap{ encoderHTML: `<kbd>input</kbd>`, | | | | 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 | }, }, { descr: "Input formatting", zmk: `''input''`, expect: expectMap{ encoderHTML: `<kbd>input</kbd>`, encoderMD: "`input`", encoderSz: `(INLINE (LITERAL-INPUT () "input"))`, encoderSHTML: `((kbd "input"))`, encoderText: `input`, encoderZmk: useZmk, }, }, { descr: "Output formatting", zmk: `==output==`, expect: expectMap{ encoderHTML: `<samp>output</samp>`, encoderMD: "`output`", encoderSz: `(INLINE (LITERAL-OUTPUT () "output"))`, encoderSHTML: `((samp "output"))`, encoderText: `output`, encoderZmk: useZmk, }, }, { |
︙ | ︙ | |||
315 316 317 318 319 320 321 | }, }, { descr: "Nested Span Quote formatting", zmk: `::""abc""::{lang=fr}`, expect: expectMap{ encoderHTML: `<span lang="fr">« abc »</span>`, | | | 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 | }, }, { descr: "Nested Span Quote formatting", zmk: `::""abc""::{lang=fr}`, expect: expectMap{ encoderHTML: `<span lang="fr">« abc »</span>`, encoderMD: "« abc »", encoderSz: `(INLINE (FORMAT-SPAN (("lang" . "fr")) (FORMAT-QUOTE () (TEXT "abc"))))`, encoderSHTML: `((span (@ (lang . "fr")) (@L (@H "«" " ") "abc" (@H " " "»"))))`, encoderText: `abc`, encoderZmk: `::""abc""::{lang="fr"}`, }, }, { |
︙ | ︙ | |||
469 470 471 472 473 474 475 | encoderZmk: useZmk, }, }, { descr: "Dummy Link", zmk: `[[abc]]`, expect: expectMap{ | | | | | | | | 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 | encoderZmk: useZmk, }, }, { descr: "Dummy Link", zmk: `[[abc]]`, expect: expectMap{ encoderHTML: `<a href="abc" rel="external">abc</a>`, encoderMD: "[abc](abc)", encoderSz: `(INLINE (LINK-EXTERNAL () "abc"))`, encoderSHTML: `((a (@ (href . "abc") (rel . "external")) "abc"))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Simple URL", zmk: `[[https://zettelstore.de]]`, expect: expectMap{ encoderHTML: `<a href="https://zettelstore.de" rel="external">https://zettelstore.de</a>`, encoderMD: "<https://zettelstore.de>", encoderSz: `(INLINE (LINK-EXTERNAL () "https://zettelstore.de"))`, encoderSHTML: `((a (@ (href . "https://zettelstore.de") (rel . "external")) "https://zettelstore.de"))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "URL with Text", zmk: `[[Home|https://zettelstore.de]]`, expect: expectMap{ encoderHTML: `<a href="https://zettelstore.de" rel="external">Home</a>`, encoderMD: "[Home](https://zettelstore.de)", encoderSz: `(INLINE (LINK-EXTERNAL () "https://zettelstore.de" (TEXT "Home")))`, encoderSHTML: `((a (@ (href . "https://zettelstore.de") (rel . "external")) "Home"))`, encoderText: `Home`, encoderZmk: useZmk, }, }, { descr: "Simple Zettel ID", zmk: `[[00000000000100]]`, |
︙ | ︙ |
Changes to encoder/htmlenc/htmlenc.go.
︙ | ︙ | |||
27 28 29 30 31 32 33 | "zettelstore.de/z/encoder/szenc" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) func init() { | > > | > > | 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/encoder/szenc" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) func init() { encoder.Register( api.EncoderHTML, func(params *encoder.CreateParameter) encoder.Encoder { return Create(params) }, ) } // Create an encoder. func Create(params *encoder.CreateParameter) *Encoder { // We need a new transformer every time, because tx.inVerse must be unique. // If we can refactor it out, the transformer can be created only once. return &Encoder{ tx: szenc.NewTransformer(), th: shtml.NewEvaluator(1), lang: params.Lang, textEnc: textenc.Create(), } } // Encoder contains all data needed for encoding. type Encoder struct { tx *szenc.Transformer th *shtml.Evaluator lang string textEnc *textenc.Encoder } |
︙ | ︙ | |||
74 75 76 77 78 79 80 | } xast := he.tx.GetSz(&zn.Ast) hast, err := he.th.Evaluate(xast, &env) if err != nil { return 0, err } | | | 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | } xast := he.tx.GetSz(&zn.Ast) hast, err := he.th.Evaluate(xast, &env) if err != nil { return 0, err } hen := shtml.Endnotes(&env) var head sx.ListBuilder head.Add(shtml.SymHead) head.Add(sx.Nil().Cons(sx.Nil().Cons(sx.Cons(sx.MakeSymbol("charset"), sx.MakeString("utf-8"))).Cons(sxhtml.SymAttr)).Cons(shtml.SymMeta)) head.ExtendBang(hm) var sb strings.Builder if hasTitle { |
︙ | ︙ | |||
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | if err != nil { return 0, err } gen := sxhtml.NewGenerator().SetNewline() return gen.WriteListHTML(w, hm) } func (he *Encoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return he.WriteBlocks(w, &zn.Ast) } // WriteBlocks encodes a block slice. func (he *Encoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { env := shtml.MakeEnvironment(he.lang) hobj, err := he.th.Evaluate(he.tx.GetSz(bs), &env) if err == nil { gen := sxhtml.NewGenerator() length, err2 := gen.WriteListHTML(w, hobj) if err2 != nil { return length, err2 } | > | | 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 | if err != nil { return 0, err } gen := sxhtml.NewGenerator().SetNewline() return gen.WriteListHTML(w, hm) } // WriteContent encodes the zettel content. func (he *Encoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return he.WriteBlocks(w, &zn.Ast) } // WriteBlocks encodes a block slice. func (he *Encoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { env := shtml.MakeEnvironment(he.lang) hobj, err := he.th.Evaluate(he.tx.GetSz(bs), &env) if err == nil { gen := sxhtml.NewGenerator() length, err2 := gen.WriteListHTML(w, hobj) if err2 != nil { return length, err2 } l, err2 := gen.WriteHTML(w, shtml.Endnotes(&env)) length += l return length, err2 } return 0, err } // WriteInlines writes an inline slice to the writer |
︙ | ︙ |
Changes to encoder/mdenc/mdenc.go.
︙ | ︙ | |||
14 15 16 17 18 19 20 21 22 23 24 25 26 | // Package mdenc encodes the abstract syntax tree back into Markdown. package mdenc import ( "io" "t73f.de/r/zsc/api" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/zettel/meta" ) func init() { | > > > > | > | > | > > | > | < | | | | > | | | | | | | | | > > | | > > > > > > > > > > > > > > > > > > > > > | 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 | // Package mdenc encodes the abstract syntax tree back into Markdown. package mdenc import ( "io" "t73f.de/r/zsc/api" "t73f.de/r/zsc/attrs" "t73f.de/r/zsc/shtml" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/zettel/meta" ) func init() { encoder.Register( api.EncoderMD, func(params *encoder.CreateParameter) encoder.Encoder { return Create(params) }, ) } // Create an encoder. func Create(params *encoder.CreateParameter) *Encoder { return &Encoder{lang: params.Lang} } // Encoder contains all data needed for encoding. type Encoder struct { lang string } // WriteZettel writes the encoded zettel to the writer. func (me *Encoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) { v := newVisitor(w, me.lang) v.acceptMeta(zn.InhMeta, evalMeta) if zn.InhMeta.YamlSep { v.b.WriteString("---\n") } else { v.b.WriteByte('\n') } ast.Walk(v, &zn.Ast) length, err := v.b.Flush() return length, err } // WriteMeta encodes meta data as markdown. func (me *Encoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) { v := newVisitor(w, me.lang) v.acceptMeta(m, evalMeta) length, err := v.b.Flush() return length, err } func (v *visitor) acceptMeta(m *meta.Meta, evalMeta encoder.EvalMetaFunc) { for _, p := range m.ComputedPairs() { key := p.Key v.b.WriteStrings(key, ": ") if meta.Type(key) == meta.TypeZettelmarkup { is := evalMeta(p.Value) ast.Walk(v, &is) } else { v.b.WriteString(p.Value) } v.b.WriteByte('\n') } } // WriteContent encodes the zettel content. func (me *Encoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return me.WriteBlocks(w, &zn.Ast) } // WriteBlocks writes the content of a block slice to the writer. func (me *Encoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { v := newVisitor(w, me.lang) ast.Walk(v, bs) length, err := v.b.Flush() return length, err } // WriteInlines writes an inline slice to the writer func (me *Encoder) WriteInlines(w io.Writer, is *ast.InlineSlice) (int, error) { v := newVisitor(w, me.lang) ast.Walk(v, is) length, err := v.b.Flush() return length, err } // visitor writes the abstract syntax tree to an EncWriter. type visitor struct { b encoder.EncWriter listInfo []int listPrefix string langStack shtml.LangStack quoteNesting uint } func newVisitor(w io.Writer, lang string) *visitor { return &visitor{b: encoder.NewEncWriter(w), langStack: shtml.NewLangStack(lang)} } // pushAttribute adds the current attributes to the visitor. func (v *visitor) pushAttributes(a attrs.Attributes) { if value, ok := a.Get("lang"); ok { v.langStack.Push(value) } else { v.langStack.Dup() } } // popAttributes removes the current attributes from the visitor. func (v *visitor) popAttributes() { v.langStack.Pop() } // getLanguage returns the current language, func (v *visitor) getLanguage() string { return v.langStack.Top() } func (v *visitor) getQuotes() (string, string, bool) { qi := shtml.GetQuoteInfo(v.getLanguage()) leftQ, rightQ := qi.GetQuotes(v.quoteNesting) return leftQ, rightQ, qi.GetNBSp() } func (v *visitor) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.BlockSlice: v.visitBlockSlice(n) case *ast.VerbatimNode: |
︙ | ︙ | |||
177 178 179 180 181 182 183 184 185 186 187 188 189 190 | } } func (v *visitor) visitRegion(rn *ast.RegionNode) { if rn.Kind != ast.RegionQuote { return } first := true for _, bn := range rn.Blocks { pn, ok := bn.(*ast.ParaNode) if !ok { continue } if !first { | > > > | > > > | 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 | } } func (v *visitor) visitRegion(rn *ast.RegionNode) { if rn.Kind != ast.RegionQuote { return } v.pushAttributes(rn.Attrs) defer v.popAttributes() first := true for _, bn := range rn.Blocks { pn, ok := bn.(*ast.ParaNode) if !ok { continue } if !first { v.b.WriteString("\n>\n") } first = false v.b.WriteString("> ") ast.Walk(v, &pn.Inlines) } } func (v *visitor) visitHeading(hn *ast.HeadingNode) { v.pushAttributes(hn.Attrs) defer v.popAttributes() const headingSigns = "###### " v.b.WriteString(headingSigns[len(headingSigns)-hn.Level-1:]) ast.Walk(v, &hn.Inlines) } func (v *visitor) visitNestedList(ln *ast.NestedListNode) { switch ln.Kind { |
︙ | ︙ | |||
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 | v.writeSpaces(4*l - 4) v.b.WriteString(v.listPrefix) } } } func (v *visitor) visitLink(ln *ast.LinkNode) { v.writeReference(ln.Ref, ln.Inlines) } func (v *visitor) visitEmbedRef(en *ast.EmbedRefNode) { v.b.WriteByte('!') v.writeReference(en.Ref, en.Inlines) } func (v *visitor) writeReference(ref *ast.Reference, is ast.InlineSlice) { if ref.State == ast.RefStateQuery { ast.Walk(v, &is) | > > > > > > | 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 | v.writeSpaces(4*l - 4) v.b.WriteString(v.listPrefix) } } } func (v *visitor) visitLink(ln *ast.LinkNode) { v.pushAttributes(ln.Attrs) defer v.popAttributes() v.writeReference(ln.Ref, ln.Inlines) } func (v *visitor) visitEmbedRef(en *ast.EmbedRefNode) { v.pushAttributes(en.Attrs) defer v.popAttributes() v.b.WriteByte('!') v.writeReference(en.Ref, en.Inlines) } func (v *visitor) writeReference(ref *ast.Reference, is ast.InlineSlice) { if ref.State == ast.RefStateQuery { ast.Walk(v, &is) |
︙ | ︙ | |||
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 | if ref.State != ast.RefStateExternal || ref.URL == nil { return false } return ref.URL.Scheme != "" } func (v *visitor) visitFormat(fn *ast.FormatNode) { switch fn.Kind { case ast.FormatEmph: v.b.WriteByte('*') ast.Walk(v, &fn.Inlines) v.b.WriteByte('*') case ast.FormatStrong: v.b.WriteString("__") ast.Walk(v, &fn.Inlines) v.b.WriteString("__") case ast.FormatQuote: | > > > | < < > > > > > > > > > > > > > > > | | 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 | if ref.State != ast.RefStateExternal || ref.URL == nil { return false } return ref.URL.Scheme != "" } func (v *visitor) visitFormat(fn *ast.FormatNode) { v.pushAttributes(fn.Attrs) defer v.popAttributes() switch fn.Kind { case ast.FormatEmph: v.b.WriteByte('*') ast.Walk(v, &fn.Inlines) v.b.WriteByte('*') case ast.FormatStrong: v.b.WriteString("__") ast.Walk(v, &fn.Inlines) v.b.WriteString("__") case ast.FormatQuote: v.writeQuote(fn) case ast.FormatMark: v.b.WriteString("<mark>") ast.Walk(v, &fn.Inlines) v.b.WriteString("</mark>") default: ast.Walk(v, &fn.Inlines) } } func (v *visitor) writeQuote(fn *ast.FormatNode) { leftQ, rightQ, withNbsp := v.getQuotes() v.b.WriteString(leftQ) if withNbsp { v.b.WriteString(" ") } v.quoteNesting++ ast.Walk(v, &fn.Inlines) v.quoteNesting-- if withNbsp { v.b.WriteString(" ") } v.b.WriteString(rightQ) } func (v *visitor) visitLiteral(ln *ast.LiteralNode) { switch ln.Kind { case ast.LiteralProg, ast.LiteralInput, ast.LiteralOutput: v.b.WriteByte('`') v.b.Write(ln.Content) v.b.WriteByte('`') case ast.LiteralComment, ast.LiteralHTML: // ignore everything default: v.b.Write(ln.Content) } |
︙ | ︙ |
Changes to encoder/shtmlenc/shtmlenc.go.
︙ | ︙ | |||
37 38 39 40 41 42 43 44 45 46 47 48 49 50 | return &Encoder{ tx: szenc.NewTransformer(), th: shtml.NewEvaluator(1), lang: params.Lang, } } type Encoder struct { tx *szenc.Transformer th *shtml.Evaluator lang string } // WriteZettel writes the encoded zettel to the writer. | > | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | return &Encoder{ tx: szenc.NewTransformer(), th: shtml.NewEvaluator(1), lang: params.Lang, } } // Encoder contains all data needed for encoding. type Encoder struct { tx *szenc.Transformer th *shtml.Evaluator lang string } // WriteZettel writes the encoded zettel to the writer. |
︙ | ︙ | |||
68 69 70 71 72 73 74 75 76 77 78 79 80 81 | metaSHTML, err := enc.th.Evaluate(enc.tx.GetMeta(m, evalMeta), &env) if err != nil { return 0, err } return sx.Print(w, metaSHTML) } func (enc *Encoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return enc.WriteBlocks(w, &zn.Ast) } // WriteBlocks writes a block slice to the writer func (enc *Encoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { env := shtml.MakeEnvironment(enc.lang) | > | 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | metaSHTML, err := enc.th.Evaluate(enc.tx.GetMeta(m, evalMeta), &env) if err != nil { return 0, err } return sx.Print(w, metaSHTML) } // WriteContent encodes the zettel content. func (enc *Encoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return enc.WriteBlocks(w, &zn.Ast) } // WriteBlocks writes a block slice to the writer func (enc *Encoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { env := shtml.MakeEnvironment(enc.lang) |
︙ | ︙ |
Changes to encoder/szenc/szenc.go.
︙ | ︙ | |||
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 | // Create a S-expr encoder func Create() *Encoder { // We need a new transformer every time, because trans.inVerse must be unique. // If we can refactor it out, the transformer can be created only once. return &Encoder{trans: NewTransformer()} } type Encoder struct { trans *Transformer } // WriteZettel writes the encoded zettel to the writer. func (enc *Encoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) { content := enc.trans.GetSz(&zn.Ast) meta := enc.trans.GetMeta(zn.InhMeta, evalMeta) return sx.MakeList(meta, content).Print(w) } // WriteMeta encodes meta data as s-expression. func (enc *Encoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) { return enc.trans.GetMeta(m, evalMeta).Print(w) } func (enc *Encoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return enc.WriteBlocks(w, &zn.Ast) } // WriteBlocks writes a block slice to the writer func (enc *Encoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { return enc.trans.GetSz(bs).Print(w) } // WriteInlines writes an inline slice to the writer func (enc *Encoder) WriteInlines(w io.Writer, is *ast.InlineSlice) (int, error) { return enc.trans.GetSz(is).Print(w) } | > > | 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 | // Create a S-expr encoder func Create() *Encoder { // We need a new transformer every time, because trans.inVerse must be unique. // If we can refactor it out, the transformer can be created only once. return &Encoder{trans: NewTransformer()} } // Encoder contains all data needed for encoding. type Encoder struct { trans *Transformer } // WriteZettel writes the encoded zettel to the writer. func (enc *Encoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) { content := enc.trans.GetSz(&zn.Ast) meta := enc.trans.GetMeta(zn.InhMeta, evalMeta) return sx.MakeList(meta, content).Print(w) } // WriteMeta encodes meta data as s-expression. func (enc *Encoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) { return enc.trans.GetMeta(m, evalMeta).Print(w) } // WriteContent encodes the zettel content. func (enc *Encoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return enc.WriteBlocks(w, &zn.Ast) } // WriteBlocks writes a block slice to the writer func (enc *Encoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { return enc.trans.GetSz(bs).Print(w) } // WriteInlines writes an inline slice to the writer func (enc *Encoder) WriteInlines(w io.Writer, is *ast.InlineSlice) (int, error) { return enc.trans.GetSz(is).Print(w) } |
Changes to encoder/szenc/transform.go.
︙ | ︙ | |||
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | // NewTransformer returns a new transformer to create s-expressions from AST nodes. func NewTransformer() *Transformer { t := Transformer{} return &t } type Transformer struct { inVerse bool } func (t *Transformer) GetSz(node ast.Node) *sx.Pair { switch n := node.(type) { case *ast.BlockSlice: return t.getBlockList(n).Cons(sz.SymBlock) case *ast.InlineSlice: return t.getInlineList(*n).Cons(sz.SymInline) case *ast.ParaNode: | > > | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | // NewTransformer returns a new transformer to create s-expressions from AST nodes. func NewTransformer() *Transformer { t := Transformer{} return &t } // Transformer contains all data needed to transform into a s-expression. type Transformer struct { inVerse bool } // GetSz transforms the given node into a sx list. func (t *Transformer) GetSz(node ast.Node) *sx.Pair { switch n := node.(type) { case *ast.BlockSlice: return t.getBlockList(n).Cons(sz.SymBlock) case *ast.InlineSlice: return t.getInlineList(*n).Cons(sz.SymInline) case *ast.ParaNode: |
︙ | ︙ | |||
357 358 359 360 361 362 363 364 365 366 367 368 369 370 | meta.TypeTagSet: sz.SymTypeTagSet, meta.TypeTimestamp: sz.SymTypeTimestamp, meta.TypeURL: sz.SymTypeURL, meta.TypeWord: sz.SymTypeWord, meta.TypeZettelmarkup: sz.SymTypeZettelmarkup, } func (t *Transformer) GetMeta(m *meta.Meta, evalMeta encoder.EvalMetaFunc) *sx.Pair { pairs := m.ComputedPairs() objs := make(sx.Vector, 0, len(pairs)) for _, p := range pairs { key := p.Key ty := m.Type(key) symType := mapGetS(mapMetaTypeS, ty) | > | 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 | meta.TypeTagSet: sz.SymTypeTagSet, meta.TypeTimestamp: sz.SymTypeTimestamp, meta.TypeURL: sz.SymTypeURL, meta.TypeWord: sz.SymTypeWord, meta.TypeZettelmarkup: sz.SymTypeZettelmarkup, } // GetMeta transforms the given metadata into a sx list. func (t *Transformer) GetMeta(m *meta.Meta, evalMeta encoder.EvalMetaFunc) *sx.Pair { pairs := m.ComputedPairs() objs := make(sx.Vector, 0, len(pairs)) for _, p := range pairs { key := p.Key ty := m.Type(key) symType := mapGetS(mapMetaTypeS, ty) |
︙ | ︙ |
Changes to encoder/textenc/textenc.go.
︙ | ︙ | |||
27 28 29 30 31 32 33 34 35 36 37 38 39 40 | func init() { encoder.Register(api.EncoderText, func(*encoder.CreateParameter) encoder.Encoder { return Create() }) } // Create an encoder. func Create() *Encoder { return &myTE } type Encoder struct{} var myTE Encoder // Only a singleton is required. // WriteZettel writes metadata and content. func (te *Encoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) { v := newVisitor(w) | > | 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | func init() { encoder.Register(api.EncoderText, func(*encoder.CreateParameter) encoder.Encoder { return Create() }) } // Create an encoder. func Create() *Encoder { return &myTE } // Encoder contains all data needed for encoding. type Encoder struct{} var myTE Encoder // Only a singleton is required. // WriteZettel writes metadata and content. func (te *Encoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) { v := newVisitor(w) |
︙ | ︙ | |||
69 70 71 72 73 74 75 76 77 78 79 80 81 82 | buf.WriteByte(' ') } buf.WriteString(meta.CleanTag(tag)) } } func (te *Encoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return te.WriteBlocks(w, &zn.Ast) } // WriteBlocks writes the content of a block slice to the writer. func (*Encoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { v := newVisitor(w) | > | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | buf.WriteByte(' ') } buf.WriteString(meta.CleanTag(tag)) } } // WriteContent encodes the zettel content. func (te *Encoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return te.WriteBlocks(w, &zn.Ast) } // WriteBlocks writes the content of a block slice to the writer. func (*Encoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { v := newVisitor(w) |
︙ | ︙ |
Changes to encoder/zmkenc/zmkenc.go.
︙ | ︙ | |||
31 32 33 34 35 36 37 38 39 40 41 42 43 44 | func init() { encoder.Register(api.EncoderZmk, func(*encoder.CreateParameter) encoder.Encoder { return Create() }) } // Create an encoder. func Create() *Encoder { return &myZE } type Encoder struct{} var myZE Encoder // WriteZettel writes the encoded zettel to the writer. func (*Encoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) { v := newVisitor(w) | > | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | func init() { encoder.Register(api.EncoderZmk, func(*encoder.CreateParameter) encoder.Encoder { return Create() }) } // Create an encoder. func Create() *Encoder { return &myZE } // Encoder contains all data needed for encoding. type Encoder struct{} var myZE Encoder // WriteZettel writes the encoded zettel to the writer. func (*Encoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) { v := newVisitor(w) |
︙ | ︙ | |||
71 72 73 74 75 76 77 78 79 80 81 82 83 84 | } else { v.b.WriteString(p.Value) } v.b.WriteByte('\n') } } func (ze *Encoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return ze.WriteBlocks(w, &zn.Ast) } // WriteBlocks writes the content of a block slice to the writer. func (*Encoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { v := newVisitor(w) | > | 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | } else { v.b.WriteString(p.Value) } v.b.WriteByte('\n') } } // WriteContent encodes the zettel content. func (ze *Encoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return ze.WriteBlocks(w, &zn.Ast) } // WriteBlocks writes the content of a block slice to the writer. func (*Encoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { v := newVisitor(w) |
︙ | ︙ |
Changes to encoding/atom/atom.go.
︙ | ︙ | |||
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 | "zettelstore.de/z/kernel" "zettelstore.de/z/query" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) const ContentType = "application/atom+xml" type Configuration struct { Title string Generator string NewURLBuilderAbs func() *api.URLBuilder } func (c *Configuration) Setup(cfg config.Config) { baseURL := kernel.Main.GetConfig(kernel.WebService, kernel.WebBaseURL).(string) c.Title = cfg.GetSiteName() c.Generator = (kernel.Main.GetConfig(kernel.CoreService, kernel.CoreProgname).(string) + " " + kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) c.NewURLBuilderAbs = func() *api.URLBuilder { return api.NewURLBuilder(baseURL, 'h') } } func (c *Configuration) Marshal(q *query.Query, ml []*meta.Meta) []byte { atomUpdated := encoding.LastUpdated(ml, time.RFC3339) feedLink := c.NewURLBuilderAbs().String() var buf bytes.Buffer buf.WriteString(`<feed xmlns="http://www.w3.org/2005/Atom">` + "\n") xml.WriteTag(&buf, " ", "title", c.Title) | > > > > | 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 | "zettelstore.de/z/kernel" "zettelstore.de/z/query" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // ContentType specifies the HTTP content type for Atom. const ContentType = "application/atom+xml" // Configuration contains data to configure the Atom encoding. type Configuration struct { Title string Generator string NewURLBuilderAbs func() *api.URLBuilder } // Setup initializes the Configuration. func (c *Configuration) Setup(cfg config.Config) { baseURL := kernel.Main.GetConfig(kernel.WebService, kernel.WebBaseURL).(string) c.Title = cfg.GetSiteName() c.Generator = (kernel.Main.GetConfig(kernel.CoreService, kernel.CoreProgname).(string) + " " + kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) c.NewURLBuilderAbs = func() *api.URLBuilder { return api.NewURLBuilder(baseURL, 'h') } } // Marshal encodes the result of a query as Atom. func (c *Configuration) Marshal(q *query.Query, ml []*meta.Meta) []byte { atomUpdated := encoding.LastUpdated(ml, time.RFC3339) feedLink := c.NewURLBuilderAbs().String() var buf bytes.Buffer buf.WriteString(`<feed xmlns="http://www.w3.org/2005/Atom">` + "\n") xml.WriteTag(&buf, " ", "title", c.Title) |
︙ | ︙ |
Changes to encoding/rss/rss.go.
︙ | ︙ | |||
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 | "zettelstore.de/z/kernel" "zettelstore.de/z/query" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) const ContentType = "application/rss+xml" type Configuration struct { Title string Language string Copyright string Generator string NewURLBuilderAbs func() *api.URLBuilder } func (c *Configuration) Setup(ctx context.Context, cfg config.Config) { baseURL := kernel.Main.GetConfig(kernel.WebService, kernel.WebBaseURL).(string) defVals := cfg.AddDefaultValues(ctx, &meta.Meta{}) c.Title = cfg.GetSiteName() c.Language = defVals.GetDefault(api.KeyLang, "") c.Copyright = defVals.GetDefault(api.KeyCopyright, "") c.Generator = (kernel.Main.GetConfig(kernel.CoreService, kernel.CoreProgname).(string) + " " + kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) c.NewURLBuilderAbs = func() *api.URLBuilder { return api.NewURLBuilder(baseURL, 'h') } } func (c *Configuration) Marshal(q *query.Query, ml []*meta.Meta) []byte { rssPublished := encoding.LastUpdated(ml, time.RFC1123Z) atomLink := "" if s := q.String(); s != "" { atomLink = c.NewURLBuilderAbs().AppendQuery(s).String() } | > > > > | 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 | "zettelstore.de/z/kernel" "zettelstore.de/z/query" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // ContentType specifies the HTTP content type for RSS. const ContentType = "application/rss+xml" // Configuration contains data to configure the RSS encoding. type Configuration struct { Title string Language string Copyright string Generator string NewURLBuilderAbs func() *api.URLBuilder } // Setup initializes the Configuration. func (c *Configuration) Setup(ctx context.Context, cfg config.Config) { baseURL := kernel.Main.GetConfig(kernel.WebService, kernel.WebBaseURL).(string) defVals := cfg.AddDefaultValues(ctx, &meta.Meta{}) c.Title = cfg.GetSiteName() c.Language = defVals.GetDefault(api.KeyLang, "") c.Copyright = defVals.GetDefault(api.KeyCopyright, "") c.Generator = (kernel.Main.GetConfig(kernel.CoreService, kernel.CoreProgname).(string) + " " + kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) c.NewURLBuilderAbs = func() *api.URLBuilder { return api.NewURLBuilder(baseURL, 'h') } } // Marshal encodes the result of a query as Atom. func (c *Configuration) Marshal(q *query.Query, ml []*meta.Meta) []byte { rssPublished := encoding.LastUpdated(ml, time.RFC1123Z) atomLink := "" if s := q.String(); s != "" { atomLink = c.NewURLBuilderAbs().AppendQuery(s).String() } |
︙ | ︙ |
Changes to evaluator/list.go.
︙ | ︙ | |||
31 32 33 34 35 36 37 | "zettelstore.de/z/query" "zettelstore.de/z/zettel/meta" ) // QueryAction transforms a list of metadata according to query actions into a AST nested list. func QueryAction(ctx context.Context, q *query.Query, ml []*meta.Meta, rtConfig config.Config) (ast.BlockNode, int) { ap := actionPara{ | | | | | | | | | | | 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 | "zettelstore.de/z/query" "zettelstore.de/z/zettel/meta" ) // QueryAction transforms a list of metadata according to query actions into a AST nested list. func QueryAction(ctx context.Context, q *query.Query, ml []*meta.Meta, rtConfig config.Config) (ast.BlockNode, int) { ap := actionPara{ ctx: ctx, q: q, ml: ml, kind: ast.NestedListUnordered, minVal: -1, maxVal: -1, title: rtConfig.GetSiteName(), } actions := q.Actions() if len(actions) == 0 { return ap.createBlockNodeMeta("") } acts := make([]string, 0, len(actions)) for i, act := range actions { if strings.HasPrefix(act, api.NumberedAction[0:1]) { ap.kind = ast.NestedListOrdered continue } if strings.HasPrefix(act, api.MinAction) { if num, err := strconv.Atoi(act[3:]); err == nil && num > 0 { ap.minVal = num continue } } if strings.HasPrefix(act, api.MaxAction) { if num, err := strconv.Atoi(act[3:]); err == nil && num > 0 { ap.maxVal = num continue } } if act == api.TitleAction && i+1 < len(actions) { ap.title = strings.Join(actions[i+1:], " ") break } |
︙ | ︙ | |||
100 101 102 103 104 105 106 | if bn != nil && numItems == 0 && firstUnknowAct == strings.ToUpper(firstUnknowAct) { bn, numItems = ap.createBlockNodeMeta("") } return bn, numItems } type actionPara struct { | | | | | | | | | 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | if bn != nil && numItems == 0 && firstUnknowAct == strings.ToUpper(firstUnknowAct) { bn, numItems = ap.createBlockNodeMeta("") } return bn, numItems } type actionPara struct { ctx context.Context q *query.Query ml []*meta.Meta kind ast.NestedListKind minVal int maxVal int title string } func (ap *actionPara) createBlockNodeWord(key string) (ast.BlockNode, int) { var buf bytes.Buffer ccs, bufLen := ap.prepareCatAction(key, &buf) if len(ccs) == 0 { return nil, 0 |
︙ | ︙ | |||
170 171 172 173 174 175 176 | ) buf.Truncate(bufLen) } return &ast.ParaNode{Inlines: para}, len(ccs) } func (ap *actionPara) limitTags(ccs meta.CountedCategories) meta.CountedCategories { | | | | | | | | | 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 | ) buf.Truncate(bufLen) } return &ast.ParaNode{Inlines: para}, len(ccs) } func (ap *actionPara) limitTags(ccs meta.CountedCategories) meta.CountedCategories { if minVal, maxVal := ap.minVal, ap.maxVal; minVal > 0 || maxVal > 0 { if minVal < 0 { minVal = ccs[len(ccs)-1].Count } if maxVal < 0 { maxVal = ccs[0].Count } if ccs[len(ccs)-1].Count < minVal || maxVal < ccs[0].Count { temp := make(meta.CountedCategories, 0, len(ccs)) for _, cat := range ccs { if minVal <= cat.Count && cat.Count <= maxVal { temp = append(temp, cat) } } return temp } } return ccs |
︙ | ︙ |
Changes to go.mod.
1 2 | module zettelstore.de/z | | | | | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | module zettelstore.de/z go 1.23 require ( github.com/fsnotify/fsnotify v1.8.0 github.com/yuin/goldmark v1.7.8 golang.org/x/crypto v0.31.0 golang.org/x/term v0.27.0 golang.org/x/text v0.21.0 t73f.de/r/sx v0.0.0-20240814083626-4df0ec6454b5 t73f.de/r/sxwebs v0.0.0-20241031144449-53c3b2ed1a6f t73f.de/r/zsc v0.0.0-20241213121052-fd15ae683e82 ) require ( golang.org/x/sys v0.28.0 // indirect t73f.de/r/webs v0.0.0-20241031141359-cd4f76a622cd // indirect ) |
Changes to go.sum.
|
| | | | | | | | | | | | | | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= t73f.de/r/sx v0.0.0-20240814083626-4df0ec6454b5 h1:ug4hohM6pK28M8Uo0o3+XvjBure2wfEtuCnHVIdqBZY= t73f.de/r/sx v0.0.0-20240814083626-4df0ec6454b5/go.mod h1:VRvsWoBErPKvMieDMMk1hsh1tb9sA4ijEQWGw/TbtQ0= t73f.de/r/sxwebs v0.0.0-20241031144449-53c3b2ed1a6f h1:VJ4S7YWy9tCJuFz5MckqUjjktPaf0kpnTkNBVRVXpo4= t73f.de/r/sxwebs v0.0.0-20241031144449-53c3b2ed1a6f/go.mod h1:IaM+U+LvYTYeuiIS5cwZW6kcEpdwoKBYVCU7LZr4Sgk= t73f.de/r/webs v0.0.0-20241031141359-cd4f76a622cd h1:+7cqJonXKDso+uPvsvOPl7BiLkhj8VQT/Has8qC5VIQ= t73f.de/r/webs v0.0.0-20241031141359-cd4f76a622cd/go.mod h1:NSoOON8be62MfQZzlCApK27Jt2zhIa6Vrmo9RJ4tOnQ= t73f.de/r/zsc v0.0.0-20241213121052-fd15ae683e82 h1:Bl3iCW9bHx9CeIyWUMLi9xqRV7mHh/1HS6rBnt5QQQU= t73f.de/r/zsc v0.0.0-20241213121052-fd15ae683e82/go.mod h1:sQWKzNp0I18aSFnKJjAyhL1zTWITIj1v2acWv4GvuIY= |
Changes to kernel/impl/cfg.go.
︙ | ︙ | |||
92 93 94 95 96 97 98 | keySiteName: {"Site name", parseString, true}, keyYAMLHeader: {"YAML header", parseBool, true}, keyZettelFileSyntax: { "Zettel file syntax", func(val string) (any, error) { return strings.Fields(val), nil }, true, }, | | | | | | | | | | | | | | | | | | | | | | | | 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 | keySiteName: {"Site name", parseString, true}, keyYAMLHeader: {"YAML header", parseBool, true}, keyZettelFileSyntax: { "Zettel file syntax", func(val string) (any, error) { return strings.Fields(val), nil }, true, }, kernel.ConfigSimpleMode: {"Simple mode", cs.noFrozen(parseBool), true}, config.KeyShowBackLinks: {"Show back links", parseString, true}, config.KeyShowFolgeLinks: {"Show folge links", parseString, true}, config.KeyShowSequelLinks: {"Show sequel links", parseString, true}, config.KeyShowSuccessorLinks: {"Show successor links", parseString, true}, } cs.next = interfaceMap{ keyDefaultCopyright: "", keyDefaultLicense: "", keyDefaultVisibility: meta.VisibilityLogin, keyExpertMode: false, config.KeyFooterZettel: id.Invalid, config.KeyHomeZettel: id.DefaultHomeZid, kernel.ConfigInsecureHTML: config.NoHTML, api.KeyLang: api.ValueLangEN, keyMaxTransclusions: int64(1024), keySiteName: "Zettelstore", keyYAMLHeader: false, keyZettelFileSyntax: nil, kernel.ConfigSimpleMode: false, config.KeyShowBackLinks: "", config.KeyShowFolgeLinks: "", config.KeyShowSequelLinks: "", config.KeyShowSuccessorLinks: "", } } 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) |
︙ | ︙ | |||
180 181 182 183 184 185 186 | } cs.mxService.Unlock() cs.SwitchNextToCur() // Poor man's restart return nil } func (cs *configService) observe(ci box.UpdateInfo) { | | | 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | } cs.mxService.Unlock() cs.SwitchNextToCur() // Poor man's restart return nil } func (cs *configService) observe(ci box.UpdateInfo) { if (ci.Reason != box.OnZettel && ci.Reason != box.OnDelete) || ci.Zid == id.ConfigurationZid { cs.logger.Debug().Uint("reason", uint64(ci.Reason)).Zid(ci.Zid).Msg("observe") go func() { cs.mxService.RLock() mgr := cs.manager cs.mxService.RUnlock() if mgr != nil { cs.doUpdate(mgr) |
︙ | ︙ |
Changes to kernel/impl/cmd.go.
︙ | ︙ | |||
140 141 142 143 144 145 146 | "bye": { "end this session", func(*cmdSession, string, []string) bool { return false }, }, "config": {"show configuration keys", cmdConfig}, "crlf": { "toggle crlf mode", | | | | | | 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 | "bye": { "end this session", func(*cmdSession, string, []string) bool { return false }, }, "config": {"show configuration keys", cmdConfig}, "crlf": { "toggle crlf mode", func(sess *cmdSession, _ string, _ []string) bool { if len(sess.eol) == 1 { sess.eol = []byte{'\r', '\n'} sess.println("crlf is on") } else { sess.eol = []byte{'\n'} sess.println("crlf is off") } return true }, }, "dump-index": {"writes the content of the index", cmdDumpIndex}, "dump-recover": {"show data of last recovery", cmdDumpRecover}, "echo": { "toggle echo mode", func(sess *cmdSession, _ string, _ []string) bool { sess.echo = !sess.echo if sess.echo { sess.println("echo is on") } else { sess.println("echo is off") } return true }, }, "end-profile": {"stop profiling", cmdEndProfile}, "env": {"show environment values", cmdEnvironment}, "get-config": {"show current configuration data", cmdGetConfig}, "header": { "toggle table header", func(sess *cmdSession, _ string, _ []string) bool { sess.header = !sess.header if sess.header { sess.println("header are on") } else { sess.println("header are off") } return true }, }, "log-level": {"get/set log level", cmdLogLevel}, "metrics": {"show Go runtime metrics", cmdMetrics}, "next-config": {"show next configuration data", cmdNextConfig}, "profile": {"start profiling", cmdProfile}, "refresh": {"refresh box data", cmdRefresh}, "restart": {"restart service", cmdRestart}, "services": {"show available services", cmdServices}, "set-config": {"set next configuration data", cmdSetConfig}, "shutdown": { "shutdown Zettelstore", func(sess *cmdSession, _ string, _ []string) bool { sess.kern.Shutdown(false); return false }, }, "start": {"start service", cmdStart}, "stat": {"show service statistics", cmdStat}, "stop": {"stop service", cmdStop}, } func cmdHelp(sess *cmdSession, _ string, _ []string) bool { |
︙ | ︙ | |||
337 338 339 340 341 342 343 | if err != nil { sess.println(err.Error()) } return true } func cmdStop(sess *cmdSession, cmd string, args []string) bool { | | < < < | < < | 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 | if err != nil { sess.println(err.Error()) } return true } func cmdStop(sess *cmdSession, cmd string, args []string) bool { if srvnum, ok := lookupService(sess, cmd, args); ok { sess.kern.doStopService(srvnum) } return true } func cmdStat(sess *cmdSession, cmd string, args []string) bool { if len(args) == 0 { sess.usage(cmd, "SERVICE") |
︙ | ︙ |
Changes to kernel/impl/config.go.
︙ | ︙ | |||
229 230 231 232 233 234 235 | case '0', 'f', 'F', 'n', 'N': return false, nil } return true, nil } func parseInt64(val string) (any, error) { | | > < < > | > < < > | 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 | case '0', 'f', 'F', 'n', 'N': return false, nil } return true, nil } func parseInt64(val string) (any, error) { u64, err := strconv.ParseInt(val, 10, 64) if err == nil { return u64, nil } return nil, err } func parseZid(val string) (any, error) { zid, err := id.Parse(val) if err == nil { return zid, nil } return id.Invalid, err } func parseInvalidZid(val string) (any, error) { zid, _ := id.Parse(val) return zid, nil } |
Changes to kernel/impl/impl.go.
︙ | ︙ | |||
442 443 444 445 446 447 448 | return err } srv.SwitchNextToCur() } return nil } | | | | < | 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 | return err } srv.SwitchNextToCur() } return nil } func (kern *myKernel) StopService(srvnum kernel.Service) { kern.mx.Lock() defer kern.mx.Unlock() kern.doStopService(srvnum) } func (kern *myKernel) doStopService(srvnum kernel.Service) { for _, srv := range kern.sortDependency(srvnum, kern.depStop, false) { srv.Stop(kern) } } func (kern *myKernel) sortDependency( srvnum kernel.Service, srvdeps serviceDependency, isStarted bool, ) []service { |
︙ | ︙ | |||
548 549 550 551 552 553 554 | // --- The kernel as a service ------------------------------------------- type kernelService struct { kernel *myKernel } | | | | | | | | | | | | | | | | 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 | // --- The kernel as a service ------------------------------------------- type kernelService struct { kernel *myKernel } func (*kernelService) Initialize(*logger.Logger) {} func (ks *kernelService) GetLogger() *logger.Logger { return ks.kernel.logger } func (*kernelService) ConfigDescriptions() []serviceConfigDescription { return nil } func (*kernelService) SetConfig(string, string) error { return errAlreadyFrozen } func (*kernelService) GetCurConfig(string) interface{} { return nil } func (*kernelService) GetNextConfig(string) interface{} { return nil } func (*kernelService) GetCurConfigList(bool) []kernel.KeyDescrValue { return nil } func (*kernelService) GetNextConfigList() []kernel.KeyDescrValue { return nil } func (*kernelService) GetStatistics() []kernel.KeyValue { return nil } func (*kernelService) Freeze() {} func (*kernelService) Start(*myKernel) error { return nil } func (*kernelService) SwitchNextToCur() {} func (*kernelService) IsStarted() bool { return true } func (*kernelService) Stop(*myKernel) {} |
Changes to kernel/impl/web.go.
︙ | ︙ | |||
43 44 45 46 47 48 49 | func (ws *webService) Initialize(logger *logger.Logger) { ws.logger = logger ws.descr = descriptionMap{ kernel.WebAssetDir: { "Asset file directory", func(val string) (any, error) { val = filepath.Clean(val) | > | < < > | 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | func (ws *webService) Initialize(logger *logger.Logger) { ws.logger = logger ws.descr = descriptionMap{ kernel.WebAssetDir: { "Asset file directory", func(val string) (any, error) { val = filepath.Clean(val) finfo, err := os.Stat(val) if err == nil && finfo.IsDir() { return val, nil } return nil, err }, true, }, kernel.WebBaseURL: { "Base URL", func(val string) (any, error) { if _, err := url.Parse(val); err != nil { |
︙ | ︙ | |||
78 79 80 81 82 83 84 85 86 87 88 89 90 91 | return "", err } return ap.String(), nil }, true}, kernel.WebMaxRequestSize: {"Max Request Size", parseInt64, true}, kernel.WebPersistentCookie: {"Persistent cookie", parseBool, true}, kernel.WebSecureCookie: {"Secure cookie", parseBool, true}, kernel.WebTokenLifetimeAPI: { "Token lifetime API", makeDurationParser(10*time.Minute, 0, 1*time.Hour), true, }, kernel.WebTokenLifetimeHTML: { | > | 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | return "", err } return ap.String(), nil }, true}, kernel.WebMaxRequestSize: {"Max Request Size", parseInt64, true}, kernel.WebPersistentCookie: {"Persistent cookie", parseBool, true}, kernel.WebProfiling: {"Runtime profiling", parseBool, true}, kernel.WebSecureCookie: {"Secure cookie", parseBool, true}, kernel.WebTokenLifetimeAPI: { "Token lifetime API", makeDurationParser(10*time.Minute, 0, 1*time.Hour), true, }, kernel.WebTokenLifetimeHTML: { |
︙ | ︙ | |||
107 108 109 110 111 112 113 114 115 116 117 118 119 120 | ws.next = interfaceMap{ kernel.WebAssetDir: "", kernel.WebBaseURL: "http://127.0.0.1:23123/", 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: "/", } } func makeDurationParser(defDur, minDur, maxDur time.Duration) parseFunc { | > | 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | ws.next = interfaceMap{ kernel.WebAssetDir: "", kernel.WebBaseURL: "http://127.0.0.1:23123/", kernel.WebListenAddress: "127.0.0.1:23123", kernel.WebMaxRequestSize: int64(16 * 1024 * 1024), kernel.WebPersistentCookie: false, kernel.WebSecureCookie: true, kernel.WebProfiling: false, kernel.WebTokenLifetimeAPI: 1 * time.Hour, kernel.WebTokenLifetimeHTML: 10 * time.Minute, kernel.WebURLPrefix: "/", } } func makeDurationParser(defDur, minDur, maxDur time.Duration) parseFunc { |
︙ | ︙ | |||
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | func (ws *webService) Start(kern *myKernel) error { baseURL := ws.GetNextConfig(kernel.WebBaseURL).(string) 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 } if !strings.HasSuffix(baseURL, urlPrefix) { ws.logger.Error().Str("base-url", baseURL).Str("url-prefix", urlPrefix).Msg( "url-prefix is not a suffix of base-url") return errWrongBasePrefix } if lap := netip.MustParseAddrPort(listenAddr); !kern.auth.manager.WithAuth() && !lap.Addr().IsLoopback() { ws.logger.Info().Str("listen", listenAddr).Msg("service may be reached from outside, but authentication is not enabled") } | > > > > > > > > > > > > | | 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 | func (ws *webService) Start(kern *myKernel) error { baseURL := ws.GetNextConfig(kernel.WebBaseURL).(string) listenAddr := ws.GetNextConfig(kernel.WebListenAddress).(string) urlPrefix := ws.GetNextConfig(kernel.WebURLPrefix).(string) persistentCookie := ws.GetNextConfig(kernel.WebPersistentCookie).(bool) secureCookie := ws.GetNextConfig(kernel.WebSecureCookie).(bool) profile := ws.GetNextConfig(kernel.WebProfiling).(bool) maxRequestSize := ws.GetNextConfig(kernel.WebMaxRequestSize).(int64) if maxRequestSize < 1024 { maxRequestSize = 1024 } if !strings.HasSuffix(baseURL, urlPrefix) { ws.logger.Error().Str("base-url", baseURL).Str("url-prefix", urlPrefix).Msg( "url-prefix is not a suffix of base-url") return errWrongBasePrefix } if lap := netip.MustParseAddrPort(listenAddr); !kern.auth.manager.WithAuth() && !lap.Addr().IsLoopback() { ws.logger.Info().Str("listen", listenAddr).Msg("service may be reached from outside, but authentication is not enabled") } sd := impl.ServerData{ Log: ws.logger, ListenAddr: listenAddr, BaseURL: baseURL, URLPrefix: urlPrefix, MaxRequestSize: maxRequestSize, Auth: kern.auth.manager, PersistentCookie: persistentCookie, SecureCookie: secureCookie, Profiling: profile, } srvw := impl.New(sd) err := kern.web.setupServer(srvw, kern.box.manager, kern.auth.manager, &kern.cfg) if err != nil { ws.logger.Error().Err(err).Msg("Unable to create") return err } if kern.core.GetNextConfig(kernel.CoreDebug).(bool) { srvw.SetDebug() |
︙ | ︙ |
Changes to kernel/kernel.go.
︙ | ︙ | |||
90 91 92 93 94 95 96 | // 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. | | | 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | // 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. StopService(Service) // GetServiceStatistics returns a key/value list with statistical data. GetServiceStatistics(Service) []KeyValue // DumpIndex writes some data about the internal index into a writer. DumpIndex(io.Writer) |
︙ | ︙ | |||
189 190 191 192 193 194 195 196 197 198 199 200 201 202 | // Constants for web service keys. const ( WebAssetDir = "asset-dir" WebBaseURL = "base-url" WebListenAddress = "listen" WebPersistentCookie = "persistent" WebMaxRequestSize = "max-request-size" WebSecureCookie = "secure" WebTokenLifetimeAPI = "api-lifetime" WebTokenLifetimeHTML = "html-lifetime" WebURLPrefix = "prefix" ) | > | 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | // Constants for web service keys. const ( WebAssetDir = "asset-dir" WebBaseURL = "base-url" WebListenAddress = "listen" WebPersistentCookie = "persistent" WebProfiling = "profiling" WebMaxRequestSize = "max-request-size" WebSecureCookie = "secure" WebTokenLifetimeAPI = "api-lifetime" WebTokenLifetimeHTML = "html-lifetime" WebURLPrefix = "prefix" ) |
︙ | ︙ |
Changes to parser/markdown/markdown.go.
︙ | ︙ | |||
326 327 328 329 330 331 332 333 334 335 336 | if lastPos < len(text) { sb.Write(text[lastPos:]) } return sb.String() } func (p *mdP) acceptCodeSpan(node *gmAst.CodeSpan) ast.InlineSlice { return ast.InlineSlice{ &ast.LiteralNode{ Kind: ast.LiteralProg, Attrs: nil, //TODO | > > > > > > > > > > > > > > > > > > > > > > > > > > | < < < < < < < < < < < < < < < < < < < | 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 | if lastPos < len(text) { sb.Write(text[lastPos:]) } return sb.String() } func (p *mdP) acceptCodeSpan(node *gmAst.CodeSpan) ast.InlineSlice { var segBuf bytes.Buffer for c := node.FirstChild(); c != nil; c = c.NextSibling() { segment := c.(*gmAst.Text).Segment segBuf.Write(segment.Value(p.source)) } content := segBuf.Bytes() // Clean code span if len(content) == 0 { content = nil } else { lastPos := 0 var buf bytes.Buffer for pos, ch := range content { if ch == '\n' { buf.Write(content[lastPos:pos]) if pos < len(content)-1 { buf.WriteByte(' ') } lastPos = pos + 1 } } buf.Write(content[lastPos:]) content = buf.Bytes() } return ast.InlineSlice{ &ast.LiteralNode{ Kind: ast.LiteralProg, Attrs: nil, //TODO Content: content, }, } } func (p *mdP) acceptEmphasis(node *gmAst.Emphasis) ast.InlineSlice { kind := ast.FormatEmph if node.Level == 2 { kind = ast.FormatStrong } return ast.InlineSlice{ &ast.FormatNode{ |
︙ | ︙ |
Changes to parser/plain/plain.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Package plain provides a parser for plain text data. package plain import ( "bytes" | < | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | //----------------------------------------------------------------------------- // Package plain provides a parser for plain text data. package plain import ( "bytes" "t73f.de/r/sx/sxreader" "t73f.de/r/zsc/attrs" "t73f.de/r/zsc/input" "zettelstore.de/z/ast" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" |
︙ | ︙ | |||
121 122 123 124 125 126 127 | return ast.InlineSlice{&ast.EmbedBLOBNode{ Blob: []byte(svgSrc), Syntax: syntax, }} } func scanSVG(inp *input.Input) string { | < | < | | > > | > > | | 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 | return ast.InlineSlice{&ast.EmbedBLOBNode{ Blob: []byte(svgSrc), Syntax: syntax, }} } func scanSVG(inp *input.Input) string { inp.SkipSpace() pos := inp.Pos if !inp.Accept("<svg") { return "" } ch := inp.Ch if input.IsSpace(ch) || input.IsEOLEOS(ch) || ch == '>' { // TODO: check proper end </svg> return string(inp.Src[pos:]) } return "" } func parseSxnBlocks(inp *input.Input, _ *meta.Meta, syntax string) ast.BlockSlice { rd := sxreader.MakeReader(bytes.NewReader(inp.Src)) _, err := rd.ReadAll() result := ast.BlockSlice{ &ast.VerbatimNode{ |
︙ | ︙ |
Added parser/plain/plain_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 | //----------------------------------------------------------------------------- // Copyright (c) 2024-present 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2024-present Detlef Stern //----------------------------------------------------------------------------- package plain_test import ( "testing" "t73f.de/r/zsc/input" "zettelstore.de/z/encoder/szenc" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) func TestParseSVG(t *testing.T) { testCases := []struct { name string src string exp string }{ {"common", " <svg bla", "(INLINE (EMBED-BLOB () \"svg\" \"<svg bla\"))"}, {"inkscape", "<svg\nbla", "(INLINE (EMBED-BLOB () \"svg\" \"<svg\\nbla\"))"}, {"selfmade", "<svg>", "(INLINE (EMBED-BLOB () \"svg\" \"<svg>\"))"}, {"error", "<svgbla", "(INLINE)"}, {"error-", "<svg-bla", "(INLINE)"}, {"error#", "<svg2bla", "(INLINE)"}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { inp := input.NewInput([]byte(tc.src)) is := parser.ParseInlines(inp, meta.SyntaxSVG) trans := szenc.NewTransformer() lst := trans.GetSz(&is) if got := lst.String(); tc.exp != got { t.Errorf("\nexp: %q\ngot: %q", tc.exp, got) } }) } } |
Changes to parser/zettelmark/block.go.
︙ | ︙ | |||
284 285 286 287 288 289 290 291 | if !cont { lastPara, _ = bn.(*ast.ParaNode) } } } func (cp *zmkP) parseRegionLastLine(rn *ast.RegionNode) { cp.clearStacked() // remove any lists defined in the region | > | | | 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 | if !cont { lastPara, _ = bn.(*ast.ParaNode) } } } func (cp *zmkP) parseRegionLastLine(rn *ast.RegionNode) { inp := cp.inp cp.clearStacked() // remove any lists defined in the region inp.SkipSpace() for { switch inp.Ch { case input.EOS, '\n', '\r': return } in := cp.parseInline() if in == nil { return } |
︙ | ︙ | |||
310 311 312 313 314 315 316 | if delims < 3 { return nil, false } if inp.Ch != ' ' { return nil, false } inp.Next() | | | 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 | if delims < 3 { return nil, false } if inp.Ch != ' ' { return nil, false } inp.Next() inp.SkipSpace() if delims > 7 { delims = 7 } hn = &ast.HeadingNode{Level: delims - 2, Inlines: nil} for { if input.IsEOLEOS(inp.Ch) { return hn, true |
︙ | ︙ | |||
357 358 359 360 361 362 363 | // parseNestedList parses a list. func (cp *zmkP) parseNestedList() (res ast.BlockNode, success bool) { inp := cp.inp kinds := cp.parseNestedListKinds() if kinds == nil { return nil, false } | | | 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 | // parseNestedList parses a list. func (cp *zmkP) parseNestedList() (res ast.BlockNode, success bool) { inp := cp.inp kinds := cp.parseNestedListKinds() if kinds == nil { return nil, false } inp.SkipSpace() if kinds[len(kinds)-1] != ast.NestedListQuote && input.IsEOLEOS(inp.Ch) { return nil, false } if len(kinds) < len(cp.lists) { cp.lists = cp.lists[:len(kinds)] } |
︙ | ︙ | |||
442 443 444 445 446 447 448 | func (cp *zmkP) parseDefTerm() (res ast.BlockNode, success bool) { inp := cp.inp inp.Next() if inp.Ch != ' ' { return nil, false } inp.Next() | | | 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 | func (cp *zmkP) parseDefTerm() (res ast.BlockNode, success bool) { inp := cp.inp inp.Next() if inp.Ch != ' ' { return nil, false } inp.Next() inp.SkipSpace() descrl := cp.descrl if descrl == nil { descrl = &ast.DescriptionListNode{} cp.descrl = descrl } descrl.Descriptions = append(descrl.Descriptions, ast.Description{}) defPos := len(descrl.Descriptions) - 1 |
︙ | ︙ | |||
476 477 478 479 480 481 482 | func (cp *zmkP) parseDefDescr() (res ast.BlockNode, success bool) { inp := cp.inp inp.Next() if inp.Ch != ' ' { return nil, false } inp.Next() | | | 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 | func (cp *zmkP) parseDefDescr() (res ast.BlockNode, success bool) { inp := cp.inp inp.Next() if inp.Ch != ' ' { return nil, false } inp.Next() inp.SkipSpace() descrl := cp.descrl if descrl == nil || len(descrl.Descriptions) == 0 { return nil, false } defPos := len(descrl.Descriptions) - 1 if len(descrl.Descriptions[defPos].Term) == 0 { return nil, false |
︙ | ︙ |
Changes to parser/zettelmark/inline.go.
︙ | ︙ | |||
157 158 159 160 161 162 163 | func hasQueryPrefix(src []byte) bool { return len(src) > len(ast.QueryPrefix) && string(src[:len(ast.QueryPrefix)]) == ast.QueryPrefix } func (cp *zmkP) parseReference(openCh, closeCh rune) (ref string, is ast.InlineSlice, _ bool) { inp := cp.inp inp.Next() | | | 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | func hasQueryPrefix(src []byte) bool { return len(src) > len(ast.QueryPrefix) && string(src[:len(ast.QueryPrefix)]) == ast.QueryPrefix } func (cp *zmkP) parseReference(openCh, closeCh rune) (ref string, is ast.InlineSlice, _ bool) { inp := cp.inp inp.Next() inp.SkipSpace() if inp.Ch == openCh { // Additional opening chars result in a fail return "", nil, false } pos := inp.Pos if !hasQueryPrefix(inp.Src[pos:]) { hasSpace, ok := cp.readReferenceToSep(closeCh) |
︙ | ︙ | |||
190 191 192 193 194 195 196 | if hasSpace { return "", nil, false } inp.SetPos(pos) } } | | | 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | if hasSpace { return "", nil, false } inp.SetPos(pos) } } inp.SkipSpace() pos = inp.Pos if !cp.readReferenceToClose(closeCh) { return "", nil, false } ref = strings.TrimSpace(string(inp.Src[pos:inp.Pos])) inp.Next() if inp.Ch != closeCh { |
︙ | ︙ | |||
309 310 311 312 313 314 315 | return nil, false } attrs := cp.parseInlineAttributes() return &ast.FootnoteNode{Inlines: ins, Attrs: attrs}, true } func (cp *zmkP) parseLinkLikeRest() (ast.InlineSlice, bool) { | > | < | 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 | return nil, false } attrs := cp.parseInlineAttributes() return &ast.FootnoteNode{Inlines: ins, Attrs: attrs}, true } func (cp *zmkP) parseLinkLikeRest() (ast.InlineSlice, bool) { inp := cp.inp inp.SkipSpace() ins := ast.InlineSlice{} for inp.Ch != ']' { in := cp.parseInline() if in == nil { return nil, false } ins = append(ins, in) if _, ok := in.(*ast.BreakNode); ok && input.IsEOLEOS(inp.Ch) { |
︙ | ︙ | |||
380 381 382 383 384 385 386 | if inp.Ch != '%' { return nil, false } for inp.Ch == '%' { inp.Next() } attrs := cp.parseInlineAttributes() | | | 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 | if inp.Ch != '%' { return nil, false } for inp.Ch == '%' { inp.Next() } attrs := cp.parseInlineAttributes() inp.SkipSpace() pos := inp.Pos for { if input.IsEOLEOS(inp.Ch) { return &ast.LiteralNode{ Kind: ast.LiteralComment, Attrs: attrs, Content: append([]byte(nil), inp.Src[pos:inp.Pos]...), |
︙ | ︙ |
Changes to parser/zettelmark/zettelmark.go.
︙ | ︙ | |||
152 153 154 155 156 157 158 | inp.Next() } if pos < inp.Pos { return attrs.Attributes{"": string(inp.Src[pos:inp.Pos])} } // No immediate name: skip spaces | | | 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | inp.Next() } if pos < inp.Pos { return attrs.Attributes{"": string(inp.Src[pos:inp.Pos])} } // No immediate name: skip spaces inp.SkipSpace() return cp.parseInlineAttributes() } func (cp *zmkP) parseInlineAttributes() attrs.Attributes { inp := cp.inp pos := inp.Pos if attrs, success := cp.doParseAttributes(); success { |
︙ | ︙ | |||
236 237 238 239 240 241 242 | inp.EatEOL() default: return } } } | < < < < < < | 236 237 238 239 240 241 242 243 244 245 | inp.EatEOL() default: return } } } func isNameRune(ch rune) bool { return unicode.IsLetter(ch) || unicode.IsDigit(ch) || ch == '-' || ch == '_' } |
Changes to query/context.go.
︙ | ︙ | |||
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 | MaxCount int Full bool } // ContextDirection specifies the direction a context should be calculated. type ContextDirection uint8 const ( ContextDirBoth ContextDirection = iota ContextDirForward ContextDirBackward ) // ContextPort is the collection of box methods needed by this directive. type ContextPort interface { GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) SelectMeta(ctx context.Context, metaSeq []*meta.Meta, q *Query) ([]*meta.Meta, error) } func (spec *ContextSpec) Print(pe *PrintEnv) { pe.printSpace() pe.writeString(api.ContextDirective) if spec.Full { pe.printSpace() pe.writeString(api.FullDirective) } switch spec.Direction { case ContextDirBackward: pe.printSpace() pe.writeString(api.BackwardDirective) case ContextDirForward: pe.printSpace() pe.writeString(api.ForwardDirective) } pe.printPosInt(api.CostDirective, spec.MaxCost) pe.printPosInt(api.MaxDirective, spec.MaxCount) } func (spec *ContextSpec) Execute(ctx context.Context, startSeq []*meta.Meta, port ContextPort) []*meta.Meta { maxCost := float64(spec.MaxCost) if maxCost <= 0 { maxCost = 17 } maxCount := spec.MaxCount if maxCount <= 0 { | > > > | 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 | MaxCount int Full bool } // ContextDirection specifies the direction a context should be calculated. type ContextDirection uint8 // Constants for ContextDirection. const ( ContextDirBoth ContextDirection = iota ContextDirForward ContextDirBackward ) // ContextPort is the collection of box methods needed by this directive. type ContextPort interface { GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) SelectMeta(ctx context.Context, metaSeq []*meta.Meta, q *Query) ([]*meta.Meta, error) } // Print the spec on the given print environment. func (spec *ContextSpec) Print(pe *PrintEnv) { pe.printSpace() pe.writeString(api.ContextDirective) if spec.Full { pe.printSpace() pe.writeString(api.FullDirective) } switch spec.Direction { case ContextDirBackward: pe.printSpace() pe.writeString(api.BackwardDirective) case ContextDirForward: pe.printSpace() pe.writeString(api.ForwardDirective) } pe.printPosInt(api.CostDirective, spec.MaxCost) pe.printPosInt(api.MaxDirective, spec.MaxCount) } // Execute the specification. func (spec *ContextSpec) Execute(ctx context.Context, startSeq []*meta.Meta, port ContextPort) []*meta.Meta { maxCost := float64(spec.MaxCost) if maxCost <= 0 { maxCost = 17 } maxCount := spec.MaxCount if maxCount <= 0 { |
︙ | ︙ | |||
175 176 177 178 179 180 181 | ct.addIDSet(ctx, newCost, value) } } func contextCost(key string) float64 { switch key { case api.KeyFolge, api.KeyPrecursor: | | | | | 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | ct.addIDSet(ctx, newCost, value) } } func contextCost(key string) float64 { switch key { case api.KeyFolge, api.KeyPrecursor: return 0.1 case api.KeySequel, api.KeyPrequel: return 1.0 case api.KeySuccessors, api.KeyPredecessor: return 7 } return 2 } func (ct *contextTask) addID(ctx context.Context, newCost float64, value string) { |
︙ | ︙ |
Changes to query/parser.go.
︙ | ︙ | |||
41 42 43 44 45 46 47 | type parserState struct { inp *input.Input } func (ps *parserState) mustStop() bool { return ps.inp.Ch == input.EOS } func (ps *parserState) acceptSingleKw(s string) bool { | > | > | | > | < | | | 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 | type parserState struct { inp *input.Input } func (ps *parserState) mustStop() bool { return ps.inp.Ch == input.EOS } func (ps *parserState) acceptSingleKw(s string) bool { inp := ps.inp if inp.Accept(s) && (inp.IsSpace() || ps.isActionSep() || ps.mustStop()) { return true } return false } func (ps *parserState) acceptKwArgs(s string) bool { inp := ps.inp if inp.Accept(s) && inp.IsSpace() { inp.SkipSpace() return true } return false } const ( actionSeparatorChar = '|' existOperatorChar = '?' searchOperatorNotChar = '!' searchOperatorEqualChar = '=' searchOperatorHasChar = ':' searchOperatorPrefixChar = '[' searchOperatorSuffixChar = ']' searchOperatorMatchChar = '~' searchOperatorLessChar = '<' searchOperatorGreaterChar = '>' ) func (ps *parserState) parse(q *Query) *Query { inp := ps.inp inp.SkipSpace() if ps.mustStop() { return q } firstPos := inp.Pos zidSet := id.NewSet() for { pos := inp.Pos zid, found := ps.scanZid() if !found { inp.SetPos(pos) break } if !zidSet.Contains(zid) { zidSet.Add(zid) q = createIfNeeded(q) q.zids = append(q.zids, zid) } inp.SkipSpace() if ps.mustStop() { q.zids = nil break } } hasContext := false for { inp.SkipSpace() if ps.mustStop() { break } pos := inp.Pos if ps.acceptSingleKw(api.ContextDirective) { if hasContext { inp.SetPos(pos) |
︙ | ︙ | |||
137 138 139 140 141 142 143 | } if q != nil && len(q.directives) == 0 { inp.SetPos(firstPos) // No directive -> restart at beginning q.zids = nil } for { | | | 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | } if q != nil && len(q.directives) == 0 { inp.SetPos(firstPos) // No directive -> restart at beginning q.zids = nil } for { inp.SkipSpace() if ps.mustStop() { break } pos := inp.Pos if ps.acceptSingleKw(api.OrDirective) { q = createIfNeeded(q) if !q.terms[len(q.terms)-1].isEmpty() { |
︙ | ︙ | |||
199 200 201 202 203 204 205 | return q } func (ps *parserState) parseContext(q *Query) *Query { inp := ps.inp spec := &ContextSpec{} for { | | | 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | return q } func (ps *parserState) parseContext(q *Query) *Query { inp := ps.inp spec := &ContextSpec{} for { inp.SkipSpace() if ps.mustStop() { break } pos := inp.Pos if ps.acceptSingleKw(api.FullDirective) { spec.Full = true continue |
︙ | ︙ | |||
264 265 266 267 268 269 270 | } func (ps *parserState) parseUnlinked(q *Query) *Query { inp := ps.inp spec := &UnlinkedSpec{} for { | | | 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 | } func (ps *parserState) parseUnlinked(q *Query) *Query { inp := ps.inp spec := &UnlinkedSpec{} for { inp.SkipSpace() if ps.mustStop() { break } pos := inp.Pos if ps.acceptKwArgs(api.PhraseDirective) { if word := ps.scanWord(); len(word) > 0 { spec.words = append(spec.words, string(word)) |
︙ | ︙ | |||
340 341 342 343 344 345 346 | if q.limit == 0 || q.limit >= num { q.limit = num } return q, true } func (ps *parserState) parseActions(q *Query) *Query { | > | | | 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 | if q.limit == 0 || q.limit >= num { q.limit = num } return q, true } func (ps *parserState) parseActions(q *Query) *Query { inp := ps.inp inp.Next() var words []string for { inp.SkipSpace() word := ps.scanWord() if len(word) == 0 { break } words = append(words, string(word)) } if len(words) > 0 { |
︙ | ︙ | |||
371 372 373 374 375 376 377 | } text, key := ps.scanSearchTextOrKey(hasOp) if len(key) > 0 { // Assert: hasOp == false op, hasOp = ps.scanSearchOp() // Assert hasOp == true if op == cmpExist || op == cmpNotExist { | | | 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 | } text, key := ps.scanSearchTextOrKey(hasOp) if len(key) > 0 { // Assert: hasOp == false op, hasOp = ps.scanSearchOp() // Assert hasOp == true if op == cmpExist || op == cmpNotExist { if inp.IsSpace() || ps.isActionSep() || ps.mustStop() { return q.addKey(string(key), op) } ps.inp.SetPos(pos) hasOp = false text = ps.scanWord() key = nil } else { |
︙ | ︙ | |||
410 411 412 413 414 415 416 | } func (ps *parserState) scanSearchTextOrKey(hasOp bool) ([]byte, []byte) { inp := ps.inp pos := inp.Pos allowKey := !hasOp | | | | 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 | } func (ps *parserState) scanSearchTextOrKey(hasOp bool) ([]byte, []byte) { inp := ps.inp pos := inp.Pos allowKey := !hasOp for !inp.IsSpace() && !ps.isActionSep() && !ps.mustStop() { if allowKey { switch inp.Ch { case searchOperatorNotChar, existOperatorChar, searchOperatorEqualChar, searchOperatorHasChar, searchOperatorPrefixChar, searchOperatorSuffixChar, searchOperatorMatchChar, searchOperatorLessChar, searchOperatorGreaterChar: 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 !inp.IsSpace() && !ps.isActionSep() && !ps.mustStop() { inp.Next() } return inp.Src[pos:inp.Pos] } func (ps *parserState) scanPosInt() (int, bool) { word := ps.scanWord() |
︙ | ︙ | |||
508 509 510 511 512 513 514 | } if negate { return op.negate(), true } return op, true } | < < < < < < < < < < < < < < < < < | 511 512 513 514 515 516 517 518 519 520 | } if negate { return op.negate(), true } return op, true } func (ps *parserState) isActionSep() bool { return ps.inp.Ch == actionSeparatorChar } |
Changes to query/print.go.
︙ | ︙ | |||
145 146 147 148 149 150 151 152 153 154 155 156 157 158 | } if s := val.value; s != "" { pe.writeString(s) } } } func (q *Query) Human() string { var sb strings.Builder q.PrintHuman(&sb) return sb.String() } // PrintHuman the query to a writer in a human readable form. | > | 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | } if s := val.value; s != "" { pe.writeString(s) } } } // Human returns the query as a human readable string. func (q *Query) Human() string { var sb strings.Builder q.PrintHuman(&sb) return sb.String() } // PrintHuman the query to a writer in a human readable form. |
︙ | ︙ |
Changes to query/specs.go.
︙ | ︙ | |||
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package query import "t73f.de/r/zsc/api" // IdentSpec contains all specification values to calculate the ident directive. type IdentSpec struct{} func (spec *IdentSpec) Print(pe *PrintEnv) { pe.printSpace() pe.writeString(api.IdentDirective) } // ItemsSpec contains all specification values to calculate items. type ItemsSpec struct{} func (spec *ItemsSpec) Print(pe *PrintEnv) { pe.printSpace() pe.writeString(api.ItemsDirective) } | > > | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | package query import "t73f.de/r/zsc/api" // IdentSpec contains all specification values to calculate the ident directive. type IdentSpec struct{} // Print the spec on the given print environment. func (spec *IdentSpec) Print(pe *PrintEnv) { pe.printSpace() pe.writeString(api.IdentDirective) } // ItemsSpec contains all specification values to calculate items. type ItemsSpec struct{} // Print the spec on the given print environment. func (spec *ItemsSpec) Print(pe *PrintEnv) { pe.printSpace() pe.writeString(api.ItemsDirective) } |
Changes to query/unlinked.go.
︙ | ︙ | |||
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | ) // UnlinkedSpec contains all specification values to calculate unlinked references. type UnlinkedSpec struct { words []string } func (spec *UnlinkedSpec) Print(pe *PrintEnv) { pe.printSpace() pe.writeString(api.UnlinkedDirective) for _, word := range spec.words { pe.writeStrings(" ", api.PhraseDirective, " ", word) } } func (spec *UnlinkedSpec) GetWords(metaSeq []*meta.Meta) []string { if words := spec.words; len(words) > 0 { result := make([]string, len(words)) copy(result, words) return result } result := make([]string, 0, len(metaSeq)*4) // Assumption: four words per title | > > | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | ) // UnlinkedSpec contains all specification values to calculate unlinked references. type UnlinkedSpec struct { words []string } // Print the spec on the given print environment. func (spec *UnlinkedSpec) Print(pe *PrintEnv) { pe.printSpace() pe.writeString(api.UnlinkedDirective) for _, word := range spec.words { pe.writeStrings(" ", api.PhraseDirective, " ", word) } } // GetWords returns all title words of a given query result. func (spec *UnlinkedSpec) GetWords(metaSeq []*meta.Meta) []string { if words := spec.words; len(words) > 0 { result := make([]string, len(words)) copy(result, words) return result } result := make([]string, 0, len(metaSeq)*4) // Assumption: four words per title |
︙ | ︙ |
Added testdata/testbox/00000999999999.zettel.
> > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 | id: 00000999999999 title: Zettelstore Application Directory role: configuration syntax: none app-zid: 00000999999999 created: 20240703235900 lang: en modified: 20240708125724 nozid-zid: 9999999998 noappzid: 00000999999999 visibility: login |
Deleted testdata/testbox/00009999999998.zettel.
|
| < < < < < < < < < < < < |
Changes to tests/client/client_test.go.
︙ | ︙ | |||
50 51 52 53 54 55 56 | } } } func TestListZettel(t *testing.T) { const ( | | | | | | 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | } } } func TestListZettel(t *testing.T) { const ( ownerZettel = 57 configRoleZettel = 35 writerZettel = ownerZettel - 25 readerZettel = ownerZettel - 25 creatorZettel = 10 publicZettel = 5 ) testdata := []struct { user string exp int |
︙ | ︙ |
Changes to tests/client/crud_test.go.
︙ | ︙ | |||
21 22 23 24 25 26 27 | "t73f.de/r/zsc/api" "t73f.de/r/zsc/client" ) // --------------------------------------------------------------------------- // Tests that change the Zettelstore must nor run parallel to other tests. | | | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | "t73f.de/r/zsc/api" "t73f.de/r/zsc/client" ) // --------------------------------------------------------------------------- // Tests that change the Zettelstore must nor run parallel to other tests. func TestCreateGetDeleteZettel(t *testing.T) { // Is not to be allowed to run in parallel with other tests. zettel := `title: A Test Example content.` c := getClient() c.SetAuth("owner", "owner") zid, err := c.CreateZettel(context.Background(), []byte(zettel)) |
︙ | ︙ | |||
48 49 50 51 52 53 54 | } exp := `title: A Test Example content.` if string(data) != exp { t.Errorf("Expected zettel data: %q, but got %q", exp, data) } | < < < < < | < | | < < < < < < | < | | 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 | } exp := `title: A Test Example content.` if string(data) != exp { t.Errorf("Expected zettel data: %q, but got %q", exp, data) } doDelete(t, c, zid) } func TestCreateGetDeleteZettelDataCreator(t *testing.T) { // Is not to be allowed to run in parallel with other tests. c := getClient() c.SetAuth("creator", "creator") zid, err := c.CreateZettelData(context.Background(), api.ZettelData{ Meta: nil, Encoding: "", Content: "Example", }) if err != nil { t.Error("Cannot create zettel:", err) return } if !zid.IsValid() { t.Error("Invalid zettel ID", zid) return } c.SetAuth("owner", "owner") doDelete(t, c, zid) } func TestCreateGetDeleteZettelData(t *testing.T) { // Is not to be allowed to run in parallel with other tests. c := getClient() c.SetAuth("owner", "owner") wrongModified := "19691231115959" |
︙ | ︙ |
Changes to tests/markdown_test.go.
︙ | ︙ | |||
84 85 86 87 88 89 90 | return parser.ParseBlocks(input.NewInput([]byte(markdown)), nil, meta.SyntaxMarkdown, hi) } func testAllEncodings(t *testing.T, tc markdownTestCase, ast *ast.BlockSlice) { var sb strings.Builder testID := tc.Example*100 + 1 for _, enc := range encodings { | | | 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | return parser.ParseBlocks(input.NewInput([]byte(markdown)), nil, meta.SyntaxMarkdown, hi) } func testAllEncodings(t *testing.T, tc markdownTestCase, ast *ast.BlockSlice) { var sb strings.Builder testID := tc.Example*100 + 1 for _, enc := range encodings { t.Run(fmt.Sprintf("Encode %v %v", enc, testID), func(*testing.T) { encoder.Create(enc, &encoder.CreateParameter{Lang: api.ValueLangEN}).WriteBlocks(&sb, ast) sb.Reset() }) } } func testZmkEncoding(t *testing.T, tc markdownTestCase, ast *ast.BlockSlice) { |
︙ | ︙ |
Changes to tools/build/build.go.
︙ | ︙ | |||
245 246 247 248 249 250 251 | arch string os string env []string name string }{ {"amd64", "linux", nil, "zettelstore"}, {"arm", "linux", []string{"GOARM=6"}, "zettelstore"}, | | | > | 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 | arch string os string env []string name string }{ {"amd64", "linux", nil, "zettelstore"}, {"arm", "linux", []string{"GOARM=6"}, "zettelstore"}, {"arm64", "darwin", nil, "zettelstore"}, {"amd64", "darwin", nil, "zettelstore"}, {"amd64", "windows", nil, "zettelstore.exe"}, {"arm64", "android", nil, "zettelstore"}, } for _, rel := range releases { env := append([]string{}, rel.env...) env = append(env, "GOARCH="+rel.arch, "GOOS="+rel.os) env = append(env, tools.EnvDirectProxy...) env = append(env, tools.EnvGoVCS...) zsName := filepath.Join("releases", rel.name) |
︙ | ︙ |
Changes to tools/devtools/devtools.go.
︙ | ︙ | |||
35 36 37 38 39 40 41 42 43 44 45 46 47 48 | tools := []struct{ name, pack string }{ {"shadow", "golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest"}, {"unparam", "mvdan.cc/unparam@latest"}, {"staticcheck", "honnef.co/go/tools/cmd/staticcheck@latest"}, {"govulncheck", "golang.org/x/vuln/cmd/govulncheck@latest"}, {"deadcode", "golang.org/x/tools/cmd/deadcode@latest"}, {"errcheck", "github.com/kisielk/errcheck@latest"}, } for _, tool := range tools { err := doGoInstall(tool.pack) if err != nil { return err } } | > | 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | tools := []struct{ name, pack string }{ {"shadow", "golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest"}, {"unparam", "mvdan.cc/unparam@latest"}, {"staticcheck", "honnef.co/go/tools/cmd/staticcheck@latest"}, {"govulncheck", "golang.org/x/vuln/cmd/govulncheck@latest"}, {"deadcode", "golang.org/x/tools/cmd/deadcode@latest"}, {"errcheck", "github.com/kisielk/errcheck@latest"}, {"revive", "github.com/mgechev/revive@latest"}, } for _, tool := range tools { err := doGoInstall(tool.pack) if err != nil { return err } } |
︙ | ︙ |
Changes to tools/htmllint/htmllint.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //----------------------------------------------------------------------------- // Copyright (c) 2023-present 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- package main import ( "context" "flag" "fmt" "log" | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //----------------------------------------------------------------------------- // Copyright (c) 2023-present 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- // Package main provides a tool to check the validity of HTML zettel documents. package main import ( "context" "flag" "fmt" "log" |
︙ | ︙ | |||
57 58 59 60 61 62 63 | msgCount := 0 fmt.Fprintf(os.Stderr, "Now checking: %s\n", kd.text) for _, zid := range zidsToUse(zids, perm, kd.sampleSize) { var nmsgs int nmsgs, err = validateHTML(client, kd.uc, api.ZettelID(zid)) if err != nil { fmt.Fprintf(os.Stderr, "* error while validating zettel %v with: %v\n", zid, err) | | | 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | msgCount := 0 fmt.Fprintf(os.Stderr, "Now checking: %s\n", kd.text) for _, zid := range zidsToUse(zids, perm, kd.sampleSize) { var nmsgs int nmsgs, err = validateHTML(client, kd.uc, api.ZettelID(zid)) if err != nil { fmt.Fprintf(os.Stderr, "* error while validating zettel %v with: %v\n", zid, err) msgCount++ } else { msgCount += nmsgs } } if msgCount == 1 { fmt.Fprintln(os.Stderr, "==> found 1 possible issue") } else if msgCount > 1 { |
︙ | ︙ | |||
105 106 107 108 109 110 111 | sampleSize int }{ {getHTMLZettel, "zettel HTML encoding", -1}, {createJustKey('h'), "zettel web view", -1}, {createJustKey('i'), "zettel info view", -1}, {createJustKey('e'), "zettel edit form", 100}, {createJustKey('c'), "zettel create form", 10}, | < | 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | sampleSize int }{ {getHTMLZettel, "zettel HTML encoding", -1}, {createJustKey('h'), "zettel web view", -1}, {createJustKey('i'), "zettel info view", -1}, {createJustKey('e'), "zettel edit form", 100}, {createJustKey('c'), "zettel create form", 10}, {createJustKey('d'), "zettel delete dialog", 200}, } type urlCreator func(*client.Client, api.ZettelID) *api.URLBuilder func createJustKey(key byte) urlCreator { return func(c *client.Client, zid api.ZettelID) *api.URLBuilder { |
︙ | ︙ |
Changes to tools/tools.go.
︙ | ︙ | |||
22 23 24 25 26 27 28 | "os" "os/exec" "strings" "zettelstore.de/z/strfun" ) | > > | | > > > > > > > > > > > > > | 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 | "os" "os/exec" "strings" "zettelstore.de/z/strfun" ) // Some constants to make Go work with fossil. var ( EnvDirectProxy = []string{"GOPROXY=direct"} EnvGoVCS = []string{"GOVCS=zettelstore.de:fossil,t73f.de:fossil"} ) // Verbose signals a verbose tool execution. var Verbose bool // ExecuteCommand executes a specific command. func ExecuteCommand(env []string, name string, arg ...string) (string, error) { LogCommand("EXEC", env, name, arg) var out strings.Builder cmd := PrepareCommand(env, name, arg, nil, &out, os.Stderr) err := cmd.Run() return out.String(), err } // ExecuteFilter executes an external program to be used as a filter. func ExecuteFilter(data []byte, env []string, name string, arg ...string) (string, string, error) { LogCommand("EXEC", env, name, arg) var stdout, stderr strings.Builder cmd := PrepareCommand(env, name, arg, bytes.NewReader(data), &stdout, &stderr) err := cmd.Run() return stdout.String(), stderr.String(), err } // PrepareCommand creates a commands to be executed. func PrepareCommand(env []string, name string, arg []string, in io.Reader, stdout, stderr io.Writer) *exec.Cmd { if len(env) > 0 { env = append(env, os.Environ()...) } cmd := exec.Command(name, arg...) cmd.Env = env cmd.Stdin = in cmd.Stdout = stdout cmd.Stderr = stderr return cmd } // LogCommand logs the execution of a command. func LogCommand(exec string, env []string, name string, arg []string) { if Verbose { if len(env) > 0 { for i, e := range env { fmt.Fprintf(os.Stderr, "ENV%d %v\n", i+1, e) } } fmt.Fprintln(os.Stderr, exec, name, arg) } } // Check the source with some linters. func Check(forRelease bool) error { if err := CheckGoTest("./..."); err != nil { return err } if err := checkGoVet(); err != nil { return err } if err := checkShadow(forRelease); err != nil { return err } if err := checkStaticcheck(); err != nil { return err } if err := checkUnparam(forRelease); err != nil { return err } if err := checkRevive(); err != nil { return err } if forRelease { if err := checkGoVulncheck(); err != nil { return err } } return checkFossilExtra() } // CheckGoTest runs all internal unti tests. func CheckGoTest(pkg string, testParams ...string) error { var env []string env = append(env, EnvDirectProxy...) env = append(env, EnvGoVCS...) args := []string{"test", pkg} args = append(args, testParams...) out, err := ExecuteCommand(env, "go", args...) |
︙ | ︙ | |||
138 139 140 141 142 143 144 145 146 147 148 149 150 151 | func checkStaticcheck() error { out, err := ExecuteCommand(EnvGoVCS, "staticcheck", "./...") if err != nil { fmt.Fprintln(os.Stderr, "Some staticcheck problems found") if len(out) > 0 { fmt.Fprintln(os.Stderr, out) } } return err } func checkUnparam(forRelease bool) error { path, err := findExecStrict("unparam", forRelease) if path == "" { | > > > > > > > > > > > | 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 | func checkStaticcheck() error { out, err := ExecuteCommand(EnvGoVCS, "staticcheck", "./...") if err != nil { fmt.Fprintln(os.Stderr, "Some staticcheck problems found") if len(out) > 0 { fmt.Fprintln(os.Stderr, out) } } return err } func checkRevive() error { out, err := ExecuteCommand(EnvGoVCS, "revive", "./...") if err != nil || out != "" { fmt.Fprintln(os.Stderr, "Some revive problems found") if len(out) > 0 { fmt.Fprintln(os.Stderr, out) } } return err } func checkUnparam(forRelease bool) error { path, err := findExecStrict("unparam", forRelease) if path == "" { |
︙ | ︙ |
Changes to usecase/create_zettel.go.
︙ | ︙ | |||
79 80 81 82 83 84 85 | m.Set(api.KeyTitle, prependTitle(title, "Folge", "Folge of ")) } updateMetaRoleTagsSyntax(m, origMeta) m.Set(api.KeyPrecursor, origMeta.Zid.String()) return zettel.Zettel{Meta: m, Content: zettel.NewContent(nil)} } | | | | | | 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | m.Set(api.KeyTitle, prependTitle(title, "Folge", "Folge of ")) } updateMetaRoleTagsSyntax(m, origMeta) m.Set(api.KeyPrecursor, origMeta.Zid.String()) return zettel.Zettel{Meta: m, Content: zettel.NewContent(nil)} } // PrepareSequel the zettel for further modification. func (*CreateZettel) PrepareSequel(origZettel zettel.Zettel) zettel.Zettel { origMeta := origZettel.Meta m := meta.New(id.Invalid) if title, found := origMeta.Get(api.KeyTitle); found { m.Set(api.KeyTitle, prependTitle(title, "Sequel", "Sequel of ")) } updateMetaRoleTagsSyntax(m, origMeta) m.Set(api.KeyPrequel, origMeta.Zid.String()) return zettel.Zettel{Meta: m, Content: zettel.NewContent(nil)} } // PrepareNew the zettel for further modification. func (*CreateZettel) PrepareNew(origZettel zettel.Zettel, newTitle string) zettel.Zettel { m := meta.New(id.Invalid) om := origZettel.Meta |
︙ | ︙ |
Deleted usecase/rename_zettel.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to web/adapter/api/api.go.
︙ | ︙ | |||
92 93 94 95 96 97 98 | } if pol.CanRead(user, m) { result |= api.ZettelCanRead } if pol.CanWrite(user, m, m) { result |= api.ZettelCanWrite } | < < < | 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | } if pol.CanRead(user, m) { result |= api.ZettelCanRead } if pol.CanWrite(user, m, m) { result |= api.ZettelCanWrite } if pol.CanDelete(user, m) { result |= api.ZettelCanDelete } if result == 0 { return api.ZettelCanNone } return result |
︙ | ︙ |
Changes to web/adapter/api/command.go.
︙ | ︙ | |||
21 22 23 24 25 26 27 | "zettelstore.de/z/usecase" ) // MakePostCommandHandler creates a new HTTP handler to execute certain commands. func (a *API) MakePostCommandHandler( ucIsAuth *usecase.IsAuthenticated, ucRefresh *usecase.Refresh, | | | | | 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 | "zettelstore.de/z/usecase" ) // MakePostCommandHandler creates a new HTTP handler to execute certain commands. func (a *API) MakePostCommandHandler( ucIsAuth *usecase.IsAuthenticated, ucRefresh *usecase.Refresh, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() switch api.Command(r.URL.Query().Get(api.QueryKeyCommand)) { case api.CommandAuthenticated: handleIsAuthenticated(ctx, w, ucIsAuth) return case api.CommandRefresh: err := ucRefresh.Run(ctx) if err != nil { a.reportUsecaseError(w, err) return } w.WriteHeader(http.StatusNoContent) return } http.Error(w, "Unknown command", http.StatusBadRequest) }) } func handleIsAuthenticated(ctx context.Context, w http.ResponseWriter, ucIsAuth *usecase.IsAuthenticated) { switch ucIsAuth.Run(ctx) { case usecase.IsAuthenticatedDisabled: w.WriteHeader(http.StatusOK) case usecase.IsAuthenticatedAndValid: |
︙ | ︙ |
Changes to web/adapter/api/create_zettel.go.
︙ | ︙ | |||
23 24 25 26 27 28 29 | "zettelstore.de/z/web/content" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" ) // MakePostCreateZettelHandler creates a new HTTP handler to store content of // an existing zettel. | | | | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | "zettelstore.de/z/web/content" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" ) // MakePostCreateZettelHandler creates a new HTTP handler to store content of // an existing zettel. func (a *API) MakePostCreateZettelHandler(createZettel *usecase.CreateZettel) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() enc, encStr := getEncoding(r, q) var zettel zettel.Zettel var err error switch enc { case api.EncoderPlain: zettel, err = buildZettelFromPlainData(r, id.Invalid) |
︙ | ︙ | |||
70 71 72 73 74 75 76 | h := adapter.PrepareHeader(w, contentType) h.Set(api.HeaderLocation, location.String()) w.WriteHeader(http.StatusCreated) if _, err = w.Write(result); err != nil { a.log.Error().Err(err).Zid(newZid).Msg("Create Zettel") } | | | 70 71 72 73 74 75 76 77 78 | h := adapter.PrepareHeader(w, contentType) h.Set(api.HeaderLocation, location.String()) w.WriteHeader(http.StatusCreated) if _, err = w.Write(result); err != nil { a.log.Error().Err(err).Zid(newZid).Msg("Create Zettel") } }) } |
Changes to web/adapter/api/delete_zettel.go.
︙ | ︙ | |||
17 18 19 20 21 22 23 | "net/http" "zettelstore.de/z/usecase" "zettelstore.de/z/zettel/id" ) // MakeDeleteZettelHandler creates a new HTTP handler to delete a zettel. | | | | | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | "net/http" "zettelstore.de/z/usecase" "zettelstore.de/z/zettel/id" ) // MakeDeleteZettelHandler creates a new HTTP handler to delete a zettel. func (a *API) MakeDeleteZettelHandler(deleteZettel *usecase.DeleteZettel) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { zid, err := id.Parse(r.URL.Path[1:]) if err != nil { http.NotFound(w, r) return } if err = deleteZettel.Run(r.Context(), zid); err != nil { a.reportUsecaseError(w, err) return } w.WriteHeader(http.StatusNoContent) }) } |
Changes to web/adapter/api/get_data.go.
︙ | ︙ | |||
18 19 20 21 22 23 24 | "t73f.de/r/sx" "zettelstore.de/z/usecase" "zettelstore.de/z/zettel/id" ) // MakeGetDataHandler creates a new HTTP handler to return zettelstore data. | | | | | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | "t73f.de/r/sx" "zettelstore.de/z/usecase" "zettelstore.de/z/zettel/id" ) // MakeGetDataHandler creates a new HTTP handler to return zettelstore data. func (a *API) MakeGetDataHandler(ucVersion usecase.Version) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { version := ucVersion.Run() err := a.writeObject(w, id.Invalid, sx.MakeList( sx.Int64(version.Major), sx.Int64(version.Minor), sx.Int64(version.Patch), sx.MakeString(version.Info), sx.MakeString(version.Hash), )) if err != nil { a.log.Error().Err(err).Msg("Write Version Info") } }) } |
Changes to web/adapter/api/get_zettel.go.
︙ | ︙ | |||
30 31 32 33 34 35 36 | "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/content" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // MakeGetZettelHandler creates a new HTTP handler to return a zettel in various encodings. | | > > > > | | | | | | 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 | "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/content" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // MakeGetZettelHandler creates a new HTTP handler to return a zettel in various encodings. func (a *API) MakeGetZettelHandler( getZettel usecase.GetZettel, parseZettel usecase.ParseZettel, evaluate usecase.Evaluate, ) http.Handler { return http.HandlerFunc(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() part := getPart(q, partContent) ctx := r.Context() switch enc, encStr := getEncoding(r, q); enc { case api.EncoderPlain: a.writePlainData(ctx, w, zid, part, getZettel) case api.EncoderData: a.writeSzData(ctx, w, zid, part, getZettel) default: var zn *ast.ZettelNode var em func(value string) ast.InlineSlice if q.Has(api.QueryKeyParseOnly) { zn, err = parseZettel.Run(ctx, zid, q.Get(api.KeySyntax)) em = parser.ParseMetadata } else { zn, err = evaluate.Run(ctx, zid, q.Get(api.KeySyntax)) em = func(value string) ast.InlineSlice { return evaluate.RunMetadata(ctx, value) } } if err != nil { a.reportUsecaseError(w, err) return } a.writeEncodedZettelPart(ctx, w, zn, em, enc, encStr, part) } }) } func (a *API) writePlainData(ctx context.Context, w http.ResponseWriter, zid id.Zid, part partType, getZettel usecase.GetZettel) { var buf bytes.Buffer var contentType string var err error z, err := getZettel.Run(box.NoEnrichContext(ctx), zid) if err != nil { a.reportUsecaseError(w, err) |
︙ | ︙ | |||
109 110 111 112 113 114 115 | return } if err = writeBuffer(w, &buf, contentType); err != nil { a.log.Error().Err(err).Zid(zid).Msg("Write Plain data") } } | | | 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | return } if err = writeBuffer(w, &buf, contentType); err != nil { a.log.Error().Err(err).Zid(zid).Msg("Write Plain data") } } func (a *API) writeSzData(ctx context.Context, w http.ResponseWriter, zid id.Zid, part partType, getZettel usecase.GetZettel) { z, err := getZettel.Run(ctx, zid) if err != nil { a.reportUsecaseError(w, err) return } var obj sx.Object switch part { |
︙ | ︙ |
Changes to web/adapter/api/login.go.
︙ | ︙ | |||
21 22 23 24 25 26 27 | "zettelstore.de/z/auth" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/zettel/id" ) // MakePostLoginHandler creates a new HTTP handler to authenticate the given user via API. | | | | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | "zettelstore.de/z/auth" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/zettel/id" ) // MakePostLoginHandler creates a new HTTP handler to authenticate the given user via API. func (a *API) MakePostLoginHandler(ucAuth *usecase.Authenticate) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !a.withAuth() { if err := a.writeToken(w, "freeaccess", 24*366*10*time.Hour); err != nil { a.log.Error().Err(err).Msg("Login/free") } return } var token []byte |
︙ | ︙ | |||
47 48 49 50 51 52 53 | http.Error(w, "Authentication failed", http.StatusUnauthorized) return } if err := a.writeToken(w, string(token), a.tokenLifetime); err != nil { a.log.Error().Err(err).Msg("Login") } | | | | | 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 | http.Error(w, "Authentication failed", http.StatusUnauthorized) return } if err := a.writeToken(w, string(token), a.tokenLifetime); err != nil { a.log.Error().Err(err).Msg("Login") } }) } func retrieveIdentCred(r *http.Request) (string, string) { if ident, cred, ok := adapter.GetCredentialsViaForm(r); ok { return ident, cred } if ident, cred, ok := r.BasicAuth(); ok { return ident, cred } return "", "" } // MakeRenewAuthHandler creates a new HTTP handler to renew the authenticate of a user. func (a *API) MakeRenewAuthHandler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if !a.withAuth() { if err := a.writeToken(w, "freeaccess", 24*366*10*time.Hour); err != nil { a.log.Error().Err(err).Msg("Refresh/free") } return } |
︙ | ︙ | |||
94 95 96 97 98 99 100 | if err != nil { a.reportUsecaseError(w, err) return } if err = a.writeToken(w, string(token), a.tokenLifetime); err != nil { a.log.Error().Err(err).Msg("Write renewed token") } | | | 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | if err != nil { a.reportUsecaseError(w, err) return } if err = a.writeToken(w, string(token), a.tokenLifetime); err != nil { a.log.Error().Err(err).Msg("Write renewed token") } }) } func (a *API) writeToken(w http.ResponseWriter, token string, lifetime time.Duration) error { return a.writeObject(w, id.Invalid, sx.MakeList( sx.MakeString("Bearer"), sx.MakeString(token), sx.Int64(int64(lifetime/time.Second)), )) } |
Changes to web/adapter/api/query.go.
︙ | ︙ | |||
30 31 32 33 34 35 36 | "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/content" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // MakeQueryHandler creates a new HTTP handler to perform a query. | | > > > > > | | 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/content" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // MakeQueryHandler creates a new HTTP handler to perform a query. func (a *API) MakeQueryHandler( queryMeta *usecase.Query, tagZettel *usecase.TagZettel, roleZettel *usecase.RoleZettel, reIndex *usecase.ReIndex, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() urlQuery := r.URL.Query() if a.handleTagZettel(w, r, tagZettel, urlQuery) || a.handleRoleZettel(w, r, roleZettel, urlQuery) { return } sq := adapter.GetQuery(urlQuery) |
︙ | ︙ | |||
92 93 94 95 96 97 98 | a.log.Error().Err(err).Str("query", sq.String()).Msg("execute query action") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } if err = writeBuffer(w, &buf, contentType); err != nil { a.log.Error().Err(err).Msg("write result buffer") } | | | | | | | | | 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 | a.log.Error().Err(err).Str("query", sq.String()).Msg("execute query action") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } if err = writeBuffer(w, &buf, contentType); err != nil { a.log.Error().Err(err).Msg("write result buffer") } }) } func queryAction(w io.Writer, enc zettelEncoder, ml []*meta.Meta, actions []string) error { minVal, maxVal := -1, -1 if len(actions) > 0 { acts := make([]string, 0, len(actions)) for _, act := range actions { if strings.HasPrefix(act, api.MinAction) { if num, err := strconv.Atoi(act[3:]); err == nil && num > 0 { minVal = num continue } } if strings.HasPrefix(act, api.MaxAction) { if num, err := strconv.Atoi(act[3:]); err == nil && num > 0 { maxVal = num continue } } acts = append(acts, act) } for _, act := range acts { if act == api.KeysAction { return encodeKeysArrangement(w, enc, ml, act) } switch key := strings.ToLower(act); meta.Type(key) { case meta.TypeWord, meta.TypeTagSet: return encodeMetaKeyArrangement(w, enc, ml, key, minVal, maxVal) } } } return enc.writeMetaList(w, ml) } func encodeKeysArrangement(w io.Writer, enc zettelEncoder, ml []*meta.Meta, act string) error { arr := make(meta.Arrangement, 128) for _, m := range ml { for k := range m.Map() { arr[k] = append(arr[k], m) } } return enc.writeArrangement(w, act, arr) } func encodeMetaKeyArrangement(w io.Writer, enc zettelEncoder, ml []*meta.Meta, key string, minVal, maxVal int) error { arr0 := meta.CreateArrangement(ml, key) arr := make(meta.Arrangement, len(arr0)) for k0, ml0 := range arr0 { if len(ml0) < minVal || (maxVal > 0 && len(ml0) > maxVal) { continue } arr[k0] = ml0 } return enc.writeArrangement(w, key, arr) } |
︙ | ︙ |
Deleted web/adapter/api/rename_zettel.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to web/adapter/api/update_zettel.go.
︙ | ︙ | |||
20 21 22 23 24 25 26 | "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" ) // MakeUpdateZettelHandler creates a new HTTP handler to update a zettel. | | | | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" ) // MakeUpdateZettelHandler creates a new HTTP handler to update a zettel. func (a *API) MakeUpdateZettelHandler(updateZettel *usecase.UpdateZettel) http.Handler { return http.HandlerFunc(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() |
︙ | ︙ | |||
49 50 51 52 53 54 55 | return } if err = updateZettel.Run(r.Context(), zettel, true); err != nil { a.reportUsecaseError(w, err) return } w.WriteHeader(http.StatusNoContent) | | | 49 50 51 52 53 54 55 56 57 | return } if err = updateZettel.Run(r.Context(), zettel, true); err != nil { a.reportUsecaseError(w, err) return } w.WriteHeader(http.StatusNoContent) }) } |
Changes to web/adapter/response.go.
︙ | ︙ | |||
66 67 68 69 70 71 72 | msg := ena.Error() return http.StatusForbidden, strings.ToUpper(msg[:1]) + msg[1:] } var eiz box.ErrInvalidZid if errors.As(err, &eiz) { return http.StatusBadRequest, fmt.Sprintf("Zettel-ID %q not appropriate in this context", eiz.Zid) } | < < < < | 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | msg := ena.Error() return http.StatusForbidden, strings.ToUpper(msg[:1]) + msg[1:] } var eiz box.ErrInvalidZid if errors.As(err, &eiz) { return http.StatusBadRequest, fmt.Sprintf("Zettel-ID %q not appropriate in this context", eiz.Zid) } var etznf usecase.ErrTagZettelNotFound if errors.As(err, &etznf) { return http.StatusNotFound, "Tag zettel not found: " + etznf.Tag } var erznf usecase.ErrRoleZettelNotFound if errors.As(err, &erznf) { return http.StatusNotFound, "Role zettel not found: " + erznf.Role |
︙ | ︙ |
Changes to web/adapter/webui/const.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 | // WebUI related constants. const queryKeyAction = "_action" // Values for queryKeyAction const ( | < > | | | | | | 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 | // WebUI related constants. const queryKeyAction = "_action" // Values for queryKeyAction const ( valueActionCopy = "copy" valueActionFolge = "folge" valueActionNew = "new" valueActionSequel = "sequel" valueActionVersion = "version" ) // Enumeration for queryKeyAction type createAction uint8 const ( actionCopy createAction = iota actionFolge actionNew actionSequel actionVersion ) var createActionMap = map[string]createAction{ valueActionSequel: actionSequel, valueActionCopy: actionCopy, valueActionFolge: actionFolge, valueActionNew: actionNew, valueActionVersion: actionVersion, } func getCreateAction(s string) createAction { |
︙ | ︙ |
Changes to web/adapter/webui/create_zettel.go.
︙ | ︙ | |||
32 33 34 35 36 37 38 | "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // MakeGetCreateZettelHandler creates a new HTTP handler to display the // HTML edit view for the various zettel creation methods. func (wui *WebUI) MakeGetCreateZettelHandler( | | > | > > | < < > > | | 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 | "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // 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.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() q := r.URL.Query() op := getCreateAction(q.Get(queryKeyAction)) path := r.URL.Path[1:] zid, err := id.Parse(path) if err != nil { wui.reportError(ctx, w, box.ErrInvalidZid{Zid: path}) return } origZettel, err := getZettel.Run(box.NoEnrichContext(ctx), zid) if err != nil { wui.reportError(ctx, w, box.ErrZettelNotFound{Zid: zid}) return } roleData, syntaxData := retrieveDataLists(ctx, ucListRoles, ucListSyntax) switch op { case actionCopy: wui.renderZettelForm(ctx, w, createZettel.PrepareCopy(origZettel), "Copy Zettel", "", roleData, syntaxData) case actionFolge: wui.renderZettelForm(ctx, w, createZettel.PrepareFolge(origZettel), "Folge Zettel", "", roleData, syntaxData) case actionNew: title := parser.NormalizedSpacedText(origZettel.Meta.GetTitle()) newTitle := parser.NormalizedSpacedText(q.Get(api.KeyTitle)) wui.renderZettelForm(ctx, w, createZettel.PrepareNew(origZettel, newTitle), title, "", roleData, syntaxData) case actionSequel: wui.renderZettelForm(ctx, w, createZettel.PrepareSequel(origZettel), "Sequel Zettel", "", roleData, syntaxData) case actionVersion: wui.renderZettelForm(ctx, w, createZettel.PrepareVersion(origZettel), "Version Zettel", "", roleData, syntaxData) } }) } func retrieveDataLists(ctx context.Context, ucListRoles usecase.ListRoles, ucListSyntax usecase.ListSyntax) ([]string, []string) { roleData := dataListFromArrangement(ucListRoles.Run(ctx)) syntaxData := dataListFromArrangement(ucListSyntax.Run(ctx)) return roleData, syntaxData } |
︙ | ︙ | |||
122 123 124 125 126 127 128 | if err := rb.err; err != nil { wui.reportError(ctx, w, err) } } // MakePostCreateZettelHandler creates a new HTTP handler to store content of // an existing zettel. | | | | 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | if err := rb.err; err != nil { wui.reportError(ctx, w, err) } } // MakePostCreateZettelHandler creates a new HTTP handler to store content of // an existing zettel. func (wui *WebUI) MakePostCreateZettelHandler(createZettel *usecase.CreateZettel) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() reEdit, zettel, err := parseZettelForm(r, id.Invalid) if err == errMissingContent { wui.reportError(ctx, w, adapter.NewErrBadRequest("Content is missing")) return } if err != nil { |
︙ | ︙ | |||
147 148 149 150 151 152 153 | return } if reEdit { wui.redirectFound(w, r, wui.NewURLBuilder('e').SetZid(newZid.ZettelID())) } else { wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(newZid.ZettelID())) } | | | > | | > | | 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 | return } if reEdit { wui.redirectFound(w, r, wui.NewURLBuilder('e').SetZid(newZid.ZettelID())) } else { wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(newZid.ZettelID())) } }) } // MakeGetZettelFromListHandler creates a new HTTP handler to store content of // an existing zettel. func (wui *WebUI) MakeGetZettelFromListHandler( queryMeta *usecase.Query, evaluate *usecase.Evaluate, ucListRoles usecase.ListRoles, ucListSyntax usecase.ListSyntax, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { q := adapter.GetQuery(r.URL.Query()) ctx := r.Context() metaSeq, err := queryMeta.Run(box.NoEnrichQuery(ctx, q), q) if err != nil { wui.reportError(ctx, w, err) return } |
︙ | ︙ | |||
183 184 185 186 187 188 189 | m.Set(api.KeySyntax, api.ValueSyntaxZmk) if qval := q.String(); qval != "" { m.Set(api.KeyQuery, qval) } zettel := zettel.Zettel{Meta: m, Content: zettel.NewContent(zmkContent.Bytes())} roleData, syntaxData := retrieveDataLists(ctx, ucListRoles, ucListSyntax) wui.renderZettelForm(ctx, w, zettel, "Zettel from list", wui.createNewURL, roleData, syntaxData) | | | 188 189 190 191 192 193 194 195 196 | m.Set(api.KeySyntax, api.ValueSyntaxZmk) if qval := q.String(); qval != "" { m.Set(api.KeyQuery, qval) } zettel := zettel.Zettel{Meta: m, Content: zettel.NewContent(zmkContent.Bytes())} roleData, syntaxData := retrieveDataLists(ctx, ucListRoles, ucListSyntax) wui.renderZettelForm(ctx, w, zettel, "Zettel from list", wui.createNewURL, roleData, syntaxData) }) } |
Changes to web/adapter/webui/delete_zettel.go.
︙ | ︙ | |||
25 26 27 28 29 30 31 | "zettelstore.de/z/web/server" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // MakeGetDeleteZettelHandler creates a new HTTP handler to display the // HTML delete view of a zettel. | | > > > | | 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | "zettelstore.de/z/web/server" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // MakeGetDeleteZettelHandler creates a new HTTP handler to display the // HTML delete view of a zettel. func (wui *WebUI) MakeGetDeleteZettelHandler( getZettel usecase.GetZettel, getAllZettel usecase.GetAllZettel, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() path := r.URL.Path[1:] zid, err := id.Parse(path) if err != nil { wui.reportError(ctx, w, box.ErrInvalidZid{Zid: path}) return } |
︙ | ︙ | |||
63 64 65 66 67 68 69 | err = wui.renderSxnTemplate(ctx, w, id.DeleteTemplateZid, env) } else { err = rb.err } if err != nil { wui.reportError(ctx, w, err) } | | | 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | err = wui.renderSxnTemplate(ctx, w, id.DeleteTemplateZid, env) } else { err = rb.err } if err != nil { wui.reportError(ctx, w, err) } }) } func (wui *WebUI) encodeIncoming(m *meta.Meta, getTextTitle getTextTitleFunc) *sx.Pair { zidMap := make(strfun.Set) addListValues(zidMap, m, api.KeyBackward) for _, kd := range meta.GetSortedKeyDescriptions() { inverseKey := kd.Inverse |
︙ | ︙ | |||
96 97 98 99 100 101 102 | for _, val := range values { zidMap.Set(val) } } } // MakePostDeleteZettelHandler creates a new HTTP handler to delete a zettel. | | | | | 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | for _, val := range values { zidMap.Set(val) } } } // MakePostDeleteZettelHandler creates a new HTTP handler to delete a zettel. func (wui *WebUI) MakePostDeleteZettelHandler(deleteZettel *usecase.DeleteZettel) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() path := r.URL.Path[1:] zid, err := id.Parse(path) if err != nil { wui.reportError(ctx, w, box.ErrInvalidZid{Zid: path}) return } if err = deleteZettel.Run(r.Context(), zid); err != nil { wui.reportError(ctx, w, err) return } wui.redirectFound(w, r, wui.NewURLBuilder('/')) }) } |
Changes to web/adapter/webui/edit_zettel.go.
︙ | ︙ | |||
20 21 22 23 24 25 26 | "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/zettel/id" ) // MakeEditGetZettelHandler creates a new HTTP handler to display the // HTML edit view of a zettel. | | > > > > | | | | | 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 | "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/zettel/id" ) // MakeEditGetZettelHandler creates a new HTTP handler to display the // HTML edit view of a zettel. func (wui *WebUI) MakeEditGetZettelHandler( getZettel usecase.GetZettel, ucListRoles usecase.ListRoles, ucListSyntax usecase.ListSyntax, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() path := r.URL.Path[1:] zid, err := id.Parse(path) if err != nil { wui.reportError(ctx, w, box.ErrInvalidZid{Zid: path}) return } zettel, err := getZettel.Run(box.NoEnrichContext(ctx), zid) if err != nil { wui.reportError(ctx, w, err) return } roleData, syntaxData := retrieveDataLists(ctx, ucListRoles, ucListSyntax) wui.renderZettelForm(ctx, w, zettel, "Edit Zettel", "", roleData, syntaxData) }) } // MakeEditSetZettelHandler creates a new HTTP handler to store content of // an existing zettel. func (wui *WebUI) MakeEditSetZettelHandler(updateZettel *usecase.UpdateZettel) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() path := r.URL.Path[1:] zid, err := id.Parse(path) if err != nil { wui.reportError(ctx, w, box.ErrInvalidZid{Zid: path}) return } |
︙ | ︙ | |||
74 75 76 77 78 79 80 | } if reEdit { wui.redirectFound(w, r, wui.NewURLBuilder('e').SetZid(zid.ZettelID())) } else { wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(zid.ZettelID())) } | | | 78 79 80 81 82 83 84 85 86 | } if reEdit { wui.redirectFound(w, r, wui.NewURLBuilder('e').SetZid(zid.ZettelID())) } else { wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(zid.ZettelID())) } }) } |
Changes to web/adapter/webui/favicon.go.
︙ | ︙ | |||
18 19 20 21 22 23 24 | "net/http" "os" "path/filepath" "zettelstore.de/z/web/adapter" ) | > | | | | 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 | "net/http" "os" "path/filepath" "zettelstore.de/z/web/adapter" ) // MakeFaviconHandler creates a HTTP handler to retrieve the favicon. func (wui *WebUI) MakeFaviconHandler(baseDir string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { filename := filepath.Join(baseDir, "favicon.ico") f, err := os.Open(filename) if err != nil { wui.log.Debug().Err(err).Msg("Favicon not found") http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } defer f.Close() data, err := io.ReadAll(f) if err != nil { wui.log.Error().Err(err).Msg("Unable to read favicon data") http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } if err = adapter.WriteData(w, data, ""); err != nil { wui.log.Error().Err(err).Msg("Write favicon") } }) } |
Changes to web/adapter/webui/get_info.go.
︙ | ︙ | |||
37 38 39 40 41 42 43 | // MakeGetInfoHandler creates a new HTTP handler for the use case "get zettel". func (wui *WebUI) MakeGetInfoHandler( ucParseZettel usecase.ParseZettel, ucEvaluate *usecase.Evaluate, ucGetZettel usecase.GetZettel, ucGetAllZettel usecase.GetAllZettel, ucQuery *usecase.Query, | | | | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | // MakeGetInfoHandler creates a new HTTP handler for the use case "get zettel". func (wui *WebUI) MakeGetInfoHandler( ucParseZettel usecase.ParseZettel, ucEvaluate *usecase.Evaluate, ucGetZettel usecase.GetZettel, ucGetAllZettel usecase.GetAllZettel, ucQuery *usecase.Query, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() q := r.URL.Query() path := r.URL.Path[1:] zid, err := id.Parse(path) if err != nil { wui.reportError(ctx, w, box.ErrInvalidZid{Zid: path}) |
︙ | ︙ | |||
113 114 115 116 117 118 119 | err = wui.renderSxnTemplate(ctx, w, id.InfoTemplateZid, env) } else { err = rb.err } if err != nil { wui.reportError(ctx, w, err) } | | | 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | err = wui.renderSxnTemplate(ctx, w, id.InfoTemplateZid, env) } else { err = rb.err } if err != nil { wui.reportError(ctx, w, err) } }) } func (wui *WebUI) splitLocSeaExtLinks(links []*ast.Reference) (locLinks, queries, extLinks *sx.Pair) { for i := len(links) - 1; i >= 0; i-- { ref := links[i] switch ref.State { case ast.RefStateHosted, ast.RefStateBased: // Local |
︙ | ︙ |
Changes to web/adapter/webui/get_zettel.go.
︙ | ︙ | |||
27 28 29 30 31 32 33 | "zettelstore.de/z/usecase" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // MakeGetHTMLZettelHandler creates a new HTTP handler for the use case "get zettel". | | > > > | | 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | "zettelstore.de/z/usecase" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // MakeGetHTMLZettelHandler creates a new HTTP handler for the use case "get zettel". func (wui *WebUI) MakeGetHTMLZettelHandler( evaluate *usecase.Evaluate, getZettel usecase.GetZettel, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() path := r.URL.Path[1:] zid, err := id.Parse(path) if err != nil { wui.reportError(ctx, w, box.ErrInvalidZid{Zid: path}) return } |
︙ | ︙ | |||
68 69 70 71 72 73 74 | } if folgeRole, found := zn.InhMeta.Get(api.KeyFolgeRole); found && folgeRole != "" { rb.bindString("folge-role-url", sx.MakeString(wui.NewURLBuilder('h').AppendQuery(api.KeyRole+api.SearchOperatorHas+folgeRole).String())) } rb.bindString("tag-refs", wui.transformTagSet(api.KeyTags, meta.ListFromValue(zn.InhMeta.GetDefault(api.KeyTags, "")))) rb.bindString("predecessor-refs", wui.identifierSetAsLinks(zn.InhMeta, api.KeyPredecessor, getTextTitle)) rb.bindString("precursor-refs", wui.identifierSetAsLinks(zn.InhMeta, api.KeyPrecursor, getTextTitle)) | | | | | 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 | } if folgeRole, found := zn.InhMeta.Get(api.KeyFolgeRole); found && folgeRole != "" { rb.bindString("folge-role-url", sx.MakeString(wui.NewURLBuilder('h').AppendQuery(api.KeyRole+api.SearchOperatorHas+folgeRole).String())) } rb.bindString("tag-refs", wui.transformTagSet(api.KeyTags, meta.ListFromValue(zn.InhMeta.GetDefault(api.KeyTags, "")))) rb.bindString("predecessor-refs", wui.identifierSetAsLinks(zn.InhMeta, api.KeyPredecessor, getTextTitle)) rb.bindString("precursor-refs", wui.identifierSetAsLinks(zn.InhMeta, api.KeyPrecursor, getTextTitle)) rb.bindString("prequel-refs", wui.identifierSetAsLinks(zn.InhMeta, api.KeyPrequel, getTextTitle)) rb.bindString("urls", metaURLAssoc(zn.InhMeta)) rb.bindString("content", content) rb.bindString("endnotes", endnotes) wui.bindLinks(ctx, &rb, "folge", zn.InhMeta, api.KeyFolge, config.KeyShowFolgeLinks, getTextTitle) wui.bindLinks(ctx, &rb, "sequel", zn.InhMeta, api.KeySequel, config.KeyShowSequelLinks, getTextTitle) wui.bindLinks(ctx, &rb, "back", zn.InhMeta, api.KeyBack, config.KeyShowBackLinks, getTextTitle) wui.bindLinks(ctx, &rb, "successor", zn.InhMeta, api.KeySuccessors, config.KeyShowSuccessorLinks, getTextTitle) if role, found := zn.InhMeta.Get(api.KeyRole); found && role != "" { for _, part := range []string{"meta", "actions", "heading"} { rb.rebindResolved("ROLE-"+role+"-"+part, "ROLE-DEFAULT-"+part) } } wui.bindCommonZettelData(ctx, &rb, user, zn.InhMeta, &zn.Content) if rb.err == nil { err = wui.renderSxnTemplate(ctx, w, id.ZettelTemplateZid, env) } else { err = rb.err } if err != nil { wui.reportError(ctx, w, err) } }) } func (wui *WebUI) identifierSetAsLinks(m *meta.Meta, key string, getTextTitle getTextTitleFunc) *sx.Pair { if values, ok := m.GetList(key); ok { return wui.transformIdentifierSet(values, getTextTitle) } return nil |
︙ | ︙ |
Changes to web/adapter/webui/goaction.go.
︙ | ︙ | |||
16 17 18 19 20 21 22 | import ( "net/http" "zettelstore.de/z/usecase" ) // MakeGetGoActionHandler creates a new HTTP handler to execute certain commands. | | | | | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | import ( "net/http" "zettelstore.de/z/usecase" ) // MakeGetGoActionHandler creates a new HTTP handler to execute certain commands. func (wui *WebUI) MakeGetGoActionHandler(ucRefresh *usecase.Refresh) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() // Currently, command "refresh" is the only command to be executed. err := ucRefresh.Run(ctx) if err != nil { wui.reportError(ctx, w, err) return } wui.redirectFound(w, r, wui.NewURLBuilder('/')) }) } |
Changes to web/adapter/webui/home.go.
︙ | ︙ | |||
22 23 24 25 26 27 28 | "zettelstore.de/z/config" "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" ) | | | | | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | "zettelstore.de/z/config" "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" ) type getRootPort interface { GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) } // MakeGetRootHandler creates a new HTTP handler to show the root URL. func (wui *WebUI) MakeGetRootHandler(s getRootPort) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if p := r.URL.Path; p != "/" { wui.reportError(ctx, w, adapter.ErrResourceNotFound{Path: p}) return } homeZid, _ := id.Parse(wui.rtConfig.Get(ctx, nil, config.KeyHomeZettel)) apiHomeZid := homeZid.ZettelID() |
︙ | ︙ | |||
53 54 55 56 57 58 59 | return } if errors.Is(err, &box.ErrNotAllowed{}) && wui.authz.WithAuth() && server.GetUser(ctx) == nil { wui.redirectFound(w, r, wui.NewURLBuilder('i')) return } wui.redirectFound(w, r, wui.NewURLBuilder('h')) | | | 53 54 55 56 57 58 59 60 61 | return } if errors.Is(err, &box.ErrNotAllowed{}) && wui.authz.WithAuth() && server.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.
︙ | ︙ | |||
127 128 129 130 131 132 133 | return obj } u := builder.NewURLBuilder('h').AppendQuery(q) assoc = assoc.Cons(sx.Cons(shtml.SymAttrHref, sx.MakeString(u.String()))) return rest.Cons(assoc.Cons(sxhtml.SymAttr)).Cons(shtml.SymA) }) rebind(th, sz.SymLinkExternal, func(obj sx.Object) sx.Object { | | | | | | | 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | return obj } u := builder.NewURLBuilder('h').AppendQuery(q) assoc = assoc.Cons(sx.Cons(shtml.SymAttrHref, sx.MakeString(u.String()))) return rest.Cons(assoc.Cons(sxhtml.SymAttr)).Cons(shtml.SymA) }) rebind(th, sz.SymLinkExternal, func(obj sx.Object) sx.Object { attr, _, rest := findA(obj) if attr == nil { return obj } a := sz.GetAttributes(attr) a = a.Set("target", "_blank") a = a.Add("rel", "external").Add("rel", "noreferrer") return rest.Cons(shtml.EvaluateAttrbute(a)).Cons(shtml.SymA) }) rebind(th, sz.SymEmbed, func(obj sx.Object) sx.Object { pair, isPair := sx.GetPair(obj) if !isPair || !shtml.SymIMG.IsEqual(pair.Car()) { return obj } attr, isPair := sx.GetPair(pair.Tail().Car()) |
︙ | ︙ | |||
271 272 273 274 275 276 277 | } sx := g.tx.GetSz(bs) env := shtml.MakeEnvironment(g.lang) sh, err := g.th.Evaluate(sx, &env) if err != nil { return nil, nil, err } | | | 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 | } sx := g.tx.GetSz(bs) env := shtml.MakeEnvironment(g.lang) sh, err := g.th.Evaluate(sx, &env) if err != nil { return nil, nil, err } return sh, shtml.Endnotes(&env), nil } // InlinesSxHTML returns an inline slice, encoded as a SxHTML object. func (g *htmlGenerator) InlinesSxHTML(is *ast.InlineSlice) *sx.Pair { if is == nil || len(*is) == 0 { return nil } |
︙ | ︙ |
Changes to web/adapter/webui/lists.go.
︙ | ︙ | |||
36 37 38 39 40 41 42 | "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // MakeListHTMLMetaHandler creates a HTTP handler for rendering the list of zettel as HTML. | | > > > > | | 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // MakeListHTMLMetaHandler creates a HTTP handler for rendering the list of zettel as HTML. func (wui *WebUI) MakeListHTMLMetaHandler( queryMeta *usecase.Query, tagZettel *usecase.TagZettel, roleZettel *usecase.RoleZettel, reIndex *usecase.ReIndex) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { urlQuery := r.URL.Query() if wui.handleTagZettel(w, r, tagZettel, urlQuery) || wui.handleRoleZettel(w, r, roleZettel, urlQuery) { return } q := adapter.GetQuery(urlQuery) q = q.SetDeterministic() |
︙ | ︙ | |||
146 147 148 149 150 151 152 | err = wui.renderSxnTemplate(ctx, w, id.ListTemplateZid, env) } else { err = rb.err } if err != nil { wui.reportError(ctx, w, err) } | | | 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | err = wui.renderSxnTemplate(ctx, w, id.ListTemplateZid, env) } else { err = rb.err } if err != nil { wui.reportError(ctx, w, err) } }) } func (wui *WebUI) transformTagZettelList(ctx context.Context, tagZettel *usecase.TagZettel, tags []string) (withZettel, withoutZettel *sx.Pair) { slices.Reverse(tags) for _, tag := range tags { tag = meta.NormalizeTag(tag) if _, err := tagZettel.Run(ctx, tag); err == nil { |
︙ | ︙ |
Changes to web/adapter/webui/login.go.
︙ | ︙ | |||
23 24 25 26 27 28 29 | "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/zettel/id" ) // MakeGetLoginOutHandler creates a new HTTP handler to display the HTML login view, // or to execute a logout. | | | | | | | 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 | "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/zettel/id" ) // MakeGetLoginOutHandler creates a new HTTP handler to display the HTML login view, // or to execute a logout. func (wui *WebUI) MakeGetLoginOutHandler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() if query.Has("logout") { wui.clearToken(r.Context(), w) wui.redirectFound(w, r, wui.NewURLBuilder('/')) return } wui.renderLoginForm(wui.clearToken(r.Context(), w), w, false) }) } func (wui *WebUI) renderLoginForm(ctx context.Context, w http.ResponseWriter, retry bool) { env, rb := wui.createRenderEnv(ctx, "login", wui.rtConfig.Get(ctx, nil, api.KeyLang), "Login", nil) rb.bindString("retry", sx.MakeBoolean(retry)) if rb.err == nil { rb.err = wui.renderSxnTemplate(ctx, w, id.LoginTemplateZid, env) } if err := rb.err; err != nil { wui.reportError(ctx, w, err) } } // MakePostLoginHandler creates a new HTTP handler to authenticate the given user. func (wui *WebUI) MakePostLoginHandler(ucAuth *usecase.Authenticate) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !wui.authz.WithAuth() { wui.redirectFound(w, r, wui.NewURLBuilder('/')) return } ctx := r.Context() ident, cred, ok := adapter.GetCredentialsViaForm(r) if !ok { |
︙ | ︙ | |||
71 72 73 74 75 76 77 | if token == nil { wui.renderLoginForm(wui.clearToken(ctx, w), w, true) return } wui.setToken(w, token) wui.redirectFound(w, r, wui.NewURLBuilder('/')) | | | 71 72 73 74 75 76 77 78 79 | if token == nil { wui.renderLoginForm(wui.clearToken(ctx, w), w, true) return } wui.setToken(w, token) wui.redirectFound(w, r, wui.NewURLBuilder('/')) }) } |
Deleted web/adapter/webui/rename_zettel.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to web/adapter/webui/template.go.
︙ | ︙ | |||
136 137 138 139 140 141 142 | if us := u.String(); us != "" { return sx.MakeList( shtml.SymA, sx.MakeList( sxhtml.SymAttr, sx.Cons(shtml.SymAttrHref, sx.MakeString(us)), sx.Cons(shtml.SymAttrTarget, sx.MakeString("_blank")), | | | 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | if us := u.String(); us != "" { return sx.MakeList( shtml.SymA, sx.MakeList( sxhtml.SymAttr, sx.Cons(shtml.SymAttrHref, sx.MakeString(us)), sx.Cons(shtml.SymAttrTarget, sx.MakeString("_blank")), sx.Cons(shtml.SymAttrRel, sx.MakeString("external noreferrer")), ), text) } } return text } |
︙ | ︙ | |||
249 250 251 252 253 254 255 | } rb.bindString("info-url", sx.MakeString(newURLBuilder('i').SetZid(apiZid).String())) if wui.canCreate(ctx, user) { if content != nil && !content.IsBinary() { rb.bindString("copy-url", sx.MakeString(newURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionCopy).String())) } rb.bindString("version-url", sx.MakeString(newURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionVersion).String())) | | < < < | 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | } rb.bindString("info-url", sx.MakeString(newURLBuilder('i').SetZid(apiZid).String())) if wui.canCreate(ctx, user) { if content != nil && !content.IsBinary() { rb.bindString("copy-url", sx.MakeString(newURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionCopy).String())) } rb.bindString("version-url", sx.MakeString(newURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionVersion).String())) rb.bindString("sequel-url", sx.MakeString(newURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionSequel).String())) rb.bindString("folge-url", sx.MakeString(newURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionFolge).String())) } if wui.canDelete(ctx, user, m) { rb.bindString("delete-url", sx.MakeString(newURLBuilder('d').SetZid(apiZid).String())) } if val, found := m.Get(api.KeyUselessFiles); found { rb.bindString("useless", sx.Cons(sx.MakeString(val), nil)) } queryContext := strZid + " " + api.ContextDirective |
︙ | ︙ |
Changes to web/adapter/webui/webui.go.
︙ | ︙ | |||
78 79 80 81 82 83 84 | // // Note: these function must not do auth checking. type webuiBox interface { CanCreateZettel(context.Context) bool GetZettel(context.Context, id.Zid) (zettel.Zettel, error) GetMeta(context.Context, id.Zid) (*meta.Meta, error) CanUpdateZettel(context.Context, zettel.Zettel) bool | < | 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | // // Note: these function must not do auth checking. type webuiBox interface { CanCreateZettel(context.Context) bool GetZettel(context.Context, id.Zid) (zettel.Zettel, error) GetMeta(context.Context, id.Zid) (*meta.Meta, error) CanUpdateZettel(context.Context, zettel.Zettel) bool CanDeleteZettel(context.Context, id.Zid) bool } // New creates a new WebUI struct. func New(log *logger.Logger, ab server.AuthBuilder, authz auth.AuthzManager, rtConfig config.Config, token auth.TokenManager, mgr box.Manager, pol auth.Policy, evalZettel *usecase.Evaluate) *WebUI { loginoutBase := ab.NewURLBuilder('i') |
︙ | ︙ | |||
172 173 174 175 176 177 178 | func (wui *WebUI) canWrite( ctx context.Context, user, meta *meta.Meta, content zettel.Content) bool { return wui.policy.CanWrite(user, meta, meta) && wui.box.CanUpdateZettel(ctx, zettel.Zettel{Meta: meta, Content: content}) } | < < < < | 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | func (wui *WebUI) canWrite( ctx context.Context, user, meta *meta.Meta, content zettel.Content) bool { return wui.policy.CanWrite(user, meta, meta) && wui.box.CanUpdateZettel(ctx, zettel.Zettel{Meta: meta, Content: content}) } func (wui *WebUI) canDelete(ctx context.Context, user, m *meta.Meta) bool { return wui.policy.CanDelete(user, m) && wui.box.CanDeleteZettel(ctx, m.Zid) } func (wui *WebUI) canRefresh(user *meta.Meta) bool { return wui.policy.CanRefresh(user) } |
︙ | ︙ |
Changes to web/content/content.go.
︙ | ︙ | |||
20 21 22 23 24 25 26 27 28 29 30 31 32 33 | "net/http" "t73f.de/r/zsc/api" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/meta" ) const ( UnknownMIME = "application/octet-stream" mimeGIF = "image/gif" mimeHTML = "text/html; charset=utf-8" mimeJPEG = "image/jpeg" mimeMarkdown = "text/markdown; charset=utf-8" PlainText = "text/plain; charset=utf-8" | > | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | "net/http" "t73f.de/r/zsc/api" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/meta" ) // Some MIME encoding values. const ( UnknownMIME = "application/octet-stream" mimeGIF = "image/gif" mimeHTML = "text/html; charset=utf-8" mimeJPEG = "image/jpeg" mimeMarkdown = "text/markdown; charset=utf-8" PlainText = "text/plain; charset=utf-8" |
︙ | ︙ | |||
96 97 98 99 100 101 102 103 104 105 106 107 108 109 | "text/plain": meta.SyntaxText, // Additional syntaxes "application/pdf": "pdf", "text/javascript": "js", } func SyntaxFromMIME(m string, data []byte) string { mt, _, _ := mime.ParseMediaType(m) if syntax, found := mime2syntax[mt]; found { return syntax } if len(data) > 0 { ct := http.DetectContentType(data) | > > | 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | "text/plain": meta.SyntaxText, // Additional syntaxes "application/pdf": "pdf", "text/javascript": "js", } // SyntaxFromMIME returns the syntax for a zettel based on MIME encoding value // and the actual data. func SyntaxFromMIME(m string, data []byte) string { mt, _, _ := mime.ParseMediaType(m) if syntax, found := mime2syntax[mt]; found { return syntax } if len(data) > 0 { ct := http.DetectContentType(data) |
︙ | ︙ |
Changes to web/server/impl/http.go.
︙ | ︙ | |||
20 21 22 23 24 25 26 | "time" ) // Server timeout values const ( shutdownTimeout = 5 * time.Second readTimeout = 5 * time.Second | | | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | "time" ) // Server timeout values const ( shutdownTimeout = 5 * time.Second readTimeout = 5 * time.Second writeTimeout = 20 * time.Second idleTimeout = 120 * time.Second ) // httpServer is a HTTP server. type httpServer struct { http.Server |
︙ | ︙ | |||
42 43 44 45 46 47 48 | } srv.Server = http.Server{ Addr: addr, Handler: http.TimeoutHandler(handler, writeTimeout, "Timeout"), // See: https://blog.cloudflare.com/exposing-go-on-the-internet/ ReadTimeout: readTimeout, | | | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | } srv.Server = http.Server{ Addr: addr, Handler: http.TimeoutHandler(handler, writeTimeout, "Timeout"), // See: https://blog.cloudflare.com/exposing-go-on-the-internet/ ReadTimeout: readTimeout, WriteTimeout: writeTimeout + 500*time.Millisecond, // Give some time to detect timeout and to write an appropriate error message. IdleTimeout: idleTimeout, } srv.origHandler = handler } // SetDebug enables debugging goroutines that are started by the server. // Basically, just the timeout values are reset. This method should be called |
︙ | ︙ |
Changes to web/server/impl/impl.go.
︙ | ︙ | |||
30 31 32 33 34 35 36 37 38 | log *logger.Logger baseURL string server httpServer router httpRouter persistentCookie bool secureCookie bool } // New creates a new web server. | > > > > > > > > > > > > > | | | | | > > > > > > > > | | | 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 | log *logger.Logger baseURL string server httpServer router httpRouter persistentCookie bool secureCookie bool } // ServerData contains the data needed to configure a server. type ServerData struct { Log *logger.Logger ListenAddr string BaseURL string URLPrefix string MaxRequestSize int64 Auth auth.TokenManager PersistentCookie bool SecureCookie bool Profiling bool } // New creates a new web server. func New(sd ServerData) server.Server { srv := myServer{ log: sd.Log, baseURL: sd.BaseURL, persistentCookie: sd.PersistentCookie, secureCookie: sd.SecureCookie, } rd := routerData{ log: sd.Log, urlPrefix: sd.URLPrefix, maxRequestSize: sd.MaxRequestSize, auth: sd.Auth, profiling: sd.Profiling, } srv.router.initializeRouter(rd) srv.server.initializeHTTPServer(sd.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) { |
︙ | ︙ |
Changes to web/server/impl/router.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 19 20 21 | //----------------------------------------------------------------------------- package impl import ( "io" "net/http" "regexp" "strings" | > > < < > > > > > > > > | | | | | > > > > > > > > > > > > > > > > | 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 | //----------------------------------------------------------------------------- package impl import ( "io" "net/http" "net/http/pprof" "regexp" rtprf "runtime/pprof" "strings" "zettelstore.de/z/auth" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/web/server" ) type ( methodHandler [server.MethodLAST]http.Handler routingTable [256]*methodHandler ) var mapMethod = map[string]server.Method{ http.MethodHead: server.MethodHead, http.MethodGet: server.MethodGet, http.MethodPost: server.MethodPost, http.MethodPut: server.MethodPut, http.MethodDelete: server.MethodDelete, } // httpRouter handles all routing for zettelstore. type httpRouter struct { log *logger.Logger urlPrefix string auth auth.TokenManager minKey byte maxKey byte reURL *regexp.Regexp listTable routingTable zettelTable routingTable ur server.UserRetriever mux *http.ServeMux maxReqSize int64 } type routerData struct { log *logger.Logger urlPrefix string maxRequestSize int64 auth auth.TokenManager profiling bool } // initializeRouter creates a new, empty router with the given root handler. func (rt *httpRouter) initializeRouter(rd routerData) { rt.log = rd.log rt.urlPrefix = rd.urlPrefix rt.auth = rd.auth rt.minKey = 255 rt.maxKey = 0 rt.reURL = regexp.MustCompile("^$") rt.mux = http.NewServeMux() rt.maxReqSize = rd.maxRequestSize if rd.profiling { rt.setRuntimeProfiling() } } func (rt *httpRouter) setRuntimeProfiling() { rt.mux.HandleFunc("GET /rtp/", pprof.Index) for _, profile := range rtprf.Profiles() { name := profile.Name() rt.mux.Handle("GET /rtp/"+name, pprof.Handler(name)) } rt.mux.HandleFunc("GET /rtp/cmdline", pprof.Cmdline) rt.mux.HandleFunc("GET /rtp/profile", pprof.Profile) rt.mux.HandleFunc("GET /rtp/symbol", pprof.Symbol) rt.mux.HandleFunc("GET /rtp/trace", pprof.Trace) } func (rt *httpRouter) addRoute(key byte, method server.Method, handler http.Handler, table *routingTable) { // Set minKey and maxKey; re-calculate regexp. if key < rt.minKey || rt.maxKey < key { if key < rt.minKey { rt.minKey = key |
︙ | ︙ |
Changes to web/server/server.go.
︙ | ︙ | |||
34 35 36 37 38 39 40 | // Values for method type const ( MethodGet Method = iota MethodHead MethodPost MethodPut | < | 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | // Values for method type const ( MethodGet Method = iota MethodHead MethodPost MethodPut MethodDelete MethodLAST // must always be the last one ) // Router allows to state routes for various URL paths. type Router interface { Handle(pattern string, handler http.Handler) |
︙ | ︙ |
Changes to www/build.md.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # How to build Zettelstore ## Prerequisites You must install the following software: * A current, supported [release of Go](https://go.dev/doc/devel/release), * [staticcheck](https://staticcheck.io/), * [shadow](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow), * [unparam](https://mvdan.cc/unparam), * [govulncheck](https://golang.org/x/vuln/cmd/govulncheck), * [Fossil](https://fossil-scm.org/), * [Git](https://git-scm.org) (so that Go can download some dependencies). See folder `docs/development` (a zettel box) for details. ## Clone the repository Most of this is covered by the excellent Fossil | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # How to build Zettelstore ## Prerequisites You must install the following software: * A current, supported [release of Go](https://go.dev/doc/devel/release), * [staticcheck](https://staticcheck.io/), * [shadow](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow), * [unparam](https://mvdan.cc/unparam), * [govulncheck](https://golang.org/x/vuln/cmd/govulncheck), * [revive](https://revive.run/), * [Fossil](https://fossil-scm.org/), * [Git](https://git-scm.org) (so that Go can download some dependencies). See folder `docs/development` (a zettel box) for details. ## Clone the repository Most of this is covered by the excellent Fossil |
︙ | ︙ | |||
26 27 28 29 30 31 32 | ## Tools to build, test, and manage In the directory `tools` there are some Go files to automate most aspects of building and testing, (hopefully) platform-independent. The build script is called as: | < | < < | < < | < | > | | | | 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 | ## Tools to build, test, and manage In the directory `tools` there are some Go files to automate most aspects of building and testing, (hopefully) platform-independent. The build script is called as: go run tools/build/build.go [-v] COMMAND The flag `-v` enables the verbose mode. It outputs all commands called by the tool. Some important `COMMAND`s are: * `build`: builds the software with correct version information and puts it into a freshly created directory `bin`. * `check`: checks the current state of the working directory to be ready for release (or commit). * `version`: prints the current version information. Therefore, the easiest way to build your own version of the Zettelstore software is to execute the command go run tools/build/build.go build In case of errors, please send the output of the verbose execution: go run tools/build/build.go -v build Other tools are: * `go run tools/clean/clean.go` cleans your Go development workspace. * `go run tools/check/check.go` executes all linters and unit tests. If you add the option `-r` linters are more strict, to be used for a release version. * `go run tools/devtools/devtools.go` install all needed software (see above). * `go run tools/htmllint/htmllint.go [URL]` checks all generated HTML of a Zettelstore accessible at the given URL (default: http://localhost:23123). * `go run tools/testapi/testapi.go` tests the API against a running Zettelstore, which is started automatically. ## A note on the use of Fossil Zettelstore is managed by the Fossil version control system. Fossil is an alternative to the ubiquitous Git version control system. However, Go seems to prefer Git and popular platforms that just support Git. Some dependencies of Zettelstore, namely [Zettelstore client](https://t73f.de/r/zsc), [webs](https://t73f.de/r/webs), [sx](https://t73f.de/r/sx), and [sxwebs](https://t73f.de/r/sxwebs) are also managed by Fossil. Depending on your development setup, some error messages might occur. If the error message mentions an environment variable called `GOVCS` you should set it to the value `GOVCS=zettelstore.de:fossil` (alternatively more generous to `GOVCS=*:all`). Since the Go build system is coupled with Git and some special platforms, you must allow Go to download a Fossil repository from the host `zettelstore.de`. The build tool sets `GOVCS` to the right value, but you may use other `go` commands that try to download a Fossil repository. On some operating systems, namely Termux on Android, an error message might state that an user cannot be determined (`cannot determine user`). In this case, Fossil is allowed to download the repository, but cannot associate it with an user name. Set the environment variable `USER` to any user name, like: `USER=nobody go run tools/build.go build`. |
Changes to www/changes.wiki.
1 2 3 | <title>Change Log</title> <a id="0_19"></a> | > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | <title>Change Log</title> <a id="0_20"></a> <h2>Changes for Version 0.20.0 (pending)</h2> <a id="0_19"></a> <h2>Changes for Version 0.19.0 (2024-12-13)</h2> * Remove support for renaming zettel, i.e. changing zettel identifier. Was announced as deprecated in version 0.18. (breaking: api, webui) * Format of zettel identifier will be not changed. The deprecation message from version 0.18 is no longer valid. (major) * Zettel content for most predefined zettel (ID less or equal than 0000099999899) is not indexed any more. If you search / query for zettel content, these zettel will not be returned. However, their metadata is still searchable. Content of predefined zettel with ID between 00000999999900 and 00000999999999 will be indexed.. (breaking: api, webui) * Metadata keys <code>superior</code> and <code>subordinate</code> are not supported on the WebUI any more. They are still typed as a set of zettel identifier, but are treated as ordinary links. Zettel should not form a hierarchy, in most cases. (major: webui) * Metadata keys <code>sequel</code> and <code>prequel</code> support a sequence of zettel. They are supported through the WebUI also. <code>sequel</code> is calculated based on <code>prequel</code>. While folge zettel are a train of thought, zettel sequences form different train of thoughts. (major) * Query aggregates <code>ATOM</code> and <code>RSS</code> will be removed in the next version of Zettelstore. They were introduced in [#0_7|v0.7] and [#0_8|v0.8]. Both are not needed for a digital zettelkasten. Their current use is to transform Zettelstore into a blogging engine. RSS and Atom feed can be provided by external software, much better than Zettelstore will ever do. (deprecation) * Fix wrong quote translation for markdown encoder. (minor) * Generate <code><th></code> in table header (was: <code><td></code>). Also applies to SHTML encoder. (minor: webui, api) * External links are now generated in shtml and html with attribute rel="external" (previously: class="external"). (minor: webui, api) * Allow to enable runtime profiling of the software, to be used by developers. (minor) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_18"></a> <h2>Changes for Version 0.18.0 (2024-07-11)</h2> * Remove Sx macro <code>defunconst</code>. Use <code>defun</code> instead. (breaking: webui) * The sz encoding of zettel does not make use of <code>(SPACE)</code> elements any more. Instead, space characters are encoded within the |
︙ | ︙ | |||
714 715 716 717 718 719 720 | a set/list of words and the suffix <kbd>-zids</kbd> a set/list of zettel identifier. (minor: api, webui) * Change generated URLs for zettel-creation forms. If you have bookmarked them, e.g. to create a new zettel, you should update. (minor: webui) * Remove support for metadata key <code>no-index</code> to suppress indexing | | | | 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 | a set/list of words and the suffix <kbd>-zids</kbd> a set/list of zettel identifier. (minor: api, webui) * Change generated URLs for zettel-creation forms. If you have bookmarked them, e.g. to create a new zettel, you should update. (minor: webui) * Remove support for metadata key <code>no-index</code> to suppress indexing selected zettel. It was introduced in [#0_0_11|v0.0.11], but disallows some future optimizations for searching zettel. (minor: api, webui) * Make some metadata-based searches a little bit faster by executing a (in-memory-based) full-text search first. Now only those zettel are loaded from file that contain the metadata value. (minor: api, webui) * Add an API call to retrieve the version of the Zettelstore. (minor: api) |
︙ | ︙ |
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.19.0</code> (2024-12-13). * [/uv/zettelstore-0.19.0-linux-amd64.zip|Linux] (amd64) * [/uv/zettelstore-0.19.0-linux-arm.zip|Linux] (arm6, e.g. Raspberry Pi) * [/uv/zettelstore-0.19.0-darwin-arm64.zip|macOS] (arm64) * [/uv/zettelstore-0.19.0-darwin-amd64.zip|macOS] (amd64) * [/uv/zettelstore-0.19.0-windows-amd64.zip|Windows] (amd64) 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.19.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/impri.wiki.
︙ | ︙ | |||
10 11 12 13 14 15 16 | If you do not log into this site, or login as the user "anonymous", the only personal data this web service will process is your IP adress. It will be used to send the data of the website you requested to you and to mitigate possible attacks against this website. This website is hosted by [https://ionos.de|1&1 IONOS SE]. According to | | | 10 11 12 13 14 15 16 17 18 | If you do not log into this site, or login as the user "anonymous", the only personal data this web service will process is your IP adress. It will be used to send the data of the website you requested to you and to mitigate possible attacks against this website. This website is hosted by [https://ionos.de|1&1 IONOS SE]. According to [https://www.ionos.de/hilfe/datenschutz/datenverarbeitung-von-webseitenbesuchern-ihres-ionos-produktes/andere-ionos-produkte/|their information], no processing of personal data is done by them. |
Changes to www/index.wiki.
︙ | ︙ | |||
22 23 24 25 26 27 28 | software, which often connects to Zettelstore via its API. Some of the software packages may be experimental. * [https://t73f.de/r/sx|Sx] provides an evaluator for symbolic expressions, which is used for HTML templates and more. [https://mastodon.social/tags/Zettelstore|Stay tuned] … <hr> | | | | | | | > | 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 | software, which often connects to Zettelstore via its API. Some of the software packages may be experimental. * [https://t73f.de/r/sx|Sx] provides an evaluator for symbolic expressions, which is used for HTML templates and more. [https://mastodon.social/tags/Zettelstore|Stay tuned] … <hr> <h3>Latest Release: 0.19.0 (2024-12-13)</h3> * [./download.wiki|Download] * [./changes.wiki#0_19|Change summary] * [/timeline?p=v0.19.0&bt=v0.18.0&y=ci|Check-ins for version 0.19], [/vdiff?to=v0.19.0&from=v0.18.0|content diff] * [/timeline?df=v0.19.0&y=ci|Check-ins derived from the 0.19 release], [/vdiff?from=v0.19.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://go.dev/dl/|Go] and some Go-based tools. Please read the [./build.md|instructions] for details. * [/dir?ci=trunk|Source code] * [/download|Download the source code] as a tarball or a ZIP file (you must [/login|login] as user "anonymous"). |
Changes to zettel/content.go.
︙ | ︙ | |||
73 74 75 76 77 78 79 | pos := inp.Pos for inp.Ch != input.EOS { if input.IsEOLEOS(inp.Ch) { inp.Next() pos = inp.Pos continue } | | | 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | pos := inp.Pos for inp.Ch != input.EOS { if input.IsEOLEOS(inp.Ch) { inp.Next() pos = inp.Pos continue } if !inp.IsSpace() { break } inp.Next() } zc.data = bytes.TrimRightFunc(inp.Src[pos:], unicode.IsSpace) } |
︙ | ︙ |
Changes to zettel/id/id.go.
︙ | ︙ | |||
42 43 44 45 46 47 48 | 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) | < | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | 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) DeleteTemplateZid = MustParse(api.ZidDeleteTemplate) ErrorTemplateZid = MustParse(api.ZidErrorTemplate) StartSxnZid = MustParse(api.ZidSxnStart) BaseSxnZid = MustParse(api.ZidSxnBase) PreludeSxnZid = MustParse(api.ZidSxnPrelude) EmojiZid = MustParse(api.ZidEmoji) TOCNewTemplateZid = MustParse(api.ZidTOCNewTemplate) |
︙ | ︙ | |||
161 162 163 164 165 166 167 | } res, err := Parse(s) if err != nil { panic(err) } return res } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 160 161 162 163 164 165 166 | } res, err := Parse(s) if err != nil { panic(err) } return res } |
Changes to zettel/id/id_test.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 | // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package id_test provides unit tests for testing zettel id specific functions. package id_test import ( | < | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package id_test provides unit tests for testing zettel id specific functions. package id_test import ( "testing" "zettelstore.de/z/zettel/id" ) func TestIsValid(t *testing.T) { t.Parallel() |
︙ | ︙ | |||
87 88 89 90 91 92 93 | func BenchmarkBytes(b *testing.B) { var bs []byte for range b.N { bs = id.Zid(12345678901200).Bytes() } bResult = bs } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 86 87 88 89 90 91 92 | func BenchmarkBytes(b *testing.B) { var bs []byte for range b.N { bs = id.Zid(12345678901200).Bytes() } bResult = bs } |
Changes to zettel/id/set.go.
︙ | ︙ | |||
283 284 285 286 287 288 289 | } // ----- unchecked base operations func newFromSlice(seq Slice) *Set { if l := len(seq); l == 0 { return nil | < < > | 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 | } // ----- unchecked base operations func newFromSlice(seq Slice) *Set { if l := len(seq); l == 0 { return nil } return &Set{seq: seq} } func (s *Set) add(zid Zid) { if pos, found := s.find(zid); !found { s.seq = slices.Insert(s.seq, pos, zid) } } |
︙ | ︙ |
Changes to zettel/meta/meta.go.
︙ | ︙ | |||
127 128 129 130 131 132 133 134 135 136 137 138 139 140 | registerKey(api.KeyTitle, TypeEmpty, usageUser, "") registerKey(api.KeyRole, TypeWord, usageUser, "") registerKey(api.KeyTags, TypeTagSet, usageUser, "") registerKey(api.KeySyntax, TypeWord, usageUser, "") // Properties that are inverse keys registerKey(api.KeyFolge, TypeIDSet, usageProperty, "") registerKey(api.KeySuccessors, TypeIDSet, usageProperty, "") registerKey(api.KeySubordinates, TypeIDSet, usageProperty, "") // Non-inverse keys registerKey(api.KeyAuthor, TypeString, usageUser, "") registerKey(api.KeyBack, TypeIDSet, usageProperty, "") registerKey(api.KeyBackward, TypeIDSet, usageProperty, "") | > | 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | registerKey(api.KeyTitle, TypeEmpty, usageUser, "") registerKey(api.KeyRole, TypeWord, usageUser, "") registerKey(api.KeyTags, TypeTagSet, usageUser, "") registerKey(api.KeySyntax, TypeWord, usageUser, "") // Properties that are inverse keys registerKey(api.KeyFolge, TypeIDSet, usageProperty, "") registerKey(api.KeySequel, TypeIDSet, usageProperty, "") registerKey(api.KeySuccessors, TypeIDSet, usageProperty, "") registerKey(api.KeySubordinates, TypeIDSet, usageProperty, "") // Non-inverse keys registerKey(api.KeyAuthor, TypeString, usageUser, "") registerKey(api.KeyBack, TypeIDSet, usageProperty, "") registerKey(api.KeyBackward, TypeIDSet, usageProperty, "") |
︙ | ︙ | |||
148 149 150 151 152 153 154 155 156 157 158 159 160 161 | registerKey(api.KeyFolgeRole, TypeWord, usageUser, "") registerKey(api.KeyForward, TypeIDSet, usageProperty, "") registerKey(api.KeyLang, TypeWord, usageUser, "") registerKey(api.KeyLicense, TypeEmpty, usageUser, "") registerKey(api.KeyModified, TypeTimestamp, usageComputed, "") registerKey(api.KeyPrecursor, TypeIDSet, usageUser, api.KeyFolge) registerKey(api.KeyPredecessor, TypeID, usageUser, api.KeySuccessors) registerKey(api.KeyPublished, TypeTimestamp, usageProperty, "") registerKey(api.KeyQuery, TypeEmpty, usageUser, "") registerKey(api.KeyReadOnly, TypeWord, usageUser, "") registerKey(api.KeySummary, TypeZettelmarkup, usageUser, "") registerKey(api.KeySuperior, TypeIDSet, usageUser, api.KeySubordinates) registerKey(api.KeyURL, TypeURL, usageUser, "") registerKey(api.KeyUselessFiles, TypeString, usageProperty, "") | > | 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | registerKey(api.KeyFolgeRole, TypeWord, usageUser, "") registerKey(api.KeyForward, TypeIDSet, usageProperty, "") registerKey(api.KeyLang, TypeWord, usageUser, "") registerKey(api.KeyLicense, TypeEmpty, usageUser, "") registerKey(api.KeyModified, TypeTimestamp, usageComputed, "") registerKey(api.KeyPrecursor, TypeIDSet, usageUser, api.KeyFolge) registerKey(api.KeyPredecessor, TypeID, usageUser, api.KeySuccessors) registerKey(api.KeyPrequel, TypeIDSet, usageUser, api.KeySequel) registerKey(api.KeyPublished, TypeTimestamp, usageProperty, "") registerKey(api.KeyQuery, TypeEmpty, usageUser, "") registerKey(api.KeyReadOnly, TypeWord, usageUser, "") registerKey(api.KeySummary, TypeZettelmarkup, usageUser, "") registerKey(api.KeySuperior, TypeIDSet, usageUser, api.KeySubordinates) registerKey(api.KeyURL, TypeURL, usageUser, "") registerKey(api.KeyUselessFiles, TypeString, usageProperty, "") |
︙ | ︙ |
Changes to zettel/meta/parse.go.
︙ | ︙ | |||
27 28 29 30 31 32 33 | func NewFromInput(zid id.Zid, inp *input.Input) *Meta { if inp.Ch == '-' && inp.PeekN(0) == '-' && inp.PeekN(1) == '-' { skipToEOL(inp) inp.EatEOL() } meta := New(zid) for { | | | 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | func NewFromInput(zid id.Zid, inp *input.Input) *Meta { if inp.Ch == '-' && inp.PeekN(0) == '-' && inp.PeekN(1) == '-' { skipToEOL(inp) inp.EatEOL() } meta := New(zid) for { inp.SkipSpace() switch inp.Ch { case '\r': if inp.Peek() == '\n' { inp.Next() } fallthrough case '\n': |
︙ | ︙ | |||
60 61 62 63 64 65 66 | func parseHeader(m *Meta, inp *input.Input) { pos := inp.Pos for isHeader(inp.Ch) { inp.Next() } key := inp.Src[pos:inp.Pos] | | | | < < < < < < | 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 | func parseHeader(m *Meta, inp *input.Input) { pos := inp.Pos for isHeader(inp.Ch) { inp.Next() } key := inp.Src[pos:inp.Pos] inp.SkipSpace() if inp.Ch == ':' { inp.Next() } var val []byte for { inp.SkipSpace() pos = inp.Pos skipToEOL(inp) val = append(val, inp.Src[pos:inp.Pos]...) inp.EatEOL() if !inp.IsSpace() { break } val = append(val, ' ') } addToMeta(m, string(key), string(val)) } func skipToEOL(inp *input.Input) { for { switch inp.Ch { case '\n', '\r', input.EOS: return } inp.Next() |
︙ | ︙ |