Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From v0.15.0 To trunk
2023-12-01
| ||
13:29 | Cleanup changes file: tt tag to code tag ... (Leaf check-in: 93010b5f17 user: stern tags: trunk) | |
13:02 | Fix some omissions in the manual ... (check-in: 9804546d28 user: stern tags: trunk) | |
2023-10-27
| ||
06:08 | Increase version to 0.16.0-dev to begin next development cycle ... (check-in: bd019d898d user: stern tags: trunk) | |
2023-10-26
| ||
16:03 | Version 0.15.0 ... (check-in: 7cde0fccea user: stern tags: trunk, release, v0.15.0) | |
2023-10-23
| ||
14:52 | Start re-indexing zettel via API and WebUI (query action REINDEX) ... (check-in: 382096929c user: stern tags: trunk) | |
Changes to VERSION.
|
| | | 1 | 0.17.0-dev |
Changes to ast/inline.go.
︙ | ︙ | |||
191 192 193 194 195 196 197 | // FormatKind specifies the format that is applied to the inline nodes. type FormatKind int // Constants for FormatCode const ( _ FormatKind = iota | | | | | | | | > | | 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | // FormatKind specifies the format that is applied to the inline nodes. type FormatKind int // Constants for FormatCode const ( _ FormatKind = iota FormatEmph // Emphasized text FormatStrong // Strongly emphasized text FormatInsert // Inserted text FormatDelete // Deleted text FormatSuper // Superscripted text FormatSub // SubscriptedText FormatQuote // Quoted text FormatMark // Marked text FormatSpan // Generic inline container ) func (*FormatNode) inlineNode() { /* Just a marker */ } // WalkChildren walks to the formatted text. func (fn *FormatNode) WalkChildren(v Visitor) { Walk(v, &fn.Inlines) } |
︙ | ︙ |
Changes to box/compbox/log.go.
︙ | ︙ | |||
20 21 22 23 24 25 26 | ) func genLogM(zid id.Zid) *meta.Meta { m := meta.New(zid) m.Set(api.KeyTitle, "Zettelstore Log") m.Set(api.KeySyntax, meta.SyntaxText) m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreStarted).(string)) | | | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | ) func genLogM(zid id.Zid) *meta.Meta { m := meta.New(zid) m.Set(api.KeyTitle, "Zettelstore Log") m.Set(api.KeySyntax, meta.SyntaxText) m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreStarted).(string)) m.Set(api.KeyModified, kernel.Main.GetLastLogTime().Local().Format(id.TimestampLayout)) return m } func genLogC(*meta.Meta) []byte { const tsFormat = "2006-01-02 15:04:05.999999" entries := kernel.Main.RetrieveLogEntries() var buf bytes.Buffer |
︙ | ︙ |
Changes to box/constbox/base.css.
︙ | ︙ | |||
79 80 81 82 83 84 85 | h1 { font-size:1.5rem; margin:.65rem 0 } h2 { font-size:1.25rem; margin:.70rem 0 } h3 { font-size:1.15rem; margin:.75rem 0 } h4 { font-size:1.05rem; margin:.8rem 0; font-weight: bold } h5 { font-size:1.05rem; margin:.8rem 0 } h6 { font-size:1.05rem; margin:.8rem 0; font-weight: lighter } p { margin: .5rem 0 0 0 } | | | 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | h1 { font-size:1.5rem; margin:.65rem 0 } h2 { font-size:1.25rem; margin:.70rem 0 } h3 { font-size:1.15rem; margin:.75rem 0 } h4 { font-size:1.05rem; margin:.8rem 0; font-weight: bold } h5 { font-size:1.05rem; margin:.8rem 0 } h6 { font-size:1.05rem; margin:.8rem 0; font-weight: lighter } p { margin: .5rem 0 0 0 } p.zs-meta-zettel { margin-top: .5rem; margin-left: 0.5rem } li,figure,figcaption,dl { margin: 0 } dt { margin: .5rem 0 0 0 } dt+dd { margin-top: 0 } dd { margin: .5rem 0 0 2rem } dd > p:first-child { margin: 0 0 0 0 } blockquote { border-left: 0.5rem solid lightgray; |
︙ | ︙ |
Changes to box/constbox/constbox.go.
︙ | ︙ | |||
163 164 165 166 167 168 169 | id.MustParse(api.ZidDependencies): { constHeader{ api.KeyTitle: "Zettelstore Dependencies", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyReadOnly: api.ValueTrue, | | | 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | id.MustParse(api.ZidDependencies): { constHeader{ api.KeyTitle: "Zettelstore Dependencies", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityPublic, api.KeyCreated: "20210504135842", api.KeyModified: "20230601163100", }, zettel.NewContent(contentDependencies)}, id.BaseTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Base HTML Template", |
︙ | ︙ | |||
194 195 196 197 198 199 200 | zettel.NewContent(contentLoginSxn)}, id.ZettelTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230510155300", | | | 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | 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: "20231126180500", api.KeyVisibility: api.ValueVisibilityExpert, }, zettel.NewContent(contentZettelSxn)}, id.InfoTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Info HTML Template", api.KeyRole: api.ValueRoleConfiguration, |
︙ | ︙ | |||
244 245 246 247 248 249 250 | zettel.NewContent(contentDeleteSxn)}, id.ListTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore List Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230704122100", | | | 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | zettel.NewContent(contentDeleteSxn)}, id.ListTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore List Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230704122100", api.KeyModified: "20231129112800", api.KeyVisibility: api.ValueVisibilityExpert, }, zettel.NewContent(contentListZettelSxn)}, id.ErrorTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Error HTML Template", api.KeyRole: api.ValueRoleConfiguration, |
︙ | ︙ | |||
297 298 299 300 301 302 303 304 305 306 307 308 309 310 | 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.KeyVisibility: api.ValueVisibilityPublic, }, zettel.NewContent(contentBaseCSS)}, id.MustParse(api.ZidUserCSS): { constHeader{ api.KeyTitle: "Zettelstore User CSS", api.KeyRole: api.ValueRoleConfiguration, | > | 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 | 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: "20231129112800", api.KeyVisibility: api.ValueVisibilityPublic, }, zettel.NewContent(contentBaseCSS)}, id.MustParse(api.ZidUserCSS): { constHeader{ api.KeyTitle: "Zettelstore User CSS", api.KeyRole: api.ValueRoleConfiguration, |
︙ | ︙ | |||
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 | id.TOCNewTemplateZid: { constHeader{ api.KeyTitle: "New Menu", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyCreated: "20210217161829", api.KeyVisibility: api.ValueVisibilityCreator, }, zettel.NewContent(contentNewTOCZettel)}, id.MustParse(api.ZidTemplateNewZettel): { constHeader{ api.KeyTitle: "New Zettel", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyCreated: "20201028185209", api.KeyModified: "20230929132900", meta.NewPrefix + api.KeyRole: api.ValueRoleZettel, api.KeyVisibility: api.ValueVisibilityCreator, }, zettel.NewContent(nil)}, id.MustParse(api.ZidTemplateNewTag): { constHeader{ api.KeyTitle: "New Tag", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyCreated: "20230929132400", | > > > > > > > > > > > > | 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 | id.TOCNewTemplateZid: { constHeader{ api.KeyTitle: "New Menu", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyCreated: "20210217161829", api.KeyModified: "20231129111800", api.KeyVisibility: api.ValueVisibilityCreator, }, zettel.NewContent(contentNewTOCZettel)}, id.MustParse(api.ZidTemplateNewZettel): { constHeader{ api.KeyTitle: "New Zettel", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyCreated: "20201028185209", api.KeyModified: "20230929132900", meta.NewPrefix + api.KeyRole: api.ValueRoleZettel, api.KeyVisibility: api.ValueVisibilityCreator, }, zettel.NewContent(nil)}, id.MustParse(api.ZidTemplateNewRole): { constHeader{ api.KeyTitle: "New Role", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyCreated: "20231129110800", meta.NewPrefix + api.KeyRole: api.ValueRoleRole, meta.NewPrefix + api.KeyTitle: "", api.KeyVisibility: api.ValueVisibilityCreator, }, zettel.NewContent(nil)}, id.MustParse(api.ZidTemplateNewTag): { constHeader{ api.KeyTitle: "New Tag", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyCreated: "20230929132400", |
︙ | ︙ | |||
363 364 365 366 367 368 369 370 371 372 373 374 375 376 | api.KeyCreated: "20201028185209", meta.NewPrefix + api.KeyCredential: "", meta.NewPrefix + api.KeyUserID: "", meta.NewPrefix + api.KeyUserRole: api.ValueUserRoleReader, api.KeyVisibility: api.ValueVisibilityOwner, }, zettel.NewContent(nil)}, id.DefaultHomeZid: { constHeader{ api.KeyTitle: "Home", api.KeyRole: api.ValueRoleZettel, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyCreated: "20210210190757", | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 | api.KeyCreated: "20201028185209", meta.NewPrefix + api.KeyCredential: "", meta.NewPrefix + api.KeyUserID: "", meta.NewPrefix + api.KeyUserRole: api.ValueUserRoleReader, api.KeyVisibility: api.ValueVisibilityOwner, }, zettel.NewContent(nil)}, id.MustParse(api.ZidRoleZettelZettel): { constHeader{ api.KeyTitle: api.ValueRoleZettel, api.KeyRole: api.ValueRoleRole, api.KeySyntax: meta.SyntaxZmk, api.KeyCreated: "20231129161400", api.KeyLang: api.ValueLangEN, api.KeyVisibility: api.ValueVisibilityLogin, }, zettel.NewContent(contentRoleZettel)}, id.MustParse(api.ZidRoleConfigurationZettel): { constHeader{ api.KeyTitle: api.ValueRoleConfiguration, api.KeyRole: api.ValueRoleRole, api.KeySyntax: meta.SyntaxZmk, api.KeyCreated: "20231129162800", api.KeyLang: api.ValueLangEN, api.KeyVisibility: api.ValueVisibilityLogin, }, zettel.NewContent(contentRoleConfiguration)}, id.MustParse(api.ZidRoleRoleZettel): { constHeader{ api.KeyTitle: api.ValueRoleRole, api.KeyRole: api.ValueRoleRole, api.KeySyntax: meta.SyntaxZmk, api.KeyCreated: "20231129162900", api.KeyLang: api.ValueLangEN, api.KeyVisibility: api.ValueVisibilityLogin, }, zettel.NewContent(contentRoleRole)}, id.MustParse(api.ZidRoleTagZettel): { constHeader{ api.KeyTitle: api.ValueRoleTag, api.KeyRole: api.ValueRoleRole, api.KeySyntax: meta.SyntaxZmk, api.KeyCreated: "20231129162000", api.KeyLang: api.ValueLangEN, api.KeyVisibility: api.ValueVisibilityLogin, }, zettel.NewContent(contentRoleTag)}, id.DefaultHomeZid: { constHeader{ api.KeyTitle: "Home", api.KeyRole: api.ValueRoleZettel, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyCreated: "20210210190757", |
︙ | ︙ | |||
427 428 429 430 431 432 433 434 435 436 | var contentBaseCSS []byte //go:embed emoji_spin.gif var contentEmoji []byte //go:embed newtoc.zettel var contentNewTOCZettel []byte //go:embed home.zettel var contentHomeZettel []byte | > > > > > > > > > > > > | 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 | var contentBaseCSS []byte //go:embed emoji_spin.gif var contentEmoji []byte //go:embed newtoc.zettel var contentNewTOCZettel []byte //go:embed rolezettel.zettel var contentRoleZettel []byte //go:embed roleconfiguration.zettel var contentRoleConfiguration []byte //go:embed rolerole.zettel var contentRoleRole []byte //go:embed roletag.zettel var contentRoleTag []byte //go:embed home.zettel var contentHomeZettel []byte |
Changes to box/constbox/listzettel.sxn.
1 2 3 4 5 | `(article (header (h1 ,heading)) (form (@ (action ,search-url)) (input (@ (class "zs-input") (type "text") (placeholder "Search..") (name ,query-key-query) (value ,query-value) (dir "auto")))) ,@(if (bound? 'tag-zettel) | | | > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | `(article (header (h1 ,heading)) (form (@ (action ,search-url)) (input (@ (class "zs-input") (type "text") (placeholder "Search..") (name ,query-key-query) (value ,query-value) (dir "auto")))) ,@(if (bound? 'tag-zettel) `((p (@ (class "zs-meta-zettel")) "Tag zettel: " ,@tag-zettel)) ) ,@(if (bound? 'create-tag-zettel) `((p (@ (class "zs-meta-zettel")) "Create tag zettel: " ,@create-tag-zettel)) ) ,@(if (bound? 'role-zettel) `((p (@ (class "zs-meta-zettel")) "Role zettel: " ,@role-zettel)) ) ,@(if (bound? 'create-role-zettel) `((p (@ (class "zs-meta-zettel")) "Create role zettel: " ,@create-role-zettel)) ) ,@content ,@endnotes (form (@ (action ,(if (bound? 'create-url) create-url))) "Other encodings: " (a (@ (href ,data-url)) "data") ", " |
︙ | ︙ |
Changes to box/constbox/newtoc.zettel.
1 2 3 | This zettel lists all zettel that should act as a template for new zettel. These zettel will be included in the ""New"" menu of the WebUI. * [[New Zettel|00000000090001]] | > | | 1 2 3 4 5 6 | This zettel lists all zettel that should act as a template for new zettel. These zettel will be included in the ""New"" menu of the WebUI. * [[New Zettel|00000000090001]] * [[New Role|00000000090004]] * [[New Tag|00000000090003]] * [[New User|00000000090002]] |
Changes to box/constbox/prelude.sxn.
︙ | ︙ | |||
15 16 17 18 19 20 21 22 23 24 25 26 27 28 | ;; Constants NIL and T (defconst NIL ()) (defconst T 'T) ;; Function not (defun not (x) (if x NIL T)) (defconst not not) ;; let macro ;; ;; (let (BINDING ...) EXPR ...), where BINDING is a list of two elements ;; (SYMBOL EXPR) (defmacro let (bindings . body) `((lambda ,(map car bindings) ,@body) ,@(map cadr bindings))) | > > > > > > | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | ;; Constants NIL and T (defconst NIL ()) (defconst T 'T) ;; Function not (defun not (x) (if x NIL T)) (defconst not not) ;; defunconst macro to define functions that are bound as a constant. ;; ;; (defunconst NAME ARGS EXPR ...) (defmacro defunconst (name args . body) `(begin (defun ,name ,args ,@body) (defconst ,name ,name))) ;; let macro ;; ;; (let (BINDING ...) EXPR ...), where BINDING is a list of two elements ;; (SYMBOL EXPR) (defmacro let (bindings . body) `((lambda ,(map car bindings) ,@body) ,@(map cadr bindings))) |
︙ | ︙ |
Added box/constbox/roleconfiguration.zettel.
> > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | Zettel with role ""configuration"" are used within Zettelstore to manage and to show the current configuration of the software. Typically, there are some public zettel that show the license of this software, its dependencies, some CSS code to make the default web user interface a litte bit nicer, and the defult image to singal a broken image. Other zettel are only visible if an user has authenticated itself, or if there is no authentication enabled. In this case, one additional configuration zettel is the zettel containing the version number of this software. Other zettel are showing the supported metadata keys and supported syntax values. Zettel that allow to configure the menu of template to create new zettel are also using the role ""configuration"". Most important is the zettel that contains the runtime configuration. You may change its metadata value to change the behaviour of the software. One configuration is the ""expert mode"". If enabled, and if you are authorized so see them, you will discover some more zettel. For example, HTML templates to customize the default web user interface, to show the application log, to see statistics about zettel boxes, to show the host name and it operating system, and many more. You are allowed to add your own configuration zettel, for example if you want to customize the look and feel of zettel by placing relevant data into your own zettel. By default, user zettel (for authentification) use also the role ""configuration"". However, you are allowed to change this. |
Added box/constbox/rolerole.zettel.
> > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 | A zettel with the role ""role"" describes a specific role. The described role must be the title of such a zettel. This zettel is such a zettel, as it describes the meaning of the role ""role"". Therefore it has the title ""role"" too. If you like, this zettel is a meta-role. You are free to create your own role-describing zettel. For example, you want to document the intended meaning of the role. You might also be interested to describe needed metadata so that some software is enabled to analyse or to process your zettel. |
Added box/constbox/roletag.zettel.
> > > > > > | 1 2 3 4 5 6 | A zettel with role ""tag"" is a zettel that describes specific tag. The tag name must be the title of such a zettel. Such zettel are similar to this specific zettel: this zettel describes zettel with a role ""tag"". These zettel with the role ""tag"" describe specific tags. These might form a hierarchy of meta-tags (and meta-roles). |
Added box/constbox/rolezettel.zettel.
> > > > > > > | 1 2 3 4 5 6 7 | A zettel with the role ""zettel"" is typically used to document your own thoughts. Such zettel are the main reason to use the software Zettelstore. The only predefined zettel with the role ""zettel"" is the [[default home zettel|00010000000000]], which contains some welcome information. You are free to change this. In this case you should modify this zettel too, so that it reflects your own use of zettel with the role ""zettel"". |
Changes to box/constbox/wuicode.sxn.
1 2 3 4 5 6 7 8 9 10 11 12 13 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-present Detlef Stern ;;; ;;; This file is part of Zettelstore. ;;; ;;; Zettelstore is licensed under the latest version of the EUPL (European ;;; Union Public License). Please see file LICENSE.txt for your rights and ;;; obligations under this license. ;;;---------------------------------------------------------------------------- ;; Contains WebUI specific code, but not related to a specific template. ;; wui-list-item returns the argument as a HTML list item. | | | | | | | | | | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-present Detlef Stern ;;; ;;; This file is part of Zettelstore. ;;; ;;; Zettelstore is licensed under the latest version of the EUPL (European ;;; Union Public License). Please see file LICENSE.txt for your rights and ;;; obligations under this license. ;;;---------------------------------------------------------------------------- ;; Contains WebUI specific code, but not related to a specific template. ;; wui-list-item returns the argument as a HTML list item. (defunconst wui-item (s) `(li ,s)) ;; wui-table-row takes a pair and translates it into a HTML table row with ;; two columns. (defunconst wui-table-row (p) `(tr (td ,(car p)) (td ,(cdr p)))) ;; wui-valid-link translates a local link into a HTML link. A link is a pair ;; (valid . url). If valid is not truish, only the invalid url is returned. (defunconst wui-valid-link (l) (if (car l) `(li (a (@ (href ,(cdr l))) ,(cdr l))) `(li ,(cdr l)))) ;; wui-link takes a link (title . url) and returns a HTML reference. (defunconst wui-link (q) `(a (@ (href ,(cdr q))) ,(car q))) ;; wui-item-link taks a pair (text . url) and returns a HTML link inside ;; a list item. (defunconst wui-item-link (q) `(li ,(wui-link q))) ;; wui-tdata-link taks a pair (text . url) and returns a HTML link inside ;; a table data item. (defunconst 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. (defunconst wui-item-popup-link (e) `(li (a (@ (href ,e) (target "_blank") (rel "noopener noreferrer")) ,e))) ;; wui-option-value returns a value for an HTML option element. (defunconst wui-option-value (v) `(option (@ (value ,v)))) ;; wui-datalist returns a HTML datalist with the given HTML identifier and a ;; list of values. (defunconst wui-datalist (id lst) (if lst `((datalist (@ (id ,id)) ,@(map wui-option-value lst))))) ;; wui-pair-desc-item takes a pair '(term . text) and returns a list with ;; a HTML description term and a HTML description data. (defunconst wui-pair-desc-item (p) `((dt ,(car p)) (dd ,(cdr p)))) ;; wui-meta-desc returns a HTML description list made from the list of pairs ;; given. (defunconst wui-meta-desc (l) `(dl ,@(apply append (map wui-pair-desc-item l)))) ;; wui-enc-matrix returns the HTML table of all encodings and parts. (defunconst wui-enc-matrix (matrix) `(table ,@(map (lambda (row) `(tr (th ,(car row)) ,@(map wui-tdata-link (cdr row)))) matrix))) ;; CSS-ROLE-map is a mapping (pair list, assoc list) of role names to zettel ;; identifier. It is used in the base template to update the metadata of the ;; HTML page to include some role specific CSS code. ;; Referenced in function "ROLE-DEFAULT-meta". (defvar CSS-ROLE-map '()) ;; ROLE-DEFAULT-meta returns some metadata for the base template. Any role ;; specific code should include the returned list of this function. (defunconst ROLE-DEFAULT-meta (env) `(,@(let* ((meta-role (environment-lookup 'meta-role env)) (entry (assoc CSS-ROLE-map meta-role))) (if (pair? entry) `((link (@ (rel "stylesheet") (href ,(zid-content-path (cdr entry)))))) ) ) ) ) ;;; ACTION-SEPARATOR defines a HTML value that separates actions links. (defvar ACTION-SEPARATOR '(@H " · ")) ;;; ROLE-DEFAULT-actions returns the default text for actions. (defunconst ROLE-DEFAULT-actions (env) `(,@(let ((copy-url (environment-lookup 'copy-url env))) (if (defined? copy-url) `((@H " · ") (a (@ (href ,copy-url)) "Copy")))) ,@(let ((version-url (environment-lookup 'version-url env))) (if (defined? version-url) `((@H " · ") (a (@ (href ,version-url)) "Version")))) ,@(let ((child-url (environment-lookup 'child-url env))) (if (defined? child-url) `((@H " · ") (a (@ (href ,child-url)) "Child")))) ,@(let ((folge-url (environment-lookup 'folge-url env))) (if (defined? folge-url) `((@H " · ") (a (@ (href ,folge-url)) "Folge")))) ) ) ;;; ROLE-tag-actions returns an additional action "Zettel" for zettel with role "tag". (defunconst ROLE-tag-actions (env) `(,@(ROLE-DEFAULT-actions env) ,@(let ((title (environment-lookup 'title env))) (if (and (defined? title) title) `(,ACTION-SEPARATOR (a (@ (href ,(query->url (string-append "tags:" title)))) "Zettel")) ) ) ) ) ;;; ROLE-DEFAULT-heading returns the default text for headings, below the ;;; references of a zettel. In most cases it should be called from an ;;; overwriting function. (defunconst ROLE-DEFAULT-heading (env) `(,@(let ((meta-url (environment-lookup 'meta-url env))) (if (defined? meta-url) `((br) "URL: " ,(url-to-html meta-url)))) ,@(let ((meta-author (environment-lookup 'meta-author env))) (if (and (defined? meta-author) meta-author) `((br) "By " ,meta-author))) ) ) |
Changes to box/constbox/zettel.sxn.
︙ | ︙ | |||
17 18 19 20 21 22 23 | ,@(ROLE-DEFAULT-heading (current-environment)) ) ) ,@content ,endnotes ,@(if (or folge-links subordinate-links back-links successor-links) `((nav | | | | | | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | ,@(ROLE-DEFAULT-heading (current-environment)) ) ) ,@content ,endnotes ,@(if (or folge-links subordinate-links back-links successor-links) `((nav ,@(if folge-links `((details (@ (,folge-open)) (summary "Folgezettel") (ul ,@(map wui-item-link folge-links))))) ,@(if subordinate-links `((details (@ (,subordinate-open)) (summary "Subordinates") (ul ,@(map wui-item-link subordinate-links))))) ,@(if back-links `((details (@ (,back-open)) (summary "Incoming") (ul ,@(map wui-item-link back-links))))) ,@(if successor-links `((details (@ (,successor-open)) (summary "Successors") (ul ,@(map wui-item-link successor-links))))) )) ) ) |
Changes to box/manager/anteroom.go.
︙ | ︙ | |||
21 22 23 24 25 26 27 | const ( arNothing arAction = iota arReload arZettel ) type anteroom struct { | < < | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | const ( arNothing arAction = iota arReload arZettel ) type anteroom struct { next *anteroom waiting id.Set curLoad int reload bool } type anteroomQueue struct { mx sync.Mutex first *anteroom last *anteroom maxLoad int } func newAnteroomQueue(maxLoad int) *anteroomQueue { return &anteroomQueue{maxLoad: maxLoad} } |
︙ | ︙ | |||
69 70 71 72 73 74 75 | } room := ar.makeAnteroom(zid) ar.last.next = room ar.last = room } func (ar *anteroomQueue) makeAnteroom(zid id.Zid) *anteroom { | < < < < < | | | | | < | < < | | | < > | | < < < | | | | | | | | | | | | > | | 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | } room := ar.makeAnteroom(zid) ar.last.next = room ar.last = room } func (ar *anteroomQueue) makeAnteroom(zid id.Zid) *anteroom { if zid == id.Invalid { panic(zid) } waiting := id.NewSetCap(max(ar.maxLoad, 100), zid) return &anteroom{next: nil, waiting: waiting, curLoad: 1, reload: false} } func (ar *anteroomQueue) Reset() { ar.mx.Lock() defer ar.mx.Unlock() ar.first = &anteroom{next: nil, waiting: nil, curLoad: 0, reload: true} ar.last = ar.first } func (ar *anteroomQueue) Reload(allZids id.Set) { ar.mx.Lock() defer ar.mx.Unlock() ar.deleteReloadedRooms() if ns := len(allZids); ns > 0 { ar.first = &anteroom{next: ar.first, waiting: allZids, curLoad: ns, reload: true} if ar.first.next == nil { ar.last = ar.first } } else { ar.first = nil ar.last = nil } } func (ar *anteroomQueue) deleteReloadedRooms() { room := ar.first for room != nil && room.reload { room = room.next } ar.first = room if room == nil { ar.last = nil } } func (ar *anteroomQueue) Dequeue() (arAction, id.Zid, bool) { ar.mx.Lock() defer ar.mx.Unlock() first := ar.first if first != nil { if first.waiting == nil && first.reload { ar.removeFirst() return arReload, id.Invalid, false } for zid := range first.waiting { delete(first.waiting, zid) if len(first.waiting) == 0 { ar.removeFirst() } return arZettel, zid, first.reload } ar.removeFirst() } return arNothing, id.Invalid, false } func (ar *anteroomQueue) removeFirst() { ar.first = ar.first.next if ar.first == nil { ar.last = nil } } |
Changes to box/manager/anteroom_test.go.
︙ | ︙ | |||
16 17 18 19 20 21 22 | "zettelstore.de/z/zettel/id" ) func TestSimple(t *testing.T) { t.Parallel() ar := newAnteroomQueue(2) ar.EnqueueZettel(id.Zid(1)) | | | | | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | "zettelstore.de/z/zettel/id" ) func TestSimple(t *testing.T) { t.Parallel() ar := newAnteroomQueue(2) ar.EnqueueZettel(id.Zid(1)) action, zid, lastReload := ar.Dequeue() if zid != id.Zid(1) || action != arZettel || lastReload { t.Errorf("Expected arZettel/1/false, but got %v/%v/%v", action, zid, lastReload) } _, zid, _ = ar.Dequeue() if zid != id.Invalid { t.Errorf("Expected invalid Zid, but got %v", zid) } ar.EnqueueZettel(id.Zid(1)) ar.EnqueueZettel(id.Zid(2)) |
︙ | ︙ |
Changes to box/manager/indexer.go.
︙ | ︙ | |||
89 90 91 92 93 94 95 | if !mgr.idxSleepService(timer, timerDuration) { return } } } func (mgr *Manager) idxWorkService(ctx context.Context) { | < | < | < < < < < > | < | 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 | if !mgr.idxSleepService(timer, timerDuration) { return } } } func (mgr *Manager) idxWorkService(ctx context.Context) { var start time.Time for { switch action, zid, lastReload := mgr.idxAr.Dequeue(); action { case arNothing: return case arReload: mgr.idxLog.Debug().Msg("reload") zids, err := mgr.FetchZids(ctx) if err == nil { start = time.Now() mgr.idxAr.Reload(zids) mgr.idxMx.Lock() mgr.idxLastReload = time.Now().Local() mgr.idxSinceReload = 0 mgr.idxMx.Unlock() } case arZettel: mgr.idxLog.Debug().Zid(zid).Msg("zettel") zettel, err := mgr.GetZettel(ctx, zid) if err != nil { // Zettel was deleted or is not accessible b/c of other reasons mgr.idxLog.Trace().Zid(zid).Msg("delete") mgr.idxDeleteZettel(ctx, zid) continue } mgr.idxLog.Trace().Zid(zid).Msg("update") mgr.idxUpdateZettel(ctx, zettel) mgr.idxMx.Lock() if lastReload { mgr.idxDurReload = time.Since(start) } mgr.idxSinceReload++ mgr.idxMx.Unlock() } } } func (mgr *Manager) idxSleepService(timer *time.Timer, timerDuration time.Duration) bool { select { case _, ok := <-mgr.idxReady: |
︙ | ︙ | |||
187 188 189 190 191 192 193 | is := parser.ParseMetadata(pair.Value) collectInlineIndexData(&is, cData) case meta.TypeURL: if _, err := url.Parse(pair.Value); err == nil { cData.urls.Add(pair.Value) } default: | > | | > > > > > > > > > > > > > | 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 | is := parser.ParseMetadata(pair.Value) collectInlineIndexData(&is, cData) case meta.TypeURL: if _, err := url.Parse(pair.Value); err == nil { cData.urls.Add(pair.Value) } default: if descr.Type.IsSet { for _, val := range meta.ListFromValue(pair.Value) { idxCollectMetaValue(cData.words, val) } } else { idxCollectMetaValue(cData.words, pair.Value) } } } } func idxCollectMetaValue(stWords store.WordSet, value string) { if words := strfun.NormalizeWords(value); len(words) > 0 { for _, word := range words { stWords.Add(word) } } else { stWords.Add(value) } } func (mgr *Manager) idxProcessData(ctx context.Context, zi *store.ZettelIndex, cData *collectData) { for ref := range cData.refs { if mgr.HasZettel(ctx, ref) { zi.AddBackRef(ref) } else { zi.AddDeadRef(ref) |
︙ | ︙ |
Changes to box/manager/memstore/memstore.go.
︙ | ︙ | |||
266 267 268 269 270 271 272 | } } } return back } func (ms *memStore) UpdateReferences(_ context.Context, zidx *store.ZettelIndex) id.Set { | < > | 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 | } } } return back } func (ms *memStore) UpdateReferences(_ context.Context, zidx *store.ZettelIndex) id.Set { ms.mx.Lock() defer ms.mx.Unlock() m := ms.makeMeta(zidx) zi, ziExist := ms.idx[zidx.Zid] if !ziExist || zi == nil { zi = &zettelData{} ziExist = false } // Is this zettel an old dead reference mentioned in other zettel? |
︙ | ︙ |
Changes to box/notify/fsdir.go.
︙ | ︙ | |||
163 164 165 166 167 168 169 | } return true } if ev.Has(fsnotify.Create) { err := fsdn.base.Add(fsdn.path) if err != nil { | | | 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | } return true } if ev.Has(fsnotify.Create) { err := fsdn.base.Add(fsdn.path) if err != nil { fsdn.log.Error().Err(err).Str("name", fsdn.path).Msg("Unable to add directory") select { case fsdn.events <- Event{Op: Error, Err: err}: case <-fsdn.done: fsdn.log.Trace().Int("i", 2).Msg("done dir event processing") return false } } |
︙ | ︙ |
Changes to cmd/cmd_file.go.
︙ | ︙ | |||
39 40 41 42 43 44 45 | zettel.Zettel{ Meta: m, Content: zettel.NewContent(inp.Src[inp.Pos:]), }, m.GetDefault(api.KeySyntax, meta.SyntaxZmk), nil, ) | | | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | zettel.Zettel{ Meta: m, Content: zettel.NewContent(inp.Src[inp.Pos:]), }, m.GetDefault(api.KeySyntax, meta.SyntaxZmk), nil, ) encdr := encoder.Create(api.Encoder(enc), &encoder.CreateParameter{Lang: m.GetDefault(api.KeyLang, api.ValueLangEN)}) if encdr == nil { fmt.Fprintf(os.Stderr, "Unknown format %q\n", enc) return 2, nil } _, err = encdr.WriteZettel(os.Stdout, z, parser.ParseMetadata) if err != nil { return 2, err |
︙ | ︙ |
Changes to cmd/cmd_run.go.
︙ | ︙ | |||
63 64 65 66 67 68 69 70 71 72 73 74 75 76 | ucGetAllZettel := usecase.NewGetAllZettel(protectedBoxManager) ucGetZettel := usecase.NewGetZettel(protectedBoxManager) ucParseZettel := usecase.NewParseZettel(rtConfig, ucGetZettel) ucQuery := usecase.NewQuery(protectedBoxManager) ucEvaluate := usecase.NewEvaluate(rtConfig, &ucGetZettel, &ucQuery) ucQuery.SetEvaluate(&ucEvaluate) ucTagZettel := usecase.NewTagZettel(protectedBoxManager, &ucQuery) ucListSyntax := usecase.NewListSyntax(protectedBoxManager) ucListRoles := usecase.NewListRoles(protectedBoxManager) ucDelete := usecase.NewDeleteZettel(logUc, protectedBoxManager) ucUpdate := usecase.NewUpdateZettel(logUc, protectedBoxManager) ucRename := usecase.NewRenameZettel(logUc, protectedBoxManager) ucRefresh := usecase.NewRefresh(logUc, protectedBoxManager) ucReIndex := usecase.NewReIndex(logUc, protectedBoxManager) | > | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | ucGetAllZettel := usecase.NewGetAllZettel(protectedBoxManager) ucGetZettel := usecase.NewGetZettel(protectedBoxManager) ucParseZettel := usecase.NewParseZettel(rtConfig, ucGetZettel) ucQuery := usecase.NewQuery(protectedBoxManager) ucEvaluate := usecase.NewEvaluate(rtConfig, &ucGetZettel, &ucQuery) 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) ucRename := usecase.NewRenameZettel(logUc, protectedBoxManager) ucRefresh := usecase.NewRefresh(logUc, protectedBoxManager) ucReIndex := usecase.NewReIndex(logUc, protectedBoxManager) |
︙ | ︙ | |||
101 102 103 104 105 106 107 | 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)) webSrv.AddZettelRoute('e', server.MethodGet, wui.MakeEditGetZettelHandler(ucGetZettel, ucListRoles, ucListSyntax)) webSrv.AddZettelRoute('e', server.MethodPost, wui.MakeEditSetZettelHandler(&ucUpdate)) } webSrv.AddListRoute('g', server.MethodGet, wui.MakeGetGoActionHandler(&ucRefresh)) | | | | 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 | 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)) webSrv.AddZettelRoute('e', server.MethodGet, wui.MakeEditGetZettelHandler(ucGetZettel, ucListRoles, ucListSyntax)) webSrv.AddZettelRoute('e', server.MethodPost, wui.MakeEditSetZettelHandler(&ucUpdate)) } webSrv.AddListRoute('g', server.MethodGet, wui.MakeGetGoActionHandler(&ucRefresh)) webSrv.AddListRoute('h', server.MethodGet, wui.MakeListHTMLMetaHandler(&ucQuery, &ucTagZettel, &ucRoleZettel, &ucReIndex)) webSrv.AddZettelRoute('h', server.MethodGet, wui.MakeGetHTMLZettelHandler(&ucEvaluate, ucGetZettel)) webSrv.AddListRoute('i', server.MethodGet, wui.MakeGetLoginOutHandler()) webSrv.AddListRoute('i', server.MethodPost, wui.MakePostLoginHandler(&ucAuthenticate)) webSrv.AddZettelRoute('i', server.MethodGet, wui.MakeGetInfoHandler( ucParseZettel, &ucEvaluate, ucGetZettel, ucGetAllZettel, &ucQuery)) // API webSrv.AddListRoute('a', server.MethodPost, a.MakePostLoginHandler(&ucAuthenticate)) webSrv.AddListRoute('a', server.MethodPut, a.MakeRenewAuthHandler()) webSrv.AddListRoute('x', server.MethodGet, a.MakeGetDataHandler(ucVersion)) 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)) webSrv.AddZettelRoute('z', server.MethodMove, a.MakeRenameZettelHandler(&ucRename)) } |
︙ | ︙ |
Changes to config/config.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 | "context" "zettelstore.de/z/zettel/meta" ) // Key values that are supported by Config.Get const ( | | | > > > > | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | "context" "zettelstore.de/z/zettel/meta" ) // Key values that are supported by Config.Get const ( KeyFooterZettel = "footer-zettel" KeyHomeZettel = "home-zettel" KeyShowBackLinks = "show-back-links" KeyShowFolgeLinks = "show-folge-links" KeyShowSubordinateLinks = "show-subordinate-links" KeyShowSuccessorLinks = "show-successor-links" // api.KeyLang ) // Config allows to retrieve all defined configuration values that can be changed during runtime. type Config interface { AuthConfig |
︙ | ︙ |
Changes to docs/manual/00001000000000.zettel.
1 2 3 4 5 | id: 00001000000000 title: Zettelstore Manual role: manual tags: #manual #zettelstore syntax: zmk | | | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | id: 00001000000000 title: Zettelstore Manual role: manual tags: #manual #zettelstore syntax: zmk created: 20210301190630 modified: 20231125185455 show-back-links: false * [[Introduction|00001001000000]] * [[Design goals|00001002000000]] * [[Installation|00001003000000]] * [[Configuration|00001004000000]] * [[Structure of Zettelstore|00001005000000]] * [[Layout of a zettel|00001006000000]] |
︙ | ︙ |
Changes to docs/manual/00001004020000.zettel.
1 2 3 4 5 6 | id: 00001004020000 title: Configure the running Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 | | > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | id: 00001004020000 title: Configure the running Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20231126180829 show-back-links: false You can configure a running Zettelstore by modifying the special zettel with the ID [[00000000000100]]. This zettel is called __configuration zettel__. The following metadata keys change the appearance / behavior of Zettelstore. Some of them can be overwritten in an [[user zettel|00001010040200]], a subset of those may be overwritten in zettel that is currently used. See the full list of [[metadata that may be overwritten|00001004020200]]. ; [!default-copyright|''default-copyright''] : Copyright value to be used when rendering content. Can be overwritten in a zettel with [[meta key|00001006020000]] ''copyright''. Default: (the empty string). ; [!default-license|''default-license''] : License value to be used when rendering content. Can be overwritten in a zettel with [[meta key|00001006020000]] ''license''. Default: (the empty string). ; [!default-visibility|''default-visibility''] : Visibility to be used, if zettel does not specify a value for the [[''visibility''|00001006020000#visibility]] metadata key. Default: ""login"". ; [!expert-mode|''expert-mode''] : If set to a [[boolean true value|00001006030500]], all zettel with [[visibility ""expert""|00001010070200]] will be shown (to the owner, if [[authentication is enabled|00001010040100]]; to all, otherwise). This affects most computed zettel. Default: ""False"". ; [!footer-zettel|''footer-zettel''] : Identifier of a zettel that is rendered as HTML and will be placed as the footer of every zettel in the [[web user interface|00001014000000]]. Zettel content, delivered via the [[API|00001012000000]] as symbolic expressions, etc. is not affected. If the zettel identifier is invalid or references a zettel that could not be read (possibly because of a limited [[visibility setting|00001010070200]]), nothing is written as the footer. May be [[overwritten|00001004020200]] in a user zettel. |
︙ | ︙ | |||
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 | This value is used as a default value, if it is not set in an user's zettel or in a zettel. It is also used to specify the language for all non-zettel content, e.g. lists or search results. Use values according to the language definition of [[RFC-5646|https://tools.ietf.org/html/rfc5646]]. ; [!max-transclusions|''max-transclusions''] : Maximum number of indirect transclusion. This is used to avoid an exploding ""transclusion bomb"", a form of a [[billion laughs attack|https://en.wikipedia.org/wiki/Billion_laughs_attack]]. Default: ""1024"". ; [!site-name|''site-name''] : Name of the Zettelstore instance. Will be used when displaying some lists. Default: ""Zettelstore"". ; [!yaml-header|''yaml-header''] : If [[true|00001006030500]], metadata and content will be separated by ''---\\n'' instead of an empty line (''\\n\\n''). Default: ""False"". You will probably use this key, if you are working with another software processing [[Markdown|https://daringfireball.net/projects/markdown/]] that uses a subset of [[YAML|https://yaml.org/]] to specify metadata. ; [!zettel-file-syntax|''zettel-file-syntax''] : If you create a new zettel with a syntax different to ""zmk"", Zettelstore will store the zettel as two files: one for the metadata (file without a filename extension) and another for the content (file extension based on the syntax value). If you want to specify alternative syntax values, for which you want new zettel to be stored in one file (file extension ''.zettel''), you can use this key. All values are case-insensitive, duplicate values are removed. For example, you could use this key if you're working with Markdown syntax and you want to store metadata and content in one ''.zettel'' file. If ''yaml-header'' evaluates to true, a zettel is always stored in one ''.zettel'' file. | > > > > > > > > > > > > > > > > > | 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 | This value is used as a default value, if it is not set in an user's zettel or in a zettel. It is also used to specify the language for all non-zettel content, e.g. lists or search results. Use values according to the language definition of [[RFC-5646|https://tools.ietf.org/html/rfc5646]]. ; [!max-transclusions|''max-transclusions''] : Maximum number of indirect transclusion. This is used to avoid an exploding ""transclusion bomb"", a form of a [[billion laughs attack|https://en.wikipedia.org/wiki/Billion_laughs_attack]]. Default: ""1024"". ; [!show-back-links|''show-back-links''], [!show-folge-links|''show-folge-links''], [!show-subordinate-links|''show-subordinate-links''], [!show-successor-links|''show-successor-links''] : When displaying a zettel in the web user interface, references to other zettel are normally shown below the content of the zettel. This affects the metadata keys [[''back''|00001006020000#back]], [[''folge''|00001006020000#folge]], [[''subordinates''|00001006020000#subordinates]], and [[''successors''|00001006020000#successors]]. These configuration keys may be used to show, not to show, or to close the list of referenced zettel. Allowed values are: ""false"" (will not show the list), ""close"" (will show the list closed), and ""open"" / """" (will show the list). Default: """". May be [[overwritten|00001004020200]] in a user zettel, so that setting will only affect the given user. Alternatively, it may be overwritten in a zettel, so that that the setting will affect only the given zettel. This zettel is an example of a zettel that sets ''show-back-links'' to ""false"". ; [!site-name|''site-name''] : Name of the Zettelstore instance. Will be used when displaying some lists. Default: ""Zettelstore"". ; [!yaml-header|''yaml-header''] : If [[true|00001006030500]], metadata and content will be separated by ''---\\n'' instead of an empty line (''\\n\\n''). Default: ""False"". You will probably use this key, if you are working with another software processing [[Markdown|https://daringfireball.net/projects/markdown/]] that uses a subset of [[YAML|https://yaml.org/]] to specify metadata. ; [!zettel-file-syntax|''zettel-file-syntax''] : If you create a new zettel with a syntax different to ""zmk"", Zettelstore will store the zettel as two files: one for the metadata (file without a filename extension) and another for the content (file extension based on the syntax value). If you want to specify alternative syntax values, for which you want new zettel to be stored in one file (file extension ''.zettel''), you can use this key. All values are case-insensitive, duplicate values are removed. For example, you could use this key if you're working with Markdown syntax and you want to store metadata and content in one ''.zettel'' file. If ''yaml-header'' evaluates to true, a zettel is always stored in one ''.zettel'' file. |
Changes to docs/manual/00001004020200.zettel.
1 2 3 4 5 6 | id: 00001004020200 title: Runtime configuration data that may be user specific or zettel specific role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20221205155521 | | > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | id: 00001004020200 title: Runtime configuration data that may be user specific or zettel specific role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20221205155521 modified: 20231126180752 Some metadata of the [[runtime configuration|00001004020000]] may be overwritten in an [[user zettel|00001010040200]]. A subset of those may be overwritten in zettel that is currently used. This allows to specify user specific or zettel specific behavior. The following metadata keys are supported to provide a more specific behavior: |=Key|User:|Zettel:|Remarks |[[''footer-zettel''|00001004020000#footer-zettel]]|Y|N| |[[''home-zettel''|00001004020000#home-zettel]]|Y|N| |[[''lang''|00001004020000#lang]]|Y|Y|Making it user-specific could make zettel for other user less useful |[[''show-back-links''|00001004020000#show-back-links]]|Y|Y| |[[''show-folge-links''|00001004020000#show-folge-links]]|Y|Y| |[[''show-subordinate-links''|00001004020000#show-subordinate-links]]|Y|Y| |[[''show-successor-links''|00001004020000#show-successor-links]]|Y|Y| |
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: 20231129173425 The following table lists all predefined zettel with their purpose. |= Identifier :|= Title | Purpose | [[00000000000001]] | Zettelstore Version | Contains the version string of the running Zettelstore | [[00000000000002]] | Zettelstore Host | Contains the name of the computer running the Zettelstore | [[00000000000003]] | Zettelstore Operating System | Contains the operating system and CPU architecture of the computer running the Zettelstore |
︙ | ︙ | |||
31 32 33 34 35 36 37 | | [[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 | > > > > | > | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | | [[00000000010405]] | Zettelstore Delete HTML Template | View to confirm the deletion of a zettel | [[00000000010700]] | Zettelstore Error HTML Template | View to show an error message | [[00000000019000]] | Zettelstore Sxn Start Code | Starting point of sxn functions to build the templates | [[00000000019990]] | Zettelstore Sxn Base Code | Base sxn functions to build the templates | [[00000000020001]] | Zettelstore Base CSS | System-defined CSS file that is included by the [[Base HTML Template|00000000010100]] | [[00000000025001]] | Zettelstore User CSS | User-defined CSS file that is included by the [[Base HTML Template|00000000010100]] | [[00000000040001]] | Generic Emoji | Image that is shown if [[original image reference|00001007040322]] is invalid | [[00000000060010]] | zettel | [[Role zettel|00001012051800]] for the role ""[[zettel|00001006020100#zettel]]"" | [[00000000060020]] | confguration | [[Role zettel|00001012051800]] for the role ""[[confguration|00001006020100#confguration]]"" | [[00000000060030]] | role | [[Role zettel|00001012051800]] for the role ""[[role|00001006020100#role]]"" | [[00000000060040]] | tag | [[Role zettel|00001012051800]] for the role ""[[tag|00001006020100#tag]]"" | [[00000000090000]] | New Menu | Contains items that should be in the zettel template menu | [[00000000090001]] | New Zettel | Template for a new zettel with role ""[[zettel|00001006020100#zettel]]"" | [[00000000090002]] | New User | Template for a new [[user zettel|00001010040200]] | [[00000000090003]] | New Tag | Template for a new [[tag zettel|00001006020100#tag]] | [[00000000090004]] | New Role | Template for a new [[role zettel|00001006020100#role]] | [[00010000000000]] | Home | Default home zettel, contains some welcome information If a zettel is not linked, it is not accessible for the current user. **Important:** All identifier may change until a stable version of the software is released. |
Changes to docs/manual/00001006020100.zettel.
1 2 3 4 5 | id: 00001006020100 title: Supported Zettel Roles role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk | | | | > > > > > > | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | id: 00001006020100 title: Supported Zettel Roles role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 modified: 20231129173620 The [[''role'' key|00001006020000#role]] defines what kind of zettel you are writing. You are free to define your own roles. It is allowed to set an empty value or to omit the role. Some roles are defined for technical reasons: ; [!configuration|''configuration''] : A zettel that contains some configuration data / information for the Zettelstore. Most prominent is [[00000000000100]], as described in [[00001004020000]]. ; [!manual|''manual''] : All zettel that document the inner workings of the Zettelstore software. This role is only used in this specific Zettelstore. ; [!role|''role''] : A zettel with the role ""role"" and a title, which names a [[role|00001006020000#role]], is treated as a __role zettel__. Basically, role zettel describe the role, and form a hierarchiy of meta-roles. ; [!tag|''tag''] : A zettel with the role ""tag"" and a title, which names a [[tag|00001006020000#tags]], is treated as a __tag zettel__. Basically, tag zettel describe the tag, and form a hierarchiy of meta-tags. ; [!zettel|''zettel''] : A zettel that contains your own thoughts. The real reason to use this software. If you adhere to the process outlined by Niklas Luhmann, a zettel could have one of the following three roles: ; [!note|''note''] : A small note, to remember something. Notes are not real zettel, they just help to create a real zettel. Think of them as Post-it notes. ; [!literature|''literature''] : Contains some remarks about a book, a paper, a web page, etc. You should add a citation key for citing it. ; ''zettel'' : (as described above) However, you are free to define additional roles, e.g. ''material'' for literature that is web-based only, ''slide'' for presentation slides, ''paper'' for the text of a scientific paper, ''project'' to define a project, ... |
Changes to docs/manual/00001006034500.zettel.
1 2 3 4 5 6 | id: 00001006034500 title: Timestamp Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | < > | | > > | > > | < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | id: 00001006034500 title: Timestamp Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20231030182858 Values of this type denote a point in time. === Allowed values Must be a sequence of 4, 6, 8, 10, 12, or 14 digits (""0""--""9"") (similar to an [[Identifier|00001006032000]]), with the restriction that it must conform to the pattern ""YYYYMMDDhhmmss"". * YYYY is the year, * MM is the month, * DD is the day, * hh is the hour, * mm is the minute, * ss is the second. If the sequence is less than 14 digits, they are expanded with the following rule: * YYYY is expanded to YYYY0101000000 * YYYYMM is expanded to YYYYMM01000000 * YYYYMMDD is expanded to YYYYMMDD000000 * YYYYMMDDhh is expanded to YYYYMMDDhh0000 * YYYYMMDDhhmm is expanded to YYYYMMDDhhmm00 === Query comparison [[Search values|00001007706000]] with more than 14 characters are truncated to contain exactly 14 characters. Then, they are treated as timestamp data, as describe above, if they contain 4, 6, 8, 10, or 12 digits. Comparison is done through the string representation. In case of the search operators ""less"", ""not less"", ""greater"", and ""not greater"", this is the same as a numerical comparison. === Sorting Sorting is done by comparing the possibly expanded values. |
Changes to docs/manual/00001007040100.zettel.
1 2 3 4 5 | id: 00001007040100 title: Zettelmarkup: Text Formatting role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001007040100 title: Zettelmarkup: Text Formatting role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20231113191353 Text formatting is the way to make your text visually different. Every text formatting element begins with two same characters. It ends when these two same characters occur the second time. It is possible that some [[attributes|00001007050000]] follow immediately, without any separating character. Text formatting can be nested, up to a reasonable limit. |
︙ | ︙ | |||
25 26 27 28 29 30 31 32 33 | * The circumflex accent character (""''^''"", U+005E) allows to enter super-scripted text. ** Example: ``e=mc^^2^^`` is rendered in HTML as: ::e=mc^^2^^::{=example}. * The comma character (""'',''"", U+002C) produces sub-scripted text. ** Example: ``H,,2,,O`` is rendered in HTML as: ::H,,2,,O::{=example}. * The quotation mark character (""''"''"", U+0022) marks an inline quotation, according to the [[specified language|00001007050100]]. ** Example: ``""To be or not""`` is rendered in HTML as: ::""To be or not""::{=example}. ** Example: ``""Sein oder nicht""{lang=de}`` is rendered in HTML as: ::""Sein oder nicht""{lang=de}::{=example}. * The colon character (""'':''"", U+003A) mark some text that should belong together. It fills a similar role as [[region blocks|00001007030800]], but just for inline elements. ** Example: ``abc ::def::{=example} ghi`` is rendered in HTML as: abc ::def::{=example} ghi. | > > > | 26 27 28 29 30 31 32 33 34 35 36 37 | * The circumflex accent character (""''^''"", U+005E) allows to enter super-scripted text. ** Example: ``e=mc^^2^^`` is rendered in HTML as: ::e=mc^^2^^::{=example}. * The comma character (""'',''"", U+002C) produces sub-scripted text. ** Example: ``H,,2,,O`` is rendered in HTML as: ::H,,2,,O::{=example}. * The quotation mark character (""''"''"", U+0022) marks an inline quotation, according to the [[specified language|00001007050100]]. ** Example: ``""To be or not""`` is rendered in HTML as: ::""To be or not""::{=example}. ** Example: ``""Sein oder nicht""{lang=de}`` is rendered in HTML as: ::""Sein oder nicht""{lang=de}::{=example}. * The number sign (""''#''"", U+0023) marks the text visually, where the mark does not belong to the text itself. It is typically used to highlight some text that is important for you, but was not important for the original author. ** Example: ``abc ##def## ghi`` is rendered in HTML as: ::abc ##def## ghi::{=example}. * The colon character (""'':''"", U+003A) mark some text that should belong together. It fills a similar role as [[region blocks|00001007030800]], but just for inline elements. ** Example: ``abc ::def::{=example} ghi`` is rendered in HTML as: abc ::def::{=example} ghi. |
Changes to docs/manual/00001007800000.zettel.
1 2 3 4 5 | id: 00001007800000 title: Zettelmarkup: Summary of Formatting Characters role: manual tags: #manual #reference #zettelmarkup #zettelstore syntax: zmk | > | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | id: 00001007800000 title: Zettelmarkup: Summary of Formatting Characters role: manual tags: #manual #reference #zettelmarkup #zettelstore syntax: zmk created: 20220805150154 modified: 20231113191330 The following table gives an overview about the use of all characters that begin a markup element. |= Character :|= [[Blocks|00001007030000]] <|= [[Inlines|00001007040000]] < | ''!'' | (free) | (free) | ''"'' | [[Verse block|00001007030700]] | [[Short inline quote|00001007040100]] | ''#'' | [[Ordered list|00001007030200]] | [[marked / highlighted text|00001007040100]] | ''$'' | (reserved) | (reserved) | ''%'' | [[Comment block|00001007030900]] | [[Comment|00001007040000]] | ''&'' | (free) | [[Entity|00001007040000]] | ''\''' | (free) | [[Computer input|00001007040200]] | ''('' | (free) | (free) | '')'' | (free) | (free) | ''*'' | [[Unordered list|00001007030200]] | [[strongly emphasized text|00001007040100]] |
︙ | ︙ |
Changes to docs/manual/00001007903000.zettel.
1 2 3 4 5 6 | id: 00001007903000 title: Zettelmarkup: First Steps role: manual tags: #manual #tutorial #zettelmarkup #zettelstore syntax: zmk created: 20220810182917 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001007903000 title: Zettelmarkup: First Steps role: manual tags: #manual #tutorial #zettelmarkup #zettelstore syntax: zmk created: 20220810182917 modified: 20231201135849 [[Zettelmarkup|00001007000000]] allows you to leave your text as it is, at least in many situations. Some characters have a special meaning, but you have to enter them is a defined way to see a visible change. Zettelmarkup is designed to be used for zettel, which are relatively short. It allows to produce longer texts, but you should probably use a different tool, if you want to produce an scientific paper, to name an example. === Paragraphs |
︙ | ︙ | |||
27 28 29 30 31 32 33 | | ''An __emphasized__ word'' | An __emphasized__ word | Put two underscore characters before and after the text you want to emphasize | ''Someone uses **bold** text'' | Someone uses **bold** text | Put two asterisks before and after the text you want to see bold | ''He says: ""I love you!""'' | Her says: ""I love you!"" | Put two quotation mark characters before and after the text you want to quote. You probably see a principle. One nice thing about the quotation mark characters: they are rendered according to the current language. | | | 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | | ''An __emphasized__ word'' | An __emphasized__ word | Put two underscore characters before and after the text you want to emphasize | ''Someone uses **bold** text'' | Someone uses **bold** text | Put two asterisks before and after the text you want to see bold | ''He says: ""I love you!""'' | Her says: ""I love you!"" | Put two quotation mark characters before and after the text you want to quote. You probably see a principle. One nice thing about the quotation mark characters: they are rendered according to the current language. Examples: ""english""{lang=en}, ""french""{lang=fr}, ""german""{lang=de}. You will see later, how to change the current language. === Lists Quite often, text consists of lists. Zettelmarkup supports different types of lists. The most important lists are: * Unnumbered lists, |
︙ | ︙ |
Changes to docs/manual/00001007990000.zettel.
1 2 3 4 5 6 | id: 00001007990000 title: Zettelmarkup: Cheat Sheet role: manual tags: #manual #reference #zettelmarkup syntax: zmk created: 20221209191905 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | id: 00001007990000 title: Zettelmarkup: Cheat Sheet role: manual tags: #manual #reference #zettelmarkup syntax: zmk created: 20221209191905 modified: 20231201140000 === Overview This Zettelmarkup cheat sheet provides a quick overview of many Zettelmarkup elements. It can not cover any special case. If you need more information about any of these elements, please refer to the detailed description. === Basic Syntax |[[Text formatting|00001007040100]]|''__italic text__'' → __italic text__, ''**bold text**'' → **bold text**, ''""quoted text""'' → ""quoted text"", ''##marked text##'' → ##marked text## |[[Text editing|00001007040100]]|''>>inserted text>>'' → >>inserted text>>, ''~~deleted text~~'' → ~~deleted text~~ |[[Text literal formatting|00001007040200]]|''\'\'entered text\'\''' → ''entered text'', ''``source code``'' → ``source code``, ''==text output=='' → ==text output== |[[Superscript, subscript|00001007040100]]|''m^^2^^'' → m^^2^^, ''H,,2,,O'' → H,,2,,O |[[Links to other zettel|00001007040310]]|''[[Link text|00001007990000]]'' → [[Link text|00001007990000]] |[[Links to external resources|00001007040310]]|''[[Zettelstore|https://zettelstore.de]]'' → [[Zettelstore|https://zettelstore.de]] |[[Embed an image|00001007040322]]|''{{Image text|00000000040001}}'' → {{Image text|00000000040001}} |[[Embed content of first paragraph|00001007040324]]|''{{00001007990000}}'' → {{00001007990000}} |
︙ | ︙ |
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 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | id: 00001012000000 title: API role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20231128183617 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. There is an [[overview zettel|00001012920000]] that shows the structure of the endpoints used by the API and gives an indication about its use. === Authentication If [[authentication is enabled|00001010040100]], most API calls must include an [[access token|00001010040700]] that proves the identity of the caller. * [[Authenticate an user|00001012050200]] to obtain an access token * [[Renew an access token|00001012050400]] without costly re-authentication * [[Provide an access token|00001012050600]] when doing an API call === Zettel lists * [[List all zettel|00001012051200]] * [[Query the list of all zettel|00001012051400]] * [[Determine a tag zettel|00001012051600]] * [[Determine a role zettel|00001012051800]] === 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]] |
︙ | ︙ |
Added docs/manual/00001012051800.zettel.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | id: 00001012051800 title: API: Determine a role zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20231128183917 modified: 20231128184701 The [[endpoint|00001012920000]] ''/z'' also allows you to determine a ""role zettel"", i.e. a zettel that documents a given role. The query parameter ""''role''"" allows you to specify a value that is interpreted as the name of a role. Zettelstore tries to determine the corresponding role zettel. A role zettel is a zettel with the [[''role''|00001006020100]] value ""role"" and a title that names the role. If there is more than one zettel that qualifies, the zettel with the highest zettel identifier is used. For example, if you want to determine the role zettel for the role ""manual"", your request will be: ```sh # curl -i 'http://127.0.0.1:23123/z?role=manual' HTTP/1.1 302 Found Content-Type: text/plain; charset=utf-8 Location: /z/20231128184200 Content-Length: 14 20231128184200 ``` If there is a corresponding role zettel, the response will use the HTTP status code 302 (""Found""), the HTTP response header ''Location'' will contain the URL of the role zettel. Its zettel identifier will be returned in the HTTP response body. If you specified some more query parameter, these will be part of the URL in the response header ''Location'': ```sh # curl -i 'http://127.0.0.1:23123/z?role=manual&part=zettel' HTTP/1.1 302 Found Content-Type: text/plain; charset=utf-8 Location: /z/20231128184200?part=zettel Content-Length: 14 20231128184200 ``` Otherwise, if no role zettel was found, the response will use the HTTP status code 404 (""Not found""). ```sh # curl -i 'http://127.0.0.1:23123/z?role=norole' HTTP/1.1 404 Not Found Content-Type: text/plain; charset=utf-8 Content-Length: 30 Role zettel not found: norole ``` To fulfill this service, Zettelstore will evaluate internally the query ''role:role title=ROLE'', there ''ROLE'' is the actual role. Of course, if you are only interested in the URL of the role zettel, you can make use of the HTTP ''HEAD'' method: ```sh # curl -I 'http://127.0.0.1:23123/z?role=manual' HTTP/1.1 302 Found Content-Type: text/plain; charset=utf-8 Location: /z/20231128184200 Content-Length: 14 ``` === HTTP Status codes ; ''302'' : Role zettel was found. The HTTP header ''Location'' contains its URL, the body of the response contains its zettel identifier. ; ''404'' : No zettel for the given role was found. |
Changes to docs/manual/00001012054200.zettel.
1 2 3 4 5 6 | id: 00001012054200 title: API: Update a zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210713150005 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | id: 00001012054200 title: API: Update a zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210713150005 modified: 20231116110417 Updating metadata and content of a zettel is technically quite similar to [[creating a new zettel|00001012053200]]. In both cases you must provide the data for the new or updated zettel in the body of the HTTP request. One difference is the endpoint. The [[endpoint|00001012920000]] to update a zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. You must send a HTTP PUT request to that endpoint. The zettel must be encoded in a [[plain|00001006000000]] format: first comes the [[metadata|00001006010000]] and the following content is separated by an empty line. This is the same format as used by storing zettel within a [[directory box|00001006010000]]. ``` # curl -X PUT --data $'title: Updated Note\n\nUpdated content.' http://127.0.0.1:23123/z/00001012054200 ``` === Data input Alternatively, you may encode the zettel as a parseable object / a [[symbolic expression|00001012930500]] by providing the query parameter ''enc=data''. The encoding is the same as the data output encoding when you [[retrieve a zettel|00001012053300#data-output]]. The encoding for [[access rights|00001012921200]] must be given, but is ignored. |
︙ | ︙ |
Changes to docs/manual/00001012931600.zettel.
1 2 3 4 5 6 | id: 00001012931600 title: Encoding of Sz Inline Elements role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161845 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001012931600 title: Encoding of Sz Inline Elements role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161845 modified: 20231113191203 === ''TEXT'' :::syntax __Text__ **=** ''(TEXT'' String '')''. ::: Specifies the string as some text content, typically a word. |
︙ | ︙ | |||
143 144 145 146 147 148 149 150 151 152 153 154 155 156 | The inline text should be treated as emphasized. :::syntax __InsertFormat__ **=** ''(FORMAT-INSERT'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''. ::: The inline text should be treated as inserted. :::syntax __QuoteFormat__ **=** ''(FORMAT-QUOTE'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''. ::: The inline text should be treated as quoted text. :::syntax __SpanFormat__ **=** ''(FORMAT-SPAN'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''. | > > > > > | 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | The inline text should be treated as emphasized. :::syntax __InsertFormat__ **=** ''(FORMAT-INSERT'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''. ::: The inline text should be treated as inserted. :::syntax __MarkFormat__ **=** ''(FORMAT-MARK'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''. ::: The inline text should be treated as highlighted for the reader (but was not important fto the original author). :::syntax __QuoteFormat__ **=** ''(FORMAT-QUOTE'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''. ::: The inline text should be treated as quoted text. :::syntax __SpanFormat__ **=** ''(FORMAT-SPAN'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''. |
︙ | ︙ |
Added docs/manual/20231128184200.zettel.
> > > > > > > | 1 2 3 4 5 6 7 | id: 20231128184200 title: manual role: role syntax: zmk created: 20231128184200 Zettel with the role ""manual"" contain the manual of the zettelstore. |
Changes to encoder/encoder.go.
︙ | ︙ | |||
41 42 43 44 45 46 47 | ErrNoWriteMeta = errors.New("method WriteMeta is not implemented") ErrNoWriteContent = errors.New("method WriteContent is not implemented") ErrNoWriteBlocks = errors.New("method WriteBlocks is not implemented") ErrNoWriteInlines = errors.New("method WriteInlines is not implemented") ) // Create builds a new encoder with the given options. | | | | > > > > > | 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 | ErrNoWriteMeta = errors.New("method WriteMeta is not implemented") ErrNoWriteContent = errors.New("method WriteContent is not implemented") ErrNoWriteBlocks = errors.New("method WriteBlocks is not implemented") ErrNoWriteInlines = errors.New("method WriteInlines is not implemented") ) // Create builds a new encoder with the given options. func Create(enc api.EncodingEnum, params *CreateParameter) Encoder { if create, ok := registry[enc]; ok { return create(params) } return nil } // CreateFunc produces a new encoder. type CreateFunc func(*CreateParameter) Encoder // CreateParameter contains values that are needed to create an encoder. type CreateParameter struct { Lang string // default language } var registry = map[api.EncodingEnum]CreateFunc{} // Register the encoder for later retrieval. func Register(enc api.EncodingEnum, create CreateFunc) { if _, ok := registry[enc]; ok { panic(fmt.Sprintf("Encoder %q already registered", enc)) |
︙ | ︙ |
Changes to encoder/encoder_inline_test.go.
︙ | ︙ | |||
143 144 145 146 147 148 149 | encoderZmk: useZmk, }, }, { descr: "Quotes formatting", zmk: `""quotes""`, expect: expectMap{ | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 | encoderZmk: useZmk, }, }, { descr: "Quotes formatting", zmk: `""quotes""`, expect: expectMap{ encoderHTML: "“quotes”", encoderMD: "<q>quotes</q>", 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: "<q>quotes</q>", encoderSz: `(INLINE (FORMAT-QUOTE (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: "<q></q>", 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: "<q></q>", encoderSz: `(INLINE (FORMAT-QUOTE (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: "<q>say: <q>yes, <q>or?</q></q></q>", encoderSz: `(INLINE (FORMAT-QUOTE () (TEXT "say:") (SPACE) (FORMAT-SPAN () (FORMAT-QUOTE () (TEXT "yes,") (SPACE) (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: "<q>yes</q> or <q>no</q>", encoderSz: `(INLINE (FORMAT-QUOTE () (TEXT "yes")) (SPACE) (TEXT "or") (SPACE) (FORMAT-QUOTE () (TEXT "no")))`, encoderSHTML: `((@L (@H "“") "yes" (@H "”")) " " "or" " " (@L (@H "“") "no" (@H "”")))`, encoderText: `yes or no`, encoderZmk: useZmk, }, }, { descr: "Mark formatting", zmk: `##marked##`, expect: expectMap{ encoderHTML: `<mark>marked</mark>`, encoderMD: "<mark>marked</mark>", encoderSz: `(INLINE (FORMAT-MARK () (TEXT "marked")))`, encoderSHTML: `((mark "marked"))`, encoderText: `marked`, encoderZmk: useZmk, }, }, { descr: "Span formatting", zmk: `::span::`, expect: expectMap{ encoderHTML: `<span>span</span>`, encoderMD: "span", encoderSz: `(INLINE (FORMAT-SPAN () (TEXT "span")))`, |
︙ | ︙ | |||
251 252 253 254 255 256 257 | encoderZmk: useZmk, }, }, { descr: "Nested Span Quote formatting", zmk: `::""abc""::{lang=fr}`, expect: expectMap{ | | | | 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 | encoderZmk: useZmk, }, }, { descr: "Nested Span Quote formatting", zmk: `::""abc""::{lang=fr}`, expect: expectMap{ encoderHTML: `<span lang="fr">« abc »</span>`, encoderMD: "<q>abc</q>", encoderSz: `(INLINE (FORMAT-SPAN (quote (("lang" . "fr"))) (FORMAT-QUOTE () (TEXT "abc"))))`, encoderSHTML: `((span (@ (lang . "fr")) (@L (@H "«" " ") "abc" (@H " " "»"))))`, encoderText: `abc`, encoderZmk: `::""abc""::{lang="fr"}`, }, }, { descr: "Simple Citation", zmk: `[@Stern18]`, |
︙ | ︙ |
Changes to encoder/encoder_test.go.
︙ | ︙ | |||
74 75 76 77 78 79 80 | checkEncodings(t, testNum, pe, tc.descr, tc.expect, tc.zmk) checkSz(t, testNum, pe, tc.descr) } } func checkEncodings(t *testing.T, testNum int, pe parserEncoder, descr string, expected expectMap, zmkDefault string) { for enc, exp := range expected { | | | 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | checkEncodings(t, testNum, pe, tc.descr, tc.expect, tc.zmk) checkSz(t, testNum, pe, tc.descr) } } func checkEncodings(t *testing.T, testNum int, pe parserEncoder, descr string, expected expectMap, zmkDefault string) { for enc, exp := range expected { encdr := encoder.Create(enc, &encoder.CreateParameter{Lang: api.ValueLangEN}) got, err := pe.encode(encdr) if err != nil { prefix := fmt.Sprintf("Test #%d", testNum) if d := descr; d != "" { prefix += "\nReason: " + d } prefix += "\nMode: " + pe.mode() |
︙ | ︙ | |||
101 102 103 104 105 106 107 | t.Errorf("%s\nEncoder: %s\nExpected: %q\nGot: %q", prefix, enc, exp, got) } } } func checkSz(t *testing.T, testNum int, pe parserEncoder, descr string) { t.Helper() | | | 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | t.Errorf("%s\nEncoder: %s\nExpected: %q\nGot: %q", prefix, enc, exp, got) } } } func checkSz(t *testing.T, testNum int, pe parserEncoder, descr string) { t.Helper() encdr := encoder.Create(encoderSz, nil) exp, err := pe.encode(encdr) if err != nil { t.Error(err) return } val, err := sxreader.MakeReader(strings.NewReader(exp)).Read() if err != nil { |
︙ | ︙ |
Changes to encoder/htmlenc/htmlenc.go.
︙ | ︙ | |||
24 25 26 27 28 29 30 | "zettelstore.de/z/encoder/szenc" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) func init() { | | | | > | > > | | | | | 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 | "zettelstore.de/z/encoder/szenc" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) func init() { encoder.Register(api.EncoderHTML, func(params *encoder.CreateParameter) encoder.Encoder { return Create(params) }) } // Create an encoder. func Create(params *encoder.CreateParameter) *Encoder { // We need a new transformer every time, because tx.inVerse must be unique. // If we can refactor it out, the transformer can be created only once. return &Encoder{ tx: szenc.NewTransformer(), th: shtml.NewEvaluator(1, nil), lang: params.Lang, textEnc: textenc.Create(), } } type Encoder struct { tx *szenc.Transformer th *shtml.Evaluator lang string textEnc *textenc.Encoder } // WriteZettel encodes a full zettel as HTML5. func (he *Encoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) { env := shtml.MakeEnvironment(he.lang) hm, err := he.th.Evaluate(he.tx.GetMeta(zn.InhMeta, evalMeta), &env) if err != nil { return 0, err } var isTitle ast.InlineSlice var htitle *sx.Pair plainTitle, hasTitle := zn.InhMeta.Get(api.KeyTitle) if hasTitle { isTitle = parser.ParseSpacedText(plainTitle) xtitle := he.tx.GetSz(&isTitle) htitle, err = he.th.Evaluate(xtitle, &env) if err != nil { return 0, err } } xast := he.tx.GetSz(&zn.Ast) hast, err := he.th.Evaluate(xast, &env) if err != nil { return 0, err } hen := he.th.Endnotes(&env) sf := he.th.SymbolFactory() symAttr := sf.MustMake(sxhtml.NameSymAttr) head := sx.MakeList(sf.MustMake("head")) curr := head curr = curr.AppendBang(sx.Nil().Cons(sx.Nil().Cons(sx.Cons(sf.MustMake("charset"), sx.String("utf-8"))).Cons(symAttr)).Cons(sf.MustMake("meta"))) |
︙ | ︙ | |||
111 112 113 114 115 116 117 | gen := sxhtml.NewGenerator(sf, sxhtml.WithNewline) return gen.WriteHTML(w, doc) } // WriteMeta encodes meta data as HTML5. func (he *Encoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) { | > | > | | > | | 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | gen := sxhtml.NewGenerator(sf, sxhtml.WithNewline) return gen.WriteHTML(w, doc) } // WriteMeta encodes meta data as HTML5. func (he *Encoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) { env := shtml.MakeEnvironment(he.lang) hm, err := he.th.Evaluate(he.tx.GetMeta(m, evalMeta), &env) if err != nil { return 0, err } gen := sxhtml.NewGenerator(he.th.SymbolFactory(), sxhtml.WithNewline) return gen.WriteListHTML(w, hm) } func (he *Encoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return he.WriteBlocks(w, &zn.Ast) } // WriteBlocks encodes a block slice. func (he *Encoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { env := shtml.MakeEnvironment(he.lang) hobj, err := he.th.Evaluate(he.tx.GetSz(bs), &env) if err == nil { gen := sxhtml.NewGenerator(he.th.SymbolFactory()) length, err2 := gen.WriteListHTML(w, hobj) if err2 != nil { return length, err2 } l, err2 := gen.WriteHTML(w, he.th.Endnotes(&env)) length += l return length, err2 } return 0, err } // WriteInlines writes an inline slice to the writer func (he *Encoder) WriteInlines(w io.Writer, is *ast.InlineSlice) (int, error) { env := shtml.MakeEnvironment(he.lang) hobj, err := he.th.Evaluate(he.tx.GetSz(is), &env) if err == nil { gen := sxhtml.NewGenerator(sx.FindSymbolFactory(hobj)) length, err2 := gen.WriteListHTML(w, hobj) if err2 != nil { return length, err2 } return length, nil } return 0, err } |
Changes to encoder/mdenc/mdenc.go.
︙ | ︙ | |||
17 18 19 20 21 22 23 | "zettelstore.de/client.fossil/api" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/zettel/meta" ) func init() { | | | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | "zettelstore.de/client.fossil/api" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/zettel/meta" ) func init() { encoder.Register(api.EncoderMD, func(*encoder.CreateParameter) encoder.Encoder { return Create() }) } // Create an encoder. func Create() *Encoder { return &myME } type Encoder struct{} |
︙ | ︙ | |||
327 328 329 330 331 332 333 334 335 336 337 338 339 340 | v.b.WriteString("__") ast.Walk(v, &fn.Inlines) v.b.WriteString("__") case ast.FormatQuote: v.b.WriteString("<q>") ast.Walk(v, &fn.Inlines) v.b.WriteString("</q>") default: ast.Walk(v, &fn.Inlines) } } func (v *visitor) visitLiteral(ln *ast.LiteralNode) { switch ln.Kind { | > > > > | 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 | v.b.WriteString("__") ast.Walk(v, &fn.Inlines) v.b.WriteString("__") case ast.FormatQuote: v.b.WriteString("<q>") ast.Walk(v, &fn.Inlines) v.b.WriteString("</q>") 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) visitLiteral(ln *ast.LiteralNode) { switch ln.Kind { |
︙ | ︙ |
Changes to encoder/shtmlenc/shtmlenc.go.
︙ | ︙ | |||
20 21 22 23 24 25 26 | "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/encoder/szenc" "zettelstore.de/z/zettel/meta" ) func init() { | | | | | > | | > > | | > | | > | | > | | | 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 | "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/encoder/szenc" "zettelstore.de/z/zettel/meta" ) func init() { encoder.Register(api.EncoderSHTML, func(params *encoder.CreateParameter) encoder.Encoder { return Create(params) }) } // Create a SHTML encoder func Create(params *encoder.CreateParameter) *Encoder { // We need a new transformer every time, because tx.inVerse must be unique. // If we can refactor it out, the transformer can be created only once. return &Encoder{ tx: szenc.NewTransformer(), th: shtml.NewEvaluator(1, nil), lang: params.Lang, } } type Encoder struct { tx *szenc.Transformer th *shtml.Evaluator lang string } // WriteZettel writes the encoded zettel to the writer. func (enc *Encoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) { env := shtml.MakeEnvironment(enc.lang) metaSHTML, err := enc.th.Evaluate(enc.tx.GetMeta(zn.InhMeta, evalMeta), &env) if err != nil { return 0, err } contentSHTML, err := enc.th.Evaluate(enc.tx.GetSz(&zn.Ast), &env) if err != nil { return 0, err } result := sx.Cons(metaSHTML, contentSHTML) return result.Print(w) } // WriteMeta encodes meta data as s-expression. func (enc *Encoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) { env := shtml.MakeEnvironment(enc.lang) metaSHTML, err := enc.th.Evaluate(enc.tx.GetMeta(m, evalMeta), &env) if err != nil { return 0, err } return sx.Print(w, metaSHTML) } func (enc *Encoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return enc.WriteBlocks(w, &zn.Ast) } // WriteBlocks writes a block slice to the writer func (enc *Encoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { env := shtml.MakeEnvironment(enc.lang) hval, err := enc.th.Evaluate(enc.tx.GetSz(bs), &env) if err != nil { return 0, err } return sx.Print(w, hval) } // WriteInlines writes an inline slice to the writer func (enc *Encoder) WriteInlines(w io.Writer, is *ast.InlineSlice) (int, error) { env := shtml.MakeEnvironment(enc.lang) hval, err := enc.th.Evaluate(enc.tx.GetSz(is), &env) if err != nil { return 0, err } return sx.Print(w, hval) } |
Changes to encoder/szenc/szenc.go.
︙ | ︙ | |||
18 19 20 21 22 23 24 | "zettelstore.de/sx.fossil" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/zettel/meta" ) func init() { | | | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | "zettelstore.de/sx.fossil" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/zettel/meta" ) func init() { encoder.Register(api.EncoderSz, func(*encoder.CreateParameter) encoder.Encoder { return Create() }) } // Create a S-expr encoder func Create() *Encoder { // We need a new transformer every time, because trans.inVerse must be unique. // If we can refactor it out, the transformer can be created only once. return &Encoder{trans: NewTransformer()} |
︙ | ︙ |
Changes to encoder/szenc/transform.go.
︙ | ︙ | |||
69 70 71 72 73 74 75 76 77 78 79 80 81 82 | ast.FormatEmph: t.zetSyms.SymFormatEmph, ast.FormatStrong: t.zetSyms.SymFormatStrong, ast.FormatDelete: t.zetSyms.SymFormatDelete, ast.FormatInsert: t.zetSyms.SymFormatInsert, ast.FormatSuper: t.zetSyms.SymFormatSuper, ast.FormatSub: t.zetSyms.SymFormatSub, ast.FormatQuote: t.zetSyms.SymFormatQuote, ast.FormatSpan: t.zetSyms.SymFormatSpan, } t.mapLiteralKindS = map[ast.LiteralKind]*sx.Symbol{ ast.LiteralZettel: t.zetSyms.SymLiteralZettel, ast.LiteralProg: t.zetSyms.SymLiteralProg, ast.LiteralInput: t.zetSyms.SymLiteralInput, ast.LiteralOutput: t.zetSyms.SymLiteralOutput, | > | 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | ast.FormatEmph: t.zetSyms.SymFormatEmph, ast.FormatStrong: t.zetSyms.SymFormatStrong, ast.FormatDelete: t.zetSyms.SymFormatDelete, ast.FormatInsert: t.zetSyms.SymFormatInsert, ast.FormatSuper: t.zetSyms.SymFormatSuper, ast.FormatSub: t.zetSyms.SymFormatSub, ast.FormatQuote: t.zetSyms.SymFormatQuote, ast.FormatMark: t.zetSyms.SymFormatMark, ast.FormatSpan: t.zetSyms.SymFormatSpan, } t.mapLiteralKindS = map[ast.LiteralKind]*sx.Symbol{ ast.LiteralZettel: t.zetSyms.SymLiteralZettel, ast.LiteralProg: t.zetSyms.SymLiteralProg, ast.LiteralInput: t.zetSyms.SymLiteralInput, ast.LiteralOutput: t.zetSyms.SymLiteralOutput, |
︙ | ︙ |
Changes to encoder/textenc/textenc.go.
︙ | ︙ | |||
17 18 19 20 21 22 23 | "zettelstore.de/client.fossil/api" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/zettel/meta" ) func init() { | | | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | "zettelstore.de/client.fossil/api" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/zettel/meta" ) func init() { encoder.Register(api.EncoderText, func(*encoder.CreateParameter) encoder.Encoder { return Create() }) } // Create an encoder. func Create() *Encoder { return &myTE } type Encoder struct{} |
︙ | ︙ |
Changes to encoder/zmkenc/zmkenc.go.
︙ | ︙ | |||
22 23 24 25 26 27 28 | "zettelstore.de/z/encoder" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/meta" ) func init() { | | | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | "zettelstore.de/z/encoder" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/meta" ) func init() { encoder.Register(api.EncoderZmk, func(*encoder.CreateParameter) encoder.Encoder { return Create() }) } // Create an encoder. func Create() *Encoder { return &myZE } type Encoder struct{} |
︙ | ︙ | |||
352 353 354 355 356 357 358 | } var sb strings.Builder v.textEnc.WriteInlines(&sb, &bn.Description) v.b.WriteStrings("%% Unable to display BLOB with description '", sb.String(), "' and syntax '", bn.Syntax, "'.") } var escapeSeqs = strfun.NewSet( | | | 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 | } var sb strings.Builder v.textEnc.WriteInlines(&sb, &bn.Description) v.b.WriteStrings("%% Unable to display BLOB with description '", sb.String(), "' and syntax '", bn.Syntax, "'.") } var escapeSeqs = strfun.NewSet( "\\", "__", "**", "~~", "^^", ",,", ">>", `""`, "::", "''", "``", "++", "==", "##", ) func (v *visitor) visitText(tn *ast.TextNode) { last := 0 for i := 0; i < len(tn.Text); i++ { if b := tn.Text[i]; b == '\\' { v.b.WriteString(tn.Text[last:i]) |
︙ | ︙ | |||
448 449 450 451 452 453 454 455 456 457 458 459 460 461 | ast.FormatEmph: []byte("__"), ast.FormatStrong: []byte("**"), ast.FormatInsert: []byte(">>"), ast.FormatDelete: []byte("~~"), ast.FormatSuper: []byte("^^"), ast.FormatSub: []byte(",,"), ast.FormatQuote: []byte(`""`), ast.FormatSpan: []byte("::"), } func (v *visitor) visitFormat(fn *ast.FormatNode) { kind, ok := mapFormatKind[fn.Kind] if !ok { panic(fmt.Sprintf("Unknown format kind %d", fn.Kind)) | > | 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 | ast.FormatEmph: []byte("__"), ast.FormatStrong: []byte("**"), ast.FormatInsert: []byte(">>"), ast.FormatDelete: []byte("~~"), ast.FormatSuper: []byte("^^"), ast.FormatSub: []byte(",,"), ast.FormatQuote: []byte(`""`), ast.FormatMark: []byte("##"), ast.FormatSpan: []byte("::"), } func (v *visitor) visitFormat(fn *ast.FormatNode) { kind, ok := mapFormatKind[fn.Kind] if !ok { panic(fmt.Sprintf("Unknown format kind %d", fn.Kind)) |
︙ | ︙ |
Changes to encoding/atom/atom.go.
︙ | ︙ | |||
72 73 74 75 76 77 78 | buf.WriteString("</feed>") return buf.Bytes() } func (c *Configuration) marshalMeta(buf *bytes.Buffer, m *meta.Meta) { entryUpdated := "" if val, found := m.Get(api.KeyPublished); found { | | | 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | buf.WriteString("</feed>") return buf.Bytes() } func (c *Configuration) marshalMeta(buf *bytes.Buffer, m *meta.Meta) { entryUpdated := "" if val, found := m.Get(api.KeyPublished); found { if published, err := time.ParseInLocation(id.TimestampLayout, val, time.Local); err == nil { entryUpdated = published.UTC().Format(time.RFC3339) } } link := c.NewURLBuilderAbs().SetZid(api.ZettelID(m.Zid.String())).String() buf.WriteString(" <entry>\n") |
︙ | ︙ |
Changes to encoding/encoding.go.
︙ | ︙ | |||
21 22 23 24 25 26 27 | ) // LastUpdated returns the formated time of the zettel which was updated at the latest time. func LastUpdated(ml []*meta.Meta, timeFormat string) string { maxPublished := time.Date(1, time.January, 1, 0, 0, 0, 0, time.Local) for _, m := range ml { if val, found := m.Get(api.KeyPublished); found { | | | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | ) // LastUpdated returns the formated time of the zettel which was updated at the latest time. func LastUpdated(ml []*meta.Meta, timeFormat string) string { maxPublished := time.Date(1, time.January, 1, 0, 0, 0, 0, time.Local) for _, m := range ml { if val, found := m.Get(api.KeyPublished); found { if published, err := time.ParseInLocation(id.TimestampLayout, val, time.Local); err == nil { if maxPublished.Before(published) { maxPublished = published } } } } if maxPublished.Year() > 1 { |
︙ | ︙ |
Changes to encoding/rss/rss.go.
︙ | ︙ | |||
82 83 84 85 86 87 88 | buf.WriteString("</channel>\n</rss>") return buf.Bytes() } func (c *Configuration) marshalMeta(buf *bytes.Buffer, m *meta.Meta) { itemPublished := "" if val, found := m.Get(api.KeyPublished); found { | | | 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | buf.WriteString("</channel>\n</rss>") return buf.Bytes() } func (c *Configuration) marshalMeta(buf *bytes.Buffer, m *meta.Meta) { itemPublished := "" if val, found := m.Get(api.KeyPublished); found { if published, err := time.ParseInLocation(id.TimestampLayout, val, time.Local); err == nil { itemPublished = published.UTC().Format(time.RFC1123Z) } } link := c.NewURLBuilderAbs().SetZid(api.ZettelID(m.Zid.String())).String() buf.WriteString(" <item>\n") |
︙ | ︙ |
Changes to go.mod.
1 2 3 4 5 6 | module zettelstore.de/z go 1.21 require ( github.com/fsnotify/fsnotify v1.7.0 | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | module zettelstore.de/z go 1.21 require ( github.com/fsnotify/fsnotify v1.7.0 github.com/yuin/goldmark v1.6.0 golang.org/x/crypto v0.16.0 golang.org/x/term v0.15.0 golang.org/x/text v0.14.0 zettelstore.de/client.fossil v0.0.0-20231130151508-751754d40c73 zettelstore.de/sx.fossil v0.0.0-20231130150648-05ef116ba207 ) require golang.org/x/sys v0.15.0 // indirect |
Changes to go.sum.
1 2 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= | | | | | | | | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= zettelstore.de/client.fossil v0.0.0-20231130151508-751754d40c73 h1:LPF8QWip3GRLBzgPdnsJnrvk1sGiMOSpqOwURiax9gg= zettelstore.de/client.fossil v0.0.0-20231130151508-751754d40c73/go.mod h1:fN+1WxRorSbHduS0T0B4GI8o82EgFuUWBv6fwsAZF6o= zettelstore.de/sx.fossil v0.0.0-20231130150648-05ef116ba207 h1:8ch54z0w53bps6a00NDofEqo3AJ1l7ITXyC3XyLmlY4= zettelstore.de/sx.fossil v0.0.0-20231130150648-05ef116ba207/go.mod h1:Uw3OLM1ufOM4Xe0G51mvkTDUv2okd+HyDBMx+0ZG7ME= |
Changes to kernel/impl/cfg.go.
︙ | ︙ | |||
89 90 91 92 93 94 95 | keySiteName: {"Site name", parseString, true}, keyYAMLHeader: {"YAML header", parseBool, true}, keyZettelFileSyntax: { "Zettel file syntax", func(val string) (any, error) { return strings.Fields(val), nil }, true, }, | | > > > > | | | | | | | | | | | | | > > > > | 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 | keySiteName: {"Site name", parseString, true}, keyYAMLHeader: {"YAML header", parseBool, true}, keyZettelFileSyntax: { "Zettel file syntax", func(val string) (any, error) { return strings.Fields(val), nil }, true, }, kernel.ConfigSimpleMode: {"Simple mode", cs.noFrozen(parseBool), true}, config.KeyShowBackLinks: {"Show back links", parseString, true}, config.KeyShowFolgeLinks: {"Show folge links", parseString, true}, config.KeyShowSubordinateLinks: {"Show subordinate links", parseString, true}, config.KeyShowSuccessorLinks: {"Show successor links", parseString, true}, } cs.next = interfaceMap{ keyDefaultCopyright: "", keyDefaultLicense: "", keyDefaultVisibility: meta.VisibilityLogin, keyExpertMode: false, config.KeyFooterZettel: id.Invalid, config.KeyHomeZettel: id.DefaultHomeZid, kernel.ConfigInsecureHTML: config.NoHTML, api.KeyLang: api.ValueLangEN, keyMaxTransclusions: int64(1024), keySiteName: "Zettelstore", keyYAMLHeader: false, keyZettelFileSyntax: nil, kernel.ConfigSimpleMode: false, config.KeyShowBackLinks: "", config.KeyShowFolgeLinks: "", config.KeyShowSubordinateLinks: "", config.KeyShowSuccessorLinks: "", } } func (cs *configService) GetLogger() *logger.Logger { return cs.logger } func (cs *configService) Start(*myKernel) error { cs.logger.Info().Msg("Start Service") data := meta.New(id.ConfigurationZid) |
︙ | ︙ |
Changes to kernel/impl/core.go.
︙ | ︙ | |||
77 78 79 80 81 82 83 | cs.next = interfaceMap{ kernel.CoreDebug: false, kernel.CoreGoArch: runtime.GOARCH, kernel.CoreGoOS: runtime.GOOS, kernel.CoreGoVersion: runtime.Version(), kernel.CoreHostname: "*unknown host*", kernel.CorePort: 0, | | | 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | cs.next = interfaceMap{ kernel.CoreDebug: false, kernel.CoreGoArch: runtime.GOARCH, kernel.CoreGoOS: runtime.GOOS, kernel.CoreGoVersion: runtime.Version(), kernel.CoreHostname: "*unknown host*", kernel.CorePort: 0, kernel.CoreStarted: time.Now().Local().Format(id.TimestampLayout), kernel.CoreVerbose: false, } if hn, err := os.Hostname(); err == nil { cs.next[kernel.CoreHostname] = hn } } |
︙ | ︙ |
Changes to kernel/impl/impl.go.
︙ | ︙ | |||
122 123 124 125 126 127 128 | } return kern } func (kern *myKernel) Setup(progname, version string, versionTime time.Time) { kern.SetConfig(kernel.CoreService, kernel.CoreProgname, progname) kern.SetConfig(kernel.CoreService, kernel.CoreVersion, version) | | | 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | } return kern } func (kern *myKernel) Setup(progname, version string, versionTime time.Time) { kern.SetConfig(kernel.CoreService, kernel.CoreProgname, progname) kern.SetConfig(kernel.CoreService, kernel.CoreVersion, version) kern.SetConfig(kernel.CoreService, kernel.CoreVTime, versionTime.Local().Format(id.TimestampLayout)) } func (kern *myKernel) Start(headline, lineServer bool, configFilename string) { for _, srvD := range kern.srvs { srvD.srv.Freeze() } if kern.cfg.GetCurConfig(kernel.ConfigSimpleMode).(bool) { |
︙ | ︙ |
Changes to kernel/impl/server.go.
︙ | ︙ | |||
35 36 37 38 39 40 41 | } }() for { conn, err := ln.Accept() if err != nil { // handle error | | | 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | } }() for { conn, err := ln.Accept() if err != nil { // handle error kern.logger.Error().Err(err).Msg("Unable to accept connection") break } go handleLineConnection(conn, kern) } ln.Close() } |
︙ | ︙ |
Changes to logger/logger.go.
︙ | ︙ | |||
179 180 181 182 183 184 185 | // Warn creates a message suitable for warning the user. func (l *Logger) Warn() *Message { return newMessage(l, WarnLevel) } // Error creates a message suitable for errors. func (l *Logger) Error() *Message { return newMessage(l, ErrorLevel) } | < < < < < < < < | 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | // Warn creates a message suitable for warning the user. func (l *Logger) Warn() *Message { return newMessage(l, WarnLevel) } // Error creates a message suitable for errors. func (l *Logger) Error() *Message { return newMessage(l, ErrorLevel) } // Fatal creates a message suitable for fatal errors. func (l *Logger) Fatal() *Message { return newMessage(l, FatalLevel) } // Panic creates a message suitable for panicing. func (l *Logger) Panic() *Message { return newMessage(l, PanicLevel) } // Mandatory creates a message that will always logged, except when logging |
︙ | ︙ |
Changes to parser/parser.go.
︙ | ︙ | |||
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | return ast.CreateInlineSliceFromWords(meta.ListFromValue(s)...) } // NormalizedSpacedText returns the given string, but normalize multiple spaces to one space. func NormalizedSpacedText(s string) string { return strings.Join(meta.ListFromValue(s), " ") } // ParseDescription returns a suitable description stored in the metadata as an inline slice. func ParseDescription(m *meta.Meta) ast.InlineSlice { if m == nil { return nil } if descr, found := m.Get(api.KeySummary); found { in := ParseMetadata(descr) cleaner.CleanInlineLinks(&in) return in } if title, found := m.Get(api.KeyTitle); found { return ParseSpacedText(title) } | > | | 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | return ast.CreateInlineSliceFromWords(meta.ListFromValue(s)...) } // NormalizedSpacedText returns the given string, but normalize multiple spaces to one space. func NormalizedSpacedText(s string) string { return strings.Join(meta.ListFromValue(s), " ") } // ParseDescription returns a suitable description stored in the metadata as an inline slice. // This is done for an image in most cases. func ParseDescription(m *meta.Meta) ast.InlineSlice { if m == nil { return nil } if descr, found := m.Get(api.KeySummary); found { in := ParseMetadata(descr) cleaner.CleanInlineLinks(&in) return in } if title, found := m.Get(api.KeyTitle); found { return ParseSpacedText(title) } return ast.CreateInlineSliceFromWords("Zettel", "without", "title:", m.Zid.String()) } // ParseZettel parses the zettel based on the syntax. func ParseZettel(ctx context.Context, zettel zettel.Zettel, syntax string, rtConfig config.Config) *ast.ZettelNode { m := zettel.Meta inhMeta := m if rtConfig != nil { |
︙ | ︙ |
Changes to parser/zettelmark/inline.go.
︙ | ︙ | |||
65 66 67 68 69 70 71 | case '{': inp.Next() if inp.Ch == '{' { in, success = cp.parseEmbed() } case '%': in, success = cp.parseComment() | | | 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | case '{': inp.Next() if inp.Ch == '{' { in, success = cp.parseEmbed() } case '%': in, success = cp.parseComment() case '_', '*', '>', '~', '^', ',', '"', '#', ':': in, success = cp.parseFormat() case '@', '\'', '`', '=', runeModGrave: in, success = cp.parseLiteral() case '$': in, success = cp.parseLiteralMath() case '\\': return cp.parseBackslash() |
︙ | ︙ | |||
98 99 100 101 102 103 104 | return cp.parseBackslashRest() } for { inp.Next() switch inp.Ch { // The following case must contain all runes that occur in parseInline! // Plus the closing brackets ] and } and ) and the middle | | | | 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | return cp.parseBackslashRest() } for { inp.Next() switch inp.Ch { // The following case must contain all runes that occur in parseInline! // Plus the closing brackets ] and } and ) and the middle | case input.EOS, '\n', '\r', ' ', '\t', '[', ']', '{', '}', '(', ')', '|', '%', '_', '*', '>', '~', '^', ',', '"', '#', ':', '\'', '@', '`', runeModGrave, '$', '=', '\\', '-', '&': return &ast.TextNode{Text: string(inp.Src[pos:inp.Pos])} } } } func (cp *zmkP) parseBackslash() ast.InlineNode { inp := cp.inp |
︙ | ︙ | |||
414 415 416 417 418 419 420 421 422 423 424 425 426 427 | '_': ast.FormatEmph, '*': ast.FormatStrong, '>': ast.FormatInsert, '~': ast.FormatDelete, '^': ast.FormatSuper, ',': ast.FormatSub, '"': ast.FormatQuote, ':': ast.FormatSpan, } func (cp *zmkP) parseFormat() (res ast.InlineNode, success bool) { inp := cp.inp fch := inp.Ch kind, ok := mapRuneFormat[fch] | > | 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 | '_': ast.FormatEmph, '*': ast.FormatStrong, '>': ast.FormatInsert, '~': ast.FormatDelete, '^': ast.FormatSuper, ',': ast.FormatSub, '"': ast.FormatQuote, '#': ast.FormatMark, ':': ast.FormatSpan, } func (cp *zmkP) parseFormat() (res ast.InlineNode, success bool) { inp := cp.inp fch := inp.Ch kind, ok := mapRuneFormat[fch] |
︙ | ︙ |
Changes to parser/zettelmark/zettelmark_test.go.
︙ | ︙ | |||
289 290 291 292 293 294 295 | {"100%", "(PARA 100%)"}, }) } func TestFormat(t *testing.T) { t.Parallel() // Not for Insert / '>', because collision with quoted list | | | | 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 | {"100%", "(PARA 100%)"}, }) } func TestFormat(t *testing.T) { t.Parallel() // Not for Insert / '>', because collision with quoted list for _, ch := range []string{"_", "*", "~", "^", ",", "\"", "#", ":"} { checkTcs(t, replace(ch, TestCases{ {"$", "(PARA $)"}, {"$$", "(PARA $$)"}, {"$$$", "(PARA $$$)"}, {"$$$$", "(PARA {$})"}, })) } for _, ch := range []string{"_", "*", ">", "~", "^", ",", "\"", "#", ":"} { checkTcs(t, replace(ch, TestCases{ {"$$a$$", "(PARA {$ a})"}, {"$$a$$$", "(PARA {$ a} $)"}, {"$$$a$$", "(PARA {$ $a})"}, {"$$$a$$$", "(PARA {$ $a} $)"}, {"$\\$", "(PARA $$)"}, {"$\\$$", "(PARA $$$)"}, |
︙ | ︙ | |||
986 987 988 989 990 991 992 993 994 995 996 997 998 999 | ast.FormatEmph: '_', ast.FormatStrong: '*', ast.FormatInsert: '>', ast.FormatDelete: '~', ast.FormatSuper: '^', ast.FormatSub: ',', ast.FormatQuote: '"', ast.FormatSpan: ':', } var mapLiteralKind = map[ast.LiteralKind]rune{ ast.LiteralZettel: '@', ast.LiteralProg: '`', ast.LiteralInput: '\'', | > | 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 | ast.FormatEmph: '_', ast.FormatStrong: '*', ast.FormatInsert: '>', ast.FormatDelete: '~', ast.FormatSuper: '^', ast.FormatSub: ',', ast.FormatQuote: '"', ast.FormatMark: '#', ast.FormatSpan: ':', } var mapLiteralKind = map[ast.LiteralKind]rune{ ast.LiteralZettel: '@', ast.LiteralProg: '`', ast.LiteralInput: '\'', |
︙ | ︙ |
Changes to query/select.go.
︙ | ︙ | |||
105 106 107 108 109 110 111 | func createMatchFunc(key string, values []expValue, addSearch addSearchFunc) matchValueFunc { if len(values) == 0 { return nil } switch meta.Type(key) { case meta.TypeCredential: return matchValueNever | | > > | 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | func createMatchFunc(key string, values []expValue, addSearch addSearchFunc) matchValueFunc { if len(values) == 0 { return nil } switch meta.Type(key) { case meta.TypeCredential: return matchValueNever case meta.TypeID: return createMatchIDFunc(values, addSearch) case meta.TypeIDSet: return createMatchIDSetFunc(values, addSearch) case meta.TypeTimestamp: return createMatchTimestampFunc(values, addSearch) case meta.TypeNumber: return createMatchNumberFunc(values, addSearch) case meta.TypeTagSet: return createMatchTagSetFunc(values, addSearch) case meta.TypeWord: return createMatchWordFunc(values, addSearch) case meta.TypeWordSet: |
︙ | ︙ | |||
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | return false } } } return true } } func createMatchNumberFunc(values []expValue, addSearch addSearchFunc) matchValueFunc { preds := valuesToNumberPredicates(values, addSearch) return func(value string) bool { for _, pred := range preds { if !pred(value) { return false } } return true } } func createMatchTagSetFunc(values []expValue, addSearch addSearchFunc) matchValueFunc { predList := valuesToWordSetPredicates(processTagSet(preprocessSet(sliceToLower(values))), addSearch) return func(value string) bool { | > > > > > > > > > > > > | < < < < | 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 | return false } } } return true } } func createMatchTimestampFunc(values []expValue, addSearch addSearchFunc) matchValueFunc { preds := valuesToTimestampPredicates(values, addSearch) return func(value string) bool { value = meta.ExpandTimestamp(value) for _, pred := range preds { if !pred(value) { return false } } return true } } func createMatchNumberFunc(values []expValue, addSearch addSearchFunc) matchValueFunc { preds := valuesToNumberPredicates(values, addSearch) return func(value string) bool { for _, pred := range preds { if !pred(value) { return false } } return true } } func createMatchTagSetFunc(values []expValue, addSearch addSearchFunc) matchValueFunc { predList := valuesToWordSetPredicates(processTagSet(preprocessSet(sliceToLower(values))), addSearch) return func(value string) bool { tags := meta.TagsFromValue(value) for _, preds := range predList { for _, pred := range preds { if !pred(tags) { return false } } } |
︙ | ︙ | |||
385 386 387 388 389 390 391 392 393 394 395 396 397 398 | } func disambiguatedIDOp(cmpOp compareOp) compareOp { return disambiguateWordOp(cmpOp) } func createIDCompareFunc(cmpVal string, cmpOp compareOp) stringPredicate { return createWordCompareFunc(cmpVal, cmpOp) } func valuesToNumberPredicates(values []expValue, addSearch addSearchFunc) []stringPredicate { result := make([]stringPredicate, len(values)) for i, v := range values { switch op := disambiguatedNumberOp(v.op); op { case cmpEqual, cmpNotEqual, cmpLess, cmpNoLess, cmpGreater, cmpNoGreater: iValue, err := strconv.ParseInt(v.value, 10, 64) | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 | } func disambiguatedIDOp(cmpOp compareOp) compareOp { return disambiguateWordOp(cmpOp) } func createIDCompareFunc(cmpVal string, cmpOp compareOp) stringPredicate { return createWordCompareFunc(cmpVal, cmpOp) } func valuesToTimestampPredicates(values []expValue, addSearch addSearchFunc) []stringPredicate { result := make([]stringPredicate, len(values)) for i, v := range values { value := meta.ExpandTimestamp(v.value) switch op := disambiguatedTimestampOp(v.op); op { case cmpLess, cmpNoLess, cmpGreater, cmpNoGreater: if isDigits(value) { // Never add the value to search. result[i] = createTimestampCompareFunc(value, op) continue } fallthrough default: // Otherwise compare as a word. if !op.isNegated() { addSearch(v) // addSearch only for positive selections } result[i] = createWordCompareFunc(value, op) } } return result } func disambiguatedTimestampOp(cmpOp compareOp) compareOp { return disambiguateWordOp(cmpOp) } func createTimestampCompareFunc(cmpVal string, cmpOp compareOp) stringPredicate { return createWordCompareFunc(cmpVal, cmpOp) } func valuesToNumberPredicates(values []expValue, addSearch addSearchFunc) []stringPredicate { result := make([]stringPredicate, len(values)) for i, v := range values { switch op := disambiguatedNumberOp(v.op); op { case cmpEqual, cmpNotEqual, cmpLess, cmpNoLess, cmpGreater, cmpNoGreater: iValue, err := strconv.ParseInt(v.value, 10, 64) |
︙ | ︙ |
Changes to query/sorter.go.
︙ | ︙ | |||
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 | keyType := meta.Type(key) if key == api.KeyID || keyType == meta.TypeCredential { if descending { return func(i, j int) bool { return ml[i].Zid > ml[j].Zid } } return func(i, j int) bool { return ml[i].Zid < ml[j].Zid } } if keyType == meta.TypeNumber { return createSortNumberFunc(ml, key, descending) } return createSortStringFunc(ml, key, descending) } func createSortNumberFunc(ml []*meta.Meta, key string, descending bool) sortFunc { if descending { return func(i, j int) bool { iVal, iOk := getNum(ml[i], key) jVal, jOk := getNum(ml[j], key) return (iOk && (!jOk || iVal > jVal)) || !jOk } } return func(i, j int) bool { iVal, iOk := getNum(ml[i], key) jVal, jOk := getNum(ml[j], key) return (iOk && (!jOk || iVal < jVal)) || !jOk } } func createSortStringFunc(ml []*meta.Meta, key string, descending bool) sortFunc { if descending { return func(i, j int) bool { iVal, iOk := ml[i].Get(key) jVal, jOk := ml[j].Get(key) return (iOk && (!jOk || iVal > jVal)) || !jOk } } return func(i, j int) bool { iVal, iOk := ml[i].Get(key) jVal, jOk := ml[j].Get(key) return (iOk && (!jOk || iVal < jVal)) || !jOk } } | > > > > > > > > > > > > > > > > > > > > > > > > > > > < < < < < < < < < | 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 | keyType := meta.Type(key) if key == api.KeyID || keyType == meta.TypeCredential { if descending { return func(i, j int) bool { return ml[i].Zid > ml[j].Zid } } return func(i, j int) bool { return ml[i].Zid < ml[j].Zid } } if keyType == meta.TypeTimestamp { return createSortTimestampFunc(ml, key, descending) } if keyType == meta.TypeNumber { return createSortNumberFunc(ml, key, descending) } return createSortStringFunc(ml, key, descending) } func createSortTimestampFunc(ml []*meta.Meta, key string, descending bool) sortFunc { if descending { return func(i, j int) bool { iVal, iOk := ml[i].Get(key) jVal, jOk := ml[j].Get(key) return (iOk && (!jOk || meta.ExpandTimestamp(iVal) > meta.ExpandTimestamp(jVal))) || !jOk } } return func(i, j int) bool { iVal, iOk := ml[i].Get(key) jVal, jOk := ml[j].Get(key) return (iOk && (!jOk || meta.ExpandTimestamp(iVal) < meta.ExpandTimestamp(jVal))) || !jOk } } func createSortNumberFunc(ml []*meta.Meta, key string, descending bool) sortFunc { if descending { return func(i, j int) bool { iVal, iOk := getNum(ml[i], key) jVal, jOk := getNum(ml[j], key) return (iOk && (!jOk || iVal > jVal)) || !jOk } } return func(i, j int) bool { iVal, iOk := getNum(ml[i], key) jVal, jOk := getNum(ml[j], key) return (iOk && (!jOk || iVal < jVal)) || !jOk } } func getNum(m *meta.Meta, key string) (int64, bool) { if s, ok := m.Get(key); ok { if i, err := strconv.ParseInt(s, 10, 64); err == nil { return i, true } } return 0, false } func createSortStringFunc(ml []*meta.Meta, key string, descending bool) sortFunc { if descending { return func(i, j int) bool { iVal, iOk := ml[i].Get(key) jVal, jOk := ml[j].Get(key) return (iOk && (!jOk || iVal > jVal)) || !jOk } } return func(i, j int) bool { iVal, iOk := ml[i].Get(key) jVal, jOk := ml[j].Get(key) return (iOk && (!jOk || iVal < jVal)) || !jOk } } |
Changes to tests/client/client_test.go.
︙ | ︙ | |||
45 46 47 48 49 50 51 | } } } func TestListZettel(t *testing.T) { const ( | | | | | | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | } } } func TestListZettel(t *testing.T) { const ( ownerZettel = 55 configRoleZettel = 33 writerZettel = ownerZettel - 24 readerZettel = ownerZettel - 24 creatorZettel = 10 publicZettel = 5 ) testdata := []struct { user string exp int }{ {"", publicZettel}, |
︙ | ︙ | |||
211 212 213 214 215 216 217 | c := getClient() c.SetAuth("owner", "owner") _, _, metaSeq, err := c.QueryZettelData(context.Background(), string(api.ZidTOCNewTemplate)+" "+api.ItemsDirective) if err != nil { t.Error(err) return } | | | | | > | 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | c := getClient() c.SetAuth("owner", "owner") _, _, metaSeq, err := c.QueryZettelData(context.Background(), string(api.ZidTOCNewTemplate)+" "+api.ItemsDirective) if err != nil { t.Error(err) return } if got := len(metaSeq); got != 4 { t.Errorf("Expected list of length 4, got %d", got) return } checkListZid(t, metaSeq, 0, api.ZidTemplateNewZettel) checkListZid(t, metaSeq, 1, api.ZidTemplateNewRole) checkListZid(t, metaSeq, 2, api.ZidTemplateNewTag) checkListZid(t, metaSeq, 3, api.ZidTemplateNewUser) } // func TestGetZettelContext(t *testing.T) { // const ( // allUserZid = api.ZettelID("20211019200500") // ownerZid = api.ZettelID("20210629163300") // writerZid = api.ZettelID("20210629165000") |
︙ | ︙ | |||
277 278 279 280 281 282 283 | c.SetAuth("owner", "owner") _, _, metaSeq, err := c.QueryZettelData(context.Background(), string(api.ZidDefaultHome)+" "+api.UnlinkedDirective) if err != nil { t.Error(err) return } if got := len(metaSeq); got != 1 { | | | 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 | c.SetAuth("owner", "owner") _, _, metaSeq, err := c.QueryZettelData(context.Background(), string(api.ZidDefaultHome)+" "+api.UnlinkedDirective) if err != nil { t.Error(err) return } if got := len(metaSeq); got != 1 { t.Errorf("Expected list of length 1, got %d:\n%v", got, metaSeq) return } } func failNoErrorOrNoCode(t *testing.T, err error, goodCode int) bool { if err != nil { if cErr, ok := err.(*client.Error); ok { |
︙ | ︙ | |||
382 383 384 385 386 387 388 | c := getClient() c.SetAuth("owner", "owner") agg, err := c.QueryAggregate(context.Background(), api.ActionSeparator+api.KeyRole) if err != nil { t.Error(err) return } | | > > > > > > > > > > > > > > > > > > > > > | 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 | c := getClient() c.SetAuth("owner", "owner") agg, err := c.QueryAggregate(context.Background(), api.ActionSeparator+api.KeyRole) if err != nil { t.Error(err) return } exp := []string{"configuration", "role", "user", "tag", "zettel"} if len(agg) != len(exp) { t.Errorf("Expected %d different roles, but got %d (%v)", len(exp), len(agg), agg) } for _, id := range exp { if _, found := agg[id]; !found { t.Errorf("Role map expected key %q", id) } } } func TestRoleZettel(t *testing.T) { t.Parallel() c := getClient() c.AllowRedirect(true) c.SetAuth("owner", "owner") ctx := context.Background() zid, err := c.RoleZettel(ctx, "nosuchrole") if err != nil { t.Error("AAA", err) } else if zid != "" { t.Errorf("no zid expected, but got %q", zid) } zid, err = c.RoleZettel(ctx, "zettel") exp := api.ZettelID("00000000060010") if err != nil { t.Error(err) } else if zid != exp { t.Errorf("role zettel for zettel should be %q, but got %q", exp, zid) } } func TestVersion(t *testing.T) { t.Parallel() c := getClient() ver, err := c.GetVersionInfo(context.Background()) if err != nil { t.Error(err) |
︙ | ︙ |
Changes to tests/markdown_test.go.
︙ | ︙ | |||
44 45 46 47 48 49 50 | Section string `json:"section"` } func TestEncoderAvailability(t *testing.T) { t.Parallel() encoderMissing := false for _, enc := range encodings { | | | 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | Section string `json:"section"` } func TestEncoderAvailability(t *testing.T) { t.Parallel() encoderMissing := false for _, enc := range encodings { enc := encoder.Create(enc, &encoder.CreateParameter{Lang: api.ValueLangEN}) if enc == nil { t.Errorf("No encoder for %q found", enc) encoderMissing = true } } if encoderMissing { panic("At least one encoder is missing. See test log") |
︙ | ︙ | |||
82 83 84 85 86 87 88 | } func testAllEncodings(t *testing.T, tc markdownTestCase, ast *ast.BlockSlice) { var sb strings.Builder testID := tc.Example*100 + 1 for _, enc := range encodings { t.Run(fmt.Sprintf("Encode %v %v", enc, testID), func(st *testing.T) { | | | | 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | } func testAllEncodings(t *testing.T, tc markdownTestCase, ast *ast.BlockSlice) { var sb strings.Builder testID := tc.Example*100 + 1 for _, enc := range encodings { t.Run(fmt.Sprintf("Encode %v %v", enc, testID), func(st *testing.T) { encoder.Create(enc, &encoder.CreateParameter{Lang: api.ValueLangEN}).WriteBlocks(&sb, ast) sb.Reset() }) } } func testZmkEncoding(t *testing.T, tc markdownTestCase, ast *ast.BlockSlice) { zmkEncoder := encoder.Create(api.EncoderZmk, nil) var buf bytes.Buffer testID := tc.Example*100 + 1 t.Run(fmt.Sprintf("Encode zmk %14d", testID), func(st *testing.T) { buf.Reset() zmkEncoder.WriteBlocks(&buf, ast) // gotFirst := buf.String() |
︙ | ︙ | |||
126 127 128 129 130 131 132 | func TestAdditionalMarkdown(t *testing.T) { testcases := []struct { md string exp string }{ {`abc<br>def`, `abc@@<br>@@{="html"}def`}, } | | | 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | func TestAdditionalMarkdown(t *testing.T) { testcases := []struct { md string exp string }{ {`abc<br>def`, `abc@@<br>@@{="html"}def`}, } zmkEncoder := encoder.Create(api.EncoderZmk, nil) var sb strings.Builder for i, tc := range testcases { ast := createMDBlockSlice(tc.md, config.MarkdownHTML) sb.Reset() zmkEncoder.WriteBlocks(&sb, &ast) got := sb.String() if got != tc.exp { t.Errorf("%d: %q -> %q, but got %q", i, tc.md, tc.exp, got) } } } |
Changes to tests/naughtystrings_test.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import ( "bufio" "io" "os" "path/filepath" "testing" _ "zettelstore.de/z/cmd" "zettelstore.de/z/encoder" "zettelstore.de/z/input" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) | > | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import ( "bufio" "io" "os" "path/filepath" "testing" "zettelstore.de/client.fossil/api" _ "zettelstore.de/z/cmd" "zettelstore.de/z/encoder" "zettelstore.de/z/input" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) |
︙ | ︙ | |||
51 52 53 54 55 56 57 | } } return result } func getAllEncoder() (result []encoder.Encoder) { for _, enc := range encoder.GetEncodings() { | | | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | } } return result } func getAllEncoder() (result []encoder.Encoder) { for _, enc := range encoder.GetEncodings() { e := encoder.Create(enc, &encoder.CreateParameter{Lang: api.ValueLangEN}) result = append(result, e) } return result } func TestNaughtyStringParser(t *testing.T) { blns, err := getNaughtyStrings() |
︙ | ︙ |
Changes to tests/regression_test.go.
︙ | ︙ | |||
117 118 119 120 121 122 123 | } return u.Path[len(root):] } func checkMetaFile(t *testing.T, resultName string, zn *ast.ZettelNode, enc api.EncodingEnum) { t.Helper() | | | 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | } return u.Path[len(root):] } func checkMetaFile(t *testing.T, resultName string, zn *ast.ZettelNode, enc api.EncodingEnum) { t.Helper() if enc := encoder.Create(enc, &encoder.CreateParameter{Lang: api.ValueLangEN}); enc != nil { var sf strings.Builder enc.WriteMeta(&sf, zn.Meta, parser.ParseMetadata) checkFileContent(t, resultName, sf.String()) return } panic(fmt.Sprintf("Unknown writer encoding %q", enc)) } |
︙ | ︙ |
Changes to usecase/create_zettel.go.
︙ | ︙ | |||
140 141 142 143 144 145 146 | // Run executes the use case. func (uc *CreateZettel) Run(ctx context.Context, zettel zettel.Zettel) (id.Zid, error) { m := zettel.Meta if m.Zid.IsValid() { return m.Zid, nil // TODO: new error: already exists } | | | 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | // Run executes the use case. func (uc *CreateZettel) Run(ctx context.Context, zettel zettel.Zettel) (id.Zid, error) { m := zettel.Meta if m.Zid.IsValid() { return m.Zid, nil // TODO: new error: already exists } m.Set(api.KeyCreated, time.Now().Local().Format(id.TimestampLayout)) m.Delete(api.KeyModified) m.YamlSep = uc.rtConfig.GetYAMLHeader() zettel.Content.TrimSpace() zid, err := uc.port.CreateZettel(ctx, zettel) uc.log.Info().User(ctx).Zid(zid).Err(err).Msg("Create zettel") return zid, err } |
Changes to usecase/get_special_zettel.go.
︙ | ︙ | |||
17 18 19 20 21 22 23 | "zettelstore.de/z/query" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // TagZettel is the usecase of retrieving a "tag zettel", i.e. a zettel that | | | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | "zettelstore.de/z/query" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // TagZettel is the usecase of retrieving a "tag zettel", i.e. a zettel that // describes a given tag. A tag zettel must have the tag's name in its title // and must have a role=tag. // TagZettelPort is the interface used by this use case. type TagZettelPort interface { // GetZettel retrieves a specific zettel. GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) } |
︙ | ︙ | |||
60 61 62 63 64 65 66 | return zettel.Zettel{}, ErrTagZettelNotFound{Tag: tag} } // ErrTagZettelNotFound is returned if a tag zettel was not found. type ErrTagZettelNotFound struct{ Tag string } func (etznf ErrTagZettelNotFound) Error() string { return "tag zettel not found: " + etznf.Tag } | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | return zettel.Zettel{}, ErrTagZettelNotFound{Tag: tag} } // ErrTagZettelNotFound is returned if a tag zettel was not found. type ErrTagZettelNotFound struct{ Tag string } func (etznf ErrTagZettelNotFound) Error() string { return "tag zettel not found: " + etznf.Tag } // RoleZettel is the usecase of retrieving a "role zettel", i.e. a zettel that // describes a given role. A role zettel must have the role's name in its title // and must have a role=role. // RoleZettelPort is the interface used by this use case. type RoleZettelPort interface { // GetZettel retrieves a specific zettel. GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) } // RoleZettel is the data for this use case. type RoleZettel struct { port GetZettelPort query *Query } // NewRoleZettel creates a new use case. func NewRoleZettel(port GetZettelPort, query *Query) RoleZettel { return RoleZettel{port: port, query: query} } // Run executes the use case. func (uc RoleZettel) Run(ctx context.Context, role string) (zettel.Zettel, error) { q := query.Parse( api.KeyTitle + api.SearchOperatorEqual + role + " " + api.KeyRole + api.SearchOperatorHas + api.ValueRoleRole) ml, err := uc.query.Run(ctx, q) if err != nil { return zettel.Zettel{}, err } for _, m := range ml { z, errZ := uc.port.GetZettel(ctx, m.Zid) if errZ == nil { return z, nil } } return zettel.Zettel{}, ErrRoleZettelNotFound{Role: role} } // ErrRoleZettelNotFound is returned if a role zettel was not found. type ErrRoleZettelNotFound struct{ Role string } func (etznf ErrRoleZettelNotFound) Error() string { return "role zettel not found: " + etznf.Role } |
Changes to web/adapter/api/api.go.
︙ | ︙ | |||
68 69 70 71 72 73 74 | func (a *API) getToken(ident *meta.Meta) ([]byte, error) { return a.token.GetToken(ident, a.tokenLifetime, auth.KindAPI) } func (a *API) reportUsecaseError(w http.ResponseWriter, err error) { code, text := adapter.CodeMessageFromError(err) if code == http.StatusInternalServerError { | | | 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | func (a *API) getToken(ident *meta.Meta) ([]byte, error) { return a.token.GetToken(ident, a.tokenLifetime, auth.KindAPI) } func (a *API) reportUsecaseError(w http.ResponseWriter, err error) { code, text := adapter.CodeMessageFromError(err) if code == http.StatusInternalServerError { a.log.Error().Err(err).Msg(text) http.Error(w, http.StatusText(code), code) return } // TODO: must call PrepareHeader somehow http.Error(w, text, code) } |
︙ | ︙ |
Changes to web/adapter/api/create_zettel.go.
︙ | ︙ | |||
64 65 66 67 68 69 70 | default: panic(encStr) } h := adapter.PrepareHeader(w, contentType) h.Set(api.HeaderLocation, location.String()) w.WriteHeader(http.StatusCreated) | | | | | > | 64 65 66 67 68 69 70 71 72 73 74 75 | default: panic(encStr) } 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/get_data.go.
︙ | ︙ | |||
25 26 27 28 29 30 31 | err := a.writeObject(w, id.Invalid, sx.MakeList( sx.Int64(version.Major), sx.Int64(version.Minor), sx.Int64(version.Patch), sx.String(version.Info), sx.String(version.Hash), )) | > | | | > | 25 26 27 28 29 30 31 32 33 34 35 36 | err := a.writeObject(w, id.Invalid, sx.MakeList( sx.Int64(version.Major), sx.Int64(version.Minor), sx.Int64(version.Patch), sx.String(version.Info), sx.String(version.Hash), )) if err != nil { a.log.Error().Err(err).Msg("Write Version Info") } } } |
Changes to web/adapter/api/get_zettel.go.
︙ | ︙ | |||
60 61 62 63 64 65 66 | return evaluate.RunMetadata(ctx, value) } } if err != nil { a.reportUsecaseError(w, err) return } | | | 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | return evaluate.RunMetadata(ctx, value) } } if err != nil { a.reportUsecaseError(w, err) return } a.writeEncodedZettelPart(ctx, w, zn, em, enc, encStr, part) } } } func (a *API) writePlainData(w http.ResponseWriter, ctx context.Context, zid id.Zid, part partType, getZettel usecase.GetZettel) { var buf bytes.Buffer var contentType string |
︙ | ︙ | |||
100 101 102 103 104 105 106 | } if err != nil { a.log.Fatal().Err(err).Zid(zid).Msg("Unable to store plain zettel/part in buffer") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } | | | > | 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | } if err != nil { a.log.Fatal().Err(err).Zid(zid).Msg("Unable to store plain zettel/part in buffer") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if err = writeBuffer(w, &buf, contentType); err != nil { a.log.Error().Err(err).Zid(zid).Msg("Write Plain data") } } func (a *API) writeSzData(w http.ResponseWriter, ctx context.Context, zid id.Zid, part partType, getZettel usecase.GetZettel) { z, err := getZettel.Run(ctx, zid) if err != nil { a.reportUsecaseError(w, err) return |
︙ | ︙ | |||
127 128 129 130 131 132 133 | case partMeta: obj = sexp.EncodeMetaRights(api.MetaRights{ Meta: z.Meta.Map(), Rights: a.getRights(ctx, z.Meta), }) } | | | > > | > > > > | 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | case partMeta: obj = sexp.EncodeMetaRights(api.MetaRights{ Meta: z.Meta.Map(), Rights: a.getRights(ctx, z.Meta), }) } if err = a.writeObject(w, zid, obj); err != nil { a.log.Error().Err(err).Zid(zid).Msg("write sx data") } } func (a *API) writeEncodedZettelPart( ctx context.Context, w http.ResponseWriter, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc, enc api.EncodingEnum, encStr string, part partType, ) { encdr := encoder.Create( enc, &encoder.CreateParameter{ Lang: a.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang), }) if encdr == nil { adapter.BadRequest(w, fmt.Sprintf("Zettel %q not available in encoding %q", zn.Meta.Zid, encStr)) return } var err error var buf bytes.Buffer switch part { |
︙ | ︙ | |||
161 162 163 164 165 166 167 | return } if buf.Len() == 0 { w.WriteHeader(http.StatusNoContent) return } | | | | > | 168 169 170 171 172 173 174 175 176 177 178 | return } if buf.Len() == 0 { w.WriteHeader(http.StatusNoContent) return } if err = writeBuffer(w, &buf, content.MIMEFromEncoding(enc)); err != nil { a.log.Error().Err(err).Zid(zn.Zid).Msg("Write Encoded Zettel") } } |
Changes to web/adapter/api/login.go.
︙ | ︙ | |||
21 22 23 24 25 26 27 | "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.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if !a.withAuth() { | | | > | | > | | > | | > | | > | 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 | "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.HandlerFunc { return 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 if ident, cred := retrieveIdentCred(r); ident != "" { var err error token, err = ucAuth.Run(r.Context(), r, ident, cred, a.tokenLifetime, auth.KindAPI) if err != nil { a.reportUsecaseError(w, err) return } } if len(token) == 0 { w.Header().Set("WWW-Authenticate", `Bearer realm="Default"`) 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.HandlerFunc { return 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 } authData := a.getAuthData(ctx) if authData == nil || len(authData.Token) == 0 || authData.User == nil { adapter.BadRequest(w, "Not authenticated") return } totalLifetime := authData.Expires.Sub(authData.Issued) currentLifetime := authData.Now.Sub(authData.Issued) // If we are in the first quarter of the tokens lifetime, return the token if currentLifetime*4 < totalLifetime { if err := a.writeToken(w, string(authData.Token), totalLifetime-currentLifetime); err != nil { a.log.Error().Err(err).Msg("Write old token") } return } // Token is a little bit aged. Create a new one token, err := a.getToken(authData.User) 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.String("Bearer"), sx.String(token), sx.Int64(int64(lifetime/time.Second)), )) } |
Changes to web/adapter/api/query.go.
︙ | ︙ | |||
27 28 29 30 31 32 33 | "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. | | > > > | 27 28 29 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" ) // 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.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() urlQuery := r.URL.Query() if a.handleTagZettel(w, r, tagZettel, urlQuery) { return } if a.handleRoleZettel(w, r, roleZettel, urlQuery) { return } sq := adapter.GetQuery(urlQuery) metaSeq, err := queryMeta.Run(ctx, sq) if err != nil { a.reportUsecaseError(w, err) |
︙ | ︙ | |||
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | } zid := z.Meta.Zid.String() w.Header().Set(api.HeaderContentType, content.PlainText) newURL := a.NewURLBuilder('z').SetZid(api.ZettelID(zid)) for key, slVals := range vals { if key == api.QueryKeyTag { continue } for _, val := range slVals { newURL.AppendKVQuery(key, val) } } http.Redirect(w, r, newURL.String(), http.StatusFound) if _, err = io.WriteString(w, zid); err != nil { a.log.Error().Err(err).Msg("redirect body") } return true } | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | } zid := z.Meta.Zid.String() w.Header().Set(api.HeaderContentType, content.PlainText) newURL := a.NewURLBuilder('z').SetZid(api.ZettelID(zid)) for key, slVals := range vals { if key == api.QueryKeyTag { continue } for _, val := range slVals { newURL.AppendKVQuery(key, val) } } http.Redirect(w, r, newURL.String(), http.StatusFound) if _, err = io.WriteString(w, zid); err != nil { a.log.Error().Err(err).Msg("redirect body") } return true } func (a *API) handleRoleZettel(w http.ResponseWriter, r *http.Request, roleZettel *usecase.RoleZettel, vals url.Values) bool { role := vals.Get(api.QueryKeyRole) if role == "" { return false } ctx := r.Context() z, err := roleZettel.Run(ctx, role) if err != nil { a.reportUsecaseError(w, err) return true } zid := z.Meta.Zid.String() w.Header().Set(api.HeaderContentType, content.PlainText) newURL := a.NewURLBuilder('z').SetZid(api.ZettelID(zid)) for key, slVals := range vals { if key == api.QueryKeyRole { continue } for _, val := range slVals { newURL.AppendKVQuery(key, val) } } http.Redirect(w, r, newURL.String(), http.StatusFound) if _, err = io.WriteString(w, zid); err != nil { a.log.Error().Err(err).Msg("redirect body") } return true } |
Changes to web/adapter/response.go.
︙ | ︙ | |||
71 72 73 74 75 76 77 78 79 80 81 82 83 84 | if errors.As(err, &ezin) { return http.StatusBadRequest, fmt.Sprintf("Zettel-ID %q already in use", ezin.Zid) } var etznf usecase.ErrTagZettelNotFound if errors.As(err, &etznf) { return http.StatusNotFound, "Tag zettel not found: " + etznf.Tag } var ebr ErrBadRequest if errors.As(err, &ebr) { return http.StatusBadRequest, ebr.Text } if errors.Is(err, box.ErrStopped) { return http.StatusInternalServerError, fmt.Sprintf("Zettelstore not operational: %v", err) } | > > > > | 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | if errors.As(err, &ezin) { return http.StatusBadRequest, fmt.Sprintf("Zettel-ID %q already in use", ezin.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 } var ebr ErrBadRequest if errors.As(err, &ebr) { return http.StatusBadRequest, ebr.Text } if errors.Is(err, box.ErrStopped) { return http.StatusInternalServerError, fmt.Sprintf("Zettelstore not operational: %v", err) } |
︙ | ︙ |
Changes to web/adapter/webui/favicon.go.
︙ | ︙ | |||
33 34 35 36 37 38 39 | data, err := io.ReadAll(f) if err != nil { wui.log.Info().Err(err).Msg("Unable to read favicon data") http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } | | | | | > | 33 34 35 36 37 38 39 40 41 42 43 44 | data, err := io.ReadAll(f) if err != nil { wui.log.Info().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.
︙ | ︙ | |||
52 53 54 55 56 57 58 | zn, err := ucParseZettel.Run(ctx, zid, q.Get(api.KeySyntax)) if err != nil { wui.reportError(ctx, w, err) return } | | | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | zn, err := ucParseZettel.Run(ctx, zid, q.Get(api.KeySyntax)) if err != nil { wui.reportError(ctx, w, err) return } enc := wui.getSimpleHTMLEncoder(wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang)) getTextTitle := wui.makeGetTextTitle(ctx, ucGetZettel) evalMeta := func(val string) ast.InlineSlice { return ucEvaluate.RunMetadata(ctx, val) } pairs := zn.Meta.ComputedPairs() metadata := sx.Nil() for i := len(pairs) - 1; i >= 0; i-- { |
︙ | ︙ |
Changes to web/adapter/webui/get_zettel.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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-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. //----------------------------------------------------------------------------- package webui import ( "net/http" "zettelstore.de/client.fossil/api" "zettelstore.de/sx.fossil" "zettelstore.de/z/box" "zettelstore.de/z/parser" "zettelstore.de/z/usecase" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) | > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //----------------------------------------------------------------------------- // Copyright (c) 2020-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. //----------------------------------------------------------------------------- package webui import ( "context" "net/http" "zettelstore.de/client.fossil/api" "zettelstore.de/sx.fossil" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/parser" "zettelstore.de/z/usecase" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) |
︙ | ︙ | |||
37 38 39 40 41 42 43 | q := r.URL.Query() zn, err := evaluate.Run(ctx, zid, q.Get(api.KeySyntax)) if err != nil { wui.reportError(ctx, w, err) return } | | | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | q := r.URL.Query() zn, err := evaluate.Run(ctx, zid, q.Get(api.KeySyntax)) if err != nil { wui.reportError(ctx, w, err) return } enc := wui.getSimpleHTMLEncoder(wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang)) metaObj := enc.MetaSxn(zn.InhMeta, createEvalMetadataFunc(ctx, evaluate)) content, endnotes, err := enc.BlocksSxn(&zn.Ast) if err != nil { wui.reportError(ctx, w, err) return } |
︙ | ︙ | |||
64 65 66 67 68 69 70 | } rb.bindString("tag-refs", wui.transformTagSet(api.KeyTags, meta.ListFromValue(zn.InhMeta.GetDefault(api.KeyTags, "")))) rb.bindString("predecessor-refs", wui.identifierSetAsLinks(zn.InhMeta, api.KeyPredecessor, getTextTitle)) rb.bindString("precursor-refs", wui.identifierSetAsLinks(zn.InhMeta, api.KeyPrecursor, getTextTitle)) rb.bindString("superior-refs", wui.identifierSetAsLinks(zn.InhMeta, api.KeySuperior, getTextTitle)) rb.bindString("content", content) rb.bindString("endnotes", endnotes) | | | | | | 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | } rb.bindString("tag-refs", wui.transformTagSet(api.KeyTags, meta.ListFromValue(zn.InhMeta.GetDefault(api.KeyTags, "")))) rb.bindString("predecessor-refs", wui.identifierSetAsLinks(zn.InhMeta, api.KeyPredecessor, getTextTitle)) rb.bindString("precursor-refs", wui.identifierSetAsLinks(zn.InhMeta, api.KeyPrecursor, getTextTitle)) rb.bindString("superior-refs", wui.identifierSetAsLinks(zn.InhMeta, api.KeySuperior, getTextTitle)) rb.bindString("content", content) rb.bindString("endnotes", endnotes) wui.bindLinks(ctx, &rb, "folge", zn.InhMeta, api.KeyFolge, config.KeyShowFolgeLinks, getTextTitle) wui.bindLinks(ctx, &rb, "subordinate", zn.InhMeta, api.KeySubordinates, config.KeyShowSubordinateLinks, getTextTitle) wui.bindLinks(ctx, &rb, "back", zn.InhMeta, api.KeyBack, config.KeyShowBackLinks, getTextTitle) wui.bindLinks(ctx, &rb, "successor", zn.InhMeta, api.KeySuccessors, config.KeyShowSuccessorLinks, getTextTitle) if role, found := zn.InhMeta.Get(api.KeyRole); found && role != "" { for _, part := range []string{"meta", "actions", "heading"} { rb.rebindResolved("ROLE-"+role+"-"+part, "ROLE-DEFAULT-"+part) } } wui.bindCommonZettelData(ctx, &rb, user, zn.InhMeta, &zn.Content) if rb.err == nil { |
︙ | ︙ | |||
91 92 93 94 95 96 97 98 99 100 101 102 103 104 | 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 } func (wui *WebUI) zettelLinksSxn(m *meta.Meta, key string, getTextTitle getTextTitleFunc) *sx.Pair { values, ok := m.GetList(key) if !ok || len(values) == 0 { return nil } return wui.zidLinksSxn(values, getTextTitle) | > > > > > > > > > > > > > > > > > > > | 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 | 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 } func (wui *WebUI) bindLinks(ctx context.Context, rb *renderBinder, varPrefix string, m *meta.Meta, key, configKey string, getTextTitle getTextTitleFunc) { varLinks := varPrefix + "-links" var symOpen *sx.Symbol switch wui.rtConfig.Get(ctx, m, configKey) { case "false": rb.bindString(varLinks, sx.Nil()) return case "close": default: symOpen = wui.symAttrOpen } lstLinks := wui.zettelLinksSxn(m, key, getTextTitle) rb.bindString(varLinks, lstLinks) if sx.IsNil(lstLinks) { return } rb.bindString(varPrefix+"-open", symOpen) } func (wui *WebUI) zettelLinksSxn(m *meta.Meta, key string, getTextTitle getTextTitleFunc) *sx.Pair { values, ok := m.GetList(key) if !ok || len(values) == 0 { return nil } return wui.zidLinksSxn(values, getTextTitle) |
︙ | ︙ |
Changes to web/adapter/webui/htmlgen.go.
︙ | ︙ | |||
16 17 18 19 20 21 22 | "zettelstore.de/client.fossil/api" "zettelstore.de/client.fossil/attrs" "zettelstore.de/client.fossil/maps" "zettelstore.de/client.fossil/shtml" "zettelstore.de/client.fossil/sz" "zettelstore.de/sx.fossil" | < | > | | | 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 | "zettelstore.de/client.fossil/api" "zettelstore.de/client.fossil/attrs" "zettelstore.de/client.fossil/maps" "zettelstore.de/client.fossil/shtml" "zettelstore.de/client.fossil/sz" "zettelstore.de/sx.fossil" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/encoder/szenc" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/meta" ) // Builder allows to build new URLs for the web service. type urlBuilder interface { GetURLPrefix() string NewURLBuilder(key byte) *api.URLBuilder } type htmlGenerator struct { tx *szenc.Transformer th *shtml.Evaluator lang string symAt *sx.Symbol } func (wui *WebUI) createGenerator(builder urlBuilder, lang string) *htmlGenerator { th := shtml.NewEvaluator(1, wui.sf) symA := wui.symA symImg := th.Make("img") symAttr := wui.symAttr symHref := wui.symHref symClass := th.Make("class") symTarget := th.Make("target") |
︙ | ︙ | |||
63 64 65 66 67 68 69 | objA := rest.Car() attr, isPair = sx.GetPair(objA) if !isPair || !symAttr.IsEqual(attr.Car()) { return nil, nil, nil } return attr, attr.Tail(), rest.Tail() } | | < < < < | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | objA := rest.Car() attr, isPair = sx.GetPair(objA) if !isPair || !symAttr.IsEqual(attr.Car()) { return nil, nil, nil } return attr, attr.Tail(), rest.Tail() } linkZettel := func(obj sx.Object) sx.Object { attr, assoc, rest := findA(obj) if attr == nil { return obj } hrefP := assoc.Assoc(symHref) if hrefP == nil { |
︙ | ︙ | |||
90 91 92 93 94 95 96 | if hasFragment { u = u.SetFragment(fragment) } assoc = assoc.Cons(sx.Cons(symHref, sx.String(u.String()))) return rest.Cons(assoc.Cons(symAttr)).Cons(symA) } | < | | | < < < < | | | | | | | | | | | | | | | | | < < < < | | | | | | | | | | | | | | | | | | | | | | | | | < < < < | | | | | | | | | | < < < < | | | | | | | | | | | | | | | | | | | | | | | | < > > > > > > > > > > > > > | | 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | if hasFragment { u = u.SetFragment(fragment) } assoc = assoc.Cons(sx.Cons(symHref, sx.String(u.String()))) return rest.Cons(assoc.Cons(symAttr)).Cons(symA) } rebind(th, sz.NameSymLinkZettel, linkZettel) rebind(th, sz.NameSymLinkFound, linkZettel) rebind(th, sz.NameSymLinkBased, func(obj sx.Object) sx.Object { attr, assoc, rest := findA(obj) if attr == nil { return obj } hrefP := assoc.Assoc(symHref) if hrefP == nil { return obj } href, ok := sx.GetString(hrefP.Cdr()) if !ok { return obj } u := builder.NewURLBuilder('/').SetRawLocal(href.String()) assoc = assoc.Cons(sx.Cons(symHref, sx.String(u.String()))) return rest.Cons(assoc.Cons(symAttr)).Cons(symA) }) rebind(th, sz.NameSymLinkQuery, func(obj sx.Object) sx.Object { attr, assoc, rest := findA(obj) if attr == nil { return obj } hrefP := assoc.Assoc(symHref) if hrefP == nil { return obj } href, ok := sx.GetString(hrefP.Cdr()) if !ok { return obj } ur, err := url.Parse(href.String()) if err != nil { return obj } q := ur.Query().Get(api.QueryKeyQuery) if q == "" { return obj } u := builder.NewURLBuilder('h').AppendQuery(q) assoc = assoc.Cons(sx.Cons(symHref, sx.String(u.String()))) return rest.Cons(assoc.Cons(symAttr)).Cons(symA) }) rebind(th, sz.NameSymLinkExternal, func(obj sx.Object) sx.Object { attr, assoc, rest := findA(obj) if attr == nil { return obj } assoc = assoc.Cons(sx.Cons(symClass, sx.String("external"))). Cons(sx.Cons(symTarget, sx.String("_blank"))). Cons(sx.Cons(symRel, sx.String("noopener noreferrer"))) return rest.Cons(assoc.Cons(symAttr)).Cons(symA) }) rebind(th, sz.NameSymEmbed, func(obj sx.Object) sx.Object { pair, isPair := sx.GetPair(obj) if !isPair || !symImg.IsEqual(pair.Car()) { return obj } attr, isPair := sx.GetPair(pair.Tail().Car()) if !isPair || !symAttr.IsEqual(attr.Car()) { return obj } symSrc := th.Make("src") srcP := attr.Tail().Assoc(symSrc) if srcP == nil { return obj } src, isString := sx.GetString(srcP.Cdr()) if !isString { return obj } zid := api.ZettelID(src) if !zid.IsValid() { return obj } u := builder.NewURLBuilder('z').SetZid(zid) imgAttr := attr.Tail().Cons(sx.Cons(symSrc, sx.String(u.String()))).Cons(symAttr) return pair.Tail().Tail().Cons(imgAttr).Cons(symImg) }) return &htmlGenerator{ tx: szenc.NewTransformer(), th: th, lang: lang, symAt: symAttr, } } func rebind(ev *shtml.Evaluator, name string, fn func(sx.Object) sx.Object) { prevFn := ev.ResolveBinding(name) ev.Rebind(name, func(args []sx.Object, env *shtml.Environment) sx.Object { obj := prevFn(args, env) if env.GetError() == nil { return fn(obj) } return sx.Nil() }) } // SetUnique sets a prefix to make several HTML ids unique. func (g *htmlGenerator) SetUnique(s string) *htmlGenerator { g.th.SetUnique(s); return g } var mapMetaKey = map[string]string{ api.KeyCopyright: "copyright", api.KeyLicense: "license", } func (g *htmlGenerator) MetaSxn(m *meta.Meta, evalMeta encoder.EvalMetaFunc) *sx.Pair { tm := g.tx.GetMeta(m, evalMeta) env := shtml.MakeEnvironment(g.lang) hm, err := g.th.Evaluate(tm, &env) if err != nil { return nil } ignore := strfun.NewSet(api.KeyTitle, api.KeyLang) metaMap := make(map[string]*sx.Pair, m.Length()) if tags, ok := m.Get(api.KeyTags); ok { |
︙ | ︙ | |||
251 252 253 254 255 256 257 | } newName, found := mapMetaKey[name] if !found { continue } a = a.Set("name", newName) | | | 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | } newName, found := mapMetaKey[name] if !found { continue } a = a.Set("name", newName) metaMap[newName] = g.th.EvaluateMeta(a) } result := sx.Nil() keys := maps.Keys(metaMap) for i := len(keys) - 1; i >= 0; i-- { result = result.Cons(metaMap[keys[i]]) } return result |
︙ | ︙ | |||
273 274 275 276 277 278 279 | } sb.WriteString(strings.TrimPrefix(val, "#")) } metaTags := sb.String() if len(metaTags) == 0 { return nil } | | > | | > | | 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 | } sb.WriteString(strings.TrimPrefix(val, "#")) } metaTags := sb.String() if len(metaTags) == 0 { return nil } return g.th.EvaluateMeta(attrs.Attributes{"name": "keywords", "content": metaTags}) } func (g *htmlGenerator) BlocksSxn(bs *ast.BlockSlice) (content, endnotes *sx.Pair, _ error) { if bs == nil || len(*bs) == 0 { return nil, nil, nil } 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, g.th.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 } sx := g.tx.GetSz(is) env := shtml.MakeEnvironment(g.lang) sh, err := g.th.Evaluate(sx, &env) if err != nil { return nil } return sh } |
Changes to web/adapter/webui/lists.go.
︙ | ︙ | |||
31 32 33 34 35 36 37 | "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. | | > > > | 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/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.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { urlQuery := r.URL.Query() if wui.handleTagZettel(w, r, tagZettel, urlQuery) { return } if wui.handleRoleZettel(w, r, roleZettel, urlQuery) { return } q := adapter.GetQuery(urlQuery) q = q.SetDeterministic() ctx := r.Context() metaSeq, err := queryMeta.Run(ctx, q) if err != nil { wui.reportError(ctx, w, err) |
︙ | ︙ | |||
73 74 75 76 77 78 79 | wui.renderRSS(ctx, w, q, metaSeq) return } } } var content, endnotes *sx.Pair if bn := evaluator.QueryAction(ctx, q, metaSeq, wui.rtConfig); bn != nil { | | | 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | wui.renderRSS(ctx, w, q, metaSeq) return } } } var content, endnotes *sx.Pair if bn := evaluator.QueryAction(ctx, q, metaSeq, wui.rtConfig); bn != nil { enc := wui.getSimpleHTMLEncoder(wui.rtConfig.Get(ctx, nil, api.KeyLang)) content, endnotes, err = enc.BlocksSxn(&ast.BlockSlice{bn}) if err != nil { wui.reportError(ctx, w, err) return } } |
︙ | ︙ | |||
99 100 101 102 103 104 105 | } rb.bindString("query-value", sx.String(q.String())) if tzl := q.GetMetaValues(api.KeyTags); len(tzl) > 0 { sxTzl, sxNoTzl := wui.transformTagZettelList(ctx, tagZettel, tzl) if !sx.IsNil(sxTzl) { rb.bindString("tag-zettel", sxTzl) } | | > > > > > > > > > | 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 | } rb.bindString("query-value", sx.String(q.String())) if tzl := q.GetMetaValues(api.KeyTags); len(tzl) > 0 { sxTzl, sxNoTzl := wui.transformTagZettelList(ctx, tagZettel, tzl) if !sx.IsNil(sxTzl) { rb.bindString("tag-zettel", sxTzl) } if !sx.IsNil(sxNoTzl) && wui.canCreate(ctx, user) { rb.bindString("create-tag-zettel", sxNoTzl) } } if rzl := q.GetMetaValues(api.KeyRole); len(rzl) > 0 { sxRzl, sxNoRzl := wui.transformRoleZettelList(ctx, roleZettel, rzl) if !sx.IsNil(sxRzl) { rb.bindString("role-zettel", sxRzl) } if !sx.IsNil(sxNoRzl) && wui.canCreate(ctx, user) { rb.bindString("create-role-zettel", sxNoRzl) } } rb.bindString("content", content) rb.bindString("endnotes", endnotes) apiURL := wui.NewURLBuilder('z').AppendQuery(q.String()) seed, found := q.GetSeed() if found { apiURL = apiURL.AppendKVQuery(api.QueryKeySeed, strconv.Itoa(seed)) |
︙ | ︙ | |||
132 133 134 135 136 137 138 139 140 | } } } func (wui *WebUI) transformTagZettelList(ctx context.Context, tagZettel *usecase.TagZettel, tags []string) (withZettel, withoutZettel *sx.Pair) { slices.Reverse(tags) for _, tag := range tags { if _, err := tagZettel.Run(ctx, tag); err == nil { u := wui.NewURLBuilder('h').AppendKVQuery(api.QueryKeyTag, tag) | > | | > > > > > > > > > > > > > > | | | 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | } } } 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 { u := wui.NewURLBuilder('h').AppendKVQuery(api.QueryKeyTag, tag) withZettel = wui.prependZettelLink(withZettel, tag, u) } else { u := wui.NewURLBuilder('c').SetZid(api.ZidTemplateNewTag).AppendKVQuery(queryKeyAction, valueActionNew).AppendKVQuery(api.KeyTitle, tag) withoutZettel = wui.prependZettelLink(withoutZettel, tag, u) } } return withZettel, withoutZettel } func (wui *WebUI) transformRoleZettelList(ctx context.Context, roleZettel *usecase.RoleZettel, roles []string) (withZettel, withoutZettel *sx.Pair) { slices.Reverse(roles) for _, role := range roles { if _, err := roleZettel.Run(ctx, role); err == nil { u := wui.NewURLBuilder('h').AppendKVQuery(api.QueryKeyRole, role) withZettel = wui.prependZettelLink(withZettel, role, u) } else { u := wui.NewURLBuilder('c').SetZid(api.ZidTemplateNewRole).AppendKVQuery(queryKeyAction, valueActionNew).AppendKVQuery(api.KeyTitle, role) withoutZettel = wui.prependZettelLink(withoutZettel, role, u) } } return withZettel, withoutZettel } func (wui *WebUI) prependZettelLink(sxZtl *sx.Pair, name string, u *api.URLBuilder) *sx.Pair { link := sx.MakeList( wui.symA, sx.MakeList( wui.symAttr, sx.Cons(wui.symHref, sx.String(u.String())), ), sx.String(name), ) if sxZtl != nil { sxZtl = sxZtl.Cons(sx.String(", ")) } return sxZtl.Cons(link) } |
︙ | ︙ | |||
173 174 175 176 177 178 179 | adapter.PrepareHeader(w, rss.ContentType) w.WriteHeader(http.StatusOK) var err error if _, err = io.WriteString(w, xml.Header); err == nil { _, err = w.Write(data) } if err != nil { | | | > > > > > > > > > > > > > > > | 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 | adapter.PrepareHeader(w, rss.ContentType) w.WriteHeader(http.StatusOK) var err error if _, err = io.WriteString(w, xml.Header); err == nil { _, err = w.Write(data) } if err != nil { wui.log.Error().Err(err).Msg("unable to write RSS data") } } func (wui *WebUI) renderAtom(w http.ResponseWriter, q *query.Query, ml []*meta.Meta) { var atomConfig atom.Configuration atomConfig.Setup(wui.rtConfig) if actions := q.Actions(); len(actions) > 2 && actions[1] == "TITLE" { atomConfig.Title = strings.Join(actions[2:], " ") } data := atomConfig.Marshal(q, ml) adapter.PrepareHeader(w, atom.ContentType) w.WriteHeader(http.StatusOK) var err error if _, err = io.WriteString(w, xml.Header); err == nil { _, err = w.Write(data) } if err != nil { wui.log.Error().Err(err).Msg("unable to write Atom data") } } func (wui *WebUI) handleTagZettel(w http.ResponseWriter, r *http.Request, tagZettel *usecase.TagZettel, vals url.Values) bool { tag := vals.Get(api.QueryKeyTag) if tag == "" { return false } ctx := r.Context() z, err := tagZettel.Run(ctx, tag) if err != nil { wui.reportError(ctx, w, err) return true } wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(api.ZettelID(z.Meta.Zid.String()))) return true } func (wui *WebUI) handleRoleZettel(w http.ResponseWriter, r *http.Request, roleZettel *usecase.RoleZettel, vals url.Values) bool { role := vals.Get(api.QueryKeyRole) if role == "" { return false } ctx := r.Context() z, err := roleZettel.Run(ctx, role) if err != nil { wui.reportError(ctx, w, err) return true } wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(api.ZettelID(z.Meta.Zid.String()))) return true } |
Changes to web/adapter/webui/sxn_code.go.
︙ | ︙ | |||
96 97 98 99 100 101 102 | if err2 == io.EOF { return nil } return err2 } wui.log.Debug().Zid(zid).Str("form", form.Repr()).Msg("Loaded sxn code") | | | 96 97 98 99 100 101 102 103 104 105 106 107 | if err2 == io.EOF { return nil } return err2 } wui.log.Debug().Zid(zid).Str("form", form.Repr()).Msg("Loaded sxn code") if _, err2 = wui.engine.Eval(form, env, nil); err2 != nil { return err2 } } } |
Changes to web/adapter/webui/template.go.
︙ | ︙ | |||
31 32 33 34 35 36 37 | "zettelstore.de/z/web/server" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func (wui *WebUI) createRenderEngine() *sxeval.Engine { | | | | | | | | < < > > | 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 | "zettelstore.de/z/web/server" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func (wui *WebUI) createRenderEngine() *sxeval.Engine { root := sxeval.MakeRootEnvironment(len(specials) + len(builtins) + 3) engine := sxeval.MakeEngine(wui.sf, root) for _, syntax := range specials { engine.BindSpecial(syntax) } for _, b := range builtins { engine.BindBuiltin(b) } engine.BindBuiltin(&sxeval.Builtin{ Name: "url-to-html", MinArity: 1, MaxArity: 1, TestPure: sxeval.AssertPure, Fn: func(_ *sxeval.Frame, args []sx.Object) (sx.Object, error) { text, err := sxbuiltins.GetString(args, 0) if err != nil { return nil, err } return wui.url2html(text), nil }, }) engine.BindBuiltin(&sxeval.Builtin{ Name: "zid-content-path", MinArity: 1, MaxArity: 1, TestPure: sxeval.AssertPure, Fn: func(_ *sxeval.Frame, args []sx.Object) (sx.Object, error) { s, err := sxbuiltins.GetString(args, 0) if err != nil { return nil, err } zid, err := id.Parse(s.String()) if err != nil { return nil, fmt.Errorf("parsing zettel identfier %q: %w", s, err) } ub := wui.NewURLBuilder('z').SetZid(api.ZettelID(zid.String())) return sx.String(ub.String()), nil }, }) engine.BindBuiltin(&sxeval.Builtin{ Name: "query->url", MinArity: 1, MaxArity: 1, TestPure: sxeval.AssertPure, Fn: func(_ *sxeval.Frame, args []sx.Object) (sx.Object, error) { qs, err := sxbuiltins.GetString(args, 0) if err != nil { return nil, err } u := wui.NewURLBuilder('h').AppendQuery(qs.String()) return sx.String(u.String()), nil }, }) root.Freeze() return engine } var ( specials = []*sxeval.Special{ &sxbuiltins.QuoteS, &sxbuiltins.QuasiquoteS, // quote, quasiquote &sxbuiltins.UnquoteS, &sxbuiltins.UnquoteSplicingS, // unquote, unquote-splicing &sxbuiltins.DefVarS, &sxbuiltins.DefConstS, // defvar, defconst &sxbuiltins.DefunS, &sxbuiltins.LambdaS, // defun, lambda &sxbuiltins.SetXS, // set! &sxbuiltins.CondS, // cond &sxbuiltins.IfS, // if &sxbuiltins.BeginS, // begin &sxbuiltins.DefMacroS, // defmacro } builtins = []*sxeval.Builtin{ &sxbuiltins.Identical, // == &sxbuiltins.NullP, // null? &sxbuiltins.PairP, // pair? &sxbuiltins.Car, &sxbuiltins.Cdr, // car, cdr |
︙ | ︙ | |||
321 322 323 324 325 326 327 | lst = lst.Cons(sx.Cons(text, link)) } return lst } func (wui *WebUI) calculateFooterSxn(ctx context.Context) *sx.Pair { if footerZid, err := id.Parse(wui.rtConfig.Get(ctx, nil, config.KeyFooterZettel)); err == nil { if zn, err2 := wui.evalZettel.Run(ctx, footerZid, ""); err2 == nil { | | | 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 | lst = lst.Cons(sx.Cons(text, link)) } return lst } func (wui *WebUI) calculateFooterSxn(ctx context.Context) *sx.Pair { if footerZid, err := id.Parse(wui.rtConfig.Get(ctx, nil, config.KeyFooterZettel)); err == nil { if zn, err2 := wui.evalZettel.Run(ctx, footerZid, ""); err2 == nil { htmlEnc := wui.getSimpleHTMLEncoder(wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang)).SetUnique("footer-") if content, endnotes, err3 := htmlEnc.BlocksSxn(&zn.Ast); err3 == nil { if content != nil && endnotes != nil { content.LastPair().SetCdr(sx.Cons(endnotes, nil)) } return content } } |
︙ | ︙ | |||
345 346 347 348 349 350 351 | reader, err := wui.makeZettelReader(ctx, zid) if err != nil { return nil, err } objs, err := reader.ReadAll() if err != nil { | | | | | | 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 | reader, err := wui.makeZettelReader(ctx, zid) if err != nil { return nil, err } objs, err := reader.ReadAll() if err != nil { wui.log.Error().Err(err).Zid(zid).Msg("reading sxn template") return nil, err } if len(objs) != 1 { return nil, fmt.Errorf("expected 1 expression in template, but got %d", len(objs)) } t, err := wui.engine.Parse(objs[0], env) if err != nil { return nil, err } wui.setSxnCache(zid, wui.engine.Rework(t, env)) return t, nil } func (wui *WebUI) makeZettelReader(ctx context.Context, zid id.Zid) (*sxreader.Reader, error) { ztl, err := wui.box.GetZettel(ctx, zid) if err != nil { return nil, err } reader := sxreader.MakeReader(bytes.NewReader(ztl.Content.AsBytes()), sxreader.WithSymbolFactory(wui.sf)) return reader, nil } func (wui *WebUI) evalSxnTemplate(ctx context.Context, zid id.Zid, env sxeval.Environment) (sx.Object, error) { templateExpr, err := wui.getSxnTemplate(ctx, zid, env) if err != nil { return nil, err } return wui.engine.Execute(templateExpr, env, nil) } func (wui *WebUI) renderSxnTemplate(ctx context.Context, w http.ResponseWriter, templateID id.Zid, env sxeval.Environment) error { return wui.renderSxnTemplateStatus(ctx, w, http.StatusOK, templateID, env) } func (wui *WebUI) renderSxnTemplateStatus(ctx context.Context, w http.ResponseWriter, code int, templateID id.Zid, env sxeval.Environment) error { detailObj, err := wui.evalSxnTemplate(ctx, templateID, env) |
︙ | ︙ | |||
400 401 402 403 404 405 406 | gen := sxhtml.NewGenerator(wui.sf, sxhtml.WithNewline) var sb bytes.Buffer _, err = gen.WriteHTML(&sb, pageObj) if err != nil { return err } wui.prepareAndWriteHeader(w, code) | | | > | 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 | gen := sxhtml.NewGenerator(wui.sf, sxhtml.WithNewline) var sb bytes.Buffer _, err = gen.WriteHTML(&sb, pageObj) if err != nil { return err } wui.prepareAndWriteHeader(w, code) if _, err = w.Write(sb.Bytes()); err != nil { wui.log.Error().Err(err).Msg("Unable to write HTML via template") } return nil // No error reporting, since we do not know what happended during write to client. } func (wui *WebUI) reportError(ctx context.Context, w http.ResponseWriter, err error) { code, text := adapter.CodeMessageFromError(err) if code == http.StatusInternalServerError { wui.log.Error().Msg(err.Error()) |
︙ | ︙ |
Changes to web/adapter/webui/webui.go.
︙ | ︙ | |||
72 73 74 75 76 77 78 79 80 81 82 83 84 85 | genHTML *sxhtml.Generator symMetaHeader *sx.Symbol symDetail *sx.Symbol symA, symHref *sx.Symbol symSpan *sx.Symbol symAttr *sx.Symbol } // webuiBox contains all box methods that are needed for WebUI operation. // // Note: these function must not do auth checking. type webuiBox interface { CanCreateZettel(context.Context) bool | > | 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | genHTML *sxhtml.Generator symMetaHeader *sx.Symbol symDetail *sx.Symbol symA, symHref *sx.Symbol symSpan *sx.Symbol symAttr *sx.Symbol symAttrOpen *sx.Symbol } // webuiBox contains all box methods that are needed for WebUI operation. // // Note: these function must not do auth checking. type webuiBox interface { CanCreateZettel(context.Context) bool |
︙ | ︙ | |||
129 130 131 132 133 134 135 136 137 138 139 140 141 142 | genHTML: sxhtml.NewGenerator(sf, sxhtml.WithNewline), symDetail: sf.MustMake("DETAIL"), symMetaHeader: sf.MustMake("META-HEADER"), symA: sf.MustMake("a"), symHref: sf.MustMake("href"), symSpan: sf.MustMake("span"), symAttr: sf.MustMake(sxhtml.NameSymAttr), } wui.engine = wui.createRenderEngine() wui.observe(box.UpdateInfo{Box: mgr, Reason: box.OnReload, Zid: id.Invalid}) mgr.RegisterObserver(wui.observe) return wui } | > | 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | genHTML: sxhtml.NewGenerator(sf, sxhtml.WithNewline), symDetail: sf.MustMake("DETAIL"), symMetaHeader: sf.MustMake("META-HEADER"), symA: sf.MustMake("a"), symHref: sf.MustMake("href"), symSpan: sf.MustMake("span"), symAttr: sf.MustMake(sxhtml.NameSymAttr), symAttrOpen: sf.MustMake("open"), } wui.engine = wui.createRenderEngine() wui.observe(box.UpdateInfo{Box: mgr, Reason: box.OnReload, Zid: id.Invalid}) mgr.RegisterObserver(wui.observe) return wui } |
︙ | ︙ | |||
191 192 193 194 195 196 197 | 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) } | | > > | 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | 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) } func (wui *WebUI) getSimpleHTMLEncoder(lang string) *htmlGenerator { return wui.createGenerator(wui, lang) } // GetURLPrefix returns the configured URL prefix of the web server. func (wui *WebUI) GetURLPrefix() string { return wui.ab.GetURLPrefix() } // NewURLBuilder creates a new URL builder object with the given key. func (wui *WebUI) NewURLBuilder(key byte) *api.URLBuilder { return wui.ab.NewURLBuilder(key) } |
︙ | ︙ |
Changes to www/build.md.
︙ | ︙ | |||
9 10 11 12 13 14 15 | * [govulncheck](https://golang.org/x/vuln/cmd/govulncheck), * [Fossil](https://fossil-scm.org/), * [Git](https://git-scm.org) (so that Go can download some dependencies). See folder `docs/development` (a zettel box) for details. ## Clone the repository | | > | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | * [govulncheck](https://golang.org/x/vuln/cmd/govulncheck), * [Fossil](https://fossil-scm.org/), * [Git](https://git-scm.org) (so that Go can download some dependencies). See folder `docs/development` (a zettel box) for details. ## Clone the repository Most of this is covered by the excellent Fossil [documentation](https://fossil-scm.org/home/doc/trunk/www/quickstart.wiki). 1. Create a directory to store your Fossil repositories. Let's assume, you have created `$HOME/fossils`. 1. Clone the repository: `fossil clone https://zettelstore.de/ $HOME/fossils/zettelstore.fossil`. 1. Create a working directory. Let's assume, you have created `$HOME/zettelstore`. 1. Change into this directory: `cd $HOME/zettelstore`. |
︙ | ︙ | |||
61 62 63 64 65 66 67 | ``` ## A note on the use of Fossil Zettelstore is managed by the Fossil version control system. Fossil is an alternative to the ubiquitous Git version control system. However, Go seems to prefer Git and popular platforms that just support Git. | | > > | > > | > | > | > | > | > | 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 | ``` ## A note on the use of Fossil Zettelstore is managed by the Fossil version control system. Fossil is an alternative to the ubiquitous Git version control system. However, Go seems to prefer Git and popular platforms that just support Git. Some dependencies of Zettelstore, namely [Zettelstore client](https://zettelstore.de/client) and [sx](https://zettelstore.de/sx), are also managed by Fossil. Depending on your development setup, some error messages might occur. If the error message mentions an environment variable called `GOVCS` you should set it to the value `GOVCS=zettelstore.de:fossil` (alternatively more generous to `GOVCS=*:all`). Since the Go build system is coupled with Git and some special platforms, you allow ot to download a Fossil repository from the host `zettelstore.de`. The build tool set `GOVCS` to the right value, but you may use other `go` commands that try to download a Fossil repository. On some operating systems, namely Termux on Android, an error message might state that an user cannot be determined (`cannot determine user`). In this case, Fossil is allowed to download the repository, but cannot associate it with an user name. Set the environment variable `USER` to any user name, like: `USER=nobody go run tools/build.go build`. |
Changes to www/changes.wiki.
1 2 3 | <title>Change Log</title> <a id="0_16"></a> | > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | | | | | | | | | > | | | | | | | | | | | | | | | | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | <title>Change Log</title> <a id="0_17"></a> <h2>Changes for Version 0.17.0 (pending)</h2> <a id="0_16"></a> <h2>Changes for Version 0.16.0 (2023-11-30)</h2> * Sx function <code>define</code> is removed, as announced for version 0.15.0. Use <code>defvar</code> (to define variables) or <code>defun</code> (to define functions) instead. In addition <code>defunconst</code> defines a constant function, which ensures a fixed binding of its name to its function body (performance optimization). (breaking: webui) * Allow to determine a role zettel for a given role. (major: api, webui) * Present user the option to create a (missing) role zettel (in list view). Results in a new predefined zettel with identifier 00000000090004, which is a template for new role zettel. (minor: webui) * Timestamp values can be abbrevated by omitting most of its components. Previously, such values that are not in the format YYYYMMDDhhmmss were ignored. Now the following formats are also allowed: YYYY, YYYYMM, YYYYMMDD, YYYYMMDDhh, YYYYMMDDhhmm. Querying and sorting work accordingly. Previously, only a sequences of zeroes were appended, resulting in illegal timestamps, e.g. for YYYY or YYYYMM. (minor) * SHTML encoder fixed w.r.t inline quoting. Previously, an <q> tag was used, which is inappropriate. Restored smart quotes from version 0.3, but with new SxHTML infrastructure. This affect the html encoder and the WebUI too. Now, an empty quote should not result in a warning by HTML linters. (minor: api, webui) * Add new zettelmarkup inline formatting: <code>##Text##</code> will mark / highlight the given Text. It is typically used to highlight some text, which is important for you, but not for the original author. When rendered as HTML, the <mark> tag is used. (minor: zettelmarkup) * Add configuration keys to show, not to show, or show the closed list of referencing zettel in the web user interface. You can set these configurations system-wide, per user, or per zettel. Often it is used to ensure a “clean” home zettel. Affects the list of incoming / back links, folge zettel, subordinate zettel, and successor zettel. (minor: webui) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_15"></a> <h2>Changes for Version 0.15.0 (2023-10-26)</h2> * Sx function <code>define</code> is now deprecated. It will be removed in version 0.16. Use <code>defvar</code> or <code>defun</code> instead. Otherwise the WebUI will not work in version 0.16. (major: webui, deprecated) * Zettel can be re-indexed via WebUI or API query action <code>REINDEX</code>. The info page of a zettel contains a link to re-index the zettel. In a query transclusion, this action is ignored. (major: api, webui). * Allow to determine a tag zettel for a given tag. (major: api, webui) * Present user the option to create a (missing) tag zettel (in list view). Results in a new predefined zettel with identifier 00000000090003, which is a template for new tag zettel. (minor: webui) * ZIP file with manual now contains a zettel 00001000000000 that contains its build date (metadata key <code>created</code>) and version (in the zettel content) (minor) * If an error page cannot be created due to template errors (or similar), a plain text error page is delivered instead. It shows the original error and the error that occured durng rendering the original error page. (minor: webui) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_14"></a> <h2>Changes for Version 0.14.0 (2023-09-22)</h2> * Remove support for JSON. This was marked deprecated in version 0.12.0. Use the <code>data</code> encoding instead, a form of symbolic expressions. (breaking: api; minor: webui) * Remove deprecated syntax for a context list: <code>CONTEXT zid</code>. Use <code>zid CONTEXT</code> instead. It was deprecated in version 0.13.0. (breaking: api, webui, zettelmarkup) * Replace CSS-role-map mechanism with a more general Sx-based one: user specific code may generates parts of resulting HTML document. (breaking: webui) * Allow meta-tags, i.e. zettel for a specific tag. Meta-tags have the tag name as a title and specify the role "tag". (major: webui) * Allow to load sx code from multiple zettel; dependencies are specified using <code>precursor</code> metadata. (major: webui) * Allow sx code to change WebUI for zettel with specified role. (major: webui) * Some minor usability improvements. (minor: webui) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_13"></a> <h2>Changes for Version 0.13.0 (2023-08-07)</h2> * There are for new search operators: less, not less, greater, not greater. These use the same syntax as the operators prefix, not prefix, suffix, not suffix. The latter are now denoted as <code>[</code>, <code>![</code>, <code>]</code>, and <code>!]</code>. The first may operate numerically for metadata like numbers, timestamps, and zettel identifier. They are not supported for full-text search. (breaking: api, webui) * The API endpoint <code>/o/{ID}</code> (order of zettel ID) is no longer available. Please use the query expression <code>{ID} ITEMS</code> instead. (breaking: api) * The API endpoint <code>/u/{ID}</code> (unlinked references of zettel ID) is no longer available. Please use the query expression <code>{ID} UNLINKED</code> instead. (breaking: api) * All API endpoints allow to encode zettel data with the <code>data</code> encodings, incl. creating, updating, retrieving, and querying zettel. (major: api) * Change syntax for context query to <code>zid ... CONTEXT</code>. This will allow to add more directives that operate on zettel identifier. Old syntax <code>CONTEXT zid</code> will be removed in 0.14. (major, deprecated) * Add query directive <code>ITEMS</code> that will produce a list of metadata of all zettel that are referenced by the originating zettel in a top-level list. It replaces the API endpoint <code>/o/{ID}</code> (and makes it more useful). (major: api, webui) * Add query directive <code>UNLINKED</code> that will produce a list of metadata of all zettel that are mentioning the originating zettel in a top-level, but do not mention them. It replaces the API endpoint <code>/u/{ID}</code> (and makes it more useful). (major: api, webui) * Add query directive <code>IDENT</code> to distinguish a search for a zettel identifier (“{ID}”), that will list all metadata of zettel containing that zettel identifier, and a request to just list the metadata of given zettel (“{ID} IDENT”). The latter could be filtered further. (minor: api, webui) * Add support for metadata key <code>folge-role</code>. (minor) * Allow to create a child from a given zettel. (minor: webui) * Make zettel entry/edit form a little friendlier: auto-prepend missing '#' to tags; ensure that role and syntax receive just a word. (minor: webui) * Use a zettel that defines builtins for evaluating WebUI templates. (minor: webui) * Add links to retrieve result of a query in other formats. (minor: webui) * Always log the found configuration file. (minor: server) * The use of the <code>json</code> zettel encoding is deprecated (since version 0.12.0). Support for this encoding will be removed in version 0.14.0. Please use the new <code>data</code> encoding instead. (deprecated: api) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_12"></a> <h2>Changes for Version 0.12.0 (2023-06-05)</h2> * Syntax of templates for the web user interface are changed from Mustache |
︙ | ︙ | |||
130 131 132 133 134 135 136 | that is the short form for "symbolic expression for HTML". (breaking) * Render footer zettel on all WebUI pages. (fix: webui) * Query search operator "=" now compares for equality, ":" compares depending on the value type. (minor: api, webui) | | | | | > | | | | > | > | | | | | < | | | | | | | | | | | | > | | | | | | > | | | | | | > | | | | | | | | | | | | | | 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | that is the short form for "symbolic expression for HTML". (breaking) * Render footer zettel on all WebUI pages. (fix: webui) * Query search operator "=" now compares for equality, ":" compares depending on the value type. (minor: api, webui) * Search term <code>PICK</code> now respects the original sort order. This makes it more useful and orthogonal to <code>RANDOM</code> and <code>LIMIT</code>. As a side effect, zettel lists retrieved via the API are no longer sorted. In case you want a specific order, you must specify it explicit. (minor: api, webui) * New metadata key <code>expire</code> records a timestamp when a zettel should be treated as, well, expired. (minor) * New metadata keys <code>superior</code> and <code>subordinate</code> (calculated from <code>superior</code>) allow to specify a hierarchy between zettel. (minor) * Metadata keys with suffix <code>-date</code> and <code>-time</code> are treated as timestamp values. (minor) * <code>sexpr</code> zettel encoding is now documented in the manual. (minor: manual) * Build tool allows to install / update external Go tools needed to build the software. (minor) * Show only useful metadata on WebUI, not the internal metadata. (minor: webui) * The use of the <code>json</code> zettel encoding is deprecated. Support for this encoding may be removed in future versions. Please use the new <code>data</code> encoding instead. (deprecated: api) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_11"></a> <h2>Changes for Version 0.11.2 (2023-04-16)</h2> * Render footer zettel on all WebUI pages. Backported from 0.12.0. Many thanks to HK for reporting it! (fix: webui) <h2>Changes for Version 0.11.1 (2023-03-28)</h2> * Make <code>PICK</code> search term a little bit more deterministic so that the “Save As Zettel” button produces the same list. (fix: webui) <h2>Changes for Version 0.11.0 (2023-03-27)</h2> * Remove ZJSON encoding. It was announced in version 0.10.0. Use Sexpr encoding instead. (breaking) * Title of a zettel is no longer interpreted as Zettelmarkup text. Now it is just a plain string, possibly empty. Therefore, no inline formatting (like bold text), no links, no footnotes, no citations (the latter made rendering the title often questionable, in some contexts). If you used special entities, please use the unicode characters directly. However, as a good practice, it is often the best to printable ASCII characters. (breaking) * Remove runtime configuration <code>marker-external</code>. It was added in version [#0_0_6|0.0.6] and updated in [#0_0_10|0.0.10]. If you want to change the marker for an external URL, you could modify zettel 00000000020001 (Zettelstore Base CSS) or zettel 00000000025001 (Zettelstore User CSS, preferred) by changing / adding a rule to add some content after an external <code><a ...></code> tag. (breaking: webui) * Add SHTML encoding. This allows to ensure the quality of generated HTML code. In addition, clients might use it, because it is easier to parse and manipulate than ordinary HTML. In the future, HTML template zettel will probably also use SHTML, deprecating the current Mustache syntax (which was added in [#0_0_9|0.0.9]). (major) * Search term <code>PICK n</code>, where <code>n</code> is an integer value greater zero, will pick randomly <code>n</code> elements from the search result list. Somehow similar (and faster) as <code>RANDOM LIMIT n</code>, but allows also later ordering of the resulting list. (minor) * Changed cost model for zettel context: a zettel with more outgoing/incoming references has higher cost than a zettel with less references. Also added support for traversing tags, with a similar cost model. As an effect, zettel hubs (in many cases your home zettel) will less likely add its references. Same for often used tags. The cost model might change in some details in the future, but the idea of a penalty applied to zettel / tags with many references will hold. (minor) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_10"></a> <h2>Changes for Version 0.10.1 (2023-01-30)</h2> * Show button to save a query into a zettel only when the current user has authorization to do it. (fix: webui) <h2>Changes for Version 0.10.0 (2023-01-24)</h2> * Remove support for endpoints <code>/j, /m, /q, /p, /v</code>. Their functions are merged into endpoint <code>/z</code>. This was announced in version 0.9.0. Please use only client library with at least version 0.10.0 too. (breaking: api) * Remove support for runtime configuration key <code>footer-html</code>. Use <code>footer-zettel</code> instead. Deprecated in version 0.9.0. (breaking: webui) * Save a query into a zettel to freeze it. (major: webui) * Allow to show all used metadata keys, linked with their occurrences and their values. (minor: webui) * Mark ZJSON encoding as deprecated for v0.11.0. Please use Sexpr encoding instead. (deprecated) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_9"></a> <h2>Changes for Version 0.9.0 (2022-12-12)</h2> * Remove support syntax <code>pikchr</code>. Although it was a nice idea to include it into Zettelstore, the implementation is too brittle (w.r.t. the expected long lifetime of Zettelstore). There should be other ways to support SVG front-ends. (breaking) * Allow to upload content when creating / updating a zettel. (major: webui) * Add syntax “draw” (again) (minor: zettelmarkup) * Allow to encode zettel in Markdown. Please note: not every aspect of a zettel can be encoded in Markdown. Those aspects will be ignored. (minor: api) * Enhance zettel context by raising the importance of folge zettel (and similar). (minor: api, webui) * Interpret zettel files with extension <code>.webp</code> as an binary image file format. (minor) * Allow to specify service specific log level via statup configuration and via command line. (minor) * Allow to specify a zettel to serve footer content via runtime comfiguration <code>footer-zettel</code>. Can be overwritten by user zettel. (minor: webui) * Footer data is automatically separated by a thematic break / horizontal rule. If you do not like it, you have to update the base template. (minor: webui) * Allow to set runtime configuration <code>home-zettel</code> in the user zettel to make it user-specific. (minor: webui) * Serve favicon.ico from the asset directory. (minor: webui) * Zettelmarkup cheat sheet (minor: manual) * Runtime configuration key <code>footer-html</code> will be removed in Version 0.10.0. Please use <code>footer-zettel</code> instead. (deprecated: webui) * In the next version 0.10.0, the API endpoints for a zettel (<code>/j</code>, <code>/p</code>, <code>/v</code>) will be merged with endpoint <code>/z</code>. Basically, the previous endpoint will be refactored as query parameter of endpoint <code>/z</code>. To reduce errors, there will be no version, where the previous endpoint are still available and the new funnctionality is still there. This is a warning to prepare for some breaking changes in v0.10.0. This also affects the API client implementation. (warning: api) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_8"></a> <h2>Changes for Version 0.8.0 (2022-10-20)</h2> * Remove support for tags within zettel content. Removes also property metadata keys <code>all-tags</code> and <code>computed-tags</code>. Deprecated in version 0.7.0. (breaking: zettelmarkup, api, webui) * Remove API endpoint <code>/m</code>, which retrieve aggregated (tags, roles) zettel identifier. Deprecated in version 0.7.0. (breaking: api) * Remove support for URL query parameter starting with an underscore. Deprecated in version 0.7.0. (breaking: api, webui) * Ignore HTML content by default, and allow HTML gradually by setting startup value <code>insecure-html</code>. (breaking: markup) * Endpoint <code>/q</code> returns list of full metadata, if no query action is specified. A HTTP call <code>GET /z</code> (retrieving metadata of all or some zettel) is now an alias for <code>GET /q</code>. (major: api) * Allow to create a zettel that acts as the new version of an existing zettel. Useful if you want to have access to older, outdated content. (minor: webui) * Allow transclusion to reference local image via URL. (minor: zettelmarkup, webui) * Add categories in RSS feed, based on zettel tags. |
︙ | ︙ | |||
332 333 334 335 336 337 338 | <a id="0_7"></a> <h2>Changes for Version 0.7.1 (2022-09-18)</h2> * Produce a RSS feed compatible to Miniflux. (minor) * Make sure to always produce a pubdata in RSS feed. (bug) * Prefix search for data that looks like a zettel identifier may end with a | | | | | | | | | > | | | | | | | | | | | | > | | | | | | | | | | | | | 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 | <a id="0_7"></a> <h2>Changes for Version 0.7.1 (2022-09-18)</h2> * Produce a RSS feed compatible to Miniflux. (minor) * Make sure to always produce a pubdata in RSS feed. (bug) * Prefix search for data that looks like a zettel identifier may end with a <code>0</code>. (bug) * Fix glitch on manual zettel. (bug) <h2>Changes for Version 0.7.0 (2022-09-17)</h2> * Removes support for URL query parameter to search for metadata values, sorting, offset, and limit a zettel list. Deprecated in version 0.6.0 (breaking: api, webui) * Allow to search for the existence / non-existence of a metadata key with the "?" operator: <code>key?</code> and <code>key!?</code>. Previously, the ":" operator was used for this by specifying an empty search value. Now you can use the ":" operator to find empty / non-empty metadata values. If you specify a search operator for metadata, the specified key is assumed to exist. (breaking: api, webui) * Rename “search expression” into “query expressions”. Similar, the reference prefix <code>search:</code> to specify a query link or a query transclusion is renamed to <code>query:</code> (breaking: zettelmarkup) * Rename query parameter for query expression from <code>_s</code> to <code>q</code>. (breaking: api, webui) * Cleanup names for HTTP query parameters in WebUI. Update your bookmarks if you used them. (For API: see below) (breaking: webui) * Allow search terms to be OR-ed. This allows to specify any search expression in disjunctive normal form. Therefore, the NEGATE term is not needed any more. (breaking: api, webui) * Replace runtime configuration <code>default-lang</code> with <code>lang</code>. Additionally, <code>lang</code> set at the zettel of the current user, will provide a default value for the current user, overwriting the global default value. (breaking) * Add new syntax <code>pikchr</code>, a markup language for diagrams in technical documentation. (major) * Add endpoint <code>/q</code> to query the zettelstore and aggregate resulting values. This is done by extending the query syntax. (major: api) * Add support for query actions. Actions may aggregate w.r.t. some metadata keys, or produce an RSS feed. (major: api, webui) * Query results can be ordered for more than one metadata key. Ordering by zettel identifier is an implicit last order expression to produce stable results. (minor: api, webui) * Add support for an asset directory, accessible via URL prefix <code>/assests/</code>. (minor: server) * Add support for metadata key <code>created</code>, a timestamp when the zettel was created. Since key <code>published</code> is now either <code>created</code> or <code>modified</code>, it will now always contains a valid time stamp. (minor) * Add support for metadata key <code>author</code>. It will be displayed on a zettel, if set. (minor: webui) * Remove CSS for lists. The browsers default value for <code>padding-left</code> will be used. (minor: webui) * Removed templates for rendering roles and tags lists. This is now done by query actions. (minor: webui) * Tags within zettel content are deprecated in version 0.8. This affects the computed metadata keys <code>content-tags</code> and <code>all-tags</code>. They will be removed. The number sign of a content tag introduces unintended tags, esp. in the english language; content tags may occur within links → links within links, when rendered as HTML; content tags may occur in the title of a zettel; naming of content tags, zettel tags, and their union is confusing for many. Migration: use zettel tags or replace content tag with a search. (deprecated: zettelmarkup) * Cleanup names for HTTP query parameter for API calls. Essentially, underscore characters in front are removed. Please use new names, old names will be deprecated in version 0.8. (deprecated: api) * Some smaller bug fixes and improvements, to the software and to the documentation. |
︙ | ︙ | |||
475 476 477 478 479 480 481 | * If authentication is enabled, a secret of at least 16 bytes must be set in the startup configuration. (breaking) * “Sexpr” encoding replaces “Native” encoding. Sexpr encoding is much easier to parse, compared with native and ZJSON encoding. In most cases it is smaller than ZJSON. (breaking: api) | | | | | | 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 | * If authentication is enabled, a secret of at least 16 bytes must be set in the startup configuration. (breaking) * “Sexpr” encoding replaces “Native” encoding. Sexpr encoding is much easier to parse, compared with native and ZJSON encoding. In most cases it is smaller than ZJSON. (breaking: api) * Endpoint <code>/r</code> is changed to <code>/m?_key=role</code> and returns now a map of role names to the list of zettel having this role. Endpoint <code>/t</code> is changed to <code>/m?_key=tags</code>. It already returned mapping described before. (breaking: api) * Remove support for a default value for metadata key title, role, and syntax. Title and role are now allowed to be empty, an empty syntax value defaults to “plain”. (breaking) * Add support for an “evaluation block” syntax in Zettelmarkup to allow interpretation of content by external software. |
︙ | ︙ | |||
523 524 525 526 527 528 529 | documentation. <a id="0_4"></a> <h2>Changes for Version 0.4 (2022-03-08)</h2> * Encoding “djson” renamed to “zjson” (<em>zettel json</em>). (breaking: api; minor: webui) | | | | 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 | documentation. <a id="0_4"></a> <h2>Changes for Version 0.4 (2022-03-08)</h2> * Encoding “djson” renamed to “zjson” (<em>zettel json</em>). (breaking: api; minor: webui) * Remove inline quotation syntax <code><<...<<</code>. Now, <code>""...""</code> generates the equivalent code. Typographical quotes are generated by the browser, not by Zettelstore. (breaking: Zettelmarkup) * Remove inline formatting for monospace. Its syntax is now used by the similar syntax element of literal computer input. Monospace was just a visual element with no semantic association. Now, the syntax <kbd>++...++</kbd> is obsolete. (breaking: Zettelmarkup). |
︙ | ︙ | |||
555 556 557 558 559 560 561 | interpreted as Zettelmarkup. Similar, the suffix <kbd>-set</kbd> denotes a set/list of words and the suffix <kbd>-zids</kbd> a set/list of zettel identifier. (minor: api, webui) * Change generated URLs for zettel-creation forms. If you have bookmarked them, e.g. to create a new zettel, you should update. (minor: webui) | | | | 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 | interpreted as Zettelmarkup. Similar, the suffix <kbd>-set</kbd> denotes a set/list of words and the suffix <kbd>-zids</kbd> a set/list of zettel identifier. (minor: api, webui) * Change generated URLs for zettel-creation forms. If you have bookmarked them, e.g. to create a new zettel, you should update. (minor: webui) * Remove support for metadata key <code>no-index</code> to suppress indexing selected zettel. It was introduced in <a href="#0_0_11">v0.0.11</a>, but disallows some future optimizations for searching zettel. (minor: api, webui) * Make some metadata-based searches a little bit faster by executing a (in-memory-based) full-text search first. Now only those zettel are loaded from file that contain the metdata value. (minor: api, webui) * Add an API call to retrieve the version of the Zettelstore. (minor: api) * Limit the amount of zettel and bytes to be stored in a memory box. Allows to use it with public access. (minor: box) * Disallow to cache the authentication cookie. Will remove most unexpected log-outs when using a mobile device. (minor: webui) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_3"></a> <h2>Changes for Version 0.3 (2022-02-09)</h2> * Zettel files with extension <code>.meta</code> are now treated as content files. Previoulsy, they were interpreted as metadata files. The interpretation as metadata files was deprecated in version 0.2. (breaking: directory and file/zip box) * Add syntax “draw” to produce some graphical representations. (major) * Add Zettelmarkup syntax to specify full transclusion of other zettel. (major: Zettelmarkup) |
︙ | ︙ | |||
599 600 601 602 603 604 605 | (minor: directory and file/zip box) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_2"></a> <h2>Changes for Version 0.2 (2022-01-19)</h2> * v0.2.1 (2021-02-01) updates the license year in some documents | | | | | | | | | | | | > | | | | | | | | | > | | | | | | | | | | > | | | | 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 | (minor: directory and file/zip box) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_2"></a> <h2>Changes for Version 0.2 (2022-01-19)</h2> * v0.2.1 (2021-02-01) updates the license year in some documents * Remove support for <code>;;small text;;</code> Zettelmarkup. (breaking: Zettelmarkup) * On macOS, the downloadable executable program is now called “zettelstore”, as on all other Unix-like platforms. (possibly breaking: macOS) * External metadata (e.g. for zettel with file extension other than <code>.zettel</code>) are stored in files without an extension. Metadata files with extension <code>.meta</code> are still recognized, but result in a warning message. In a future version (probably v0.3), <code>.meta</code> files will be treated as ordinary content files, possibly resulting in duplicate content. In other words: usage of <code>.meta</code> files for storing metadata is deprecated. (possibly breaking: directory and file box) * Show unlinked references in info page of each zettel. Unlinked references are phrases within zettel content that might reference another zettel with the same title as the phase. (major: webui) * Add endpoint <code>/u/{ID}</code> to retrieve unlinked references. (major: api) * Provide a logging facility. Log messages are written to standard output. Messages with level “information” are also written to a circular buffer (of length 8192) which can be retrieved via a computed zettel. There is a command line flag <code>-l LEVEL</code> to specify an application global logging level on startup (default: “information”). Logging level can also be changed via the administrator console, even for specific (sub-) services. (major) * The internal handling of zettel files is rewritten. This allows less reloads ands detects when the directory containing the zettel files is removed. The API, WebUI, and the admin console allow to manually refresh the internal state on demand. (major: box, webui) * <code>.zettel</code> files with YAML header are now correctly written. (bug) * Selecting zettel based on their metadata allows the same syntax as searching for zettel content. For example, you can list all zettel that have an identifier not ending with <code>00</code> by using the query <code>id=!<00</code>. (minor: api, webui) * Remove support for <code>//deprecated emphasized//</code> Zettelmarkup. (minor: Zettelmarkup) * Add options to profile the software. Profiling can be enabled at the command line or via the administrator console. (minor) * Add computed zettel that lists all supported parser / recognized zettel syntaxes. (minor) * Add API call to check for enabled authentication. (minor: api) * Renewing an API access token works even if authentication is not enabled. This corresponds to the behaviour of optaining an access token. (minor: api) * If there is nothing to return, use HTTP status code 204, instead of 200 + <code>Content-Length: 0</code>. (minor: api) * Metadata key <code>duplicates</code> stores the duplicate file names, instead of just a boolean value that there were duplicate file names. (minor) * Document autostarting Zettelstore on Windows, macOS, and Linux. (minor) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_1"></a><a id="0_1_0"></a> <h2>Changes for Version 0.1 (2021-11-11)</h2> * v0.1.3 (2021-12-15) fixes a bug where the modification date could be set when a new zettel is created. * v0.1.2 (2021-11-18) fixes a bug when selecting zettel from a list when more than one comparison is negated. * v0.1.1 (2021-11-12) updates the documentation, mostly related to the deprecation of the <code>//</code> markup. * Remove visual Zettelmarkup (italic, underline). Semantic Zettelmarkup (emphasize, insert) is still allowed, but got a different syntax. The new syntax for <ins>inserted text</ins> is <code>>>inserted>></code>, while its previous syntax now denotes <em>emphasized text</em>: <code>__emphasized__</code>. The previous syntax for emphasized text is now deprecated: <code>//deprecated emphasized//</code>. Starting with Version 0.2.0, the deprecated syntax will not be supported. The reason is the collision with URLs that also contain the characters <code>//</code>. The ZMK encoding of a zettel may help with the transition (<code>/v/{ZettelID}?_part=zettel&_enc=zmk</code>, on the Info page of each zettel in the WebUI). Additionally, all deprecated uses of <code>//</code> will be rendered with a dashed box within the WebUI. (breaking: Zettelmarkup). * API client software is now a [https://zettelstore.de/client/|separate] project. (breaking) * Initial support for HTTP security headers (Content-Security-Policy, Permissions-Policy, Referrer-Policy, X-Content-Type-Options, X-Frame-Options). Header values are currently some constant values. (possibly breaking: api, webui) * Remove visual Zettelmarkup (bold, striketrough). Semantic Zettelmarkup (strong, delete) is still allowed and replaces the visual elements syntactically. The visual appearance should not change (depends on your changes / additions to CSS zettel). (possibly breaking: Zettelmarkup). * Add API endpoint <code>POST /v</code> to retrieve HTMl and text encoded strings from given ZettelMarkup encoded values. This will be used to render a HTML page from a given zettel: in many cases the title of a zettel must be treated separately. (minor: api) * Add API endpoint <code>/m</code> to retrieve only the metadata of a zettel. (minor: api) * New metadata value <code>content-tags</code> contains the tags that were given in the zettel content. To put it simply, <code>all-tags</code> = <code>tags</code> + <code>content-tags</code>. (minor) * Calculating the context of a zettel stops at the home zettel. (minor: api, webui) * When renaming or deleting a zettel, a warning will be given, if other zettel references the given zettel, or when “deleting” will uncover zettel in overlay box. (minor: webui) |
︙ | ︙ | |||
730 731 732 733 734 735 736 | (info) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_0_15"></a> <h2>Changes for Version 0.0.15 (2021-09-17)</h2> * Move again endpoint characters for authentication to make room for future | | > | | | | < | | | | | | | | | | | | | | | | | | | | | | | | | | > | | | | | | | | | | | | | | > | | | | | | | > | > | | | > | | | > | | | | | | | | | | > | | > | | | > | > | > | | > | > | > | | | | | | | | | | | | | | | | | > | | | | | | | | | | | | | | | | | | | | | | > | | | | | | | | | 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 | (info) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_0_15"></a> <h2>Changes for Version 0.0.15 (2021-09-17)</h2> * Move again endpoint characters for authentication to make room for future features. WebUI authentication moves from <code>/a</code> to <code>/i</code> (login) and <code>/i?logout</code> (logout). API authentication moves from <code>/v</code> to </code>/a</code>. JSON-based basic zettel handling moves from <code>/z</code> to <code>/j</code> and <code>/z/{ID}</code> to <code>/j/{ID}</code>. Since the API client is updated too, this should not be a breaking change for most users. (minor: api, webui; possibly breaking) * Add API endpoint <code>/v/{ID}</code> to retrieve an evaluated zettel in various encodings. Mostly replaces endpoint <code>/z/{ID}</code> for other encodings except “json” and “raw”. Endpoint <code>/j/{ID}</code> now only returns JSON data, endpoint <code>/z/{ID}</code> is used to retrieve plain zettel data (previously called “raw”). See documentation for details. (major: api; breaking) * Metadata values of type <em>tag set</em> (the metadata with key <code>tags</code> is its most prominent example), are now compared in a case-insensitive manner. Tags that only differ in upper / lower case character are now treated identical. This might break your workflow, if you depend on case-sensitive comparison of tag values. Tag values are translated to their lower case equivalent before comparing them and when you edit a zettel through Zettelstore. If you just modify the zettel files, your tag values remain unchanged. (major; breaking) * Endpoint <code>/z/{ID}</code> allows the same methods as endpoint <code>/j/{ID}</code>: <code>GET</code> retrieves zettel (see above), <code>PUT</code> updates a zettel, <code>DELETE</code> deletes a zettel, <code>MOVE</code> renames a zettel. In addtion, <code>POST /z</code> will create a new zettel. When zettel data must be given, the format is plain text, with metadata separated from content by an empty line. See documentation for more details. (major: api (plus WebUI for some details)) * Allows to transclude / expand the content of another zettel into a target zettel when the zettel is rendered. By using the syntax of embedding an image (which is some kind of expansion too), the first top-level paragraph of a zettel may be transcluded into the target zettel. Endless recursion is checked, as well as a possible “transclusion bomb ” (similar to a XML bomb). See manual for details. (major: zettelmarkup) * The endpoint <code>/z</code> allows to list zettel in a simpler format than endpoint <code>/j</code>: one line per zettel, and only zettel identifier plus zettel title. (minor: api) * Folgezettel are now displayed with full title at the bottom of a page. (minor: webui) * Add API endpoint <code>/p/{ID}</code> to retrieve a parsed, but not evaluated zettel in various encodings. (minor: api) * Fix: do not list a shadowed zettel that matches the select criteria. (minor) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_0_14"></a> <h2>Changes for Version 0.0.14 (2021-07-23)</h2> * Rename “place” into “box”. This also affects the configuration keys to specify boxes <code>box-uri<em>X</em></code> (previously <code>place-uri-<em>X</em></code>. Older changes documented here are renamed too. (breaking) * Add API for creating, updating, renaming, and deleting zettel. (major: api) * Initial API client for Go. (major: api) * Remove support for paging of WebUI list. Runtime configuration key <code>list-page-size</code> is removed. If you still specify it, it will be ignored. (major: webui) * Use endpoint <code>/v</code> for user authentication via API. Endpoint <code>/a</code> is now used for the web user interface only. Similar, endpoint <code>/y</code> (“zettel context”) is renamed to <code>/x</code>. (minor, possibly breaking) * Type of used-defined metadata is determined by suffix of key: <code>-number</code>, <code>-url</code>, <code>-zid</code> will result the values to be interpreted as a number, an URL, or a zettel identifier. (minor, but possibly breaking if you already used a metadata key with above suffixes, but as a string type) * New <code>user-role</code> “creator”, which is only allowed to create new zettel (except user zettel). This role may only read and update public zettel or its own user zettel. Added to support future client software (e.g. on a mobile device) that automatically creates new zettel but, in case of a password loss, should not allow to read existing zettel. (minor, possibly breaking, because new zettel template zettel must always prepend the string <code>new-</code> before metdata keys that should be transferred to the new zettel) * New suported metadata key <code>box-number</code>, which gives an indication from which box the zettel was loaded. (minor) * New supported syntax <code>html</code>. (minor) * New predefined zettel “User CSS” that can be used to redefine some predefined CSS (without modifying the base CSS zettel). (minor: webui) * When a user moves a zettel file with additional characters into the box directory, these characters are preserved when zettel is updated. (bug) * The phase “filtering a zettel list” is more precise “selecting zettel” (documentation) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_0_13"></a> <h2>Changes for Version 0.0.13 (2021-06-01)</h2> * Startup configuration <code>box-<em>X</em>-uri</code> (where <em>X</em> is a number greater than zero) has been renamed to <code>box-uri-<em>X</em></code>. (breaking) * Web server processes startup configuration <code>url-prefix</code>. There is no need for stripping the prefix by a front-end web server any more. (breaking: webui, api) * Administrator console (only optional accessible locally). Enable it only on systems with a single user or with trusted users. It is disabled by default. (major: core) * Remove visibility value “simple-expert” introduced in [#0_0_8|version 0.0.8]. It was too complicated, esp. authorization. There was a name collision with the “simple” directory box sub-type. (major) * For security reasons, HTML blocks are not encoded as HTML if they contain certain snippets, such as <code><script</code> or <code><iframe</code>. These may be caused by using CommonMark as a zettel syntax. (major) * Full-text search can be a prefix search or a search for equal words, in addition to the search whether a word just contains word of the search term. (minor: api, webui) * Full-text search for URLs, with above additional operators. (minor: api, webui) * Add system zettel about license, contributors, and dependencies (and their license). For a nicer layout of zettel identifier, the zettel about environment values and about runtime metrics got new zettel identifier. This affects only user that referenced those zettel. (minor) * Local images that cannot be read (not found or no access rights) are substituted with the new default image, a spinning emoji. See [/file?name=box/constbox/emoji_spin.gif]. (minor: webui) * Add zettelmarkup syntax for a table row that should be ignored: <code>|%</code>. This allows to paste output of the administrator console into a zettel. (minor: zmk) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_0_12"></a> <h2>Changes for Version 0.0.12 (2021-04-16)</h2> * Raise the per-process limit of open files on macOS to 1.048.576. This allows most macOS users to use at least 500.000 zettel. That should be enough for the near future. (major) * Mitigate the shortcomings of the macOS version by introducing types of directory boxes. The original directory box type is now called "notify" (the default value). There is a new type called "simple". This new type does not notify Zettelstore when some of the underlying Zettel files change. (major) * Add new startup configuration <code>default-dir-box-type</code>, which gives the default value for specifying a directory box type. The default value is “notify”. On macOS, the default value may be changed “simple” if some errors occur while raising the per-process limit of open files. (minor) <a id="0_0_11"></a> <h2>Changes for Version 0.0.11 (2021-04-05)</h2> * New box schema "file" allows to read zettel from a ZIP file. A zettel collection can now be packaged and distributed easier. (major: server) * Non-restricted search is a full-text search. The search string will be normalized according to Unicode NFKD. Every character that is not a letter or a number will be ignored for the search. It is sufficient if the words to be searched are part of words inside a zettel, both content and metadata. (major: api, webui) * A zettel can be excluded from being indexed (and excluded from being found in a search) if it contains the metadata <code>no-index: true</code>. (minor: api, webui) * Menu bar is shown when displaying error messages. (minor: webui) * When selecting zettel, it can be specified that a given value should <em>not</em> match. Previously, only the whole select criteria could be negated (which is still possible). (minor: api, webui) * You can select a zettel by specifying that specific metadata keys must (or must not) be present. (minor: api, webui) * Context of a zettel (introduced in version 0.0.10) does not take tags into account any more. Using some tags for determining the context resulted into erratic, non-deterministic context lists. (minor: api, webui) * Selecting zettel depending on tag values can be both by comparing only the prefix or the whole string. If a search value begins with '#', only zettel with the exact tag will be returned. Otherwise a zettel will be returned if the search string just matches the prefix of only one of its tags. (minor: api, webui) * Many smaller bug fixes and improvements, to the software and to the documentation. A note for users of macOS: in the current release and with macOS's default values, a zettel directory must not contain more than approx. 250 files. There are three options to mitigate this limitation temporarily: # You update the per-process limit of open files on macOS. # You setup a virtualization environment to run Zettelstore on Linux or Windows. # You wait for version 0.0.12 which addresses this issue. <a id="0_0_10"></a> <h2>Changes for Version 0.0.10 (2021-02-26)</h2> * Menu item “Home” now redirects to a home zettel. Its default identifier is <code>000100000000</code>. The identifier can be changed with configuration key <code>home-zettel</code>, which supersedes key <code>start</code>. The default home zettel contains some welcoming information for the new user. (major: webui) * Show context of a zettel by following all backward and/or forward reference up to a defined depth and list the resulting zettel. Additionally, some zettel with similar tags as the initial zettel are also taken into account. (major: api, webui) * A zettel that references other zettel within first-level list items, can act as a “table of contents” zettel. The API endpoint <code>/o/{ID}</code> allows to retrieve the referenced zettel in the same order as they occur in the zettel. (major: api) * The zettel “New Menu” with identifier <code>00000000090000</code> contains a list of all zettel that should act as a template for new zettel. They are listed in the WebUIs ”New“ menu. This is an application of the previous item. It supersedes the usage of a role <code>new-template</code> introduced in [#0_0_6|version 0.0.6]. <b>Please update your zettel if you make use of the now deprecated feature.</b> (major: webui) * A reference that starts with two slash characters (“<code>//</code>”) it will be interpreted relative to the value of <code>url-prefix</code>. For example, if <code>url-prefix</code> has the value <code>/manual/</code>, the reference <code>[[Zettel list|//h]]</code> will render as <code><a href="/manual/h">Zettel list</a></code>. (minor: syntax) * Searching/selecting ignores the leading '#' character of tags. (minor: api, webui) * When result of selecting or searching is presented, the query is written as the page heading. (minor: webui) * A reference to a zettel that contains a URL fragment, will now be processed by the indexer. (bug: server) * Runtime configuration key <code>marker-external</code> now defaults to “&#10138;” (“➚”). It is more beautiful than the previous “&#8599;&#xfe0e;” (“↗︎”), which also needed the additional “&#xfe0e;” to disable the conversion to an emoji on iPadOS. (minor: webui) * A pre-build binary for macOS ARM64 (also known as Apple silicon) is available. (minor: infrastructure) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_0_9"></a> <h2>Changes for Version 0.0.9 (2021-01-29)</h2> This is the first version that is managed by [https://fossil-scm.org|Fossil] instead of GitHub. To access older versions, use the Git repository under [https://github.com/zettelstore/zettelstore-github|zettelstore-github]. <h3>Server / API</h3> * (major) Support for property metadata. Metadata key <code>published</code> is the first example of such a property. * (major) A background activity (called <i>indexer</i>) continuously monitors zettel changes to establish the reverse direction of found internal links. This affects the new metadata keys <code>precursor</code> and <code>folge</code>. A user specifies the precursor of a zettel and the indexer computes the property metadata for [https://forum.zettelkasten.de/discussion/996/definition-folgezettel|Folgezettel]. Metadata keys with type “Identifier” or “IdentifierSet” that have no inverse key (like <code>precursor</code> and <code>folge</code> with add to the key <code>forward</code> that also collects all internal links within the content. The computed inverse is <code>backward</code>, which provides all backlinks. The key <code>back</code> is computed as the value of <code>backward</code>, but without forward links. Therefore, <code>back</code> is something like the list of “smart backlinks”. * (minor) If Zettelstore is being stopped, an appropriate message is written in the console log. * (minor) New computed zettel with environmental data, the list of supported meta data keys, and statistics about all configured zettel boxes. Some other computed zettel got a new identifier (to make room for other variant). * (minor) Remove zettel <code>00000000000004</code>, which contained the Go version that produced the Zettelstore executable. It was too specific to the current implementation. This information is now included in zettel <code>00000000000006</code> (<i>Zettelstore Environment Values</i>). * (minor) Predefined templates for new zettel do not contain any value for attribute <code>visibility</code> any more. * (minor) Add a new metadata key type called “Zettelmarkup”. It is a non-empty string, that will be formatted with Zettelmarkup. <code>title</code> and <code>default-title</code> have this type. * (major) Rename zettel syntax “meta” to “none”. Please update the <i>Zettelstore Runtime Configuration</i> and all other zettel that previously used the value “meta”. Other zettel are typically user zettel, used for authentication. However, there is no real harm, if you do not update these zettel. In this case, the metadata is just not presented when rendered. Zettelstore will still work. * (minor) Login will take at least 500 milliseconds to mitigate login attacks. This affects both the API and the WebUI. * (minor) Add a sort option “_random” to produce a zettel list in random order. <code>_order</code> / <code>order</code> are now an aliases for the query parameters <code>_sort</code> / <code>sort</code>. <h3>WebUI</h3> * (major) HTML template zettel for WebUI now use [https://mustache.github.io/|Mustache] syntax instead of previously used [https://golang.org/pkg/html/template/|Go template] syntax. This allows these zettel to be used, even when there is another Zettelstore implementation, in another programming language. Mustache is available for approx. 48 programming languages, instead of only one for Go templates. <b>If you modified your templates, you <i>must</i> adapt them to the new syntax. Otherwise the WebUI will not work.</b> * (major) Show zettel identifier of folgezettel and precursor zettel in the header of a rendered zettel. If a zettel has real backlinks, they are shown at the botton of the page (“Additional links to this zettel”). * (minor) All property metadata, even computed metadata is shown in the info page of a zettel. * (minor) Rendering of metadata keys <code>title</code> and <code>default-title</code> in info page changed to a full HTML output for these Zettelmarkup encoded values. * (minor) Always show the zettel identifier on the zettel detail view. Previously, the identifier was not shown if the zettel was not editable. * (minor) Do not show computed metadata in edit forms anymore. <a id="0_0_8"></a> <h2>Changes for Version 0.0.8 (2020-12-23)</h2> <h3>Server / API</h3> * (bug) Zettel files with extension <code>.jpg</code> and without metadata will get a <code>syntax</code> value “jpg”. The internal data structure got the same value internally, instead of “jpeg”. This has been fixed for all possible alternative syntax values. * (bug) If a file, e.g. an image file like <code>20201130190200.jpg</code>, is added to the directory box, its metadata are just calculated from the information available. Updated metadata did not find its way into the zettel box, because the <code>.meta</code> file was not written. * (bug) If just the <code>.meta</code> file was deleted manually, the zettel was assumed to be missing. A workaround is to restart the software. If the <code>.meta</code> file is deleted, metadata is now calculated in the same way when the <code>.meta</code> file is non-existing at the start of the software. * (bug) A link to the current zettel, only using a fragment (e.g. <code>[[Title|#title]]</code>) is now handled correctly as a zettel link (and not as a link to external material). * (minor) Allow zettel to be marked as “read only”. This is done through the metadata key <code>read-only</code>. * (bug) When renaming a zettel, check all boxes for the new zettel identifier, not just the first one. Otherwise it will be possible to shadow a read-only zettel from a next box, effectively modifying it. * (minor) Add support for a configurable default value for metadata key <code>visibility</code>. * (bug) If <code>list-page-size</code> is set to a relatively small value and the authenticated user is <i>not</i> the owner, some zettel were not shown in the list of zettel or were not returned by the API. * (minor) Add support for new visibility “expert”. An owner becomes an expert, if the runtime configuration key <code>expert-mode</code> is set to true. * (major) Add support for computed zettel. These zettel have an identifier less than <code>0000000000100</code>. Most of them are only visible, if <code>expert-mode</code> is enabled. * (bug) Fixes a memory leak that results in too many open files after approx. 125 reload operations. * (major) Predefined templates for new zettel got an explicit value for visibility: “login”. Please update these zettel if you modified them. * (major) Rename key <code>readonly</code> of <i>Zettelstore Startup Configuration</i> to <code>read-only-mode</code>. This was done to avoid some confusion with the the zettel metadata key <code>read-only</code>. <b>Please adapt your startup configuration. Otherwise your Zettelstore will be accidentally writable.</b> * (minor) References starting with “./” and “../” are treated as a local reference. Previously, only the prefix “/” was treated as a local reference. * (major) Metadata key <code>modified</code> will be set automatically to the current local time if a zettel is updated through Zettelstore. <b>If you used that key previously for your own, you should rename it before you upgrade.</b> * (minor) The new visibility value “simple-expert” ensures that many computed zettel are shown for new users. This is to enable them to send useful bug reports. * (minor) When a zettel is stored as a file, its identifier is additionally stored within the metadata. This helps for better robustness in case the file names were corrupted. In addition, there could be a tool that compares the identifier with the file name. <h3>WebUI</h3> * (minor) Remove list of tags in “List Zettel” and search results. There was some feedback that the additional tags were not helpful. * (minor) Move zettel field "role" above "tags" and move "syntax" more to "content". * (minor) Rename zettel operation “clone” to “copy”. * (major) All predefined HTML templates have now a visibility value “expert”. If you want to see them as an non-expert owner, you must temporary enable <code>expert-mode</code> and change the <code>visibility</code> metadata value. * (minor) Initial support for [https://zettelkasten.de/posts/tags/folgezettel/|Folgezettel]. If you click on “Folge” (detail view or info view), a new zettel is created with a reference (<code>precursor</code>) to the original zettel. Title, role, tags, and syntax are copied from the original zettel. * (major) Most predefined zettel have a title prefix of “Zettelstore”. * (minor) If started in simple mode, e.g. via double click or without any command, some information for the new user is presented. In the terminal, there is a hint about opening the web browser and use |
︙ | ︙ | |||
1159 1160 1161 1162 1163 1164 1165 | to stay with the old licenses (AGPLv3+, CC BY-SA 4.0), you are free to fork from the previous version. <a id="0_0_6"></a> <h2>Changes for Version 0.0.6 (2020-11-23)</h2> <h3>Server</h3> * (major) Rename identifier of <i>Zettelstore Runtime Configuration</i> to | | > | | | | | | | | | | 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 | to stay with the old licenses (AGPLv3+, CC BY-SA 4.0), you are free to fork from the previous version. <a id="0_0_6"></a> <h2>Changes for Version 0.0.6 (2020-11-23)</h2> <h3>Server</h3> * (major) Rename identifier of <i>Zettelstore Runtime Configuration</i> to <code>00000000000100</code> (previously <code>00000000000001</code>). This is done to gain some free identifier with smaller number to be used internally. <b>If you customized this zettel, please make sure to rename it to the new identifier.</b> * (major) Rename the two essential metadata keys of a user zettel to <code>credential</code> and <code>user-id</code>. The previous values were <code>cred</code> and <code>ident</code>. <b>If you enabled user authentication and added some user zettel, make sure to change them accordingly. Otherwise these users will not authenticated any more.</b> * (minor) Rename the scheme of the box URL where predefined zettel are stored to “const”. The previous value was “globals”. <h3>Zettelmarkup</h3> * (bug) Allow to specify a <i>fragment</i> in a reference to a zettel. Used to link to an internal position within a zettel. This applies to CommonMark too. <h3>API</h3> * (bug) Encoding binary content in format “json” now results in valid JSON content. * (bug) All query parameters of selecting zettel must be true, regardless if a specific key occurs more than one or not. * (minor) Encode all inherited meta values in all formats except “raw”. A meta value is called <i>inherited</i> if there is a key starting with <code>default-</code> in the <i>Zettelstore Runtime Configuration</i>. Applies to WebUI also. * (minor) Automatic calculated identifier for headings (only for “html”, “djson”, “native” format and for the Web user interface). You can use this to provide a zettel reference that links to the heading, without specifying an explicit mark (<code>[!mark]</code>). * (major) Allow to retrieve all references of a given zettel. |
︙ | ︙ | |||
1210 1211 1212 1213 1214 1215 1216 | contrast to “zettel references” and “external references”). When a local reference is displayed as an URL on the WebUI, it will not opened in a new window/tab. They will receive a <i>local</i> marker, when encoded as “djson” or “native”. Local references are listed on the <i>Info page</i> of each zettel. * (minor) Change the default value for some visual sugar put after an | | | | > | | | | | | | | | > | | | | | > | 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 | contrast to “zettel references” and “external references”). When a local reference is displayed as an URL on the WebUI, it will not opened in a new window/tab. They will receive a <i>local</i> marker, when encoded as “djson” or “native”. Local references are listed on the <i>Info page</i> of each zettel. * (minor) Change the default value for some visual sugar put after an external URL to <code>&\#8599;&\#xfe0e;</code> (“↗︎”). This affects the former key <code>icon-material</code> of the <i>Zettelstore Runtime Configuration</i>, which is renamed to <code>marker-external</code>. * (major) Allow multiple zettel to act as templates for creating new zettel. All zettel with a role value “new-template” act as a template to create a new zettel. The WebUI menu item “New” changed to a drop-down list with all those zettel, ordered by their identifier. All metadata keys with the prefix <code>new-</code> will be translated to a new or updated keys/value without that prefix. You can use this mechanism to specify a role for the new zettel, or a different title. The title of the template zettel is used in the drop-down list. The initial template zettel “New Zettel” has now a different zettel identifier (now: <code>00000000091001</code>, was: <code>00000000040001</code>). <b>Please update it, if you changed that zettel.</b> <br>Note: this feature was superseded in [#0_0_10|version 0.0.10] by the “New Menu” zettel. * (minor) When a page should be opened in a new windows (e.g. for external references), the web browser is instructed to decouple the new page from the previous one for privacy and security reasons. In detail, the web browser is instructed to omit referrer information and to omit a JS object linking to the page that contained the external link. * (minor) If the value of the <i>Zettelstore Runtime Configuration</i> key <code>list-page-size</code> is greater than zero, the number of WebUI list elements will be restricted and it is possible to change to the next/previous page to list more elements. * (minor) Change CSS to enhance reading: make <code>line-height</code> a little smaller (previous: 1.6, now 1.4) and move list items to the left. <a id="0_0_5"></a> <h2>Changes for Version 0.0.5 (2020-10-22)</h2> * Application Programming Interface (API) to allow external software to retrieve zettel data from the Zettelstore. * Specify boxes, where zettel are stored, via an URL. * Add support for a custom footer. <a id="0_0_4"></a> <h2>Changes for Version 0.0.4 (2020-09-11)</h2> * Optional user authentication/authorization. * New sub-commands <code>file</code> (use Zettelstore as a command line filter), <code>password</code> (for authentication), and <code>config</code>. <a id="0_0_3"></a> <h2>Changes for Version 0.0.3 (2020-08-31)</h2> * Starting Zettelstore has been changed by introducing sub-commands. This change is also reflected on the server installation procedures. * Limitations on renaming zettel has been relaxed. <a id="0_0_2"></a> <h2>Changes for Version 0.0.2 (2020-08-28)</h2> * Configuration zettel now has ID <code>00000000000001</code> (previously: <code>00000000000000</code>). * The zettel with ID <code>00000000000000</code> is no longer shown in any zettel list. If you changed the configuration zettel, you should rename it manually in its file directory. * Creating a new zettel is now done by cloning an existing zettel. To mimic the previous behaviour, a zettel with ID <code>00000000040001</code> is introduced. You can change it if you need a different template zettel. <a id="0_0_1"></a> <h2>Changes for Version 0.0.1 (2020-08-21)</h2> * Initial public release. |
Changes to www/download.wiki.
1 2 3 4 5 6 7 8 9 10 11 | <title>Download</title> <h1>Download of Zettelstore Software</h1> <h2>Foreword</h2> * Zettelstore is free/libre open source software, licensed under EUPL-1.2-or-later. * The software is provided as-is. * There is no guarantee that it will not damage your system. * However, it is in use by the main developer since March 2020 without any damage. * It may be useful for you. It is useful for me. * Take a look at the [https://zettelstore.de/manual/|manual] to know how to start and use it. <h2>ZIP-ped Executables</h2> | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <title>Download</title> <h1>Download of Zettelstore Software</h1> <h2>Foreword</h2> * Zettelstore is free/libre open source software, licensed under EUPL-1.2-or-later. * The software is provided as-is. * There is no guarantee that it will not damage your system. * However, it is in use by the main developer since March 2020 without any damage. * It may be useful for you. It is useful for me. * Take a look at the [https://zettelstore.de/manual/|manual] to know how to start and use it. <h2>ZIP-ped Executables</h2> Build: <code>v0.16.0</code> (2023-11-30). * [/uv/zettelstore-0.16.0-linux-amd64.zip|Linux] (amd64) * [/uv/zettelstore-0.16.0-linux-arm.zip|Linux] (arm6, e.g. Raspberry Pi) * [/uv/zettelstore-0.16.0-darwin-arm64.zip|macOS] (arm64) * [/uv/zettelstore-0.16.0-darwin-amd64.zip|macOS] (amd64) * [/uv/zettelstore-0.16.0-windows-amd64.zip|Windows] (amd64) Unzip the appropriate file, install and execute Zettelstore according to the manual. <h2>Zettel for the manual</h2> As a starter, you can download the zettel for the manual [/uv/manual-0.16.0.zip|here]. Just unzip the contained files and put them into your zettel folder or configure a file box to read the zettel directly from the ZIP file. |
Changes to www/index.wiki.
︙ | ︙ | |||
22 23 24 25 26 27 28 | software, which often connects to Zettelstore via its API. Some of the software packages may be experimental. * [https://zettelstore.de/sx|Sx] provides an evaluator for symbolic expressions, which is unsed for HTML templates and more. [https://mastodon.social/tags/Zettelstore|Stay tuned] … <hr> | | | | | | | | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | software, which often connects to Zettelstore via its API. Some of the software packages may be experimental. * [https://zettelstore.de/sx|Sx] provides an evaluator for symbolic expressions, which is unsed for HTML templates and more. [https://mastodon.social/tags/Zettelstore|Stay tuned] … <hr> <h3>Latest Release: 0.16.0 (2023-11-30)</h3> * [./download.wiki|Download] * [./changes.wiki#0_16|Change summary] * [/timeline?p=v0.16.0&bt=v0.15.0&y=ci|Check-ins for version 0.16.0], [/vdiff?to=v0.16.0&from=v0.15.0|content diff] * [/timeline?df=v0.16.0&y=ci|Check-ins derived from the 0.16.0 release], [/vdiff?from=v0.16.0&to=trunk|content diff] * [./plan.wiki|Limitations and planned improvements] * [/timeline?t=release|Timeline of all past releases] <hr> <h2>Build instructions</h2> Just install [https://go.dev/dl/|Go] and some Go-based tools. Please read the [./build.md|instructions] for details. * [/dir?ci=trunk|Source code] * [/download|Download the source code] as a tarball or a ZIP file (you must [/login|login] as user "anonymous"). |
Changes to zettel/id/id.go.
︙ | ︙ | |||
111 112 113 114 115 116 117 | // toByteArray converts the Zid into a fixed byte array, usable for printing. // // Based on idea by Daniel Lemire: "Converting integers to fix-digit representations quickly" // https://lemire.me/blog/2021/11/18/converting-integers-to-fix-digit-representations-quickly/ func (zid Zid) toByteArray(result *[14]byte) { date := uint64(zid) / 1000000 fullyear := date / 10000 | < | < | < | | | | | | | 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | // toByteArray converts the Zid into a fixed byte array, usable for printing. // // Based on idea by Daniel Lemire: "Converting integers to fix-digit representations quickly" // https://lemire.me/blog/2021/11/18/converting-integers-to-fix-digit-representations-quickly/ func (zid Zid) toByteArray(result *[14]byte) { date := uint64(zid) / 1000000 fullyear := date / 10000 century, year := fullyear/100, fullyear%100 monthday := date % 10000 month, day := monthday/100, monthday%100 time := uint64(zid) % 1000000 hmtime, second := time/100, time%100 hour, minute := hmtime/100, hmtime%100 result[0] = byte(century/10) + '0' result[1] = byte(century%10) + '0' result[2] = byte(year/10) + '0' result[3] = byte(year%10) + '0' result[4] = byte(month/10) + '0' result[5] = byte(month%10) + '0' result[6] = byte(day/10) + '0' result[7] = byte(day%10) + '0' result[8] = byte(hour/10) + '0' result[9] = byte(hour%10) + '0' result[10] = byte(minute/10) + '0' result[11] = byte(minute%10) + '0' result[12] = byte(second/10) + '0' result[13] = byte(second%10) + '0' } // IsValid determines if zettel id is a valid one, e.g. consists of max. 14 digits. func (zid Zid) IsValid() bool { return 0 < zid && zid <= maxZid } // TimestampLayout to transform a date into a Zid and into other internal dates. const TimestampLayout = "20060102150405" // New returns a new zettel id based on the current time. func New(withSeconds bool) Zid { now := time.Now().Local() var s string if withSeconds { s = now.Format(TimestampLayout) } else { s = now.Format("20060102150400") } res, err := Parse(s) if err != nil { panic(err) } return res } |
Changes to zettel/meta/meta_test.go.
︙ | ︙ | |||
62 63 64 65 66 67 68 | addToMeta(m, api.KeyTitle, at) addToMeta(m, api.KeyTitle, " ") if got, ok := m.Get(api.KeyTitle); !ok || got != at { t.Errorf("Title is not %q, but %q", at, got) } } | | | | | | | | | 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 | addToMeta(m, api.KeyTitle, at) addToMeta(m, api.KeyTitle, " ") if got, ok := m.Get(api.KeyTitle); !ok || got != at { t.Errorf("Title is not %q, but %q", at, got) } } func checkTags(t *testing.T, exp []string, m *Meta) { t.Helper() got, _ := m.GetList(api.KeyTags) for i, tag := range exp { if i < len(got) { if tag != got[i] { t.Errorf("Pos=%d, expected %q, got %q", i, exp[i], got[i]) } } else { t.Errorf("Expected %q, but is missing", exp[i]) } } if len(exp) < len(got) { t.Errorf("Extra tags: %q", got[len(exp):]) } } func TestTagsHeader(t *testing.T) { t.Parallel() m := New(testID) checkTags(t, []string{}, m) addToMeta(m, api.KeyTags, "") checkTags(t, []string{}, m) addToMeta(m, api.KeyTags, " #t1 #t2 #t3 #t4 ") checkTags(t, []string{"#t1", "#t2", "#t3", "#t4"}, m) addToMeta(m, api.KeyTags, "#t5") checkTags(t, []string{"#t1", "#t2", "#t3", "#t4", "#t5"}, m) addToMeta(m, api.KeyTags, "t6") checkTags(t, []string{"#t1", "#t2", "#t3", "#t4", "#t5"}, m) } func TestSyntax(t *testing.T) { t.Parallel() m := New(testID) if got, ok := m.Get(api.KeySyntax); ok || got != "" { t.Errorf("Syntax is not %q, but %q", "", got) |
︙ | ︙ |
Changes to zettel/meta/parse_test.go.
︙ | ︙ | |||
72 73 74 75 76 77 78 | {api.KeyTags + ": #c #", "c"}, {api.KeyTags + ": #c #b", "b c"}, {api.KeyTags + ": #c # #", "c"}, {api.KeyTags + ": #c # #b", "b c"}, } for i, tc := range testcases { m := parseMetaStr(tc.src) | | > | 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | {api.KeyTags + ": #c #", "c"}, {api.KeyTags + ": #c #b", "b c"}, {api.KeyTags + ": #c # #", "c"}, {api.KeyTags + ": #c # #b", "b c"}, } for i, tc := range testcases { m := parseMetaStr(tc.src) tagsString, found := m.Get(api.KeyTags) if !found { if tc.exp != "" { t.Errorf("%d / %q: no %s found", i, tc.src, api.KeyTags) } continue } tags := meta.TagsFromValue(tagsString) if tc.exp == "" && len(tags) > 0 { t.Errorf("%d / %q: expected no %s, but got %v", i, tc.src, api.KeyTags, tags) continue } got := strings.Join(tags, " ") if tc.exp != got { t.Errorf("%d / %q: expected %q, got: %q", i, tc.src, tc.exp, got) |
︙ | ︙ |
Changes to zettel/meta/type.go.
︙ | ︙ | |||
117 118 119 120 121 122 123 | if slist := ListFromValue(word); len(slist) > 0 { m.Set(key, slist[0]) } } // SetNow stores the current timestamp under the given key. func (m *Meta) SetNow(key string) { | | | 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | if slist := ListFromValue(word); len(slist) > 0 { m.Set(key, slist[0]) } } // SetNow stores the current timestamp under the given key. func (m *Meta) SetNow(key string) { m.Set(key, time.Now().Local().Format(id.TimestampLayout)) } // BoolValue returns the value interpreted as a bool. func BoolValue(value string) bool { if len(value) > 0 { switch value[0] { case '0', 'f', 'F', 'n', 'N': |
︙ | ︙ | |||
141 142 143 144 145 146 147 | return BoolValue(value) } return false } // TimeValue returns the time value of the given value. func TimeValue(value string) (time.Time, bool) { | | | | > > > > | > > > | > > > | | > | < | < < < < | > | | > | > > > > > > > > | 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | return BoolValue(value) } return false } // TimeValue returns the time value of the given value. func TimeValue(value string) (time.Time, bool) { if t, err := time.Parse(id.TimestampLayout, ExpandTimestamp(value)); err == nil { return t, true } return time.Time{}, false } // ExpandTimestamp makes a short-form timestamp larger. func ExpandTimestamp(value string) string { switch l := len(value); l { case 4: // YYYY return value + "0101000000" case 6: // YYYYMM return value + "01000000" case 8, 10, 12: // YYYYMMDD, YYYYMMDDhh, YYYYMMDDhhmm return value + "000000"[:14-l] case 14: // YYYYMMDDhhmmss return value default: if l > 14 { return value[:14] } return value } } // ListFromValue transforms a string value into a list value. func ListFromValue(value string) []string { return strings.Fields(value) } // GetList retrieves the string list value of a given key. The bool value // signals, whether there was a value stored or not. func (m *Meta) GetList(key string) ([]string, bool) { value, ok := m.Get(key) if !ok { return nil, false } return ListFromValue(value), true } // TagsFromValue returns the value as a sequence of normalized tags. func TagsFromValue(value string) []string { tags := ListFromValue(strings.ToLower(value)) for i, tag := range tags { if len(tag) > 1 && tag[0] == '#' { tags[i] = tag[1:] } } return tags } // CleanTag removes the number character ('#') from a tag value and lowercases it. func CleanTag(tag string) string { if len(tag) > 1 && tag[0] == '#' { return tag[1:] } return tag } // NormalizeTag adds a missing prefix "#" to the tag func NormalizeTag(tag string) string { if len(tag) > 0 && tag[0] == '#' { return tag } return "#" + tag } // GetNumber retrieves the numeric value of a given key. func (m *Meta) GetNumber(key string, def int64) int64 { if value, ok := m.Get(key); ok { if num, err := strconv.ParseInt(value, 10, 64); err == nil { return num } } return def } |
Changes to zettel/meta/type_test.go.
︙ | ︙ | |||
29 30 31 32 33 34 35 | } if len(val) != 14 { t.Errorf("Value is not 14 digits long: %q", val) } if _, err := strconv.ParseInt(val, 10, 64); err != nil { t.Errorf("Unable to parse %q as an int64: %v", val, err) } | | | > > > > > > > > > > > > | 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 | } if len(val) != 14 { t.Errorf("Value is not 14 digits long: %q", val) } if _, err := strconv.ParseInt(val, 10, 64); err != nil { t.Errorf("Unable to parse %q as an int64: %v", val, err) } if _, ok = meta.TimeValue(val); !ok { t.Errorf("Unable to get time from value %q", val) } } func TestTimeValue(t *testing.T) { t.Parallel() testCases := []struct { value string valid bool exp time.Time }{ {"", false, time.Time{}}, {"1", false, time.Time{}}, {"00000000000000", false, time.Time{}}, {"98765432109876", false, time.Time{}}, {"20201221111905", true, time.Date(2020, time.December, 21, 11, 19, 5, 0, time.UTC)}, {"2023", true, time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC)}, {"20231", false, time.Time{}}, {"202310", true, time.Date(2023, time.October, 1, 0, 0, 0, 0, time.UTC)}, {"2023103", false, time.Time{}}, {"20231030", true, time.Date(2023, time.October, 30, 0, 0, 0, 0, time.UTC)}, {"202310301", false, time.Time{}}, {"2023103016", true, time.Date(2023, time.October, 30, 16, 0, 0, 0, time.UTC)}, {"20231030165", false, time.Time{}}, {"202310301654", true, time.Date(2023, time.October, 30, 16, 54, 0, 0, time.UTC)}, {"2023103016541", false, time.Time{}}, {"20231030165417", true, time.Date(2023, time.October, 30, 16, 54, 17, 0, time.UTC)}, {"2023103916541700", false, time.Time{}}, } for i, tc := range testCases { got, ok := meta.TimeValue(tc.value) if ok != tc.valid { t.Errorf("%d: parsing of %q should be %v, but got %v", i, tc.value, tc.valid, ok) continue } if got != tc.exp { t.Errorf("%d: parsing of %q should return %v, but got %v", i, tc.value, tc.exp, got) } } } |
Changes to zettel/meta/values.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 | // under this license. //----------------------------------------------------------------------------- package meta import ( "fmt" | < | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // under this license. //----------------------------------------------------------------------------- package meta import ( "fmt" "zettelstore.de/client.fossil/api" ) // Supported syntax values. const ( SyntaxCSS = api.ValueSyntaxCSS |
︙ | ︙ | |||
105 106 107 108 109 110 111 | // GetUserRole role returns the user role of the given string. func GetUserRole(val string) UserRole { if ur, ok := urMap[val]; ok { return ur } return UserRoleUnknown } | < < < < < < < < | 104 105 106 107 108 109 110 | // GetUserRole role returns the user role of the given string. func GetUserRole(val string) UserRole { if ur, ok := urMap[val]; ok { return ur } return UserRoleUnknown } |