Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From v0.16.1 To v0.15.0
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.15.0 |
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 | // 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. 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.ZidLayout)) 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-tag-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 | //----------------------------------------------------------------------------- // 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. | < < < | 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" |
︙ | ︙ | |||
166 167 168 169 170 171 172 | id.MustParse(api.ZidDependencies): { constHeader{ api.KeyTitle: "Zettelstore Dependencies", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyReadOnly: api.ValueTrue, | | | 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | id.MustParse(api.ZidDependencies): { constHeader{ api.KeyTitle: "Zettelstore Dependencies", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityLogin, api.KeyCreated: "20210504135842", api.KeyModified: "20230601163100", }, zettel.NewContent(contentDependencies)}, id.BaseTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Base HTML Template", |
︙ | ︙ | |||
197 198 199 200 201 202 203 | zettel.NewContent(contentLoginSxn)}, id.ZettelTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230510155300", | | | 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | zettel.NewContent(contentLoginSxn)}, id.ZettelTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230510155300", api.KeyModified: "20230907203300", api.KeyVisibility: api.ValueVisibilityExpert, }, zettel.NewContent(contentZettelSxn)}, id.InfoTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Info HTML Template", api.KeyRole: api.ValueRoleConfiguration, |
︙ | ︙ | |||
247 248 249 250 251 252 253 | zettel.NewContent(contentDeleteSxn)}, id.ListTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore List Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230704122100", | | | 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | zettel.NewContent(contentDeleteSxn)}, id.ListTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore List Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230704122100", api.KeyModified: "20231002120600", api.KeyVisibility: api.ValueVisibilityExpert, }, zettel.NewContent(contentListZettelSxn)}, id.ErrorTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Error HTML Template", api.KeyRole: api.ValueRoleConfiguration, |
︙ | ︙ | |||
277 278 279 280 281 282 283 | zettel.NewContent(contentStartCodeSxn)}, id.BaseSxnZid: { constHeader{ api.KeyTitle: "Zettelstore Sxn Base Code", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230619132800", | | | < | 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 | 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: "20231012154500", 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: "20231019140400", 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.KeyVisibility: api.ValueVisibilityPublic, }, zettel.NewContent(contentBaseCSS)}, id.MustParse(api.ZidUserCSS): { constHeader{ api.KeyTitle: "Zettelstore User CSS", api.KeyRole: api.ValueRoleConfiguration, |
︙ | ︙ | |||
330 331 332 333 334 335 336 | id.TOCNewTemplateZid: { constHeader{ api.KeyTitle: "New Menu", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyCreated: "20210217161829", | < < < < < < < < < < < < | 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", |
︙ | ︙ | |||
379 380 381 382 383 384 385 | api.KeyCreated: "20201028185209", meta.NewPrefix + api.KeyCredential: "", meta.NewPrefix + api.KeyUserID: "", meta.NewPrefix + api.KeyUserRole: api.ValueUserRoleReader, api.KeyVisibility: api.ValueVisibilityOwner, }, zettel.NewContent(nil)}, | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 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", |
︙ | ︙ | |||
484 485 486 487 488 489 490 | //go:embed emoji_spin.gif var contentEmoji []byte //go:embed newtoc.zettel var contentNewTOCZettel []byte | < < < < < < < < < < < < | 428 429 430 431 432 433 434 435 436 | //go:embed emoji_spin.gif var contentEmoji []byte //go:embed newtoc.zettel var contentNewTOCZettel []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 | `(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-tag-zettel")) "Tag zettel: " ,@tag-zettel)) ) ,@(if (bound? 'create-tag-zettel) `((p (@ (class "zs-tag-zettel")) "Create tag zettel: " ,@create-tag-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 | 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 Template|00000000090003]] * [[New User|00000000090002]] |
Changes to box/constbox/prelude.sxn.
1 2 3 4 5 6 7 8 | ;;;---------------------------------------------------------------------------- ;;; 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 | ;;;---------------------------------------------------------------------------- ;;; 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. ;;;---------------------------------------------------------------------------- ;;; This zettel contains all sxn definitions that are independent of specific ;;; subsystems, such as WebUI, API, or other. It just contains generic code to ;;; be used elsewhere. ;; Constants NIL and T (defconst NIL ()) (defconst T 'T) ;; Function not (defun not (x) (if x NIL T)) (defconst not not) ;; let macro ;; ;; (let (BINDING ...) EXPR ...), where BINDING is a list of two elements ;; (SYMBOL EXPR) (defmacro let (bindings . body) `((lambda ,(map car bindings) ,@body) ,@(map cadr bindings))) |
︙ | ︙ |
Deleted box/constbox/roleconfiguration.zettel.
|
| < < < < < < < < < < < < < < < < < < < < |
Deleted box/constbox/rolerole.zettel.
|
| < < < < < < < < < < |
Deleted box/constbox/roletag.zettel.
|
| < < < < < < |
Deleted box/constbox/rolezettel.zettel.
|
| < < < < < < < |
Changes to box/constbox/wuicode.sxn.
1 2 3 4 5 6 7 8 | ;;;---------------------------------------------------------------------------- ;;; 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 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 | ;;;---------------------------------------------------------------------------- ;;; 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. (defun wui-item (s) `(li ,s)) ;; wui-table-row takes a pair and translates it into a HTML table row with ;; two columns. (defun 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. (defun 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. (defun 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. (defun 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. (defun wui-tdata-link (q) `(td ,(wui-link q))) ;; wui-item-popup-link is like 'wui-item-link, but the HTML link will open ;; a new tab / window. (defun wui-item-popup-link (e) `(li (a (@ (href ,e) (target "_blank") (rel "noopener noreferrer")) ,e))) ;; wui-option-value returns a value for an HTML option element. (defun wui-option-value (v) `(option (@ (value ,v)))) ;; wui-datalist returns a HTML datalist with the given HTML identifier and a ;; list of values. (defun wui-datalist (id lst) (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. (defun 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. (defun 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. (defun 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 |
︙ | ︙ | |||
84 85 86 87 88 89 90 | (if (pair? entry) `((link (@ (rel "stylesheet") (href ,(zid-content-path (cdr entry)))))) ) ) ) ) | | | | | | | | 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 | (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 (@ (open)) (summary "Folgezettel") (ul ,@(map wui-item-link folge-links))))) ,@(if subordinate-links `((details (@ (open)) (summary "Subordinates") (ul ,@(map wui-item-link subordinate-links))))) ,@(if back-links `((details (@ (open)) (summary "Incoming") (ul ,@(map wui-item-link back-links))))) ,@(if successor-links `((details (@ (open)) (summary "Successors") (ul ,@(map wui-item-link successor-links))))) )) ) ) |
Changes to box/manager/anteroom.go.
︙ | ︙ | |||
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} } | > > | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | const ( arNothing arAction = iota arReload arZettel ) type anteroom struct { num uint64 next *anteroom waiting id.Set curLoad int reload bool } type anteroomQueue struct { mx sync.Mutex nextNum uint64 first *anteroom last *anteroom maxLoad int } func newAnteroomQueue(maxLoad int) *anteroomQueue { return &anteroomQueue{maxLoad: maxLoad} } |
︙ | ︙ | |||
67 68 69 70 71 72 73 74 | } room := ar.makeAnteroom(zid) ar.last.next = room ar.last = room } func (ar *anteroomQueue) makeAnteroom(zid id.Zid) *anteroom { if zid == id.Invalid { | > > > > > | | | | | > | > > | | | < > | | > > > | | | | | | | | | | | | < | | 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | } room := ar.makeAnteroom(zid) ar.last.next = room ar.last = room } func (ar *anteroomQueue) makeAnteroom(zid id.Zid) *anteroom { ar.nextNum++ if zid == id.Invalid { return &anteroom{num: ar.nextNum, next: nil, waiting: nil, curLoad: 0, reload: true} } c := ar.maxLoad if c == 0 { c = 100 } waiting := id.NewSetCap(ar.maxLoad, zid) return &anteroom{num: ar.nextNum, next: nil, waiting: waiting, curLoad: 1, reload: false} } func (ar *anteroomQueue) Reset() { ar.mx.Lock() defer ar.mx.Unlock() ar.first = ar.makeAnteroom(id.Invalid) ar.last = ar.first } func (ar *anteroomQueue) Reload(allZids id.Set) uint64 { ar.mx.Lock() defer ar.mx.Unlock() ar.deleteReloadedRooms() if ns := len(allZids); ns > 0 { ar.nextNum++ ar.first = &anteroom{num: ar.nextNum, next: ar.first, waiting: allZids, curLoad: ns, reload: true} if ar.first.next == nil { ar.last = ar.first } return ar.nextNum } ar.first = nil ar.last = nil return 0 } 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, uint64) { ar.mx.Lock() defer ar.mx.Unlock() first := ar.first if first == nil { return arNothing, id.Invalid, 0 } roomNo := first.num if first.waiting == nil && first.reload { ar.removeFirst() return arReload, id.Invalid, roomNo } for zid := range first.waiting { delete(first.waiting, zid) if len(first.waiting) == 0 { ar.removeFirst() } return arZettel, zid, roomNo } ar.removeFirst() return arNothing, id.Invalid, 0 } 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, rno := ar.Dequeue() if zid != id.Zid(1) || action != arZettel || rno != 1 { t.Errorf("Expected arZettel/1/1, but got %v/%v/%v", action, zid, rno) } _, 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 96 97 | if !mgr.idxSleepService(timer, timerDuration) { return } } } func (mgr *Manager) idxWorkService(ctx context.Context) { var start time.Time for { | > | > | > > > > > < | > | 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 | if !mgr.idxSleepService(timer, timerDuration) { return } } } func (mgr *Manager) idxWorkService(ctx context.Context) { var roomNum uint64 var start time.Time for { switch action, zid, arRoomNum := mgr.idxAr.Dequeue(); action { case arNothing: return case arReload: mgr.idxLog.Debug().Msg("reload") roomNum = 0 zids, err := mgr.FetchZids(ctx) if err == nil { start = time.Now() if rno := mgr.idxAr.Reload(zids); rno > 0 { roomNum = rno } mgr.idxMx.Lock() mgr.idxLastReload = time.Now().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.idxMx.Lock() mgr.idxSinceReload++ mgr.idxMx.Unlock() mgr.idxDeleteZettel(ctx, zid) continue } mgr.idxLog.Trace().Zid(zid).Msg("update") mgr.idxMx.Lock() if arRoomNum == roomNum { mgr.idxDurReload = time.Since(start) } mgr.idxSinceReload++ mgr.idxMx.Unlock() mgr.idxUpdateZettel(ctx, zettel) } } } func (mgr *Manager) idxSleepService(timer *time.Timer, timerDuration time.Duration) bool { select { case _, ok := <-mgr.idxReady: |
︙ | ︙ | |||
180 181 182 183 184 185 186 | 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: | < | | | < < | < < < < < < < < < < < | 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | 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: for _, word := range strfun.NormalizeWords(pair.Value) { cData.words.Add(word) } } } } 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) |
︙ | ︙ |
Changes to box/manager/memstore/memstore.go.
︙ | ︙ | |||
266 267 268 269 270 271 272 273 274 | } } } return back } func (ms *memStore) UpdateReferences(_ context.Context, zidx *store.ZettelIndex) id.Set { ms.mx.Lock() defer ms.mx.Unlock() | > < | 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 { m := ms.makeMeta(zidx) ms.mx.Lock() defer ms.mx.Unlock() 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.IfErr(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)) 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 | 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) | < | 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) |
︙ | ︙ | |||
102 103 104 105 106 107 108 | 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)) | | | | 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 | 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, &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, &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 | "context" "zettelstore.de/z/zettel/meta" ) // Key values that are supported by Config.Get const ( KeyFooterZettel = "footer-zettel" KeyHomeZettel = "home-zettel" // 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 | id: 00001000000000 title: Zettelstore Manual role: manual tags: #manual #zettelstore syntax: zmk created: 00010101000000 modified: 20231002143058 * [[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 | id: 00001004020000 title: Configure the running Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20230807171016 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. |
︙ | ︙ | |||
54 55 56 57 58 59 60 | 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]]. | < < < < < < < < < < < < < < < < < | 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. |
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 | 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: 20230317183403 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 |
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: 20231002104819 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 | | [[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 | [[00000000090000]] | New Menu | Contains items that should contain in the zettel template menu | [[00000000090001]] | New Zettel | Template for a new zettel with role ""[[zettel|00001006020100#zettel]]"" | [[00000000090002]] | New User | Template for a new [[user zettel|00001010040200]] | [[00000000090003]] | New Tag | Template for a new [[tag zettel|00001006020100#tag]] | [[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 | id: 00001006020100 title: Supported Zettel Roles role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 00010101000000 modified: 20230829233016 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 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. ; [!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. 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|''zettel''] : A real zettel that contains your own thoughts. 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 | id: 00001006034500 title: Timestamp Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20230612183509 Values of this type denote a point in time. === Allowed values Must be a sequence of 14 digits (""0""--""9"") (same as an [[Identifier|00001006032000]]), with the restriction that is conforms to the pattern ""YYYYMMDDhhmmss"". * YYYY is the year, * MM is the month, * DD is the day, * hh is the hour, * mm is the minute, * ss is the second. === Query comparison [[Search values|00001007706000]] with more than 14 characters are truncated to contain exactly 14 characters. When the [[search operators|00001007705000]] ""less"", ""not less"", ""greater"", and ""not greater"" are given, the length of the search value is checked. If it contains less than 14 digits, zero digits (""0"") are appended, until it contains exactly 14 digits. All other comparisons assume that up to 14 characters are given. 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 [[String|00001006033500]] values. If both values are timestamp values, this works well because both have the same length. |
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 | id: 00001007040100 title: Zettelmarkup: Text Formatting role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218131003 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. |
︙ | ︙ | |||
26 27 28 29 30 31 32 | * 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}. | < < < | 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. |
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 | id: 00001007800000 title: Zettelmarkup: Summary of Formatting Characters role: manual tags: #manual #reference #zettelmarkup #zettelstore syntax: zmk modified: 20220810095559 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]] | [[Tag|00001007040000]] | ''$'' | (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 | id: 00001012000000 title: API role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20230928183244 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]] === 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]] |
︙ | ︙ |
Deleted docs/manual/00001012051800.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
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: 20230807165948 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 POST --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: 20230405123222 === ''TEXT'' :::syntax __Text__ **=** ''(TEXT'' String '')''. ::: Specifies the string as some text content, typically a word. |
︙ | ︙ | |||
143 144 145 146 147 148 149 | 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. | < < < < < | 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]] … '')''. |
︙ | ︙ |
Deleted docs/manual/20231128184200.zettel.
|
| < < < < < < < |
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 | 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) Encoder { if create, ok := registry[enc]; ok { return create() } return nil } // CreateFunc produces a new encoder. type CreateFunc func() Encoder 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 | encoderZmk: useZmk, }, }, { descr: "Quotes formatting", zmk: `""quotes""`, expect: expectMap{ encoderHTML: "<q>quotes</q>", encoderMD: "<q>quotes</q>", encoderSz: `(INLINE (FORMAT-QUOTE () (TEXT "quotes")))`, encoderSHTML: `((q "quotes"))`, encoderText: `quotes`, encoderZmk: useZmk, }, }, { descr: "Quotes formatting (german)", zmk: `""quotes""{lang=de}`, expect: expectMap{ encoderHTML: `<span lang="de"><q>quotes</q></span>`, encoderMD: "<q>quotes</q>", encoderSz: `(INLINE (FORMAT-QUOTE (quote (("lang" . "de"))) (TEXT "quotes")))`, encoderSHTML: `((span (@ (lang . "de")) (q "quotes")))`, encoderText: `quotes`, encoderZmk: `""quotes""{lang="de"}`, }, }, { descr: "Span formatting", zmk: `::span::`, expect: expectMap{ encoderHTML: `<span>span</span>`, encoderMD: "span", encoderSz: `(INLINE (FORMAT-SPAN () (TEXT "span")))`, encoderSHTML: `((span "span"))`, |
︙ | ︙ | |||
311 312 313 314 315 316 317 | encoderZmk: useZmk, }, }, { descr: "Nested Span Quote formatting", zmk: `::""abc""::{lang=fr}`, expect: expectMap{ | | | | 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | encoderZmk: useZmk, }, }, { descr: "Nested Span Quote formatting", zmk: `::""abc""::{lang=fr}`, expect: expectMap{ encoderHTML: `<span lang="fr"><q>abc</q></span>`, encoderMD: "<q>abc</q>", encoderSz: `(INLINE (FORMAT-SPAN (quote (("lang" . "fr"))) (FORMAT-QUOTE () (TEXT "abc"))))`, encoderSHTML: `((span (@ (lang . "fr")) (q "abc")))`, 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) 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) 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 | "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() encoder.Encoder { return Create() }) } // Create an encoder. func Create() *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.NewTransformer(1, nil), textEnc: textenc.Create(), } } type Encoder struct { tx *szenc.Transformer th *shtml.Transformer 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) { hm, err := he.th.Transform(he.tx.GetMeta(zn.InhMeta, evalMeta)) 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.Transform(xtitle) if err != nil { return 0, err } } xast := he.tx.GetSz(&zn.Ast) hast, err := he.th.Transform(xast) if err != nil { return 0, err } hen := he.th.Endnotes() 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"))) |
︙ | ︙ | |||
114 115 116 117 118 119 120 | 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) { | < | < | | < | | 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 | 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) { hm, err := he.th.Transform(he.tx.GetMeta(m, evalMeta)) 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) { hobj, err := he.th.Transform(he.tx.GetSz(bs)) 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()) 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) { hobj, err := he.th.Transform(he.tx.GetSz(is)) 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.Encoder { return Create() }) } // Create an encoder. func Create() *Encoder { return &myME } type Encoder struct{} |
︙ | ︙ | |||
327 328 329 330 331 332 333 | 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>") | < < < < | 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 { |
︙ | ︙ |
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 | "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() encoder.Encoder { return Create() }) } // Create a SHTML encoder func Create() *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.NewTransformer(1, nil), } } type Encoder struct { tx *szenc.Transformer th *shtml.Transformer } // WriteZettel writes the encoded zettel to the writer. func (enc *Encoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) { metaSHTML, err := enc.th.Transform(enc.tx.GetMeta(zn.InhMeta, evalMeta)) if err != nil { return 0, err } contentSHTML, err := enc.th.Transform(enc.tx.GetSz(&zn.Ast)) 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) { metaSHTML, err := enc.th.Transform(enc.tx.GetMeta(m, evalMeta)) if err != nil { return 0, err } return metaSHTML.Print(w) } func (enc *Encoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return enc.WriteBlocks(w, &zn.Ast) } // WriteBlocks writes a block slice to the writer func (enc *Encoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { hval, err := enc.th.Transform(enc.tx.GetSz(bs)) if err != nil { return 0, err } return hval.Print(w) } // WriteInlines writes an inline slice to the writer func (enc *Encoder) WriteInlines(w io.Writer, is *ast.InlineSlice) (int, error) { hval, err := enc.th.Transform(enc.tx.GetSz(is)) if err != nil { return 0, err } return hval.Print(w) } |
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.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 | 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, | < | 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, |
︙ | ︙ |
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.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.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 | ast.FormatEmph: []byte("__"), ast.FormatStrong: []byte("**"), ast.FormatInsert: []byte(">>"), ast.FormatDelete: []byte("~~"), ast.FormatSuper: []byte("^^"), ast.FormatSub: []byte(",,"), ast.FormatQuote: []byte(`""`), | < | 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)) |
︙ | ︙ |
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.ZidLayout, 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.ZidLayout, 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.ZidLayout, 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.5.6 golang.org/x/crypto v0.14.0 golang.org/x/term v0.13.0 golang.org/x/text v0.13.0 zettelstore.de/client.fossil v0.0.0-20231026155719-8c6fa07a0d0f zettelstore.de/sx.fossil v0.0.0-20231026154942-e6a183740a4f ) require golang.org/x/sys v0.13.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.5.6 h1:COmQAWTCcGetChm3Ig7G/t8AFAN00t+o8Mt4cf7JpwA= github.com/yuin/goldmark v1.5.6/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= zettelstore.de/client.fossil v0.0.0-20231026155719-8c6fa07a0d0f h1:eW8wEMcqR+LvIwWxE1rl+6LZbXRM9wgBGQ9pPw/k1j8= zettelstore.de/client.fossil v0.0.0-20231026155719-8c6fa07a0d0f/go.mod h1:uYFsUH4hQ/TLEjDFxzOLJkT/sltvcQ5aIM29XNkjR+c= zettelstore.de/sx.fossil v0.0.0-20231026154942-e6a183740a4f h1:NFblKWyhNnXDDcF7C6zx7cDyIJ/7GGAusIwR6uZrfkM= zettelstore.de/sx.fossil v0.0.0-20231026154942-e6a183740a4f/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 | 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}, } 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, } } 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.ZidLayout), 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.ZidLayout)) } 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.IfErr(err).Msg("Unable to accept connection") break } go handleLineConnection(conn, kern) } ln.Close() } |
︙ | ︙ |
Changes to logger/logger.go.
︙ | ︙ | |||
178 179 180 181 182 183 184 185 186 187 188 189 190 191 | func (l *Logger) Info() *Message { return newMessage(l, InfoLevel) } // 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) } | > > > > > > > > | 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | func (l *Logger) Info() *Message { return newMessage(l, InfoLevel) } // 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) } // IfErr creates an error message and sets the go error, if there is an error. func (l *Logger) IfErr(err error) *Message { if err != nil { return newMessage(l, ErrorLevel).Err(err) } return nil } // 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) } |
︙ | ︙ |
Changes to parser/parser.go.
︙ | ︙ | |||
131 132 133 134 135 136 137 | 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. | < | | 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 | 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) } return nil } // 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 | '_': ast.FormatEmph, '*': ast.FormatStrong, '>': ast.FormatInsert, '~': ast.FormatDelete, '^': ast.FormatSuper, ',': ast.FormatSub, '"': ast.FormatQuote, | < | 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] |
︙ | ︙ |
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 | ast.FormatEmph: '_', ast.FormatStrong: '*', ast.FormatInsert: '>', ast.FormatDelete: '~', ast.FormatSuper: '^', ast.FormatSub: ',', ast.FormatQuote: '"', | < | 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: '\'', |
︙ | ︙ |
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 | 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, meta.TypeTimestamp: // ID and timestamp use the same layout return createMatchIDFunc(values, addSearch) case meta.TypeIDSet: return createMatchIDSetFunc(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: |
︙ | ︙ | |||
151 152 153 154 155 156 157 | return false } } } return true } } | < < < < < < < < < < < < | > > > > | 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 | 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.ListFromValue(value) // Remove leading '#' from each tag for i, tag := range tags { tags[i] = meta.CleanTag(tag) } for _, preds := range predList { for _, pred := range preds { if !pred(tags) { return false } } } |
︙ | ︙ | |||
396 397 398 399 400 401 402 | func disambiguatedIDOp(cmpOp compareOp) compareOp { return disambiguateWordOp(cmpOp) } func createIDCompareFunc(cmpVal string, cmpOp compareOp) stringPredicate { return createWordCompareFunc(cmpVal, cmpOp) } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 386 387 388 389 390 391 392 393 394 395 396 397 398 399 | 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) if err == nil { |
︙ | ︙ |
Changes to query/sorter.go.
︙ | ︙ | |||
53 54 55 56 57 58 59 | 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 } } | < < < < < < < < < < < < < < < < < < < < < < < < < < < > > > > > > > > > | 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 | keyType := meta.Type(key) if key == api.KeyID || keyType == meta.TypeCredential { if descending { return func(i, j int) bool { return ml[i].Zid > ml[j].Zid } } return func(i, j int) bool { return ml[i].Zid < ml[j].Zid } } if keyType == meta.TypeNumber { return createSortNumberFunc(ml, key, descending) } return createSortStringFunc(ml, key, descending) } func createSortNumberFunc(ml []*meta.Meta, key string, descending bool) sortFunc { if descending { return func(i, j int) bool { iVal, iOk := getNum(ml[i], key) jVal, jOk := getNum(ml[j], key) return (iOk && (!jOk || iVal > jVal)) || !jOk } } return func(i, j int) bool { iVal, iOk := getNum(ml[i], key) jVal, jOk := getNum(ml[j], key) return (iOk && (!jOk || iVal < jVal)) || !jOk } } func createSortStringFunc(ml []*meta.Meta, key string, descending bool) sortFunc { if descending { return func(i, j int) bool { iVal, iOk := ml[i].Get(key) jVal, jOk := ml[j].Get(key) return (iOk && (!jOk || iVal > jVal)) || !jOk } } return func(i, j int) bool { iVal, iOk := ml[i].Get(key) jVal, jOk := ml[j].Get(key) return (iOk && (!jOk || iVal < jVal)) || !jOk } } func getNum(m *meta.Meta, key string) (int64, bool) { if s, ok := m.Get(key); ok { if i, err := strconv.ParseInt(s, 10, 64); err == nil { return i, true } } return 0, false } |
Changes to 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 = 50 configRoleZettel = 32 writerZettel = ownerZettel - 24 readerZettel = ownerZettel - 24 creatorZettel = 8 publicZettel = 4 ) 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 | 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 != 3 { t.Errorf("Expected list of length 3, got %d", got) return } checkListZid(t, metaSeq, 0, api.ZidTemplateNewZettel) checkListZid(t, metaSeq, 1, api.ZidTemplateNewTag) checkListZid(t, metaSeq, 2, api.ZidTemplateNewUser) } // func TestGetZettelContext(t *testing.T) { // const ( // allUserZid = api.ZettelID("20211019200500") // ownerZid = api.ZettelID("20210629163300") // writerZid = api.ZettelID("20210629165000") |
︙ | ︙ | |||
278 279 280 281 282 283 284 | 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 { | | | 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | 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", got) return } } func failNoErrorOrNoCode(t *testing.T, err error, goodCode int) bool { if err != nil { if cErr, ok := err.(*client.Error); ok { |
︙ | ︙ | |||
383 384 385 386 387 388 389 | c := getClient() c.SetAuth("owner", "owner") agg, err := c.QueryAggregate(context.Background(), api.ActionSeparator+api.KeyRole) if err != nil { t.Error(err) return } | | < < < < < < < < < < < < < < < < < < < < < | 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 | 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", "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 TestVersion(t *testing.T) { t.Parallel() c := getClient() ver, err := c.GetVersionInfo(context.Background()) if err != nil { t.Error(err) return |
︙ | ︙ |
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) 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).WriteBlocks(&sb, ast) sb.Reset() }) } } func testZmkEncoding(t *testing.T, tc markdownTestCase, ast *ast.BlockSlice) { zmkEncoder := encoder.Create(api.EncoderZmk) 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) 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 | import ( "bufio" "io" "os" "path/filepath" "testing" | < | 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" ) |
︙ | ︙ | |||
52 53 54 55 56 57 58 | } } return result } func getAllEncoder() (result []encoder.Encoder) { for _, enc := range encoder.GetEncodings() { | | | 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | } } return result } func getAllEncoder() (result []encoder.Encoder) { for _, enc := range encoder.GetEncodings() { e := encoder.Create(enc) 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); 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.ZidLayout)) 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 habe 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 | 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 } |
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.IfErr(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 | default: panic(encStr) } h := adapter.PrepareHeader(w, contentType) h.Set(api.HeaderLocation, location.String()) w.WriteHeader(http.StatusCreated) _, err = w.Write(result) a.log.IfErr(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 | 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), )) a.log.IfErr(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(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 | } 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 } err = writeBuffer(w, &buf, contentType) a.log.IfErr(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 |
︙ | ︙ | |||
128 129 130 131 132 133 134 | case partMeta: obj = sexp.EncodeMetaRights(api.MetaRights{ Meta: z.Meta.Map(), Rights: a.getRights(ctx, z.Meta), }) } | | | < < | < < < < | 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | case partMeta: obj = sexp.EncodeMetaRights(api.MetaRights{ Meta: z.Meta.Map(), Rights: a.getRights(ctx, z.Meta), }) } err = a.writeObject(w, zid, obj) a.log.IfErr(err).Zid(zid).Msg("write sx data") } func (a *API) writeEncodedZettelPart( w http.ResponseWriter, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc, enc api.EncodingEnum, encStr string, part partType, ) { encdr := encoder.Create(enc) 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 { |
︙ | ︙ | |||
168 169 170 171 172 173 174 | return } if buf.Len() == 0 { w.WriteHeader(http.StatusNoContent) return } | | | | < | 161 162 163 164 165 166 167 168 169 170 | return } if buf.Len() == 0 { w.WriteHeader(http.StatusNoContent) return } err = writeBuffer(w, &buf, content.MIMEFromEncoding(enc)) a.log.IfErr(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 | "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() { err := a.writeToken(w, "freeaccess", 24*366*10*time.Hour) a.log.IfErr(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 } err := a.writeToken(w, string(token), a.tokenLifetime) a.log.IfErr(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() { err := a.writeToken(w, "freeaccess", 24*366*10*time.Hour) a.log.IfErr(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 { err := a.writeToken(w, string(authData.Token), totalLifetime-currentLifetime) a.log.IfErr(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 } err = a.writeToken(w, string(token), a.tokenLifetime) a.log.IfErr(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 | "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, 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 } sq := adapter.GetQuery(urlQuery) metaSeq, err := queryMeta.Run(ctx, sq) if err != nil { a.reportUsecaseError(w, err) return |
︙ | ︙ | |||
250 251 252 253 254 255 256 | 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 } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 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 } |
Changes to web/adapter/response.go.
︙ | ︙ | |||
71 72 73 74 75 76 77 | 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 } | < < < < | 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) } |
︙ | ︙ |
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 | 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 } err = adapter.WriteData(w, data, "") wui.log.IfErr(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() 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 | //----------------------------------------------------------------------------- // 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 ( | < < | 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" ) |
︙ | ︙ | |||
39 40 41 42 43 44 45 | q := r.URL.Query() zn, err := evaluate.Run(ctx, zid, q.Get(api.KeySyntax)) if err != nil { wui.reportError(ctx, w, err) return } | | | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | 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() metaObj := enc.MetaSxn(zn.InhMeta, createEvalMetadataFunc(ctx, evaluate)) content, endnotes, err := enc.BlocksSxn(&zn.Ast) if err != nil { wui.reportError(ctx, w, err) return } |
︙ | ︙ | |||
66 67 68 69 70 71 72 | } 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) | | | | | | 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | } 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) rb.bindString("folge-links", wui.zettelLinksSxn(zn.InhMeta, api.KeyFolge, getTextTitle)) rb.bindString("subordinate-links", wui.zettelLinksSxn(zn.InhMeta, api.KeySubordinates, getTextTitle)) rb.bindString("back-links", wui.zettelLinksSxn(zn.InhMeta, api.KeyBack, getTextTitle)) rb.bindString("successor-links", wui.zettelLinksSxn(zn.InhMeta, api.KeySuccessors, 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 { |
︙ | ︙ | |||
94 95 96 97 98 99 100 | 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 } | < < < < < < < < < < < < < < < < < < < | 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | 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) } |
︙ | ︙ |
Changes to web/adapter/webui/htmlgen.go.
︙ | ︙ | |||
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | "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 | > | < | | | 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/sx.fossil/sxeval" "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.Transformer symAt *sx.Symbol } func (wui *WebUI) createGenerator(builder urlBuilder) *htmlGenerator { th := shtml.NewTransformer(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 78 79 80 81 | 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(args []sx.Object, prevFn sxeval.Callable) sx.Object { obj, err := prevFn.Call(nil, args) if err != nil { return sx.Nil() } attr, assoc, rest := findA(obj) if attr == nil { return obj } hrefP := assoc.Assoc(symHref) if hrefP == nil { |
︙ | ︙ | |||
86 87 88 89 90 91 92 | if hasFragment { u = u.SetFragment(fragment) } assoc = assoc.Cons(sx.Cons(symHref, sx.String(u.String()))) return rest.Cons(assoc.Cons(symAttr)).Cons(symA) } | > | | | > > > > | | | | | | | | | | | | | | | | | > > > > | | | | | | | | | | | | | | | | | | | | | | | | | > > > > | | | | | | | | | | > > > > | | | | | | | | | | | | | | | | | | | | | | | | > < < < < < < < < < < < < < | | 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | if hasFragment { u = u.SetFragment(fragment) } assoc = assoc.Cons(sx.Cons(symHref, sx.String(u.String()))) return rest.Cons(assoc.Cons(symAttr)).Cons(symA) } th.SetRebinder(func(te *shtml.TransformEnv) { te.Rebind(sz.NameSymLinkZettel, linkZettel) te.Rebind(sz.NameSymLinkFound, linkZettel) te.Rebind(sz.NameSymLinkBased, func(args []sx.Object, prevFn sxeval.Callable) sx.Object { obj, err := prevFn.Call(nil, args) if err != nil { return sx.Nil() } 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) }) te.Rebind(sz.NameSymLinkQuery, func(args []sx.Object, prevFn sxeval.Callable) sx.Object { obj, err := prevFn.Call(nil, args) if err != nil { return sx.Nil() } 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) }) te.Rebind(sz.NameSymLinkExternal, func(args []sx.Object, prevFn sxeval.Callable) sx.Object { obj, err := prevFn.Call(nil, args) if err != nil { return sx.Nil() } 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) }) te.Rebind(sz.NameSymEmbed, func(args []sx.Object, prevFn sxeval.Callable) sx.Object { obj, err := prevFn.Call(nil, args) if err != nil { return sx.Nil() } 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, symAt: symAttr, } } // 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) hm, err := g.th.Transform(tm) 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 { |
︙ | ︙ | |||
242 243 244 245 246 247 248 | } newName, found := mapMetaKey[name] if !found { continue } a = a.Set("name", newName) | | | 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | } newName, found := mapMetaKey[name] if !found { continue } a = a.Set("name", newName) metaMap[newName] = g.th.TransformMeta(a) } result := sx.Nil() keys := maps.Keys(metaMap) for i := len(keys) - 1; i >= 0; i-- { result = result.Cons(metaMap[keys[i]]) } return result |
︙ | ︙ | |||
264 265 266 267 268 269 270 | } sb.WriteString(strings.TrimPrefix(val, "#")) } metaTags := sb.String() if len(metaTags) == 0 { return nil } | | < | | < | | 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | } sb.WriteString(strings.TrimPrefix(val, "#")) } metaTags := sb.String() if len(metaTags) == 0 { return nil } return g.th.TransformMeta(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) sh, err := g.th.Transform(sx) if err != nil { return nil, nil, err } return sh, g.th.Endnotes(), 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) sh, err := g.th.Transform(sx) 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 | "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, 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 } q := adapter.GetQuery(urlQuery) q = q.SetDeterministic() ctx := r.Context() metaSeq, err := queryMeta.Run(ctx, q) if err != nil { wui.reportError(ctx, w, err) return |
︙ | ︙ | |||
76 77 78 79 80 81 82 | wui.renderRSS(ctx, w, q, metaSeq) return } } } var content, endnotes *sx.Pair if bn := evaluator.QueryAction(ctx, q, metaSeq, wui.rtConfig); bn != nil { | | | 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | 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() content, endnotes, err = enc.BlocksSxn(&ast.BlockSlice{bn}) if err != nil { wui.reportError(ctx, w, err) return } } |
︙ | ︙ | |||
102 103 104 105 106 107 108 | } 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) } | | < < < < < < < < < | 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | } 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) { rb.bindString("create-tag-zettel", sxNoTzl) } } 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)) } else { |
︙ | ︙ | |||
144 145 146 147 148 149 150 | } } } func (wui *WebUI) transformTagZettelList(ctx context.Context, tagZettel *usecase.TagZettel, tags []string) (withZettel, withoutZettel *sx.Pair) { slices.Reverse(tags) for _, tag := range tags { | < | | < < < < < < < < < < < < < | < | | 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 | } } } 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) withZettel = wui.prependTagZettel(withZettel, tag, u) } else { u := wui.NewURLBuilder('c').SetZid(api.ZidTemplateNewTag).AppendKVQuery(queryKeyAction, valueActionNew).AppendKVQuery(api.KeyTitle, tag) withoutZettel = wui.prependTagZettel(withoutZettel, tag, u) } } return withZettel, withoutZettel } func (wui *WebUI) prependTagZettel(sxZtl *sx.Pair, tag 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(tag), ) if sxZtl != nil { sxZtl = sxZtl.Cons(sx.String(", ")) } return sxZtl.Cons(link) } |
︙ | ︙ | |||
200 201 202 203 204 205 206 | 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 { | | | < < < < < < < < < < < < < < < | 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 | 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.IfErr(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.IfErr(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 } |
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(env, form); 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(syntaxes) + len(builtins) + 3) engine := sxeval.MakeEngine(wui.sf, root) for _, syntax := range syntaxes { engine.BindSyntax(syntax) } for _, b := range builtins { engine.BindBuiltin(b) } engine.BindBuiltin(&sxeval.Builtin{ Name: "url-to-html", MinArity: 1, MaxArity: 1, IsPure: true, 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, IsPure: true, 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, IsPure: true, 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 ( syntaxes = []*sxeval.Syntax{ &sxbuiltins.QuoteS, &sxbuiltins.QuasiquoteS, // quote, quasiquote &sxbuiltins.UnquoteS, &sxbuiltins.UnquoteSplicingS, // unquote, unquote-splicing &sxbuiltins.DefVarS, &sxbuiltins.DefConstS, // defvar, defconst &sxbuiltins.SetXS, // set! &sxbuiltins.DefineS, // define (DEPRECATED) &sxbuiltins.DefunS, &sxbuiltins.LambdaS, // defun, lambda &sxbuiltins.CondS, // cond &sxbuiltins.IfS, // if &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().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.IfErr(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(env, objs[0]) if err != nil { return nil, err } wui.setSxnCache(zid, wui.engine.Rework(env, t)) 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(env, templateExpr) } 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 | 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) _, err = w.Write(sb.Bytes()) wui.log.IfErr(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 | genHTML *sxhtml.Generator symMetaHeader *sx.Symbol symDetail *sx.Symbol symA, symHref *sx.Symbol symSpan *sx.Symbol symAttr *sx.Symbol | < | 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 |
︙ | ︙ | |||
130 131 132 133 134 135 136 | 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), | < | 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 } |
︙ | ︙ | |||
193 194 195 196 197 198 199 | 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) } | | < < | 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | 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() *htmlGenerator { return wui.createGenerator(wui) } // 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 | * [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`. |
︙ | ︙ | |||
62 63 64 65 66 67 68 | ``` ## 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. | | < < | < < | < | < | < | < | < | 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | ``` ## 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=zezzelstore.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 | <title>Change Log</title> | < < < < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 1 2 3 4 5 6 7 8 9 10 11 | <title>Change Log</title> <a id="0_16"></a> <h2>Changes for Version 0.16.0 (pending)</h2> <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.15.0</code> (2023-10-26). * [/uv/zettelstore-0.15.0-linux-amd64.zip|Linux] (amd64) * [/uv/zettelstore-0.15.0-linux-arm.zip|Linux] (arm6, e.g. Raspberry Pi) * [/uv/zettelstore-0.15.0-darwin-arm64.zip|macOS] (arm64) * [/uv/zettelstore-0.15.0-darwin-amd64.zip|macOS] (amd64) * [/uv/zettelstore-0.15.0-windows-amd64.zip|Windows] (amd64) Unzip the appropriate file, install and execute Zettelstore according to the manual. <h2>Zettel for the manual</h2> As a starter, you can download the zettel for the manual [/uv/manual-0.15.0.zip|here]. Just unzip the contained files and put them into your zettel folder or configure a file box to read the zettel directly from the ZIP file. |
Changes to www/index.wiki.
︙ | ︙ | |||
22 23 24 25 26 27 28 | software, which often connects to Zettelstore via its API. Some of the software packages may be experimental. * [https://zettelstore.de/sx|Sx] provides an evaluator for symbolic expressions, which is unsed for HTML templates and more. [https://mastodon.social/tags/Zettelstore|Stay tuned] … <hr> | | | | | | | | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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.15.0 (2023-10-26)</h3> * [./download.wiki|Download] * [./changes.wiki#0_15|Change summary] * [/timeline?p=v0.15.0&bt=v0.14.0&y=ci|Check-ins for version 0.15.0], [/vdiff?to=v0.15.0&from=v0.14.0|content diff] * [/timeline?df=v0.15.0&y=ci|Check-ins derived from the 0.15.0 release], [/vdiff?from=v0.15.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 162 163 164 | // 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 := fullyear / 100 year := fullyear % 100 monthday := date % 10000 month := monthday / 100 day := monthday % 100 time := uint64(zid) % 1000000 hmtime := time / 100 second := time % 100 hour := hmtime / 100 minute := 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 } // ZidLayout to transform a date into a Zid and into other internal dates. const ZidLayout = "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(ZidLayout) } 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 checkSet(t *testing.T, exp []string, m *Meta, key string) { t.Helper() got, _ := m.GetList(key) 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) checkSet(t, []string{}, m, api.KeyTags) addToMeta(m, api.KeyTags, "") checkSet(t, []string{}, m, api.KeyTags) addToMeta(m, api.KeyTags, " #t1 #t2 #t3 #t4 ") checkSet(t, []string{"#t1", "#t2", "#t3", "#t4"}, m, api.KeyTags) addToMeta(m, api.KeyTags, "#t5") checkSet(t, []string{"#t1", "#t2", "#t3", "#t4", "#t5"}, m, api.KeyTags) addToMeta(m, api.KeyTags, "t6") checkSet(t, []string{"#t1", "#t2", "#t3", "#t4", "#t5"}, m, api.KeyTags) } 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 | {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) tags, found := m.GetTags(api.KeyTags) if !found { if tc.exp != "" { t.Errorf("%d / %q: no %s found", i, tc.src, api.KeyTags) } continue } 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.ZidLayout)) } // 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 | 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.ZidLayout, value); err == nil { return t, true } return time.Time{}, false } // GetTime returns the time value of the given key. func (m *Meta) GetTime(key string) (time.Time, bool) { if value, ok := m.Get(key); ok { return TimeValue(value) } return time.Time{}, false } // 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 } // GetTags returns the list of tags as a string list. Each tag does not begin // with the '#' character, in contrast to `GetList`. func (m *Meta) GetTags(key string) ([]string, bool) { tagsValue, ok := m.Get(key) if !ok { return nil, false } tags := ListFromValue(strings.ToLower(tagsValue)) for i, tag := range tags { tags[i] = CleanTag(tag) } return tags, len(tags) > 0 } // 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 } // 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 | } 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 = m.GetTime("key"); !ok { t.Errorf("Unable to get time from value %q", val) } } func TestGetTime(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)}, } 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 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 | > | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // under this license. //----------------------------------------------------------------------------- package meta import ( "fmt" "strings" "zettelstore.de/client.fossil/api" ) // Supported syntax values. const ( SyntaxCSS = api.ValueSyntaxCSS |
︙ | ︙ | |||
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 } | > > > > > > > > | 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | // 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 } // NormalizeTag adds a missing prefix "#" to the tag func NormalizeTag(tag string) string { if strings.HasPrefix(tag, "#") { return tag } return "#" + tag } |