Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From v0.18.0 To trunk
2024-09-08
| ||
19:59 | Fix mapping startup error that results in timeouts ... (Leaf check-in: 850ee74ffd user: t73fde tags: trunk) | |
19:44 | Update golang dependency ... (check-in: af4e57e975 user: t73fde 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-dev |
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 | 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 |
︙ | ︙ |
Changes to box/compbox/compbox.go.
︙ | ︙ | |||
62 63 64 65 66 67 68 | 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}, id.MustParse(api.ZidWarnings): {genWarningsM, genWarningsC}, | | | 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | 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}, id.MustParse(api.ZidWarnings): {genWarningsM, genWarningsC}, 9999999996: {genMappingM, genMappingC}, // TEMP for v0.19-dev } // Get returns the one program box. func getCompBox(boxNumber int, mf box.Enricher, mapper manager.Mapper) *compBox { return &compBox{ log: kernel.Main.GetLogger(kernel.BoxService).Clone(). Str("box", "comp").Int("boxnum", int64(boxNumber)).Child(), |
︙ | ︙ | |||
138 139 140 141 142 143 144 | handle(m) } } } return nil } | < < < < < < < < < < < < < < < | 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | 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} |
︙ | ︙ |
Changes to box/compbox/mapping.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | package compbox import ( "bytes" "context" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // Zettelstore Identifier Mapping. // // In the first stage of migration process, it is a computed zettel showing a // hypothetical mapping. In later stages, it will be stored as a normal zettel // that is updated when a new zettel is created or an old zettel is deleted. func genMappingM(zid id.Zid) *meta.Meta { | > | > > > < | > < < < < < < < < < < < < < < < < < < < < < | | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | package compbox import ( "bytes" "context" "t73f.de/r/zsc/api" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // Zettelstore Identifier Mapping. // // In the first stage of migration process, it is a computed zettel showing a // hypothetical mapping. In later stages, it will be stored as a normal zettel // that is updated when a new zettel is created or an old zettel is deleted. func genMappingM(zid id.Zid) *meta.Meta { m := getTitledMeta(zid, "Zettelstore Identifier Mapping View (TEMP for v0.19-dev)") m.Set(api.KeySyntax, meta.SyntaxText) m.Set(api.KeyVisibility, api.ValueVisibilityLogin) return m } func genMappingC(ctx context.Context, cb *compBox) []byte { src, err := cb.mapper.FetchAsBytes(ctx) if err != nil { var buf bytes.Buffer buf.WriteString("**Error while fetching: ") buf.WriteString(err.Error()) buf.WriteString("**\n") return buf.Bytes() } return src } |
Changes to box/compbox/warnings.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 20 21 22 23 24 | package compbox import ( "bytes" "context" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func genWarningsM(zid id.Zid) *meta.Meta { | > | > > | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | package compbox import ( "bytes" "context" "t73f.de/r/zsc/api" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func genWarningsM(zid id.Zid) *meta.Meta { m := getTitledMeta(zid, "Zettelstore Warnings") m.Set(api.KeyVisibility, api.ValueVisibilityLogin) return m } func genWarningsC(ctx context.Context, cb *compBox) []byte { var buf bytes.Buffer buf.WriteString("* [[Zettel without stored creation date|query:created-missing:true]]\n") buf.WriteString("* [[Zettel with strange creation date|query:created<19700101000000]]\n") |
︙ | ︙ |
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.
︙ | ︙ | |||
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: "20240826110000", 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: "20240826110800", 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: "20240826110800", 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: "20240826142000", 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, |
︙ | ︙ | |||
429 430 431 432 433 434 435 436 437 438 439 440 441 442 | api.KeyTitle: "Zettelstore Application Directory", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxNone, api.KeyLang: api.ValueLangEN, api.KeyCreated: "20240703235900", api.KeyVisibility: api.ValueVisibilityLogin, }, zettel.NewContent(nil)}, id.DefaultHomeZid: { constHeader{ api.KeyTitle: "Home", api.KeyRole: api.ValueRoleZettel, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, | > > > > > > > > > > > | 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 | api.KeyTitle: "Zettelstore Application Directory", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxNone, api.KeyLang: api.ValueLangEN, api.KeyCreated: "20240703235900", api.KeyVisibility: api.ValueVisibilityLogin, }, zettel.NewContent(nil)}, id.MustParse(api.ZidMapping): { constHeader{ api.KeyTitle: "Zettelstore Identifier Mapping", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxText, api.KeyLang: api.ValueLangEN, api.KeyCreated: "20240807114600", api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityLogin, }, zettel.NewContent(nil)}, id.DefaultHomeZid: { constHeader{ api.KeyTitle: "Home", api.KeyRole: api.ValueRoleZettel, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, |
︙ | ︙ | |||
465 466 467 468 469 470 471 | //go:embed info.sxn var contentInfoSxn []byte //go:embed form.sxn var contentFormSxn []byte | < < < | 451 452 453 454 455 456 457 458 459 460 461 462 463 464 | //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/delete.sxn.
︙ | ︙ | |||
8 9 10 11 12 13 14 | ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article | | | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 "Delete Zettel " ,zid " / " ,zid-n)) (p "Do you really want to delete this zettel?") ,@(if shadowed-box `((div (@ (class "zs-info")) (h2 "Information") (p "If you delete this zettel, the previously shadowed zettel from overlayed box " ,shadowed-box " becomes available.") )) ) |
︙ | ︙ |
Changes to box/constbox/info.sxn.
︙ | ︙ | |||
8 9 10 11 12 13 14 | ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article | | < | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 "Information for Zettel " ,zid " / " ,zid-n) (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/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) |
︙ | ︙ |
Changes to box/constbox/zettel.sxn.
︙ | ︙ | |||
12 13 14 15 16 17 18 | ;;;---------------------------------------------------------------------------- `(article (header (h1 ,heading) (div (@ (class "zs-meta")) ,@(if (bound? 'edit-url) `((a (@ (href ,edit-url)) "Edit") (@H " · "))) | | | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | ;;;---------------------------------------------------------------------------- `(article (header (h1 ,heading) (div (@ (class "zs-meta")) ,@(if (bound? 'edit-url) `((a (@ (href ,edit-url)) "Edit") (@H " · "))) ,zid " / " ,zid-n (@H " · ") (a (@ (href ,info-url)) "Info") (@H " · ") "(" ,@(if (bound? 'role-url) `((a (@ (href ,role-url)) ,meta-role))) ,@(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)) |
︙ | ︙ |
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 chci := dp.cdata.Notify; chci != nil { dp.log.Trace().Zid(zid).Uint("reason", uint64(reason)).Msg("notifyChanged") chci <- box.UpdateInfo{Box: dp, Reason: reason, Zid: zid} } } func (dp *dirBox) getFileChan(zid id.Zid) chan fileCmd { // Based on https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function sum := 2166136261 ^ uint32(zid) sum *= 16777619 |
︙ | ︙ | |||
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.
︙ | ︙ | |||
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/manager/anteroom.go.
︙ | ︙ | |||
40 41 42 43 44 45 46 | last *anteroom maxLoad int } func newAnteroomQueue(maxLoad int) *anteroomQueue { return &anteroomQueue{maxLoad: maxLoad} } func (ar *anteroomQueue) EnqueueZettel(zid id.Zid) { | | | 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | last *anteroom maxLoad int } func newAnteroomQueue(maxLoad int) *anteroomQueue { return &anteroomQueue{maxLoad: maxLoad} } func (ar *anteroomQueue) EnqueueZettel(zid id.Zid) { if !zid.IsValid() || zid == id.MappingZid || zid == 9999999996 { return } ar.mx.Lock() defer ar.mx.Unlock() if ar.first == nil { ar.first = ar.makeAnteroom(zid) ar.last = ar.first |
︙ | ︙ |
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | 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) err = mgr.createMapping(ctx, zidO) } return zidO, err } return id.Invalid, box.ErrReadOnly } func (mgr *Manager) createMapping(ctx context.Context, zidO id.Zid) error { mgr.mappingMx.Lock() defer mgr.mappingMx.Unlock() mappingZettel, err := mgr.getZettel(ctx, id.MappingZid) if err != nil { mgr.mgrLog.Error().Err(err).Msg("Unable to get mapping zettel") return err } zidN := mgr.zidMapper.GetZidN(zidO) mappingZettel.Content = zettel.NewContent(mgr.zidMapper.AsBytes()) if err = mgr.UpdateZettel(ctx, mappingZettel); err != nil { mgr.mgrLog.Error().Err(err).Zid(zidO).Uint("zidN", uint64(zidN)).Msg("Unable to update mapping zettel") return err } mgr.mgrLog.Debug().Zid(zidO).Msg("add to mapping zettel") return nil } // 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 } | | | 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | if err != nil { return nil, err } } return result, nil } 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 { |
︙ | ︙ | |||
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 } | > > > < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | | > > | | > > > > > > > > > > > > > > > > > | 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 | // 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) err = mgr.deleteMapping(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} } func (mgr *Manager) deleteMapping(ctx context.Context, zidO id.Zid) error { mgr.mappingMx.Lock() defer mgr.mappingMx.Unlock() mappingZettel, err := mgr.getZettel(ctx, id.MappingZid) if err != nil { mgr.mgrLog.Error().Err(err).Msg("Unable to get mapping zettel") return err } mgr.zidMapper.DeleteO(zidO) mappingZettel.Content = zettel.NewContent(mgr.zidMapper.AsBytes()) if err = mgr.UpdateZettel(ctx, mappingZettel); err != nil { mgr.mgrLog.Error().Err(err).Zid(zidO).Msg("Unable to update mapping zettel") return err } mgr.mgrLog.Debug().Zid(zidO).Msg("remove from mapping zettel") return nil } // 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 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)) } | > > > > > > > > > > > > > > | 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 | "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 new zid if m.ZidN.IsValid() { if zidN, found := mgr.zidMapper.LookupZidN(m.Zid); found && m.ZidN != zidN { mgr.mgrLog.Error().Zid(m.Zid). Uint("stored", uint64(m.ZidN)).Uint("mapped", uint64(zidN)). Msg("mapped != stored") } } else { if zidN, found := mgr.zidMapper.LookupZidN(m.Zid); found { m.ZidN = zidN } else { mgr.mgrLog.Error().Zid(m.Zid).Msg("no mapping found") } } // Calculate computed, but stored values. _, hasCreated := m.Get(api.KeyCreated) if !hasCreated { m.Set(api.KeyCreated, computeCreated(m.Zid)) } |
︙ | ︙ |
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.DefaultHomeZid } 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.
︙ | ︙ | |||
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 | // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package manager coordinates the various boxes and indexes of a Zettelstore. package manager import ( "context" "io" "net/url" "sync" "time" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/box/manager/mapstore" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/config" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // ConnectData contains all administration related values. type ConnectData struct { Number int // number of the box, starting with 1. Config config.Config Enricher box.Enricher Notify chan<- box.UpdateInfo Mapper Mapper } // Mapper allows to inspect the mapping between old-style and new-style zettel identifier. type Mapper interface { Warnings(context.Context) (*id.Set, error) // Fetch problematic zettel identifier | > > | | 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 | // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package manager coordinates the various boxes and indexes of a Zettelstore. package manager import ( "bytes" "context" "io" "net/url" "sync" "time" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/box/manager/mapstore" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/config" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // ConnectData contains all administration related values. type ConnectData struct { Number int // number of the box, starting with 1. Config config.Config Enricher box.Enricher Notify chan<- box.UpdateInfo Mapper Mapper } // Mapper allows to inspect the mapping between old-style and new-style zettel identifier. type Mapper interface { Warnings(context.Context) (*id.Set, error) // Fetch problematic zettel identifier FetchAsBytes(context.Context) ([]byte, error) } // 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: |
︙ | ︙ | |||
99 100 101 102 103 104 105 106 107 108 109 110 111 112 | boxes []box.ManagedBox observers []box.UpdateFunc mxObserver sync.RWMutex done chan struct{} infos chan box.UpdateInfo propertyKeys strfun.Set // Set of property key names zidMapper *zidMapper // Indexer data idxLog *logger.Logger idxStore store.Store idxAr *anteroomQueue idxReady chan struct{} // Signal a non-empty anteroom to background task | > | 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | boxes []box.ManagedBox observers []box.UpdateFunc mxObserver sync.RWMutex done chan struct{} infos chan box.UpdateInfo propertyKeys strfun.Set // Set of property key names zidMapper *zidMapper mappingMx sync.Mutex // protects updates to mapping zettel // Indexer data idxLog *logger.Logger idxStore store.Store idxAr *anteroomQueue idxReady chan struct{} // Signal a non-empty anteroom to background task |
︙ | ︙ | |||
220 221 222 223 224 225 226 | 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 } | > | | | 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 | 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, isStarted) 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 } | | > > > > > | > > > > > | | 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 | cache[zid] = destutterData{ deadAt: now.Add(500 * time.Millisecond), reason: reason, } return false } func (mgr *Manager) idxEnqueue(reason box.UpdateReason, zidO id.Zid, isStarted bool) { switch reason { case box.OnReady: return case box.OnReload: mgr.idxAr.Reset() case box.OnZettel: if isStarted && zidO != id.MappingZid { if _, found := mgr.zidMapper.LookupZidN(zidO); !found { mgr.createMapping(context.Background(), zidO) } } mgr.idxAr.EnqueueZettel(zidO) case box.OnDelete: if isStarted && zidO != id.MappingZid { mgr.deleteMapping(context.Background(), zidO) } 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: } } |
︙ | ︙ | |||
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 | return err } 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() { | > > | 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 | return err } mgr.idxAr.Reset() // Ensure an initial index run mgr.done = make(chan struct{}) go mgr.notifier() mgr.waitBoxesAreStarted() mgr.setupIdentifierMapping() mgr.setState(box.StartStateStarted) mgr.notifyObserver(&box.UpdateInfo{Box: mgr, Reason: box.OnReady}) go mgr.idxIndexer() return nil } func (mgr *Manager) waitBoxesAreStarted() { |
︙ | ︙ | |||
342 343 344 345 346 347 348 349 350 351 352 353 354 355 | for _, bx := range mgr.boxes { if b, ok := bx.(box.StartStopper); ok && b.State() != box.StartStateStarted { return false } } return true } // Stop the started box. Now only the Start() function is allowed. func (mgr *Manager) Stop(ctx context.Context) { mgr.mgrMx.Lock() defer mgr.mgrMx.Unlock() if err := mgr.checkContinue(ctx); err != nil { return | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | for _, bx := range mgr.boxes { if b, ok := bx.(box.StartStopper); ok && b.State() != box.StartStateStarted { return false } } return true } func (mgr *Manager) setupIdentifierMapping() { ctx := context.Background() z, err := mgr.getZettel(ctx, id.MappingZid) if err != nil { mgr.mgrLog.Error().Err(err).Msg("unable to get identifier mapping zettel") return } if z.Content.IsBinary() { mgr.mgrLog.Error().Msg("identifier mapping is non-text zettel") return } z.Content.TrimSpace() content := z.Content.AsBytes() if err = mgr.zidMapper.parseAndUpdate(content); err != nil { mgr.mgrLog.Error().Err(err).Msg("identifier zettel parsing") } mapping, err := mgr.zidMapper.FetchAsBytes(ctx) if err != nil { mgr.mgrLog.Error().Err(err).Msg("unable to get current identifier mapping") return } if !bytes.Equal(content, mapping) { z.Content = zettel.NewContent(mapping) if err = mgr.updateZettel(ctx, z); err != nil { mgr.mgrLog.Error().Err(err).Msg("unable to write identifier mapping zettel") } else { mgr.mgrLog.Info().Msg("Mapping was updated") } } else { mgr.mgrLog.Info().Msg("No mapping update") } } // Stop the started box. Now only the Start() function is allowed. func (mgr *Manager) Stop(ctx context.Context) { mgr.mgrMx.Lock() defer mgr.mgrMx.Unlock() if err := mgr.checkContinue(ctx); err != nil { return |
︙ | ︙ | |||
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 } | | | 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 | // 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() |
︙ | ︙ |
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() |
︙ | ︙ |
Changes to box/manager/zidmapper.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package manager import ( "context" "maps" "sync" "time" "zettelstore.de/z/zettel/id" ) // zidMapper transforms old-style zettel identifier (14 digits) into new one (4 alphanums). // // Since there are no new-style identifier defined, there is only support for old-style // identifier by checking, whether they are suported as new-style or not. | > > > | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package manager import ( "bytes" "context" "fmt" "maps" "sync" "time" "t73f.de/r/zsc/input" "zettelstore.de/z/zettel/id" ) // zidMapper transforms old-style zettel identifier (14 digits) into new one (4 alphanums). // // Since there are no new-style identifier defined, there is only support for old-style // identifier by checking, whether they are suported as new-style or not. |
︙ | ︙ | |||
72 73 74 75 76 77 78 | 102: id.MustParseN("000n"), // ZidWarnings 10100: id.MustParseN("000s"), // Base HTML Template 10200: id.MustParseN("000t"), // Login Form Template 10300: id.MustParseN("000u"), // List Zettel Template 10401: id.MustParseN("000v"), // Detail Template 10402: id.MustParseN("000w"), // Info Template 10403: id.MustParseN("000x"), // Form Template | < > | 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 | 102: id.MustParseN("000n"), // ZidWarnings 10100: id.MustParseN("000s"), // Base HTML Template 10200: id.MustParseN("000t"), // Login Form Template 10300: id.MustParseN("000u"), // List Zettel Template 10401: id.MustParseN("000v"), // Detail Template 10402: id.MustParseN("000w"), // Info Template 10403: id.MustParseN("000x"), // Form Template 10405: id.MustParseN("000y"), // Delete Template 10700: id.MustParseN("000z"), // Error Template 19000: id.MustParseN("000q"), // Sxn Start Code 19990: id.MustParseN("000r"), // Sxn Base Code 20001: id.MustParseN("0010"), // Base CSS 25001: id.MustParseN("0011"), // User CSS 40001: id.MustParseN("000o"), // Generic Emoji 59900: id.MustParseN("000p"), // Sxn Prelude 60010: id.MustParseN("0012"), // zettel 60020: id.MustParseN("0013"), // confguration 60030: id.MustParseN("0014"), // role 60040: id.MustParseN("0015"), // tag 90000: id.MustParseN("0016"), // New Menu 90001: id.MustParseN("0017"), // New Zettel 90002: id.MustParseN("0018"), // New User 90003: id.MustParseN("0019"), // New Tag 90004: id.MustParseN("001a"), // New Role // 100000000, // Manual -> 0020-00yz 9999999996: id.MustParseN("00zw"), // Current ZidMapping, TEMP for v0.19-dev 9999999997: id.MustParseN("00zx"), // ZidSession 9999999998: id.MustParseN("00zy"), // ZidAppDirectory 9999999999: id.MustParseN("00zz"), // ZidMapping 10000000000: id.MustParseN("0100"), // ZidDefaultHome } toNew := maps.Clone(defined) toOld := make(map[id.ZidN]id.Zid, len(toNew)) |
︙ | ︙ | |||
144 145 146 147 148 149 150 | if !zm.isWellDefined(zid) { warnings = warnings.Add(zid) } }) return warnings, nil } | | > > > | | | | | > > > > | 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 | if !zm.isWellDefined(zid) { warnings = warnings.Add(zid) } }) return warnings, nil } func (zm *zidMapper) LookupZidN(zidO id.Zid) (id.ZidN, bool) { if !zidO.IsValid() { panic(zidO) } zm.mx.RLock() zidN, found := zm.toNew[zidO] zm.mx.RUnlock() return zidN, found } func (zm *zidMapper) GetZidN(zidO id.Zid) id.ZidN { if zidN, found := zm.LookupZidN(zidO); found { return zidN } zm.mx.Lock() defer zm.mx.Unlock() // Double check to avoid races if zidN, found := zm.toNew[zidO]; found { return zidN } |
︙ | ︙ | |||
179 180 181 182 183 184 185 | zidN := zm.nextZidN zm.nextZidN++ zm.toNew[zidO] = zidN zm.toOld[zidN] = zidO return zidN } | > > > > > > > > > > > > > > | > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > < < | < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > | 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | zidN := zm.nextZidN zm.nextZidN++ zm.toNew[zidO] = zidN zm.toOld[zidN] = zidO return zidN } func (zm *zidMapper) DeleteO(zidO id.Zid) { if _, found := zm.defined[zidO]; found { return } zm.mx.Lock() if zidN, found := zm.toNew[zidO]; found { delete(zm.toNew, zidO) delete(zm.toOld, zidN) if lastZidN := zm.nextZidN - 1; zidN == lastZidN { zm.nextZidN = lastZidN } } zm.mx.Unlock() } // AsBytes returns the current mapping as lines, where each line contains the // old and the new zettel identifier. func (zm *zidMapper) AsBytes() []byte { zm.mx.RLock() defer zm.mx.RUnlock() return zm.asBytes() } func (zm *zidMapper) asBytes() []byte { allZidsO := id.NewSetCap(len(zm.toNew)) for zidO := range zm.toNew { allZidsO = allZidsO.Add(zidO) } var buf bytes.Buffer first := true allZidsO.ForEach(func(zidO id.Zid) { if !first { buf.WriteByte('\n') } first = false zidN := zm.toNew[zidO] buf.WriteString(zidO.String()) buf.WriteByte(' ') buf.WriteString(zidN.String()) }) return buf.Bytes() } // FetchAsBytes fetches all zettel identifier and returns the mapping as lines, // where each line contains the old zid and the new zid. func (zm *zidMapper) FetchAsBytes(ctx context.Context) ([]byte, error) { allZids, err := zm.fetcher.fetchZids(ctx) if err != nil { return nil, err } allZids.ForEach(func(zidO id.Zid) { _ = zm.GetZidN(zidO) }) zm.mx.Lock() defer zm.mx.Unlock() if len(zm.toNew) != allZids.Length() { for zidO, zidN := range zm.toNew { if allZids.Contains(zidO) { continue } delete(zm.toNew, zidO) delete(zm.toOld, zidN) } } return zm.asBytes(), nil } func (zm *zidMapper) parseAndUpdate(content []byte) (err error) { zm.mx.Lock() defer zm.mx.Unlock() inp := input.NewInput(content) for inp.Ch != input.EOS { inp.SkipSpace() pos := inp.Pos zidO := readZidO(inp) if !zidO.IsValid() { inp.SkipToEOL() inp.EatEOL() if err == nil { err = fmt.Errorf("unable to parse old zid: %q", string(inp.Src[pos:inp.Pos])) } continue } inp.SkipSpace() zidN := readZidN(inp) if !zidN.IsValid() { inp.SkipToEOL() inp.EatEOL() if err == nil { err = fmt.Errorf("unable to parse new zid: %q", string(inp.Src[pos:inp.Pos])) } continue } inp.SkipToEOL() inp.EatEOL() if oldZidN, found := zm.toNew[zidO]; found { if oldZidN != zidN { err = fmt.Errorf("old zid %v already mapped to %v, overwrite: %v", zidO, oldZidN, zidN) } continue } zm.toNew[zidO] = zidN zm.toOld[zidN] = zidO zm.nextZidN = max(zm.nextZidN, zidN+1) } return err } func readZidO(inp *input.Input) id.Zid { pos := inp.Pos for '0' <= inp.Ch && inp.Ch <= '9' { inp.Next() } zidO, _ := id.Parse(string(inp.Src[pos:inp.Pos])) return zidO } func readZidN(inp *input.Input) id.ZidN { pos := inp.Pos for ('0' <= inp.Ch && inp.Ch <= '9') || ('a' <= inp.Ch && inp.Ch <= 'z') || ('A' <= inp.Ch && inp.Ch <= 'Z') { inp.Next() } zidN, _ := id.ParseN(string(inp.Src[pos:inp.Pos])) return zidN } |
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 chci := mb.cdata.Notify; chci != nil { chci <- box.UpdateInfo{Box: mb, Reason: reason, Zid: zid} } } 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" |
︙ | ︙ | |||
182 183 184 185 186 187 188 | if ds.entries == nil { return ds.logMissingEntry("update") } ds.entries[entry.Zid] = &entry return nil } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | 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 { | | | | | | | 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 | 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 } | | | | | 570 571 572 573 574 575 576 577 578 579 580 581 582 | newLen := len(newExt) if oldLen != newLen { return newLen < oldLen } return newExt < oldExt } func (ds *DirService) notifyChange(zid id.Zid, reason box.UpdateReason) { if chci := ds.infos; chci != nil { ds.log.Trace().Zid(zid).Uint("reason", uint64(reason)).Msg("notifyChange") chci <- box.UpdateInfo{Box: ds.box, Reason: reason, Zid: zid} } } |
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 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/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 | id: 00001005000000 title: Structure of Zettelstore role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20240711183257 Zettelstore is a software that manages your zettel. Since every zettel must be readable without any special tool, most zettel has to be stored as ordinary files within specific directories. Typically, file names and file content must comply to specific rules so that Zettelstore can manage them. If you add, delete, or change zettel files with other tools, e.g. a text editor, Zettelstore will monitor these actions. Zettelstore provides additional services to the user. |
︙ | ︙ | |||
29 30 31 32 33 34 35 | Every file in this directory that should be monitored by Zettelstore must have a file name that begins with 14 digits (0-9), the [[zettel identifier|00001006050000]]. If you create a new zettel via the [[web user interface|00001014000000]] or via the [[API|00001012053200]], the zettel identifier will be the timestamp of the current date and time (format is ''YYYYMMDDhhmmss''). This allows zettel to be sorted naturally by creation time.[^Zettel identifier format will be migrated to a new format after version 0.19, without reference to the creation date. See [[Alphanumeric Zettel Identifier|00001006050200]] for some details.] Since the only restriction on zettel identifiers are the 14 digits, you are free to use other digit sequences.[^Zettel identifier format will be migrated to a new format after version 0.19, without reference to the creation date.] The [[configuration zettel|00001004020000]] is one prominent example, as well as these manual zettel. | | | 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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.[^Zettel identifier format will be migrated to a new format after version 0.19, without reference to the creation date. See [[Alphanumeric Zettel Identifier|00001006050200]] for some details.] Since the only restriction on zettel identifiers are the 14 digits, you are free to use other digit sequences.[^Zettel identifier format will be migrated to a new format after version 0.19, without reference to the creation date.] 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; |
︙ | ︙ | |||
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 | id: 00001005090000 title: List of predefined zettel role: manual tags: #manual #reference #zettelstore syntax: zmk created: 20210126175322 modified: 20240711183318 The following table lists all predefined zettel with their purpose.[^Zettel identifier format will be migrated to a new format after version 0.19.] |= 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 |
︙ | ︙ | |||
26 27 28 29 30 31 32 | | [[00000000000102]] | Zettelstore Warnings | Warnings about potential problematic zettel identifier | [[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 | < | 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | | [[00000000000102]] | Zettelstore Warnings | Warnings about potential problematic zettel identifier | [[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 |
︙ | ︙ |
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: 20240711183409 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''] |
︙ | ︙ | |||
138 139 140 141 142 143 144 | ; [!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]]. | | | 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | ; [!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/00001006050200.zettel.
1 2 3 4 5 6 | id: 00001006050200 title: Alphanumeric Zettel Identifier role: manual tags: #design #manual #zettelstore syntax: zmk created: 20240705200557 | | | | | > > < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | id: 00001006050200 title: Alphanumeric Zettel Identifier role: manual tags: #design #manual #zettelstore syntax: zmk created: 20240705200557 modified: 20240807173414 precursor: 00001006050000 Timestamp-based zettel identifier (14 digits) will be migrated to a new format. Instead of using the current date and time of zettel creation, the new format is based in incrementing zettel identifier. When creating a new zettel, its identifier is calculated by adding one to the current maximum zettel identifier. The external representation if the new format identifier is a sequence of four alphanumeric characters, i.e. the 36 characters ''0'' … ''9'', and ''a'' … ''z''. The external representation is basically a ""base-36"" encoding of the number. The characters ''A'' … ''Z'' are mapped to the lower-case ''a'' … ''z''. === Migration process Please note: the following is just a plan. Plans tend to be revised if they get in contact with reality. ; Version 0.18 (current) : Provides some tools to check your own zettelstore for problematic zettel identifier. For example, zettel without metadata key ''created'' should be updated by the user, especially if the zettel identifier is below ''19700101000000''. Most likely, this is the case for zettel created before version 0.7 (2022-08-17). Zettel [[Zettelstore Warnings|00000000000102]] (''00000000000102'') lists these problematic zettel identifier. You should update your zettel to remove these warnings to ensure a smooth migration. If you have developed an application, that defines a specific zettel identifier to be used as application configuration, you should must the new zettel [[Zettelstore Application Directory|00009999999998]] (''00009999999998''). There is an explicit, but preliminary mapping of the old format to the new one, and vice versa. This mapping will be calculated with the order of the identifier in the old format. The zettel [[Zettelstore Identifier Mapping|00009999999999]] (''00009999999999'') will show this mapping. ; Version 0.19 : The new identifier format will be used initially internal. Operation to rename a zettel, i.e. assigning a new identifier to a zettel, is removed permanently. The old format with 14 digits is still used to create URIs and to link zettel. You will have some time to update your zettel data if you detect some issues. ; Version 0.20 : The internal search index is based on the new format identifier. ; Version 0.21 : The new format is used to calculate URIs and to form links. ; Version 0.22 : Old format identifier are full legacy. |
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 | id: 00001006055000 title: Reserved zettel identifier role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210721105704 modified: 20240711183638 [[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[^Zettel identifier format will be migrated to a new format after version 0.19.]. 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. |
︙ | ︙ |
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 | id: 00001018000000 title: Troubleshooting role: manual tags: #manual #zettelstore syntax: zmk created: 20211027105921 modified: 20240830155745 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. |
︙ | ︙ | |||
50 51 52 53 54 55 56 | 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"". | > > > > > > | 50 51 52 53 54 55 56 57 58 59 60 61 62 | 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.
︙ | ︙ | |||
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 | "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{ |
︙ | ︙ | |||
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 } | | | 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | } 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 { |
︙ | ︙ | |||
134 135 136 137 138 139 140 | if err == nil { gen := sxhtml.NewGenerator() length, err2 := gen.WriteListHTML(w, hobj) if err2 != nil { return length, err2 } | | | 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | 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 | // 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} } 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') } } 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 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | } } 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 { v.b.WriteString("\n\n") } first = false v.b.WriteString("> ") ast.Walk(v, &pn.Inlines) } } func (v *visitor) visitHeading(hn *ast.HeadingNode) { 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 { | > > > > > > | 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 | } } 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) | > > > > > > | 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 | 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: | > > > | < < > > > > > > > > > > > > > > > | | 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 | 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 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.7.0 github.com/yuin/goldmark v1.7.4 golang.org/x/crypto v0.27.0 golang.org/x/term v0.24.0 golang.org/x/text v0.18.0 t73f.de/r/sx v0.0.0-20240814083626-4df0ec6454b5 t73f.de/r/sxwebs v0.0.0-20240814085618-5b4b5c496c94 t73f.de/r/zsc v0.0.0-20240826124629-97640fce4430 ) require ( golang.org/x/sys v0.25.0 // indirect t73f.de/r/webs v0.0.0-20240814085020-19dac746d568 // indirect ) |
Changes to go.sum.
1 2 3 4 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= | | | | | | | | | | | | | | | | | | 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.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 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-20240814085618-5b4b5c496c94 h1:gLneaEyYotvcY/dDznzdcSXK1RqsJVi2AfeYDc1iVwM= t73f.de/r/sxwebs v0.0.0-20240814085618-5b4b5c496c94/go.mod h1:83W3QFkmrniIKv6R+Xq+imvbSolhoutTnNhW0ErJoco= t73f.de/r/webs v0.0.0-20240814085020-19dac746d568 h1:Pa+vO2r++qhcShv0p7t/gIrJ1DHPMn4gopEXLxDmoRg= t73f.de/r/webs v0.0.0-20240814085020-19dac746d568/go.mod h1:NSoOON8be62MfQZzlCApK27Jt2zhIa6Vrmo9RJ4tOnQ= t73f.de/r/zsc v0.0.0-20240826124629-97640fce4430 h1:35PQJZlo05a1rJHTreTQn6bBfTcII9UN2lMxr/7YUFk= t73f.de/r/zsc v0.0.0-20240826124629-97640fce4430/go.mod h1:PWnU0AvNxVumQiQBMBr9GeGTaAv8ZD78voHaPIs0omI= |
Changes to kernel/impl/cfg.go.
︙ | ︙ | |||
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 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/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 tests/client/client_test.go.
︙ | ︙ | |||
52 53 54 55 56 57 58 | } } func TestListZettel(t *testing.T) { const ( ownerZettel = 60 configRoleZettel = 38 | | | | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | } } func TestListZettel(t *testing.T) { const ( ownerZettel = 60 configRoleZettel = 38 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 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 | arch string os string env []string name string }{ {"amd64", "linux", nil, "zettelstore"}, {"arm", "linux", []string{"GOARM=6"}, "zettelstore"}, {"arm64", "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/htmllint/htmllint.go.
︙ | ︙ | |||
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}, | < | 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | 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 { |
︙ | ︙ |
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, r *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 | "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() |
︙ | ︙ | |||
66 67 68 69 70 71 72 | } if err != nil { a.reportUsecaseError(w, err) return } a.writeEncodedZettelPart(ctx, w, zn, em, enc, encStr, part) } | | | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | } if err != nil { a.reportUsecaseError(w, err) return } a.writeEncodedZettelPart(ctx, w, zn, em, enc, encStr, part) } }) } func (a *API) writePlainData(w http.ResponseWriter, ctx context.Context, zid id.Zid, part partType, getZettel usecase.GetZettel) { var buf bytes.Buffer var contentType string var err error |
︙ | ︙ |
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 | 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 { min, max := -1, -1 if len(actions) > 0 { acts := make([]string, 0, len(actions)) for _, act := range actions { if strings.HasPrefix(act, api.MinAction) { |
︙ | ︙ |
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/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 | "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}) |
︙ | ︙ | |||
65 66 67 68 69 70 71 | 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 actionVersion: wui.renderZettelForm(ctx, w, createZettel.PrepareVersion(origZettel), "Version Zettel", "", roleData, syntaxData) } | | | 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | 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 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 | "net/http" "os" "path/filepath" "zettelstore.de/z/web/adapter" ) func (wui *WebUI) MakeFaviconHandler(baseDir string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *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 } |
︙ | ︙ | |||
90 91 92 93 94 95 96 | err = wui.renderSxnTemplate(ctx, w, id.ZettelTemplateZid, env) } else { err = rb.err } if err != nil { wui.reportError(ctx, w, err) } | | | 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | 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 } |
︙ | ︙ | |||
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | func (wui *WebUI) bindCommonZettelData(ctx context.Context, rb *renderBinder, user, m *meta.Meta, content *zettel.Content) { strZid := m.Zid.String() apiZid := api.ZettelID(strZid) newURLBuilder := wui.NewURLBuilder rb.bindString("zid", sx.MakeString(strZid)) rb.bindString("web-url", sx.MakeString(newURLBuilder('h').SetZid(apiZid).String())) if content != nil && wui.canWrite(ctx, user, m, *content) { rb.bindString("edit-url", sx.MakeString(newURLBuilder('e').SetZid(apiZid).String())) } 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("child-url", sx.MakeString(newURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionChild).String())) rb.bindString("folge-url", sx.MakeString(newURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionFolge).String())) } | > < < < | 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 | func (wui *WebUI) bindCommonZettelData(ctx context.Context, rb *renderBinder, user, m *meta.Meta, content *zettel.Content) { strZid := m.Zid.String() apiZid := api.ZettelID(strZid) newURLBuilder := wui.NewURLBuilder rb.bindString("zid", sx.MakeString(strZid)) rb.bindString("zid-n", sx.MakeString(m.ZidN.String())) rb.bindString("web-url", sx.MakeString(newURLBuilder('h').SetZid(apiZid).String())) if content != nil && wui.canWrite(ctx, user, m, *content) { rb.bindString("edit-url", sx.MakeString(newURLBuilder('e').SetZid(apiZid).String())) } 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("child-url", sx.MakeString(newURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionChild).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/server/impl/router.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 | import ( "io" "net/http" "regexp" "strings" | < < | 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 | import ( "io" "net/http" "regexp" "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 |
︙ | ︙ |
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/changes.wiki.
1 2 3 4 5 6 7 8 9 10 11 | <title>Change Log</title> <a id="0_19"></a> <h2>Changes for Version 0.19.0 (pending)</h2> <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 | > > > > > > > > > > > > > > > > > | 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 | <title>Change Log</title> <a id="0_19"></a> <h2>Changes for Version 0.19.0 (pending)</h2> * Remove support for renaming zettel, i.e. changing zettel identifier. Was announced as deprecated in version 0.18. (breaking: api, webui) * Zettel content for zettel with ID starting with 0000 is not indexed any more. If you search / query for zettel content, these zettel will not be returned. (breaking: api, webui) * Fix wrong quote translation for markdown encoder. (minor) * Generate <th> in table header (was: <td>). Also applies to SHTML encoder. (minor: webui, api) * External links are now generatd in shtml and html with attribute rel="external" (previously: class="external"). (minor: webui, api) * Show new format zettel identifier in zettel view, info view and delete view. (minor: webui) <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 |
︙ | ︙ |
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 56 57 58 59 60 61 62 63 | 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) MappingZid = MustParse(api.ZidMapping) DefaultHomeZid = MustParse(api.ZidDefaultHome) ) const maxZid = 99999999999999 // ParseUint interprets a string as a possible zettel identifier // and returns its integer value. |
︙ | ︙ | |||
243 244 245 246 247 248 249 | d12 := uint32(zid) / (36 * 36) d1 := d12 / 36 d2 := d12 % 36 d34 := uint32(zid) % (36 * 36) d3 := d34 / 36 d4 := d34 % 36 | | | 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | d12 := uint32(zid) / (36 * 36) d1 := d12 / 36 d2 := d12 % 36 d34 := uint32(zid) % (36 * 36) d3 := d34 / 36 d4 := d34 % 36 const digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" result[0] = digits[d1] result[1] = digits[d2] result[2] = digits[d3] result[3] = digits[d4] } // IsValid determines if zettel id is a valid one, e.g. consists of max. 14 digits. func (zid ZidN) IsValid() bool { return 0 < zid && zid <= maxZidN } |
Changes to zettel/meta/meta.go.
︙ | ︙ | |||
166 167 168 169 170 171 172 173 174 175 176 177 178 179 | // NewPrefix is the prefix for metadata key in template zettel for creating new zettel. const NewPrefix = "new-" // Meta contains all meta-data of a zettel. type Meta struct { Zid id.Zid pairs map[string]string YamlSep bool } // New creates a new chunk for storing metadata. func New(zid id.Zid) *Meta { return &Meta{Zid: zid, pairs: make(map[string]string, 5)} | > | 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | // NewPrefix is the prefix for metadata key in template zettel for creating new zettel. const NewPrefix = "new-" // Meta contains all meta-data of a zettel. type Meta struct { Zid id.Zid ZidN id.ZidN pairs map[string]string YamlSep bool } // New creates a new chunk for storing metadata. func New(zid id.Zid) *Meta { return &Meta{Zid: zid, pairs: make(map[string]string, 5)} |
︙ | ︙ | |||
200 201 202 203 204 205 206 207 208 209 210 211 212 213 | return result } // Clone returns a new copy of the metadata. func (m *Meta) Clone() *Meta { return &Meta{ Zid: m.Zid, pairs: m.Map(), YamlSep: m.YamlSep, } } // Map returns a copy of the meta data as a string map. func (m *Meta) Map() map[string]string { | > | 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | return result } // Clone returns a new copy of the metadata. func (m *Meta) Clone() *Meta { return &Meta{ Zid: m.Zid, ZidN: m.ZidN, pairs: m.Map(), YamlSep: m.YamlSep, } } // Map returns a copy of the meta data as a string map. func (m *Meta) Map() map[string]string { |
︙ | ︙ |
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() |
︙ | ︙ |