Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From v0.16.1
To v0.15.0
2023-12-28
| | |
17:05 |
|
...
(check-in: 4c6aa26df6 user: t73fde tags: trunk)
|
16:48 |
|
...
(Leaf
check-in: 900b273924 user: t73fde tags: release, v0.16.1, release-0.16)
|
16:41 |
|
...
(check-in: e721174596 user: t73fde tags: release-0.16)
|
2023-10-27
| | |
06:08 |
|
...
(check-in: bd019d898d user: stern tags: trunk)
|
2023-10-26
| | |
16:03 |
|
...
(check-in: 7cde0fccea user: stern tags: trunk, release, v0.15.0)
|
2023-10-23
| | |
14:52 |
|
...
(check-in: 382096929c user: stern tags: trunk)
|
| | |
Changes to VERSION.
Changes to ast/inline.go.
︙ | | |
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
|
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
FormatEmph // Emphasized text.
FormatStrong // Strongly emphasized text.
FormatInsert // Inserted text.
FormatDelete // Deleted text.
FormatSuper // Superscripted text.
FormatSub // SubscriptedText.
FormatQuote // Quoted text.
FormatMark // Marked text
FormatSpan // Generic inline container
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
27
28
29
30
31
32
33
34
|
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
-
+
|
)
func genLogM(zid id.Zid) *meta.Meta {
m := meta.New(zid)
m.Set(api.KeyTitle, "Zettelstore Log")
m.Set(api.KeySyntax, meta.SyntaxText)
m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreStarted).(string))
m.Set(api.KeyModified, kernel.Main.GetLastLogTime().Local().Format(id.TimestampLayout))
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
86
87
88
89
90
91
92
93
|
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
-
+
|
h1 { font-size:1.5rem; margin:.65rem 0 }
h2 { font-size:1.25rem; margin:.70rem 0 }
h3 { font-size:1.15rem; margin:.75rem 0 }
h4 { font-size:1.05rem; margin:.8rem 0; font-weight: bold }
h5 { font-size:1.05rem; margin:.8rem 0 }
h6 { font-size:1.05rem; margin:.8rem 0; font-weight: lighter }
p { margin: .5rem 0 0 0 }
p.zs-meta-zettel { margin-top: .5rem; margin-left: 0.5rem }
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
9
10
11
12
13
14
15
16
17
18
|
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.
//
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2020-present Detlef Stern
//-----------------------------------------------------------------------------
// Package constbox puts zettel inside the executable.
package constbox
import (
"context"
|
︙ | | |
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
|
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
-
+
|
id.MustParse(api.ZidDependencies): {
constHeader{
api.KeyTitle: "Zettelstore Dependencies",
api.KeyRole: api.ValueRoleConfiguration,
api.KeySyntax: meta.SyntaxZmk,
api.KeyLang: api.ValueLangEN,
api.KeyReadOnly: api.ValueTrue,
api.KeyVisibility: api.ValueVisibilityPublic,
api.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
204
205
206
207
208
209
210
211
|
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
|
-
+
|
zettel.NewContent(contentLoginSxn)},
id.ZettelTemplateZid: {
constHeader{
api.KeyTitle: "Zettelstore Zettel HTML Template",
api.KeyRole: api.ValueRoleConfiguration,
api.KeySyntax: meta.SyntaxSxn,
api.KeyCreated: "20230510155300",
api.KeyModified: "20231126180500",
api.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
254
255
256
257
258
259
260
261
|
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
|
-
+
|
zettel.NewContent(contentDeleteSxn)},
id.ListTemplateZid: {
constHeader{
api.KeyTitle: "Zettelstore List Zettel HTML Template",
api.KeyRole: api.ValueRoleConfiguration,
api.KeySyntax: meta.SyntaxSxn,
api.KeyCreated: "20230704122100",
api.KeyModified: "20231129112800",
api.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
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
|
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: "20231228172200",
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: "20231228171900",
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.KeyModified: "20231129112800",
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
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
|
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.KeyModified: "20231129111800",
api.KeyVisibility: api.ValueVisibilityCreator,
},
zettel.NewContent(contentNewTOCZettel)},
id.MustParse(api.ZidTemplateNewZettel): {
constHeader{
api.KeyTitle: "New Zettel",
api.KeyRole: api.ValueRoleConfiguration,
api.KeySyntax: meta.SyntaxZmk,
api.KeyCreated: "20201028185209",
api.KeyModified: "20230929132900",
meta.NewPrefix + api.KeyRole: api.ValueRoleZettel,
api.KeyVisibility: api.ValueVisibilityCreator,
},
zettel.NewContent(nil)},
id.MustParse(api.ZidTemplateNewRole): {
constHeader{
api.KeyTitle: "New Role",
api.KeyRole: api.ValueRoleConfiguration,
api.KeySyntax: meta.SyntaxZmk,
api.KeyCreated: "20231129110800",
meta.NewPrefix + api.KeyRole: api.ValueRoleRole,
meta.NewPrefix + api.KeyTitle: "",
api.KeyVisibility: api.ValueVisibilityCreator,
},
zettel.NewContent(nil)},
id.MustParse(api.ZidTemplateNewTag): {
constHeader{
api.KeyTitle: "New Tag",
api.KeyRole: api.ValueRoleConfiguration,
api.KeySyntax: meta.SyntaxZmk,
api.KeyCreated: "20230929132400",
|
︙ | | |
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
|
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.MustParse(api.ZidRoleZettelZettel): {
constHeader{
api.KeyTitle: api.ValueRoleZettel,
api.KeyRole: api.ValueRoleRole,
api.KeySyntax: meta.SyntaxZmk,
api.KeyCreated: "20231129161400",
api.KeyLang: api.ValueLangEN,
api.KeyVisibility: api.ValueVisibilityLogin,
},
zettel.NewContent(contentRoleZettel)},
id.MustParse(api.ZidRoleConfigurationZettel): {
constHeader{
api.KeyTitle: api.ValueRoleConfiguration,
api.KeyRole: api.ValueRoleRole,
api.KeySyntax: meta.SyntaxZmk,
api.KeyCreated: "20231129162800",
api.KeyLang: api.ValueLangEN,
api.KeyVisibility: api.ValueVisibilityLogin,
},
zettel.NewContent(contentRoleConfiguration)},
id.MustParse(api.ZidRoleRoleZettel): {
constHeader{
api.KeyTitle: api.ValueRoleRole,
api.KeyRole: api.ValueRoleRole,
api.KeySyntax: meta.SyntaxZmk,
api.KeyCreated: "20231129162900",
api.KeyLang: api.ValueLangEN,
api.KeyVisibility: api.ValueVisibilityLogin,
},
zettel.NewContent(contentRoleRole)},
id.MustParse(api.ZidRoleTagZettel): {
constHeader{
api.KeyTitle: api.ValueRoleTag,
api.KeyRole: api.ValueRoleRole,
api.KeySyntax: meta.SyntaxZmk,
api.KeyCreated: "20231129162000",
api.KeyLang: api.ValueLangEN,
api.KeyVisibility: api.ValueVisibilityLogin,
},
zettel.NewContent(contentRoleTag)},
id.DefaultHomeZid: {
constHeader{
api.KeyTitle: "Home",
api.KeyRole: api.ValueRoleZettel,
api.KeySyntax: meta.SyntaxZmk,
api.KeyLang: api.ValueLangEN,
api.KeyCreated: "20210210190757",
|
︙ | | |
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
|
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 rolezettel.zettel
var contentRoleZettel []byte
//go:embed roleconfiguration.zettel
var contentRoleConfiguration []byte
//go:embed rolerole.zettel
var contentRoleRole []byte
//go:embed roletag.zettel
var contentRoleTag []byte
//go:embed home.zettel
var contentHomeZettel []byte
|
Changes to box/constbox/listzettel.sxn.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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-meta-zettel")) "Tag zettel: " ,@tag-zettel))
`((p (@ (class "zs-tag-zettel")) "Tag zettel: " ,@tag-zettel))
)
,@(if (bound? 'create-tag-zettel)
`((p (@ (class "zs-meta-zettel")) "Create tag zettel: " ,@create-tag-zettel))
`((p (@ (class "zs-tag-zettel")) "Create tag zettel: " ,@create-tag-zettel))
)
,@(if (bound? 'role-zettel)
`((p (@ (class "zs-meta-zettel")) "Role zettel: " ,@role-zettel))
)
,@(if (bound? 'create-role-zettel)
`((p (@ (class "zs-meta-zettel")) "Create role zettel: " ,@create-role-zettel))
)
,@content
,@endnotes
(form (@ (action ,(if (bound? 'create-url) create-url)))
"Other encodings: "
(a (@ (href ,data-url)) "data")
", "
|
︙ | | |
Changes to box/constbox/newtoc.zettel.
1
2
3
4
5
6
|
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 Role|00000000090004]]
* [[New Tag|00000000090003]]
* [[New Template|00000000090003]]
* [[New User|00000000090002]]
|
Changes to box/constbox/prelude.sxn.
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
|
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.
;;;
;;; SPDX-License-Identifier: EUPL-1.2
;;; SPDX-FileCopyrightText: 2023-present Detlef Stern
;;;----------------------------------------------------------------------------
;;; This zettel contains sxn definitions that are independent of specific
;;; 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 in all places.
;;; be used elsewhere.
;; Constants NIL and T
(defconst NIL ())
(defconst T 'T)
;; defunconst macro to define functions that are bound as a constant.
;;
;; (defunconst NAME ARGS EXPR ...)
(defmacro defunconst (name args . body)
`(begin (defun ,name ,args ,@body) (defconst ,name ,name)))
;; Function not
(defunconst not (x) (if x NIL T))
(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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
Zettel with role ""configuration"" are used within Zettelstore to manage and to show the current configuration of the software.
Typically, there are some public zettel that show the license of this software, its dependencies, some CSS code to make the default web user interface a litte bit nicer, and the defult image to singal a broken image.
Other zettel are only visible if an user has authenticated itself, or if there is no authentication enabled.
In this case, one additional configuration zettel is the zettel containing the version number of this software.
Other zettel are showing the supported metadata keys and supported syntax values.
Zettel that allow to configure the menu of template to create new zettel are also using the role ""configuration"".
Most important is the zettel that contains the runtime configuration.
You may change its metadata value to change the behaviour of the software.
One configuration is the ""expert mode"".
If enabled, and if you are authorized so see them, you will discover some more zettel.
For example, HTML templates to customize the default web user interface, to show the application log, to see statistics about zettel boxes, to show the host name and it operating system, and many more.
You are allowed to add your own configuration zettel, for example if you want to customize the look and feel of zettel by placing relevant data into your own zettel.
By default, user zettel (for authentification) use also the role ""configuration"".
However, you are allowed to change this.
|
Deleted box/constbox/rolerole.zettel.
1
2
3
4
5
6
7
8
9
10
|
|
-
-
-
-
-
-
-
-
-
-
|
A zettel with the role ""role"" describes a specific role.
The described role must be the title of such a zettel.
This zettel is such a zettel, as it describes the meaning of the role ""role"".
Therefore it has the title ""role"" too.
If you like, this zettel is a meta-role.
You are free to create your own role-describing zettel.
For example, you want to document the intended meaning of the role.
You might also be interested to describe needed metadata so that some software is enabled to analyse or to process your zettel.
|
Deleted box/constbox/roletag.zettel.
1
2
3
4
5
6
|
|
-
-
-
-
-
-
|
A zettel with role ""tag"" is a zettel that describes specific tag.
The tag name must be the title of such a zettel.
Such zettel are similar to this specific zettel: this zettel describes zettel with a role ""tag"".
These zettel with the role ""tag"" describe specific tags.
These might form a hierarchy of meta-tags (and meta-roles).
|
Deleted box/constbox/rolezettel.zettel.
1
2
3
4
5
6
7
|
|
-
-
-
-
-
-
-
|
A zettel with the role ""zettel"" is typically used to document your own thoughts.
Such zettel are the main reason to use the software Zettelstore.
The only predefined zettel with the role ""zettel"" is the [[default home zettel|00010000000000]], which contains some welcome information.
You are free to change this.
In this case you should modify this zettel too, so that it reflects your own use of zettel with the role ""zettel"".
|
Changes to box/constbox/wuicode.sxn.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
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.
;;;
;;; SPDX-License-Identifier: EUPL-1.2
;;; SPDX-FileCopyrightText: 2023-present Detlef Stern
;;;----------------------------------------------------------------------------
;; Contains WebUI specific code, but not related to a specific template.
;; wui-list-item returns the argument as a HTML list item.
(defunconst wui-item (s) `(li ,s))
(defun wui-item (s) `(li ,s))
;; wui-table-row takes a pair and translates it into a HTML table row with
;; two columns.
(defunconst wui-table-row (p)
(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.
(defunconst wui-valid-link (l)
(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.
(defunconst wui-link (q)
(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.
(defunconst wui-item-link (q) `(li ,(wui-link q)))
(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.
(defunconst wui-tdata-link (q) `(td ,(wui-link q)))
(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.
(defunconst wui-item-popup-link (e)
(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.
(defunconst wui-option-value (v) `(option (@ (value ,v))))
(defun wui-option-value (v) `(option (@ (value ,v))))
;; wui-datalist returns a HTML datalist with the given HTML identifier and a
;; list of values.
(defunconst wui-datalist (id lst)
(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.
(defunconst wui-pair-desc-item (p) `((dt ,(car p)) (dd ,(cdr p))))
(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.
(defunconst wui-meta-desc (l)
(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.
(defunconst wui-enc-matrix (matrix)
(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
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
|
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.
;;; ACTION-SEPARATOR defines a HTML value that separates actions links.
(defvar ACTION-SEPARATOR '(@H " · "))
;; ROLE-DEFAULT-actions returns the default text for actions.
;;; 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".
;;; 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.
;;; 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
24
25
26
27
28
29
30
|
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
-
-
-
-
+
+
+
+
|
,@(ROLE-DEFAULT-heading (current-environment))
)
)
,@content
,endnotes
,@(if (or folge-links subordinate-links back-links successor-links)
`((nav
,@(if folge-links `((details (@ (,folge-open)) (summary "Folgezettel") (ul ,@(map wui-item-link folge-links)))))
,@(if subordinate-links `((details (@ (,subordinate-open)) (summary "Subordinates") (ul ,@(map wui-item-link subordinate-links)))))
,@(if back-links `((details (@ (,back-open)) (summary "Incoming") (ul ,@(map wui-item-link back-links)))))
,@(if successor-links `((details (@ (,successor-open)) (summary "Successors") (ul ,@(map wui-item-link successor-links)))))
,@(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
|
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
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
|
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 {
panic(zid)
c = 100
}
waiting := id.NewSetCap(max(ar.maxLoad, 100), zid)
return &anteroom{next: nil, waiting: waiting, curLoad: 1, reload: false}
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 = &anteroom{next: nil, waiting: nil, curLoad: 0, reload: true}
ar.first = ar.makeAnteroom(id.Invalid)
ar.last = ar.first
}
func (ar *anteroomQueue) Reload(allZids id.Set) {
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{next: ar.first, waiting: allZids, curLoad: ns, reload: true}
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
}
} else {
ar.first = nil
ar.last = nil
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, bool) {
func (ar *anteroomQueue) Dequeue() (arAction, id.Zid, uint64) {
ar.mx.Lock()
defer ar.mx.Unlock()
first := ar.first
if first != nil {
if first.waiting == nil && first.reload {
ar.removeFirst()
return arReload, id.Invalid, false
}
for zid := range first.waiting {
delete(first.waiting, zid)
if len(first.waiting) == 0 {
ar.removeFirst()
}
return arZettel, zid, first.reload
}
ar.removeFirst()
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, false
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
23
24
25
26
27
28
29
30
31
32
|
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
-
-
-
+
+
+
|
"zettelstore.de/z/zettel/id"
)
func TestSimple(t *testing.T) {
t.Parallel()
ar := newAnteroomQueue(2)
ar.EnqueueZettel(id.Zid(1))
action, zid, lastReload := ar.Dequeue()
if zid != id.Zid(1) || action != arZettel || lastReload {
t.Errorf("Expected arZettel/1/false, but got %v/%v/%v", action, zid, lastReload)
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
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
|
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, lastReload := mgr.idxAr.Dequeue(); action {
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()
mgr.idxAr.Reload(zids)
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.idxUpdateZettel(ctx, zettel)
mgr.idxMx.Lock()
if lastReload {
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
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
|
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:
if descr.Type.IsSet {
for _, val := range meta.ListFromValue(pair.Value) {
idxCollectMetaValue(cData.words, val)
}
for _, word := range strfun.NormalizeWords(pair.Value) {
cData.words.Add(word)
}
} else {
idxCollectMetaValue(cData.words, pair.Value)
}
}
}
}
}
func idxCollectMetaValue(stWords store.WordSet, value string) {
if words := strfun.NormalizeWords(value); len(words) > 0 {
for _, word := range words {
stWords.Add(word)
}
} else {
stWords.Add(value)
}
}
func (mgr *Manager) idxProcessData(ctx context.Context, zi *store.ZettelIndex, cData *collectData) {
for ref := range cData.refs {
if mgr.HasZettel(ctx, ref) {
zi.AddBackRef(ref)
|
︙ | | |
Changes to box/manager/memstore/memstore.go.
︙ | | |
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
|
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()
m := ms.makeMeta(zidx)
zi, ziExist := ms.idx[zidx.Zid]
if !ziExist || zi == nil {
zi = &zettelData{}
ziExist = false
}
// Is this zettel an old dead reference mentioned in other zettel?
|
︙ | | |
Changes to box/notify/fsdir.go.
︙ | | |
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
-
+
|
}
return true
}
if ev.Has(fsnotify.Create) {
err := fsdn.base.Add(fsdn.path)
if err != nil {
fsdn.log.Error().Err(err).Str("name", fsdn.path).Msg("Unable to add directory")
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
46
47
48
49
50
51
52
53
|
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
-
+
|
zettel.Zettel{
Meta: m,
Content: zettel.NewContent(inp.Src[inp.Pos:]),
},
m.GetDefault(api.KeySyntax, meta.SyntaxZmk),
nil,
)
encdr := encoder.Create(api.Encoder(enc), &encoder.CreateParameter{Lang: m.GetDefault(api.KeyLang, api.ValueLangEN)})
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
70
71
72
73
74
75
76
77
|
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)
ucRoleZettel := usecase.NewRoleZettel(protectedBoxManager, &ucQuery)
ucListSyntax := usecase.NewListSyntax(protectedBoxManager)
ucListRoles := usecase.NewListRoles(protectedBoxManager)
ucDelete := usecase.NewDeleteZettel(logUc, protectedBoxManager)
ucUpdate := usecase.NewUpdateZettel(logUc, protectedBoxManager)
ucRename := usecase.NewRenameZettel(logUc, protectedBoxManager)
ucRefresh := usecase.NewRefresh(logUc, protectedBoxManager)
ucReIndex := usecase.NewReIndex(logUc, protectedBoxManager)
|
︙ | | |
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
|
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, &ucRoleZettel, &ucReIndex))
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, &ucRoleZettel, &ucReIndex))
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
22
23
24
25
26
27
28
29
30
31
32
33
34
|
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"
KeyFooterZettel = "footer-zettel"
KeyHomeZettel = "home-zettel"
KeyShowBackLinks = "show-back-links"
KeyShowFolgeLinks = "show-folge-links"
KeyShowSubordinateLinks = "show-subordinate-links"
KeyShowSuccessorLinks = "show-successor-links"
// api.KeyLang
)
// Config allows to retrieve all defined configuration values that can be changed during runtime.
type Config interface {
AuthConfig
|
︙ | | |
Changes to docs/manual/00001000000000.zettel.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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: 20210301190630
modified: 20231125185455
created: 00010101000000
modified: 20231002143058
show-back-links: false
* [[Introduction|00001001000000]]
* [[Design goals|00001002000000]]
* [[Installation|00001003000000]]
* [[Configuration|00001004000000]]
* [[Structure of Zettelstore|00001005000000]]
* [[Layout of a zettel|00001006000000]]
|
︙ | | |
Changes to docs/manual/00001004020000.zettel.
1
2
3
4
5
6
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
|
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: 20231126180829
modified: 20230807171016
show-back-links: false
You can configure a running Zettelstore by modifying the special zettel with the ID [[00000000000100]].
This zettel is called __configuration zettel__.
The following metadata keys change the appearance / behavior of Zettelstore.
Some of them can be overwritten in an [[user zettel|00001010040200]], a subset of those may be overwritten in zettel that is currently used.
See the full list of [[metadata that may be overwritten|00001004020200]].
; [!default-copyright|''default-copyright'']
: Copyright value to be used when rendering content.
Can be overwritten in a zettel with [[meta key|00001006020000]] ''copyright''.
Default: (the empty string).
; [!default-license|''default-license'']
: License value to be used when rendering content.
Can be overwritten in a zettel with [[meta key|00001006020000]] ''license''.
Default: (the empty string).
; [!default-visibility|''default-visibility'']
: Visibility to be used, if zettel does not specify a value for the [[''visibility''|00001006020000#visibility]] metadata key.
Default: ""login"".
; [!expert-mode|''expert-mode'']
: If set to a [[boolean true value|00001006030500]], all zettel with [[visibility ""expert""|00001010070200]] will be shown (to the owner, if [[authentication is enabled|00001010040100]]; to all, otherwise).
This affects most computed zettel.
Default: ""False"".
; [!footer-zettel|''footer-zettel'']
: Identifier of a zettel that is rendered as HTML and will be placed as the footer of every zettel in the [[web user interface|00001014000000]].
Zettel content, delivered via the [[API|00001012000000]] as symbolic expressions, etc. is not affected.
If the zettel identifier is invalid or references a zettel that could not be read (possibly because of a limited [[visibility setting|00001010070200]]), nothing is written as the footer.
May be [[overwritten|00001004020200]] in a user zettel.
|
︙ | | |
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
|
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"".
; [!show-back-links|''show-back-links''], [!show-folge-links|''show-folge-links''], [!show-subordinate-links|''show-subordinate-links''], [!show-successor-links|''show-successor-links'']
: When displaying a zettel in the web user interface, references to other zettel are normally shown below the content of the zettel.
This affects the metadata keys [[''back''|00001006020000#back]], [[''folge''|00001006020000#folge]], [[''subordinates''|00001006020000#subordinates]], and [[''successors''|00001006020000#successors]].
These configuration keys may be used to show, not to show, or to close the list of referenced zettel.
Allowed values are: ""false"" (will not show the list), ""close"" (will show the list closed), and ""open"" / """" (will show the list).
Default: """".
May be [[overwritten|00001004020200]] in a user zettel, so that setting will only affect the given user.
Alternatively, it may be overwritten in a zettel, so that that the setting will affect only the given zettel.
This zettel is an example of a zettel that sets ''show-back-links'' to ""false"".
; [!site-name|''site-name'']
: Name of the Zettelstore instance.
Will be used when displaying some lists.
Default: ""Zettelstore"".
; [!yaml-header|''yaml-header'']
: If [[true|00001006030500]], metadata and content will be separated by ''---\\n'' instead of an empty line (''\\n\\n'').
Default: ""False"".
You will probably use this key, if you are working with another software processing [[Markdown|https://daringfireball.net/projects/markdown/]] that uses a subset of [[YAML|https://yaml.org/]] to specify metadata.
; [!zettel-file-syntax|''zettel-file-syntax'']
: If you create a new zettel with a syntax different to ""zmk"", Zettelstore will store the zettel as two files:
one for the metadata (file without a filename extension) and another for the content (file extension based on the syntax value).
If you want to specify alternative syntax values, for which you want new zettel to be stored in one file (file extension ''.zettel''), you can use this key.
All values are case-insensitive, duplicate values are removed.
For example, you could use this key if you're working with Markdown syntax and you want to store metadata and content in one ''.zettel'' file.
If ''yaml-header'' evaluates to true, a zettel is always stored in one ''.zettel'' file.
|
Changes to docs/manual/00001004020200.zettel.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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: 20231126180752
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
|[[''show-back-links''|00001004020000#show-back-links]]|Y|Y|
|[[''show-folge-links''|00001004020000#show-folge-links]]|Y|Y|
|[[''show-subordinate-links''|00001004020000#show-subordinate-links]]|Y|Y|
|[[''show-successor-links''|00001004020000#show-successor-links]]|Y|Y|
|
Changes to docs/manual/00001005090000.zettel.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
-
+
|
id: 00001005090000
title: List of predefined zettel
role: manual
tags: #manual #reference #zettelstore
syntax: zmk
created: 20210126175322
modified: 20231129173425
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
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
| [[00000000060010]] | zettel | [[Role zettel|00001012051800]] for the role ""[[zettel|00001006020100#zettel]]""
| [[00000000060020]] | confguration | [[Role zettel|00001012051800]] for the role ""[[confguration|00001006020100#confguration]]""
| [[00000000060030]] | role | [[Role zettel|00001012051800]] for the role ""[[role|00001006020100#role]]""
| [[00000000060040]] | tag | [[Role zettel|00001012051800]] for the role ""[[tag|00001006020100#tag]]""
| [[00000000090000]] | New Menu | Contains items that should be in the zettel template menu
| [[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]]
| [[00000000090004]] | New Role | Template for a new [[role zettel|00001006020100#role]]
| [[00010000000000]] | Home | Default home zettel, contains some welcome information
If a zettel is not linked, it is not accessible for the current user.
**Important:** All identifier may change until a stable version of the software is released.
|
Changes to docs/manual/00001006020100.zettel.
1
2
3
4
5
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
|
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: 20210126175322
modified: 20231129173620
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 / information for the Zettelstore.
: 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.
; [!role|''role'']
: A zettel with the role ""role"" and a title, which names a [[role|00001006020000#role]], is treated as a __role zettel__.
Basically, role zettel describe the role, and form a hierarchiy of meta-roles.
; [!tag|''tag'']
: A zettel with the role ""tag"" and a title, which names a [[tag|00001006020000#tags]], is treated as a __tag zettel__.
Basically, tag zettel describe the tag, and form a hierarchiy of meta-tags.
; [!zettel|''zettel'']
: A zettel that contains your own thoughts.
The real reason to use this software.
If you adhere to the process outlined by Niklas Luhmann, a zettel could have one of the following three roles:
; [!note|''note'']
: A small note, to remember something.
Notes are not real zettel, they just help to create a real zettel.
Think of them as Post-it notes.
; [!literature|''literature'']
: Contains some remarks about a book, a paper, a web page, etc.
You should add a citation key for citing it.
; ''zettel''
: (as described above)
; [!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
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
|
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: 20231030182858
modified: 20230612183509
Values of this type denote a point in time.
=== Allowed values
Must be a sequence of 4, 6, 8, 10, 12, or 14 digits (""0""--""9"") (similar to an [[Identifier|00001006032000]]), with the restriction that it must conform to the pattern ""YYYYMMDDhhmmss"".
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.
If the sequence is less than 14 digits, they are expanded with the following rule:
=== Query comparison
[[Search values|00001007706000]] with more than 14 characters are truncated to contain exactly 14 characters.
* YYYY is expanded to YYYY0101000000
* YYYYMM is expanded to YYYYMM01000000
* YYYYMMDD is expanded to YYYYMMDD000000
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.
* YYYYMMDDhh is expanded to YYYYMMDDhh0000
* YYYYMMDDhhmm is expanded to YYYYMMDDhhmm00
=== Query comparison
All other comparisons assume that up to 14 characters are given.
[[Search values|00001007706000]] with more than 14 characters are truncated to contain exactly 14 characters.
Then, they are treated as timestamp data, as describe above, if they contain 4, 6, 8, 10, or 12 digits.
Comparison is done through the string representation.
In case of the search operators ""less"", ""not less"", ""greater"", and ""not greater"", this is the same as a numerical comparison.
=== Sorting
Sorting is done by comparing the possibly expanded values.
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
6
7
8
9
10
11
12
13
14
|
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
created: 20210126175322
modified: 20231113191353
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
33
34
35
36
37
|
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 number sign (""''#''"", U+0023) marks the text visually, where the mark does not belong to the text itself.
It is typically used to highlight some text that is important for you, but was not important for the original author.
** Example: ``abc ##def## ghi`` is rendered in HTML as: ::abc ##def## ghi::{=example}.
* The colon character (""'':''"", U+003A) mark some text that should belong together. It fills a similar role as [[region blocks|00001007030800]], but just for inline elements.
** Example: ``abc ::def::{=example} ghi`` is rendered in HTML as: abc ::def::{=example} ghi.
|
Changes to docs/manual/00001007800000.zettel.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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
created: 20220805150154
modified: 20231113191330
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]] | [[marked / highlighted text|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
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
|
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: 20231128183617
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]]
* [[Determine a role zettel|00001012051800]]
=== Working with zettel
* [[Create a new zettel|00001012053200]]
* [[Retrieve metadata and content of an existing zettel|00001012053300]]
* [[Retrieve metadata of an existing zettel|00001012053400]]
* [[Retrieve evaluated metadata and content of an existing zettel in various encodings|00001012053500]]
* [[Retrieve parsed metadata and content of an existing zettel in various encodings|00001012053600]]
|
︙ | | |
Deleted docs/manual/00001012051800.zettel.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
id: 00001012051800
title: API: Determine a role zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20231128183917
modified: 20231128184701
The [[endpoint|00001012920000]] ''/z'' also allows you to determine a ""role zettel"", i.e. a zettel that documents a given role.
The query parameter ""''role''"" allows you to specify a value that is interpreted as the name of a role.
Zettelstore tries to determine the corresponding role zettel.
A role zettel is a zettel with the [[''role''|00001006020100]] value ""role"" and a title that names the role.
If there is more than one zettel that qualifies, the zettel with the highest zettel identifier is used.
For example, if you want to determine the role zettel for the role ""manual"", your request will be:
```sh
# curl -i 'http://127.0.0.1:23123/z?role=manual'
HTTP/1.1 302 Found
Content-Type: text/plain; charset=utf-8
Location: /z/20231128184200
Content-Length: 14
20231128184200
```
If there is a corresponding role zettel, the response will use the HTTP status code 302 (""Found""), the HTTP response header ''Location'' will contain the URL of the role zettel.
Its zettel identifier will be returned in the HTTP response body.
If you specified some more query parameter, these will be part of the URL in the response header ''Location'':
```sh
# curl -i 'http://127.0.0.1:23123/z?role=manual&part=zettel'
HTTP/1.1 302 Found
Content-Type: text/plain; charset=utf-8
Location: /z/20231128184200?part=zettel
Content-Length: 14
20231128184200
```
Otherwise, if no role zettel was found, the response will use the HTTP status code 404 (""Not found"").
```sh
# curl -i 'http://127.0.0.1:23123/z?role=norole'
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
Content-Length: 30
Role zettel not found: norole
```
To fulfill this service, Zettelstore will evaluate internally the query ''role:role title=ROLE'', there ''ROLE'' is the actual role.
Of course, if you are only interested in the URL of the role zettel, you can make use of the HTTP ''HEAD'' method:
```sh
# curl -I 'http://127.0.0.1:23123/z?role=manual'
HTTP/1.1 302 Found
Content-Type: text/plain; charset=utf-8
Location: /z/20231128184200
Content-Length: 14
```
=== HTTP Status codes
; ''302''
: Role zettel was found.
The HTTP header ''Location'' contains its URL, the body of the response contains its zettel identifier.
; ''404''
: No zettel for the given role was found.
|
Changes to docs/manual/00001012054200.zettel.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
-
+
-
+
|
id: 00001012054200
title: API: Update a zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20210713150005
modified: 20231116110417
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 PUT --data $'title: Updated Note\n\nUpdated content.' http://127.0.0.1:23123/z/00001012054200
# 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
7
8
9
10
11
12
13
14
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
-
+
|
id: 00001012931600
title: Encoding of Sz Inline Elements
role: manual
tags: #api #manual #reference #zettelstore
syntax: zmk
created: 20230403161845
modified: 20231113191203
modified: 20230405123222
=== ''TEXT''
:::syntax
__Text__ **=** ''(TEXT'' String '')''.
:::
Specifies the string as some text content, typically a word.
|
︙ | | |
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
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
__MarkFormat__ **=** ''(FORMAT-MARK'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''.
:::
The inline text should be treated as highlighted for the reader (but was not important fto the original author).
:::syntax
__QuoteFormat__ **=** ''(FORMAT-QUOTE'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''.
:::
The inline text should be treated as quoted text.
:::syntax
__SpanFormat__ **=** ''(FORMAT-SPAN'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''.
|
︙ | | |
Deleted docs/manual/20231128184200.zettel.
1
2
3
4
5
6
7
|
|
-
-
-
-
-
-
-
|
id: 20231128184200
title: manual
role: role
syntax: zmk
created: 20231128184200
Zettel with the role ""manual"" contain the manual of the zettelstore.
|
Changes to encoder/encoder.go.
︙ | | |
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
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, params *CreateParameter) Encoder {
func Create(enc api.EncodingEnum) Encoder {
if create, ok := registry[enc]; ok {
return create(params)
return create()
}
return nil
}
// CreateFunc produces a new encoder.
type CreateFunc func(*CreateParameter) Encoder
type CreateFunc func() Encoder
// CreateParameter contains values that are needed to create an encoder.
type CreateParameter struct {
Lang string // default language
}
var registry = map[api.EncodingEnum]CreateFunc{}
// Register the encoder for later retrieval.
func Register(enc api.EncodingEnum, create CreateFunc) {
if _, ok := registry[enc]; ok {
panic(fmt.Sprintf("Encoder %q already registered", enc))
|
︙ | | |
Changes to encoder/encoder_inline_test.go.
︙ | | |
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
|
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: "“quotes”",
encoderHTML: "<q>quotes</q>",
encoderMD: "<q>quotes</q>",
encoderSz: `(INLINE (FORMAT-QUOTE () (TEXT "quotes")))`,
encoderSHTML: `((@L (@H "“") "quotes" (@H "”")))`,
encoderSHTML: `((q "quotes"))`,
encoderText: `quotes`,
encoderZmk: useZmk,
},
},
{
descr: "Quotes formatting (german)",
zmk: `""quotes""{lang=de}`,
expect: expectMap{
encoderHTML: `<span lang="de">„quotes“</span>`,
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")) (@H "„") "quotes" (@H "“")))`,
encoderSHTML: `((span (@ (lang . "de")) (q "quotes")))`,
encoderText: `quotes`,
encoderZmk: `""quotes""{lang="de"}`,
},
},
{
descr: "Empty quotes (default)",
zmk: `""""`,
expect: expectMap{
encoderHTML: `“”`,
encoderMD: "<q></q>",
encoderSz: `(INLINE (FORMAT-QUOTE ()))`,
encoderSHTML: `((@L (@H "“" "”")))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
descr: "Empty quotes (unknown)",
zmk: `""""{lang=unknown}`,
expect: expectMap{
encoderHTML: `<span lang="unknown">""</span>`,
encoderMD: "<q></q>",
encoderSz: `(INLINE (FORMAT-QUOTE (quote (("lang" . "unknown")))))`,
encoderSHTML: `((span (@ (lang . "unknown")) (@H """ """)))`,
encoderText: ``,
encoderZmk: `""""{lang="unknown"}`,
},
},
{
descr: "Nested quotes (default)",
zmk: `""say: ::""yes, ::""or?""::""::""`,
expect: expectMap{
encoderHTML: `“say: <span>‘yes, <span>“or?”</span>’</span>”`,
encoderMD: "<q>say: <q>yes, <q>or?</q></q></q>",
encoderSz: `(INLINE (FORMAT-QUOTE () (TEXT "say:") (SPACE) (FORMAT-SPAN () (FORMAT-QUOTE () (TEXT "yes,") (SPACE) (FORMAT-SPAN () (FORMAT-QUOTE () (TEXT "or?")))))))`,
encoderSHTML: `((@L (@H "“") "say:" " " (span (@L (@H "‘") "yes," " " (span (@L (@H "“") "or?" (@H "”"))) (@H "’"))) (@H "”")))`,
encoderText: `say: yes, or?`,
encoderZmk: useZmk,
},
},
{
descr: "Two quotes",
zmk: `""yes"" or ""no""`,
expect: expectMap{
encoderHTML: `“yes” or “no”`,
encoderMD: "<q>yes</q> or <q>no</q>",
encoderSz: `(INLINE (FORMAT-QUOTE () (TEXT "yes")) (SPACE) (TEXT "or") (SPACE) (FORMAT-QUOTE () (TEXT "no")))`,
encoderSHTML: `((@L (@H "“") "yes" (@H "”")) " " "or" " " (@L (@H "“") "no" (@H "”")))`,
encoderText: `yes or no`,
encoderZmk: useZmk,
},
},
{
descr: "Mark formatting",
zmk: `##marked##`,
expect: expectMap{
encoderHTML: `<mark>marked</mark>`,
encoderMD: "<mark>marked</mark>",
encoderSz: `(INLINE (FORMAT-MARK () (TEXT "marked")))`,
encoderSHTML: `((mark "marked"))`,
encoderText: `marked`,
encoderZmk: useZmk,
},
},
{
descr: "Span formatting",
zmk: `::span::`,
expect: expectMap{
encoderHTML: `<span>span</span>`,
encoderMD: "span",
encoderSz: `(INLINE (FORMAT-SPAN () (TEXT "span")))`,
encoderSHTML: `((span "span"))`,
|
︙ | | |
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
|
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">« abc »</span>`,
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")) (@L (@H "«" " ") "abc" (@H " " "»"))))`,
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
81
82
83
84
85
86
87
88
|
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
-
+
|
checkEncodings(t, testNum, pe, tc.descr, tc.expect, tc.zmk)
checkSz(t, testNum, pe, tc.descr)
}
}
func checkEncodings(t *testing.T, testNum int, pe parserEncoder, descr string, expected expectMap, zmkDefault string) {
for enc, exp := range expected {
encdr := encoder.Create(enc, &encoder.CreateParameter{Lang: api.ValueLangEN})
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
108
109
110
111
112
113
114
115
|
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
-
+
|
t.Errorf("%s\nEncoder: %s\nExpected: %q\nGot: %q", prefix, enc, exp, got)
}
}
}
func checkSz(t *testing.T, testNum int, pe parserEncoder, descr string) {
t.Helper()
encdr := encoder.Create(encoderSz, nil)
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
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
|
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(params *encoder.CreateParameter) encoder.Encoder { return Create(params) })
encoder.Register(api.EncoderHTML, func() encoder.Encoder { return Create() })
}
// Create an encoder.
func Create(params *encoder.CreateParameter) *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.NewEvaluator(1, nil),
th: shtml.NewTransformer(1, nil),
lang: params.Lang,
textEnc: textenc.Create(),
}
}
type Encoder struct {
tx *szenc.Transformer
th *shtml.Evaluator
th *shtml.Transformer
lang string
textEnc *textenc.Encoder
}
// WriteZettel encodes a full zettel as HTML5.
func (he *Encoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) {
env := shtml.MakeEnvironment(he.lang)
hm, err := he.th.Evaluate(he.tx.GetMeta(zn.InhMeta, evalMeta), &env)
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.Evaluate(xtitle, &env)
htitle, err = he.th.Transform(xtitle)
if err != nil {
return 0, err
}
}
xast := he.tx.GetSz(&zn.Ast)
hast, err := he.th.Evaluate(xast, &env)
hast, err := he.th.Transform(xast)
if err != nil {
return 0, err
}
hen := he.th.Endnotes(&env)
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
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
|
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) {
env := shtml.MakeEnvironment(he.lang)
hm, err := he.th.Evaluate(he.tx.GetMeta(m, evalMeta), &env)
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) {
env := shtml.MakeEnvironment(he.lang)
hobj, err := he.th.Evaluate(he.tx.GetSz(bs), &env)
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(&env))
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) {
env := shtml.MakeEnvironment(he.lang)
hobj, err := he.th.Evaluate(he.tx.GetSz(is), &env)
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
24
25
26
27
28
29
30
31
|
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
-
+
|
"zettelstore.de/client.fossil/api"
"zettelstore.de/z/ast"
"zettelstore.de/z/encoder"
"zettelstore.de/z/zettel/meta"
)
func init() {
encoder.Register(api.EncoderMD, func(*encoder.CreateParameter) encoder.Encoder { return Create() })
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
334
335
336
337
338
339
340
341
342
343
344
|
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>")
case ast.FormatMark:
v.b.WriteString("<mark>")
ast.Walk(v, &fn.Inlines)
v.b.WriteString("</mark>")
default:
ast.Walk(v, &fn.Inlines)
}
}
func (v *visitor) visitLiteral(ln *ast.LiteralNode) {
switch ln.Kind {
|
︙ | | |
Changes to encoder/shtmlenc/shtmlenc.go.
︙ | | |
20
21
22
23
24
25
26
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
|
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(params *encoder.CreateParameter) encoder.Encoder { return Create(params) })
encoder.Register(api.EncoderSHTML, func() encoder.Encoder { return Create() })
}
// Create a SHTML encoder
func Create(params *encoder.CreateParameter) *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.NewEvaluator(1, nil),
tx: szenc.NewTransformer(),
th: shtml.NewTransformer(1, nil),
lang: params.Lang,
}
}
type Encoder struct {
tx *szenc.Transformer
th *shtml.Evaluator
tx *szenc.Transformer
th *shtml.Transformer
lang string
}
// WriteZettel writes the encoded zettel to the writer.
func (enc *Encoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) {
env := shtml.MakeEnvironment(enc.lang)
metaSHTML, err := enc.th.Evaluate(enc.tx.GetMeta(zn.InhMeta, evalMeta), &env)
metaSHTML, err := enc.th.Transform(enc.tx.GetMeta(zn.InhMeta, evalMeta))
if err != nil {
return 0, err
}
contentSHTML, err := enc.th.Evaluate(enc.tx.GetSz(&zn.Ast), &env)
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) {
env := shtml.MakeEnvironment(enc.lang)
metaSHTML, err := enc.th.Evaluate(enc.tx.GetMeta(m, evalMeta), &env)
metaSHTML, err := enc.th.Transform(enc.tx.GetMeta(m, evalMeta))
if err != nil {
return 0, err
}
return sx.Print(w, metaSHTML)
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) {
env := shtml.MakeEnvironment(enc.lang)
hval, err := enc.th.Evaluate(enc.tx.GetSz(bs), &env)
hval, err := enc.th.Transform(enc.tx.GetSz(bs))
if err != nil {
return 0, err
}
return sx.Print(w, hval)
return hval.Print(w)
}
// WriteInlines writes an inline slice to the writer
func (enc *Encoder) WriteInlines(w io.Writer, is *ast.InlineSlice) (int, error) {
env := shtml.MakeEnvironment(enc.lang)
hval, err := enc.th.Evaluate(enc.tx.GetSz(is), &env)
hval, err := enc.th.Transform(enc.tx.GetSz(is))
if err != nil {
return 0, err
}
return sx.Print(w, hval)
return hval.Print(w)
}
|
Changes to encoder/szenc/szenc.go.
︙ | | |
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
-
+
|
"zettelstore.de/sx.fossil"
"zettelstore.de/z/ast"
"zettelstore.de/z/encoder"
"zettelstore.de/z/zettel/meta"
)
func init() {
encoder.Register(api.EncoderSz, func(*encoder.CreateParameter) encoder.Encoder { return Create() })
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
76
77
78
79
80
81
82
83
|
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.FormatMark: t.zetSyms.SymFormatMark,
ast.FormatSpan: t.zetSyms.SymFormatSpan,
}
t.mapLiteralKindS = map[ast.LiteralKind]*sx.Symbol{
ast.LiteralZettel: t.zetSyms.SymLiteralZettel,
ast.LiteralProg: t.zetSyms.SymLiteralProg,
ast.LiteralInput: t.zetSyms.SymLiteralInput,
ast.LiteralOutput: t.zetSyms.SymLiteralOutput,
|
︙ | | |
Changes to encoder/textenc/textenc.go.
︙ | | |
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
-
+
|
"zettelstore.de/client.fossil/api"
"zettelstore.de/z/ast"
"zettelstore.de/z/encoder"
"zettelstore.de/z/zettel/meta"
)
func init() {
encoder.Register(api.EncoderText, func(*encoder.CreateParameter) encoder.Encoder { return Create() })
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
29
30
31
32
33
34
35
36
|
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
-
+
|
"zettelstore.de/z/encoder"
"zettelstore.de/z/encoder/textenc"
"zettelstore.de/z/strfun"
"zettelstore.de/z/zettel/meta"
)
func init() {
encoder.Register(api.EncoderZmk, func(*encoder.CreateParameter) encoder.Encoder { return Create() })
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
359
360
361
362
363
364
365
366
|
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
|
-
+
|
}
var sb strings.Builder
v.textEnc.WriteInlines(&sb, &bn.Description)
v.b.WriteStrings("%% Unable to display BLOB with description '", sb.String(), "' and syntax '", bn.Syntax, "'.")
}
var escapeSeqs = strfun.NewSet(
"\\", "__", "**", "~~", "^^", ",,", ">>", `""`, "::", "''", "``", "++", "==", "##",
"\\", "__", "**", "~~", "^^", ",,", ">>", `""`, "::", "''", "``", "++", "==",
)
func (v *visitor) visitText(tn *ast.TextNode) {
last := 0
for i := 0; i < len(tn.Text); i++ {
if b := tn.Text[i]; b == '\\' {
v.b.WriteString(tn.Text[last:i])
|
︙ | | |
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
|
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.FormatMark: []byte("##"),
ast.FormatSpan: []byte("::"),
}
func (v *visitor) visitFormat(fn *ast.FormatNode) {
kind, ok := mapFormatKind[fn.Kind]
if !ok {
panic(fmt.Sprintf("Unknown format kind %d", fn.Kind))
|
︙ | | |
Changes to encoding/atom/atom.go.
︙ | | |
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
-
+
|
buf.WriteString("</feed>")
return buf.Bytes()
}
func (c *Configuration) marshalMeta(buf *bytes.Buffer, m *meta.Meta) {
entryUpdated := ""
if val, found := m.Get(api.KeyPublished); found {
if published, err := time.ParseInLocation(id.TimestampLayout, val, time.Local); err == nil {
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
28
29
30
31
32
33
34
35
|
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
-
+
|
)
// LastUpdated returns the formated time of the zettel which was updated at the latest time.
func LastUpdated(ml []*meta.Meta, timeFormat string) string {
maxPublished := time.Date(1, time.January, 1, 0, 0, 0, 0, time.Local)
for _, m := range ml {
if val, found := m.Get(api.KeyPublished); found {
if published, err := time.ParseInLocation(id.TimestampLayout, val, time.Local); err == nil {
if 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
89
90
91
92
93
94
95
96
|
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
-
+
|
buf.WriteString("</channel>\n</rss>")
return buf.Bytes()
}
func (c *Configuration) marshalMeta(buf *bytes.Buffer, m *meta.Meta) {
itemPublished := ""
if val, found := m.Get(api.KeyPublished); found {
if published, err := time.ParseInLocation(id.TimestampLayout, val, time.Local); err == nil {
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
7
8
9
10
11
12
13
14
15
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
-
-
-
-
-
-
+
+
+
+
+
+
-
+
|
module zettelstore.de/z
go 1.21
require (
github.com/fsnotify/fsnotify v1.7.0
github.com/yuin/goldmark v1.6.0
golang.org/x/crypto v0.16.0
golang.org/x/term v0.15.0
golang.org/x/text v0.14.0
zettelstore.de/client.fossil v0.0.0-20231130151508-751754d40c73
zettelstore.de/sx.fossil v0.0.0-20231130150648-05ef116ba207
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.15.0 // indirect
require golang.org/x/sys v0.13.0 // indirect
|
Changes to go.sum.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
zettelstore.de/client.fossil v0.0.0-20231130151508-751754d40c73 h1:LPF8QWip3GRLBzgPdnsJnrvk1sGiMOSpqOwURiax9gg=
zettelstore.de/client.fossil v0.0.0-20231130151508-751754d40c73/go.mod h1:fN+1WxRorSbHduS0T0B4GI8o82EgFuUWBv6fwsAZF6o=
zettelstore.de/sx.fossil v0.0.0-20231130150648-05ef116ba207 h1:8ch54z0w53bps6a00NDofEqo3AJ1l7ITXyC3XyLmlY4=
zettelstore.de/sx.fossil v0.0.0-20231130150648-05ef116ba207/go.mod h1:Uw3OLM1ufOM4Xe0G51mvkTDUv2okd+HyDBMx+0ZG7ME=
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
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
|
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},
kernel.ConfigSimpleMode: {"Simple mode", cs.noFrozen(parseBool), true},
config.KeyShowBackLinks: {"Show back links", parseString, true},
config.KeyShowFolgeLinks: {"Show folge links", parseString, true},
config.KeyShowSubordinateLinks: {"Show subordinate links", parseString, true},
config.KeyShowSuccessorLinks: {"Show successor links", parseString, true},
}
cs.next = interfaceMap{
keyDefaultCopyright: "",
keyDefaultLicense: "",
keyDefaultVisibility: meta.VisibilityLogin,
keyExpertMode: false,
config.KeyFooterZettel: id.Invalid,
config.KeyHomeZettel: id.DefaultHomeZid,
kernel.ConfigInsecureHTML: config.NoHTML,
api.KeyLang: api.ValueLangEN,
keyMaxTransclusions: int64(1024),
keySiteName: "Zettelstore",
keyYAMLHeader: false,
keyZettelFileSyntax: nil,
kernel.ConfigSimpleMode: false,
keyDefaultCopyright: "",
keyDefaultLicense: "",
keyDefaultVisibility: meta.VisibilityLogin,
keyExpertMode: false,
config.KeyFooterZettel: id.Invalid,
config.KeyHomeZettel: id.DefaultHomeZid,
kernel.ConfigInsecureHTML: config.NoHTML,
api.KeyLang: api.ValueLangEN,
keyMaxTransclusions: int64(1024),
keySiteName: "Zettelstore",
keyYAMLHeader: false,
keyZettelFileSyntax: nil,
kernel.ConfigSimpleMode: false,
config.KeyShowBackLinks: "",
config.KeyShowFolgeLinks: "",
config.KeyShowSubordinateLinks: "",
config.KeyShowSuccessorLinks: "",
}
}
func (cs *configService) GetLogger() *logger.Logger { return cs.logger }
func (cs *configService) Start(*myKernel) error {
cs.logger.Info().Msg("Start Service")
data := meta.New(id.ConfigurationZid)
|
︙ | | |
Changes to kernel/impl/core.go.
︙ | | |
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
-
+
|
cs.next = interfaceMap{
kernel.CoreDebug: false,
kernel.CoreGoArch: runtime.GOARCH,
kernel.CoreGoOS: runtime.GOOS,
kernel.CoreGoVersion: runtime.Version(),
kernel.CoreHostname: "*unknown host*",
kernel.CorePort: 0,
kernel.CoreStarted: time.Now().Local().Format(id.TimestampLayout),
kernel.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
129
130
131
132
133
134
135
136
|
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
-
+
|
}
return kern
}
func (kern *myKernel) Setup(progname, version string, versionTime time.Time) {
kern.SetConfig(kernel.CoreService, kernel.CoreProgname, progname)
kern.SetConfig(kernel.CoreService, kernel.CoreVersion, version)
kern.SetConfig(kernel.CoreService, kernel.CoreVTime, versionTime.Local().Format(id.TimestampLayout))
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
42
43
44
45
46
47
48
49
|
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
-
+
|
}
}()
for {
conn, err := ln.Accept()
if err != nil {
// handle error
kern.logger.Error().Err(err).Msg("Unable to accept connection")
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
|
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
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.
// This is done for an image in most cases.
func ParseDescription(m *meta.Meta) ast.InlineSlice {
if m == nil {
return nil
}
if descr, found := m.Get(api.KeySummary); found {
in := ParseMetadata(descr)
cleaner.CleanInlineLinks(&in)
return in
}
if title, found := m.Get(api.KeyTitle); found {
return ParseSpacedText(title)
}
return ast.CreateInlineSliceFromWords("Zettel", "without", "title:", m.Zid.String())
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
72
73
74
75
76
77
78
79
|
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 '_', '*', '>', '~', '^', ',', '"', '#', ':':
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
105
106
107
108
109
110
111
112
|
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, '$', '=', '\\', '-', '&':
case input.EOS, '\n', '\r', ' ', '\t', '[', ']', '{', '}', '(', ')', '|', '%', '_', '*', '>', '~', '^', ',', '"', ':', '\'', '@', '`', runeModGrave, '$', '=', '\\', '-', '&':
return &ast.TextNode{Text: string(inp.Src[pos:inp.Pos])}
}
}
}
func (cp *zmkP) parseBackslash() ast.InlineNode {
inp := cp.inp
|
︙ | | |
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
|
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.FormatMark,
':': ast.FormatSpan,
}
func (cp *zmkP) parseFormat() (res ast.InlineNode, success bool) {
inp := cp.inp
fch := inp.Ch
kind, ok := mapRuneFormat[fch]
|
︙ | | |
Changes to parser/zettelmark/zettelmark_test.go.
︙ | | |
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
|
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{"_", "*", "~", "^", ",", "\"", "#", ":"} {
for _, ch := range []string{"_", "*", "~", "^", ",", "\"", ":"} {
checkTcs(t, replace(ch, TestCases{
{"$", "(PARA $)"},
{"$$", "(PARA $$)"},
{"$$$", "(PARA $$$)"},
{"$$$$", "(PARA {$})"},
}))
}
for _, ch := range []string{"_", "*", ">", "~", "^", ",", "\"", "#", ":"} {
for _, ch := range []string{"_", "*", ">", "~", "^", ",", "\"", ":"} {
checkTcs(t, replace(ch, TestCases{
{"$$a$$", "(PARA {$ a})"},
{"$$a$$$", "(PARA {$ a} $)"},
{"$$$a$$", "(PARA {$ $a})"},
{"$$$a$$$", "(PARA {$ $a} $)"},
{"$\\$", "(PARA $$)"},
{"$\\$$", "(PARA $$$)"},
|
︙ | | |
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
|
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.FormatMark: '#',
ast.FormatSpan: ':',
}
var mapLiteralKind = map[ast.LiteralKind]rune{
ast.LiteralZettel: '@',
ast.LiteralProg: '`',
ast.LiteralInput: '\'',
|
︙ | | |
Changes to query/select.go.
︙ | | |
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
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:
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.TypeTimestamp:
return createMatchTimestampFunc(values, addSearch)
case meta.TypeNumber:
return createMatchNumberFunc(values, addSearch)
case meta.TypeTagSet:
return createMatchTagSetFunc(values, addSearch)
case meta.TypeWord:
return createMatchWordFunc(values, addSearch)
case meta.TypeWordSet:
|
︙ | | |
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
|
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 createMatchTimestampFunc(values []expValue, addSearch addSearchFunc) matchValueFunc {
preds := valuesToTimestampPredicates(values, addSearch)
return func(value string) bool {
value = meta.ExpandTimestamp(value)
for _, pred := range preds {
if !pred(value) {
return false
}
}
return true
}
}
func createMatchNumberFunc(values []expValue, addSearch addSearchFunc) matchValueFunc {
preds := valuesToNumberPredicates(values, addSearch)
return func(value string) bool {
for _, pred := range preds {
if !pred(value) {
return false
}
}
return true
}
}
func createMatchTagSetFunc(values []expValue, addSearch addSearchFunc) matchValueFunc {
predList := valuesToWordSetPredicates(processTagSet(preprocessSet(sliceToLower(values))), addSearch)
return func(value string) bool {
tags := meta.TagsFromValue(value)
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
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
|
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 valuesToTimestampPredicates(values []expValue, addSearch addSearchFunc) []stringPredicate {
result := make([]stringPredicate, len(values))
for i, v := range values {
value := meta.ExpandTimestamp(v.value)
switch op := disambiguatedTimestampOp(v.op); op {
case cmpLess, cmpNoLess, cmpGreater, cmpNoGreater:
if isDigits(value) {
// Never add the value to search.
result[i] = createTimestampCompareFunc(value, op)
continue
}
fallthrough
default:
// Otherwise compare as a word.
if !op.isNegated() {
addSearch(v) // addSearch only for positive selections
}
result[i] = createWordCompareFunc(value, op)
}
}
return result
}
func disambiguatedTimestampOp(cmpOp compareOp) compareOp { return disambiguateWordOp(cmpOp) }
func createTimestampCompareFunc(cmpVal string, cmpOp compareOp) stringPredicate {
return createWordCompareFunc(cmpVal, cmpOp)
}
func valuesToNumberPredicates(values []expValue, addSearch addSearchFunc) []stringPredicate {
result := make([]stringPredicate, len(values))
for i, v := range values {
switch op := disambiguatedNumberOp(v.op); op {
case cmpEqual, cmpNotEqual, cmpLess, cmpNoLess, cmpGreater, cmpNoGreater:
iValue, err := strconv.ParseInt(v.value, 10, 64)
if err == nil {
|
︙ | | |
Changes to query/sorter.go.
︙ | | |
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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
|
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.TypeTimestamp {
return createSortTimestampFunc(ml, key, descending)
}
if keyType == meta.TypeNumber {
return createSortNumberFunc(ml, key, descending)
}
return createSortStringFunc(ml, key, descending)
}
func createSortTimestampFunc(ml []*meta.Meta, key string, descending bool) sortFunc {
if descending {
return func(i, j int) bool {
iVal, iOk := ml[i].Get(key)
jVal, jOk := ml[j].Get(key)
return (iOk && (!jOk || meta.ExpandTimestamp(iVal) > meta.ExpandTimestamp(jVal))) || !jOk
}
}
return func(i, j int) bool {
iVal, iOk := ml[i].Get(key)
jVal, jOk := ml[j].Get(key)
return (iOk && (!jOk || meta.ExpandTimestamp(iVal) < meta.ExpandTimestamp(jVal))) || !jOk
}
}
func createSortNumberFunc(ml []*meta.Meta, key string, descending bool) sortFunc {
if descending {
return func(i, j int) bool {
iVal, iOk := getNum(ml[i], key)
jVal, jOk := getNum(ml[j], key)
return (iOk && (!jOk || iVal > jVal)) || !jOk
}
}
return func(i, j int) bool {
iVal, iOk := getNum(ml[i], key)
jVal, jOk := getNum(ml[j], key)
return (iOk && (!jOk || iVal < jVal)) || !jOk
}
}
func getNum(m *meta.Meta, key string) (int64, bool) {
if s, ok := m.Get(key); ok {
if i, err := strconv.ParseInt(s, 10, 64); err == nil {
return i, true
}
}
return 0, false
}
func createSortStringFunc(ml []*meta.Meta, key string, descending bool) sortFunc {
if descending {
return func(i, j int) bool {
iVal, iOk := ml[i].Get(key)
jVal, jOk := ml[j].Get(key)
return (iOk && (!jOk || iVal > jVal)) || !jOk
}
}
return func(i, j int) bool {
iVal, iOk := ml[i].Get(key)
jVal, jOk := ml[j].Get(key)
return (iOk && (!jOk || iVal < jVal)) || !jOk
}
}
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
52
53
54
55
56
57
58
59
60
61
62
63
64
|
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
-
-
+
+
-
-
+
+
|
}
}
}
func TestListZettel(t *testing.T) {
const (
ownerZettel = 55
configRoleZettel = 33
ownerZettel = 50
configRoleZettel = 32
writerZettel = ownerZettel - 24
readerZettel = ownerZettel - 24
creatorZettel = 10
publicZettel = 5
creatorZettel = 8
publicZettel = 4
)
testdata := []struct {
user string
exp int
}{
{"", publicZettel},
|
︙ | | |
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
|
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 != 4 {
t.Errorf("Expected list of length 4, got %d", got)
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.ZidTemplateNewRole)
checkListZid(t, metaSeq, 2, api.ZidTemplateNewTag)
checkListZid(t, metaSeq, 1, api.ZidTemplateNewTag)
checkListZid(t, metaSeq, 2, api.ZidTemplateNewUser)
checkListZid(t, metaSeq, 3, 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
285
286
287
288
289
290
291
292
|
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:\n%v", got, metaSeq)
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
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
|
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", "role", "user", "tag", "zettel"}
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 TestRoleZettel(t *testing.T) {
t.Parallel()
c := getClient()
c.AllowRedirect(true)
c.SetAuth("owner", "owner")
ctx := context.Background()
zid, err := c.RoleZettel(ctx, "nosuchrole")
if err != nil {
t.Error("AAA", err)
} else if zid != "" {
t.Errorf("no zid expected, but got %q", zid)
}
zid, err = c.RoleZettel(ctx, "zettel")
exp := api.ZettelID("00000000060010")
if err != nil {
t.Error(err)
} else if zid != exp {
t.Errorf("role zettel for zettel should be %q, but got %q", exp, zid)
}
}
func TestVersion(t *testing.T) {
t.Parallel()
c := getClient()
ver, err := c.GetVersionInfo(context.Background())
if err != nil {
t.Error(err)
return
|
︙ | | |
Changes to tests/markdown_test.go.
︙ | | |
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
-
+
|
Section string `json:"section"`
}
func TestEncoderAvailability(t *testing.T) {
t.Parallel()
encoderMissing := false
for _, enc := range encodings {
enc := encoder.Create(enc, &encoder.CreateParameter{Lang: api.ValueLangEN})
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
-
+
-
+
|
}
func testAllEncodings(t *testing.T, tc markdownTestCase, ast *ast.BlockSlice) {
var sb strings.Builder
testID := tc.Example*100 + 1
for _, enc := range encodings {
t.Run(fmt.Sprintf("Encode %v %v", enc, testID), func(st *testing.T) {
encoder.Create(enc, &encoder.CreateParameter{Lang: api.ValueLangEN}).WriteBlocks(&sb, ast)
encoder.Create(enc).WriteBlocks(&sb, ast)
sb.Reset()
})
}
}
func testZmkEncoding(t *testing.T, tc markdownTestCase, ast *ast.BlockSlice) {
zmkEncoder := encoder.Create(api.EncoderZmk, nil)
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
133
134
135
136
137
138
139
140
141
142
143
144
|
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
-
+
|
func TestAdditionalMarkdown(t *testing.T) {
testcases := []struct {
md string
exp string
}{
{`abc<br>def`, `abc@@<br>@@{="html"}def`},
}
zmkEncoder := encoder.Create(api.EncoderZmk, nil)
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
20
21
22
23
24
25
26
27
|
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
-
|
import (
"bufio"
"io"
"os"
"path/filepath"
"testing"
"zettelstore.de/client.fossil/api"
_ "zettelstore.de/z/cmd"
"zettelstore.de/z/encoder"
"zettelstore.de/z/input"
"zettelstore.de/z/parser"
"zettelstore.de/z/zettel/meta"
)
|
︙ | | |
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
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, &encoder.CreateParameter{Lang: api.ValueLangEN})
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
124
125
126
127
128
129
130
131
|
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
-
+
|
}
return u.Path[len(root):]
}
func checkMetaFile(t *testing.T, resultName string, zn *ast.ZettelNode, enc api.EncodingEnum) {
t.Helper()
if enc := encoder.Create(enc, &encoder.CreateParameter{Lang: api.ValueLangEN}); enc != nil {
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
147
148
149
150
151
152
153
154
|
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
-
+
|
// Run executes the use case.
func (uc *CreateZettel) Run(ctx context.Context, zettel zettel.Zettel) (id.Zid, error) {
m := zettel.Meta
if m.Zid.IsValid() {
return m.Zid, nil // TODO: new error: already exists
}
m.Set(api.KeyCreated, time.Now().Local().Format(id.TimestampLayout))
m.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
24
25
26
27
28
29
30
31
|
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
-
+
|
"zettelstore.de/z/query"
"zettelstore.de/z/zettel"
"zettelstore.de/z/zettel/id"
"zettelstore.de/z/zettel/meta"
)
// TagZettel is the usecase of retrieving a "tag zettel", i.e. a zettel that
// describes a given tag. A tag zettel must have the tag's name in its title
// 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
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
|
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 }
// RoleZettel is the usecase of retrieving a "role zettel", i.e. a zettel that
// describes a given role. A role zettel must have the role's name in its title
// and must have a role=role.
// RoleZettelPort is the interface used by this use case.
type RoleZettelPort interface {
// GetZettel retrieves a specific zettel.
GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error)
}
// RoleZettel is the data for this use case.
type RoleZettel struct {
port GetZettelPort
query *Query
}
// NewRoleZettel creates a new use case.
func NewRoleZettel(port GetZettelPort, query *Query) RoleZettel {
return RoleZettel{port: port, query: query}
}
// Run executes the use case.
func (uc RoleZettel) Run(ctx context.Context, role string) (zettel.Zettel, error) {
q := query.Parse(
api.KeyTitle + api.SearchOperatorEqual + role + " " +
api.KeyRole + api.SearchOperatorHas + api.ValueRoleRole)
ml, err := uc.query.Run(ctx, q)
if err != nil {
return zettel.Zettel{}, err
}
for _, m := range ml {
z, errZ := uc.port.GetZettel(ctx, m.Zid)
if errZ == nil {
return z, nil
}
}
return zettel.Zettel{}, ErrRoleZettelNotFound{Role: role}
}
// ErrRoleZettelNotFound is returned if a role zettel was not found.
type ErrRoleZettelNotFound struct{ Role string }
func (etznf ErrRoleZettelNotFound) Error() string { return "role zettel not found: " + etznf.Role }
|
Changes to web/adapter/api/api.go.
︙ | | |
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
-
+
|
func (a *API) getToken(ident *meta.Meta) ([]byte, error) {
return a.token.GetToken(ident, a.tokenLifetime, auth.KindAPI)
}
func (a *API) reportUsecaseError(w http.ResponseWriter, err error) {
code, text := adapter.CodeMessageFromError(err)
if code == http.StatusInternalServerError {
a.log.Error().Err(err).Msg(text)
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
71
72
73
74
75
|
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)
if _, err = w.Write(result); err != nil {
a.log.Error().Err(err).Zid(newZid).Msg("Create Zettel")
_, 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
32
33
34
35
36
|
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),
))
if err != nil {
a.log.Error().Err(err).Msg("Write Version Info")
a.log.IfErr(err).Msg("Write Version Info")
}
}
}
|
Changes to web/adapter/api/get_zettel.go.
︙ | | |
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
-
+
|
return evaluate.RunMetadata(ctx, value)
}
}
if err != nil {
a.reportUsecaseError(w, err)
return
}
a.writeEncodedZettelPart(ctx, w, zn, em, enc, encStr, part)
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
107
108
109
110
111
112
113
114
115
116
|
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
}
if err = writeBuffer(w, &buf, contentType); err != nil {
a.log.Error().Err(err).Zid(zid).Msg("Write Plain data")
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
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),
})
}
if err = a.writeObject(w, zid, obj); err != nil {
a.log.Error().Err(err).Zid(zid).Msg("write sx data")
err = a.writeObject(w, zid, obj)
a.log.IfErr(err).Zid(zid).Msg("write sx data")
}
}
func (a *API) writeEncodedZettelPart(
ctx context.Context,
w http.ResponseWriter, zn *ast.ZettelNode,
evalMeta encoder.EvalMetaFunc,
enc api.EncodingEnum, encStr string, part partType,
) {
encdr := encoder.Create(
encdr := encoder.Create(enc)
enc,
&encoder.CreateParameter{
Lang: a.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang),
})
if encdr == nil {
adapter.BadRequest(w, fmt.Sprintf("Zettel %q not available in encoding %q", zn.Meta.Zid, encStr))
return
}
var err error
var buf bytes.Buffer
switch part {
|
︙ | | |
168
169
170
171
172
173
174
175
176
177
178
|
161
162
163
164
165
166
167
168
169
170
|
-
-
-
+
+
+
-
|
return
}
if buf.Len() == 0 {
w.WriteHeader(http.StatusNoContent)
return
}
if err = writeBuffer(w, &buf, content.MIMEFromEncoding(enc)); err != nil {
a.log.Error().Err(err).Zid(zn.Zid).Msg("Write Encoded Zettel")
}
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
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() {
if err := a.writeToken(w, "freeaccess", 24*366*10*time.Hour); err != nil {
a.log.Error().Err(err).Msg("Login/free")
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
}
if err := a.writeToken(w, string(token), a.tokenLifetime); err != nil {
a.log.Error().Err(err).Msg("Login")
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() {
if err := a.writeToken(w, "freeaccess", 24*366*10*time.Hour); err != nil {
a.log.Error().Err(err).Msg("Refresh/free")
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 {
if err := a.writeToken(w, string(authData.Token), totalLifetime-currentLifetime); err != nil {
a.log.Error().Err(err).Msg("Write old token")
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
}
if err = a.writeToken(w, string(token), a.tokenLifetime); err != nil {
a.log.Error().Err(err).Msg("Write renewed token")
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
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, roleZettel *usecase.RoleZettel, reIndex *usecase.ReIndex) http.HandlerFunc {
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
}
if a.handleRoleZettel(w, r, roleZettel, urlQuery) {
return
}
sq := adapter.GetQuery(urlQuery)
metaSeq, err := queryMeta.Run(ctx, sq)
if err != nil {
a.reportUsecaseError(w, err)
return
|
︙ | | |
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
|
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
}
func (a *API) handleRoleZettel(w http.ResponseWriter, r *http.Request, roleZettel *usecase.RoleZettel, vals url.Values) bool {
role := vals.Get(api.QueryKeyRole)
if role == "" {
return false
}
ctx := r.Context()
z, err := roleZettel.Run(ctx, role)
if err != nil {
a.reportUsecaseError(w, err)
return true
}
zid := z.Meta.Zid.String()
w.Header().Set(api.HeaderContentType, content.PlainText)
newURL := a.NewURLBuilder('z').SetZid(api.ZettelID(zid))
for key, slVals := range vals {
if key == api.QueryKeyRole {
continue
}
for _, val := range slVals {
newURL.AppendKVQuery(key, val)
}
}
http.Redirect(w, r, newURL.String(), http.StatusFound)
if _, err = io.WriteString(w, zid); err != nil {
a.log.Error().Err(err).Msg("redirect body")
}
return true
}
|
Changes to web/adapter/response.go.
︙ | | |
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
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 erznf usecase.ErrRoleZettelNotFound
if errors.As(err, &erznf) {
return http.StatusNotFound, "Role zettel not found: " + erznf.Role
}
var ebr ErrBadRequest
if errors.As(err, &ebr) {
return http.StatusBadRequest, ebr.Text
}
if errors.Is(err, box.ErrStopped) {
return http.StatusInternalServerError, fmt.Sprintf("Zettelstore not operational: %v", err)
}
|
︙ | | |
Changes to web/adapter/webui/favicon.go.
︙ | | |
33
34
35
36
37
38
39
40
41
42
43
44
|
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
}
if err = adapter.WriteData(w, data, ""); err != nil {
wui.log.Error().Err(err).Msg("Write favicon")
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
59
60
61
62
63
64
65
66
|
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
-
+
|
zn, err := ucParseZettel.Run(ctx, zid, q.Get(api.KeySyntax))
if err != nil {
wui.reportError(ctx, w, err)
return
}
enc := wui.getSimpleHTMLEncoder(wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang))
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
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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 (
"context"
"net/http"
"zettelstore.de/client.fossil/api"
"zettelstore.de/sx.fossil"
"zettelstore.de/z/box"
"zettelstore.de/z/config"
"zettelstore.de/z/parser"
"zettelstore.de/z/usecase"
"zettelstore.de/z/web/server"
"zettelstore.de/z/zettel/id"
"zettelstore.de/z/zettel/meta"
)
|
︙ | | |
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
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(wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang))
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
73
74
75
76
77
78
79
80
81
82
83
|
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)
wui.bindLinks(ctx, &rb, "folge", zn.InhMeta, api.KeyFolge, config.KeyShowFolgeLinks, getTextTitle)
wui.bindLinks(ctx, &rb, "subordinate", zn.InhMeta, api.KeySubordinates, config.KeyShowSubordinateLinks, getTextTitle)
wui.bindLinks(ctx, &rb, "back", zn.InhMeta, api.KeyBack, config.KeyShowBackLinks, getTextTitle)
wui.bindLinks(ctx, &rb, "successor", zn.InhMeta, api.KeySuccessors, config.KeyShowSuccessorLinks, getTextTitle)
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
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
|
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) bindLinks(ctx context.Context, rb *renderBinder, varPrefix string, m *meta.Meta, key, configKey string, getTextTitle getTextTitleFunc) {
varLinks := varPrefix + "-links"
var symOpen *sx.Symbol
switch wui.rtConfig.Get(ctx, m, configKey) {
case "false":
rb.bindString(varLinks, sx.Nil())
return
case "close":
default:
symOpen = wui.symAttrOpen
}
lstLinks := wui.zettelLinksSxn(m, key, getTextTitle)
rb.bindString(varLinks, lstLinks)
if sx.IsNil(lstLinks) {
return
}
rb.bindString(varPrefix+"-open", symOpen)
}
func (wui *WebUI) zettelLinksSxn(m *meta.Meta, key string, getTextTitle getTextTitleFunc) *sx.Pair {
values, ok := m.GetList(key)
if !ok || len(values) == 0 {
return nil
}
return wui.zidLinksSxn(values, getTextTitle)
}
|
︙ | | |
Changes to web/adapter/webui/htmlgen.go.
︙ | | |
16
17
18
19
20
21
22
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
|
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.Evaluator
th *shtml.Transformer
lang string
symAt *sx.Symbol
}
func (wui *WebUI) createGenerator(builder urlBuilder, lang string) *htmlGenerator {
th := shtml.NewEvaluator(1, wui.sf)
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
70
71
72
73
74
75
76
77
|
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(obj sx.Object) sx.Object {
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
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
|
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) {
rebind(th, sz.NameSymLinkZettel, linkZettel)
rebind(th, sz.NameSymLinkFound, linkZettel)
rebind(th, sz.NameSymLinkBased, func(obj sx.Object) sx.Object {
attr, assoc, rest := findA(obj)
if attr == nil {
return obj
}
hrefP := assoc.Assoc(symHref)
if hrefP == nil {
return obj
}
href, ok := sx.GetString(hrefP.Cdr())
if !ok {
return obj
}
u := builder.NewURLBuilder('/').SetRawLocal(href.String())
assoc = assoc.Cons(sx.Cons(symHref, sx.String(u.String())))
return rest.Cons(assoc.Cons(symAttr)).Cons(symA)
})
rebind(th, sz.NameSymLinkQuery, func(obj sx.Object) sx.Object {
attr, assoc, rest := findA(obj)
if attr == nil {
return obj
}
hrefP := assoc.Assoc(symHref)
if hrefP == nil {
return obj
}
href, ok := sx.GetString(hrefP.Cdr())
if !ok {
return obj
}
ur, err := url.Parse(href.String())
if err != nil {
return obj
}
q := ur.Query().Get(api.QueryKeyQuery)
if q == "" {
return obj
}
u := builder.NewURLBuilder('h').AppendQuery(q)
assoc = assoc.Cons(sx.Cons(symHref, sx.String(u.String())))
return rest.Cons(assoc.Cons(symAttr)).Cons(symA)
})
rebind(th, sz.NameSymLinkExternal, func(obj sx.Object) sx.Object {
attr, assoc, rest := findA(obj)
if attr == nil {
return obj
}
assoc = assoc.Cons(sx.Cons(symClass, sx.String("external"))).
Cons(sx.Cons(symTarget, sx.String("_blank"))).
Cons(sx.Cons(symRel, sx.String("noopener noreferrer")))
return rest.Cons(assoc.Cons(symAttr)).Cons(symA)
})
rebind(th, sz.NameSymEmbed, func(obj sx.Object) sx.Object {
pair, isPair := sx.GetPair(obj)
if !isPair || !symImg.IsEqual(pair.Car()) {
return obj
}
attr, isPair := sx.GetPair(pair.Tail().Car())
if !isPair || !symAttr.IsEqual(attr.Car()) {
return obj
}
symSrc := th.Make("src")
srcP := attr.Tail().Assoc(symSrc)
if srcP == nil {
return obj
}
src, isString := sx.GetString(srcP.Cdr())
if !isString {
return obj
}
zid := api.ZettelID(src)
if !zid.IsValid() {
return obj
}
u := builder.NewURLBuilder('z').SetZid(zid)
imgAttr := attr.Tail().Cons(sx.Cons(symSrc, sx.String(u.String()))).Cons(symAttr)
return pair.Tail().Tail().Cons(imgAttr).Cons(symImg)
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,
lang: lang,
symAt: symAttr,
}
}
func rebind(ev *shtml.Evaluator, name string, fn func(sx.Object) sx.Object) {
prevFn := ev.ResolveBinding(name)
ev.Rebind(name, func(args []sx.Object, env *shtml.Environment) sx.Object {
obj := prevFn(args, env)
if env.GetError() == nil {
return fn(obj)
}
return sx.Nil()
})
}
// SetUnique sets a prefix to make several HTML ids unique.
func (g *htmlGenerator) SetUnique(s string) *htmlGenerator { g.th.SetUnique(s); return g }
var mapMetaKey = map[string]string{
api.KeyCopyright: "copyright",
api.KeyLicense: "license",
}
func (g *htmlGenerator) MetaSxn(m *meta.Meta, evalMeta encoder.EvalMetaFunc) *sx.Pair {
tm := g.tx.GetMeta(m, evalMeta)
env := shtml.MakeEnvironment(g.lang)
hm, err := g.th.Evaluate(tm, &env)
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
249
250
251
252
253
254
255
256
|
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.EvaluateMeta(a)
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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
|
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.EvaluateMeta(attrs.Attributes{"name": "keywords", "content": metaTags})
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)
env := shtml.MakeEnvironment(g.lang)
sh, err := g.th.Evaluate(sx, &env)
sh, err := g.th.Transform(sx)
if err != nil {
return nil, nil, err
}
return sh, g.th.Endnotes(&env), nil
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)
env := shtml.MakeEnvironment(g.lang)
sh, err := g.th.Evaluate(sx, &env)
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
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, roleZettel *usecase.RoleZettel, reIndex *usecase.ReIndex) http.HandlerFunc {
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
}
if wui.handleRoleZettel(w, r, roleZettel, urlQuery) {
return
}
q := adapter.GetQuery(urlQuery)
q = q.SetDeterministic()
ctx := r.Context()
metaSeq, err := queryMeta.Run(ctx, q)
if err != nil {
wui.reportError(ctx, w, err)
return
|
︙ | | |
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
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(wui.rtConfig.Get(ctx, nil, api.KeyLang))
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
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) && wui.canCreate(ctx, user) {
if !sx.IsNil(sxNoTzl) {
rb.bindString("create-tag-zettel", sxNoTzl)
}
}
if rzl := q.GetMetaValues(api.KeyRole); len(rzl) > 0 {
sxRzl, sxNoRzl := wui.transformRoleZettelList(ctx, roleZettel, rzl)
if !sx.IsNil(sxRzl) {
rb.bindString("role-zettel", sxRzl)
}
if !sx.IsNil(sxNoRzl) && wui.canCreate(ctx, user) {
rb.bindString("create-role-zettel", sxNoRzl)
}
}
rb.bindString("content", content)
rb.bindString("endnotes", endnotes)
apiURL := wui.NewURLBuilder('z').AppendQuery(q.String())
seed, found := q.GetSeed()
if found {
apiURL = apiURL.AppendKVQuery(api.QueryKeySeed, strconv.Itoa(seed))
} else {
|
︙ | | |
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
|
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 {
tag = meta.NormalizeTag(tag)
if _, err := tagZettel.Run(ctx, tag); err == nil {
u := wui.NewURLBuilder('h').AppendKVQuery(api.QueryKeyTag, tag)
withZettel = wui.prependZettelLink(withZettel, tag, u)
withZettel = wui.prependTagZettel(withZettel, tag, u)
} else {
u := wui.NewURLBuilder('c').SetZid(api.ZidTemplateNewTag).AppendKVQuery(queryKeyAction, valueActionNew).AppendKVQuery(api.KeyTitle, tag)
withoutZettel = wui.prependZettelLink(withoutZettel, tag, u)
withoutZettel = wui.prependTagZettel(withoutZettel, tag, u)
}
}
return withZettel, withoutZettel
}
func (wui *WebUI) transformRoleZettelList(ctx context.Context, roleZettel *usecase.RoleZettel, roles []string) (withZettel, withoutZettel *sx.Pair) {
slices.Reverse(roles)
for _, role := range roles {
if _, err := roleZettel.Run(ctx, role); err == nil {
u := wui.NewURLBuilder('h').AppendKVQuery(api.QueryKeyRole, role)
withZettel = wui.prependZettelLink(withZettel, role, u)
} else {
u := wui.NewURLBuilder('c').SetZid(api.ZidTemplateNewRole).AppendKVQuery(queryKeyAction, valueActionNew).AppendKVQuery(api.KeyTitle, role)
withoutZettel = wui.prependZettelLink(withoutZettel, role, u)
}
}
return withZettel, withoutZettel
}
func (wui *WebUI) prependTagZettel(sxZtl *sx.Pair, tag string, u *api.URLBuilder) *sx.Pair {
func (wui *WebUI) prependZettelLink(sxZtl *sx.Pair, name string, u *api.URLBuilder) *sx.Pair {
link := sx.MakeList(
wui.symA,
sx.MakeList(
wui.symAttr,
sx.Cons(wui.symHref, sx.String(u.String())),
),
sx.String(name),
sx.String(tag),
)
if sxZtl != nil {
sxZtl = sxZtl.Cons(sx.String(", "))
}
return sxZtl.Cons(link)
}
|
︙ | | |
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
|
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.Error().Err(err).Msg("unable to write RSS data")
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.Error().Err(err).Msg("unable to write Atom data")
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
}
func (wui *WebUI) handleRoleZettel(w http.ResponseWriter, r *http.Request, roleZettel *usecase.RoleZettel, vals url.Values) bool {
role := vals.Get(api.QueryKeyRole)
if role == "" {
return false
}
ctx := r.Context()
z, err := roleZettel.Run(ctx, role)
if err != nil {
wui.reportError(ctx, w, err)
return true
}
wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(api.ZettelID(z.Meta.Zid.String())))
return true
}
|
Changes to web/adapter/webui/sxn_code.go.
︙ | | |
96
97
98
99
100
101
102
103
104
105
106
107
|
96
97
98
99
100
101
102
103
104
105
106
107
|
-
+
|
if err2 == io.EOF {
return nil
}
return err2
}
wui.log.Debug().Zid(zid).Str("form", form.Repr()).Msg("Loaded sxn code")
if _, err2 = wui.engine.Eval(form, env, nil); err2 != nil {
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
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
|
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
-
+
-
-
+
+
-
+
-
+
-
+
-
+
+
+
-
-
|
"zettelstore.de/z/web/server"
"zettelstore.de/z/zettel"
"zettelstore.de/z/zettel/id"
"zettelstore.de/z/zettel/meta"
)
func (wui *WebUI) createRenderEngine() *sxeval.Engine {
root := sxeval.MakeRootEnvironment(len(specials) + len(builtins) + 3)
root := sxeval.MakeRootEnvironment(len(syntaxes) + len(builtins) + 3)
engine := sxeval.MakeEngine(wui.sf, root)
for _, syntax := range specials {
engine.BindSpecial(syntax)
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,
TestPure: sxeval.AssertPure,
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,
TestPure: sxeval.AssertPure,
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,
TestPure: sxeval.AssertPure,
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 (
specials = []*sxeval.Special{
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.SetXS, // set!
&sxbuiltins.CondS, // cond
&sxbuiltins.IfS, // if
&sxbuiltins.BeginS, // begin
&sxbuiltins.DefMacroS, // defmacro
}
builtins = []*sxeval.Builtin{
&sxbuiltins.Identical, // ==
&sxbuiltins.NullP, // null?
&sxbuiltins.PairP, // pair?
&sxbuiltins.Car, &sxbuiltins.Cdr, // car, cdr
|
︙ | | |
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
|
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
|
-
+
|
lst = lst.Cons(sx.Cons(text, link))
}
return lst
}
func (wui *WebUI) calculateFooterSxn(ctx context.Context) *sx.Pair {
if footerZid, err := id.Parse(wui.rtConfig.Get(ctx, nil, config.KeyFooterZettel)); err == nil {
if zn, err2 := wui.evalZettel.Run(ctx, footerZid, ""); err2 == nil {
htmlEnc := wui.getSimpleHTMLEncoder(wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang)).SetUnique("footer-")
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
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
|
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
|
-
+
-
+
-
+
-
+
|
reader, err := wui.makeZettelReader(ctx, zid)
if err != nil {
return nil, err
}
objs, err := reader.ReadAll()
if err != nil {
wui.log.Error().Err(err).Zid(zid).Msg("reading sxn template")
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(objs[0], env)
t, err := wui.engine.Parse(env, objs[0])
if err != nil {
return nil, err
}
wui.setSxnCache(zid, wui.engine.Rework(t, env))
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(templateExpr, env, nil)
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
407
408
409
410
411
412
413
414
415
416
|
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)
if _, err = w.Write(sb.Bytes()); err != nil {
wui.log.Error().Err(err).Msg("Unable to write HTML via template")
_, 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
79
80
81
82
83
84
85
86
|
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
symAttrOpen *sx.Symbol
}
// webuiBox contains all box methods that are needed for WebUI operation.
//
// Note: these function must not do auth checking.
type webuiBox interface {
CanCreateZettel(context.Context) bool
|
︙ | | |
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
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),
symAttrOpen: sf.MustMake("open"),
}
wui.engine = wui.createRenderEngine()
wui.observe(box.UpdateInfo{Box: mgr, Reason: box.OnReload, Zid: id.Invalid})
mgr.RegisterObserver(wui.observe)
return wui
}
|
︙ | | |
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
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(lang string) *htmlGenerator {
func (wui *WebUI) getSimpleHTMLEncoder() *htmlGenerator { return wui.createGenerator(wui) }
return wui.createGenerator(wui, lang)
}
// GetURLPrefix returns the configured URL prefix of the web server.
func (wui *WebUI) GetURLPrefix() string { return wui.ab.GetURLPrefix() }
// NewURLBuilder creates a new URL builder object with the given key.
func (wui *WebUI) NewURLBuilder(key byte) *api.URLBuilder { return wui.ab.NewURLBuilder(key) }
|
︙ | | |
Changes to www/build.md.
︙ | | |
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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
Most of this is covered by the excellent Fossil [documentation](https://fossil-scm.org/home/doc/trunk/www/quickstart.wiki).
[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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
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
Some dependencies of Zettelstore, namely [Zettelstore client](https://zettelstore.de/client) and [sx](https://zettelstore.de/sx), are also managed by Fossil.
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
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`).
set it to the value `GOVCS=zettelstore.de:fossil` (alternatively more generous
to `GOVCS=*:all`).
Since the Go build system is coupled with Git and some special platforms, you
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`.
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`
The build tool set `GOVCS` to the right value, but you may use other `go` commands that try to download a Fossil repository.
commands that try to download a Fossil repository.
On some operating systems, namely Termux on Android, an error message might
On some operating systems, namely Termux on Android, an error message might state that an user cannot be determined (`cannot determine user`).
state that an user cannot be determined (`cannot determine user`).
In this case, Fossil is allowed to download the repository, but cannot
In this case, Fossil is allowed to download the repository, but cannot associate it with an user name.
associate it with an user name.
Set the environment variable `USER` to any user name, like:
Set the environment variable `USER` to any user name, like: `USER=nobody go run tools/build.go build`.
`USER=nobody go run tools/build.go build`.
|
Changes to www/changes.wiki.
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
|
1
2
3
4
5
6
7
8
9
10
11
|
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
<title>Change Log</title>
<a id="0_17"></a>
<h2>Changes for Version 0.17.0 (pending)</h2>
<a id="0_16"></a>
<h2>Changes for Version 0.16.1 (2023-12-28)</h2>
* Fix some Sxn definitions to allow role-based UI customizations.
(minor: webui)
<h2>Changes for Version 0.16.0 (2023-11-30)</h2>
<h2>Changes for Version 0.16.0 (pending)</h2>
* Sx function <code>define</code> is removed, as announced for version
0.15.0. Use <code>defvar</code> (to define variables) or
<code>defun</code> (to define functions) instead. In addition
<code>defunconst</code> defines a constant function, which ensures a fixed
binding of its name to its function body (performance optimization).
(breaking: webui)
* Allow to determine a role zettel for a given role.
(major: api, webui)
* Present user the option to create a (missing) role zettel (in list view).
Results in a new predefined zettel with identifier 00000000090004, which
is a template for new role zettel.
(minor: webui)
* Timestamp values can be abbrevated by omitting most of its components.
Previously, such values that are not in the format YYYYMMDDhhmmss were
ignored. Now the following formats are also allowed: YYYY, YYYYMM,
YYYYMMDD, YYYYMMDDhh, YYYYMMDDhhmm. Querying and sorting work accordingly.
Previously, only a sequences of zeroes were appended, resulting in illegal
timestamps, e.g. for YYYY or YYYYMM.
(minor)
* SHTML encoder fixed w.r.t inline quoting. Previously, an <q> tag was
used, which is inappropriate. Restored smart quotes from version 0.3, but
with new SxHTML infrastructure. This affect the html encoder and the WebUI
too. Now, an empty quote should not result in a warning by HTML linters.
(minor: api, webui)
* Add new zettelmarkup inline formatting: <tt>##Text##</tt> will mark /
highlight the given Text. It is typically used to highlight some text,
which is important for you, but not for the original author. When rendered
as HTML, the <mark> tag is used.
(minor: zettelmarkup)
* Add configuration keys to show, not to show, or show the closed list of
referencing zettel in the web user interface. You can set these
configurations system-wide, per user, or per zettel. Often it is used to
ensure a “clean” home zettel. Affects the list of incoming
/ back links, folge zettel, subordinate zettel, and successor zettel.
(minor: webui)
* Some smaller bug fixes and improvements, to the software and to the
documentation.
<a id="0_15"></a>
<h2>Changes for Version 0.15.0 (2023-10-26)</h2>
* Sx function <tt>define</tt> is now deprecated. It will be removed in
version 0.16. Use <tt>defvar</tt> or <tt>defun</tt> instead. Otherwise
the WebUI will not work in version 0.16.
(major: webui, deprecated)
|
︙ | | |
Changes to www/download.wiki.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
-
+
-
-
-
-
-
+
+
+
+
+
-
+
|
<title>Download</title>
<h1>Download of Zettelstore Software</h1>
<h2>Foreword</h2>
* Zettelstore is free/libre open source software, licensed under EUPL-1.2-or-later.
* The software is provided as-is.
* There is no guarantee that it will not damage your system.
* However, it is in use by the main developer since March 2020 without any damage.
* It may be useful for you. It is useful for me.
* Take a look at the [https://zettelstore.de/manual/|manual] to know how to start and use it.
<h2>ZIP-ped Executables</h2>
Build: <code>v0.16.1</code> (2023-12-28).
Build: <code>v0.15.0</code> (2023-10-26).
* [/uv/zettelstore-0.16.1-linux-amd64.zip|Linux] (amd64)
* [/uv/zettelstore-0.16.1-linux-arm.zip|Linux] (arm6, e.g. Raspberry Pi)
* [/uv/zettelstore-0.16.1-darwin-arm64.zip|macOS] (arm64)
* [/uv/zettelstore-0.16.1-darwin-amd64.zip|macOS] (amd64)
* [/uv/zettelstore-0.16.1-windows-amd64.zip|Windows] (amd64)
* [/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.16.1.zip|here].
[/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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
-
+
-
-
-
-
-
+
+
+
+
+
|
software, which often connects to Zettelstore via its API. Some of the
software packages may be experimental.
* [https://zettelstore.de/sx|Sx] provides an evaluator for symbolic
expressions, which is unsed for HTML templates and more.
[https://mastodon.social/tags/Zettelstore|Stay tuned] …
<hr>
<h3>Latest Release: 0.16.1 (2023-12-28)</h3>
<h3>Latest Release: 0.15.0 (2023-10-26)</h3>
* [./download.wiki|Download]
* [./changes.wiki#0_16|Change summary]
* [/timeline?p=v0.16.1&bt=v0.15.0&y=ci|Check-ins for version 0.16.1],
[/vdiff?to=v0.16.1&from=v0.15.0|content diff]
* [/timeline?df=v0.16.0&y=ci|Check-ins derived from the 0.16.0 release],
[/vdiff?from=v0.16.0&to=trunk|content diff]
* [./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
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
|
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
century, year := fullyear/100, fullyear%100
year := fullyear % 100
monthday := date % 10000
month := monthday / 100
month, day := monthday/100, monthday%100
day := monthday % 100
time := uint64(zid) % 1000000
hmtime := time / 100
hmtime, second := time/100, time%100
hour, minute := hmtime/100, hmtime%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 }
// TimestampLayout to transform a date into a Zid and into other internal dates.
const TimestampLayout = "20060102150405"
// 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(TimestampLayout)
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
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
|
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
-
+
-
+
-
+
-
+
-
+
-
+
-
+
|
addToMeta(m, api.KeyTitle, at)
addToMeta(m, api.KeyTitle, " ")
if got, ok := m.Get(api.KeyTitle); !ok || got != at {
t.Errorf("Title is not %q, but %q", at, got)
}
}
func checkTags(t *testing.T, exp []string, m *Meta) {
func checkSet(t *testing.T, exp []string, m *Meta, key string) {
t.Helper()
got, _ := m.GetList(api.KeyTags)
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)
checkTags(t, []string{}, m)
checkSet(t, []string{}, m, api.KeyTags)
addToMeta(m, api.KeyTags, "")
checkTags(t, []string{}, m)
checkSet(t, []string{}, m, api.KeyTags)
addToMeta(m, api.KeyTags, " #t1 #t2 #t3 #t4 ")
checkTags(t, []string{"#t1", "#t2", "#t3", "#t4"}, m)
checkSet(t, []string{"#t1", "#t2", "#t3", "#t4"}, m, api.KeyTags)
addToMeta(m, api.KeyTags, "#t5")
checkTags(t, []string{"#t1", "#t2", "#t3", "#t4", "#t5"}, m)
checkSet(t, []string{"#t1", "#t2", "#t3", "#t4", "#t5"}, m, api.KeyTags)
addToMeta(m, api.KeyTags, "t6")
checkTags(t, []string{"#t1", "#t2", "#t3", "#t4", "#t5"}, m)
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
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)
tagsString, found := m.Get(api.KeyTags)
tags, found := m.GetTags(api.KeyTags)
if !found {
if tc.exp != "" {
t.Errorf("%d / %q: no %s found", i, tc.src, api.KeyTags)
}
continue
}
tags := meta.TagsFromValue(tagsString)
if tc.exp == "" && len(tags) > 0 {
t.Errorf("%d / %q: expected no %s, but got %v", i, tc.src, api.KeyTags, tags)
continue
}
got := strings.Join(tags, " ")
if tc.exp != got {
t.Errorf("%d / %q: expected %q, got: %q", i, tc.src, tc.exp, got)
|
︙ | | |
Changes to zettel/meta/type.go.
︙ | | |
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
-
+
|
if slist := ListFromValue(word); len(slist) > 0 {
m.Set(key, slist[0])
}
}
// SetNow stores the current timestamp under the given key.
func (m *Meta) SetNow(key string) {
m.Set(key, time.Now().Local().Format(id.TimestampLayout))
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
|
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.TimestampLayout, ExpandTimestamp(value)); err == nil {
if t, err := time.Parse(id.ZidLayout, value); err == nil {
return t, true
}
return time.Time{}, false
}
// ExpandTimestamp makes a short-form timestamp larger.
func ExpandTimestamp(value string) string {
// GetTime returns the time value of the given key.
func (m *Meta) GetTime(key string) (time.Time, bool) {
switch l := len(value); l {
case 4: // YYYY
return value + "0101000000"
case 6: // YYYYMM
return value + "01000000"
if value, ok := m.Get(key); ok {
case 8, 10, 12: // YYYYMMDD, YYYYMMDDhh, YYYYMMDDhhmm
return value + "000000"[:14-l]
case 14: // YYYYMMDDhhmmss
return value
return TimeValue(value)
default:
if l > 14 {
return value[:14]
}
return 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
}
// TagsFromValue returns the value as a sequence of normalized tags.
func TagsFromValue(value string) []string {
tags := ListFromValue(strings.ToLower(value))
// 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 {
if len(tag) > 1 && tag[0] == '#' {
tags[i] = tag[1:]
}
tags[i] = CleanTag(tag)
}
}
return tags
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
}
// NormalizeTag adds a missing prefix "#" to the tag
func NormalizeTag(tag string) string {
if len(tag) > 0 && tag[0] == '#' {
return tag
}
return "#" + tag
}
// GetNumber retrieves the numeric value of a given key.
func (m *Meta) GetNumber(key string, def int64) int64 {
if value, ok := m.Get(key); ok {
if num, err := strconv.ParseInt(value, 10, 64); err == nil {
return num
}
}
return def
}
|
Changes to zettel/meta/type_test.go.
︙ | | |
29
30
31
32
33
34
35
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
|
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 = meta.TimeValue(val); !ok {
if _, ok = m.GetTime("key"); !ok {
t.Errorf("Unable to get time from value %q", val)
}
}
func TestTimeValue(t *testing.T) {
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)},
{"2023", true, time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC)},
{"20231", false, time.Time{}},
{"202310", true, time.Date(2023, time.October, 1, 0, 0, 0, 0, time.UTC)},
{"2023103", false, time.Time{}},
{"20231030", true, time.Date(2023, time.October, 30, 0, 0, 0, 0, time.UTC)},
{"202310301", false, time.Time{}},
{"2023103016", true, time.Date(2023, time.October, 30, 16, 0, 0, 0, time.UTC)},
{"20231030165", false, time.Time{}},
{"202310301654", true, time.Date(2023, time.October, 30, 16, 54, 0, 0, time.UTC)},
{"2023103016541", false, time.Time{}},
{"20231030165417", true, time.Date(2023, time.October, 30, 16, 54, 17, 0, time.UTC)},
{"2023103916541700", false, time.Time{}},
}
for i, tc := range testCases {
got, ok := meta.TimeValue(tc.value)
if ok != tc.valid {
t.Errorf("%d: parsing of %q should be %v, but got %v", i, tc.value, tc.valid, ok)
continue
}
|
︙ | | |
Changes to zettel/meta/values.go.
︙ | | |
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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
|
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
}
|