Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From v0.15.0 To v0.16.1
2023-12-28
| ||
17:05 | Merge fix from v0.16.1 ... (check-in: 4c6aa26df6 user: t73fde tags: trunk) | |
16:48 | Version 0.16.1 ... (Leaf check-in: 900b273924 user: t73fde tags: release, v0.16.1, release-0.16) | |
16:41 | Fix sxn code that removed role-based customization, esp. for an additional action if role is "tag" ... (check-in: e721174596 user: t73fde tags: release-0.16) | |
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.16.1 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 constbox puts zettel inside the executable. package constbox import ( "context" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package constbox puts zettel inside the executable. package constbox import ( "context" |
︙ | ︙ | |||
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, | | | 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | 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", | | | 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | 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", | | | 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 | 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, |
︙ | ︙ | |||
274 275 276 277 278 279 280 | zettel.NewContent(contentStartCodeSxn)}, id.BaseSxnZid: { constHeader{ api.KeyTitle: "Zettelstore Sxn Base Code", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230619132800", | | | > | 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 | zettel.NewContent(contentStartCodeSxn)}, id.BaseSxnZid: { constHeader{ api.KeyTitle: "Zettelstore Sxn Base Code", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230619132800", api.KeyModified: "20231228172200", api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityExpert, api.KeyPrecursor: string(api.ZidSxnPrelude), }, zettel.NewContent(contentBaseCodeSxn)}, id.PreludeSxnZid: { constHeader{ api.KeyTitle: "Zettelstore Sxn Prelude", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20231006181700", api.KeyModified: "20231228171900", api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityExpert, }, 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", | > > > > > > > > > > > > | 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 | 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", | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | 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 | > > > > > > > > > > > > | 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 | 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.
1 2 3 4 5 6 7 8 9 10 | ;;;---------------------------------------------------------------------------- ;;; 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. ;;;---------------------------------------------------------------------------- | > > > | | > > > > > > | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-present Detlef Stern ;;; ;;; This file is part of Zettelstore. ;;; ;;; Zettelstore is licensed under the latest version of the EUPL (European ;;; Union Public License). Please see file LICENSE.txt for your rights and ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- ;;; This zettel contains sxn definitions that are independent of specific ;;; subsystems, such as WebUI, API, or other. It just contains generic code to ;;; be used in all places. ;; Constants NIL and T (defconst NIL ()) (defconst T 'T) ;; 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))) ;; Function not (defunconst not (x) (if x NIL T)) ;; 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 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-present Detlef Stern ;;; ;;; This file is part of Zettelstore. ;;; ;;; Zettelstore is licensed under the latest version of the EUPL (European ;;; Union Public License). Please see file LICENSE.txt for your rights and ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- ;; 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 |
︙ | ︙ | |||
81 82 83 84 85 86 87 | (if (pair? entry) `((link (@ (rel "stylesheet") (href ,(zid-content-path (cdr entry)))))) ) ) ) ) | | | | | | | | 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 | (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. (defun 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". (defun 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. (defun 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/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 | // 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 | <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.1 (2023-12-28)</h2> * Fix some Sxn definitions to allow role-based UI customizations. (minor: webui) <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: <tt>##Text##</tt> 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 <tt>define</tt> is now deprecated. It will be removed in version 0.16. Use <tt>defvar</tt> or <tt>defun</tt> instead. Otherwise the WebUI will not work in version 0.16. (major: webui, deprecated) |
︙ | ︙ |
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.1</code> (2023-12-28). * [/uv/zettelstore-0.16.1-linux-amd64.zip|Linux] (amd64) * [/uv/zettelstore-0.16.1-linux-arm.zip|Linux] (arm6, e.g. Raspberry Pi) * [/uv/zettelstore-0.16.1-darwin-arm64.zip|macOS] (arm64) * [/uv/zettelstore-0.16.1-darwin-amd64.zip|macOS] (amd64) * [/uv/zettelstore-0.16.1-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.1.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 | 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.1 (2023-12-28)</h3> * [./download.wiki|Download] * [./changes.wiki#0_16|Change summary] * [/timeline?p=v0.16.1&bt=v0.15.0&y=ci|Check-ins for version 0.16.1], [/vdiff?to=v0.16.1&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. |
︙ | ︙ |
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 | } 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 } |
︙ | ︙ |
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 } |