Zettelstore

Check-in Differences
Login

Check-in Differences

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Difference From v0.19.0 To trunk

2024-12-16
17:27
Refactor config handling in WebUI package ... (Leaf check-in: 87c8fd7048 user: stern tags: trunk)
10:03
Fix some typos in the manual and in predefined zettel ... (check-in: 96c60bbcc2 user: stern tags: trunk)
2024-12-13
14:01
Increase version to 0.20.0-dev to begin next development cycle ... (check-in: 8d481c1e92 user: stern tags: trunk)
13:26
Version 0.19.0 ... (check-in: bb751329ab user: stern tags: trunk, release, v0.19.0)
12:36
Update to latest zettelstore client ... (check-in: 5ed6d8e8e3 user: stern tags: trunk)

Changes to VERSION.

1
0.19.0
|
1
0.20.0-dev

Changes to box/constbox/constbox.go.

407
408
409
410
411
412
413
414
415
416
417
418

419
420
421
422
423
424
425
			api.KeyLang:       api.ValueLangEN,
			api.KeyCreated:    "20240703235900",
			api.KeyVisibility: api.ValueVisibilityLogin,
		},
		zettel.NewContent(nil)},
	id.DefaultHomeZid: {
		constHeader{
			api.KeyTitle:   "Home",
			api.KeyRole:    api.ValueRoleZettel,
			api.KeySyntax:  meta.SyntaxZmk,
			api.KeyLang:    api.ValueLangEN,
			api.KeyCreated: "20210210190757",

		},
		zettel.NewContent(contentHomeZettel)},
}

//go:embed license.txt
var contentLicense []byte








|
|
|
|
|
>







407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
			api.KeyLang:       api.ValueLangEN,
			api.KeyCreated:    "20240703235900",
			api.KeyVisibility: api.ValueVisibilityLogin,
		},
		zettel.NewContent(nil)},
	id.DefaultHomeZid: {
		constHeader{
			api.KeyTitle:    "Home",
			api.KeyRole:     api.ValueRoleZettel,
			api.KeySyntax:   meta.SyntaxZmk,
			api.KeyLang:     api.ValueLangEN,
			api.KeyCreated:  "20210210190757",
			api.KeyModified: "20241216105800",
		},
		zettel.NewContent(contentHomeZettel)},
}

//go:embed license.txt
var contentLicense []byte

Changes to box/constbox/home.zettel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
=== Thank you for using Zettelstore!

You will find the lastest information about Zettelstore at [[https://zettelstore.de]].
Check that website regulary for [[upgrades|https://zettelstore.de/home/doc/trunk/www/download.wiki]] to the latest version.
You should consult the [[change log|https://zettelstore.de/home/doc/trunk/www/changes.wiki]] before upgrading.
Sometimes, you have to edit some of your Zettelstore-related zettel before upgrading.
Since Zettelstore is currently in a development state, every upgrade might fix some of your problems.

If you have problems concerning Zettelstore,
do not hesitate to get in [[contact with the main developer|mailto:ds@zettelstore.de]].

=== Reporting errors
If you have encountered an error, please include the content of the following zettel in your mail (if possible):
* [[Zettelstore Version|00000000000001]]: {{00000000000001}}
* [[Zettelstore Operating System|00000000000003]]
* [[Zettelstore Startup Configuration|00000000000096]]
* [[Zettelstore Runtime Configuration|00000000000100]]



|
|
|
|

<
|







1
2
3
4
5
6
7
8

9
10
11
12
13
14
15
16
=== Thank you for using Zettelstore!

You will find the lastest information about Zettelstore at [[https://zettelstore.de]].
Check this website regularly for [[updates|https://zettelstore.de/home/doc/trunk/www/download.wiki]] to the latest version.
You should consult the [[change log|https://zettelstore.de/home/doc/trunk/www/changes.wiki]] before updating.
Sometimes, you have to edit some of your Zettelstore-related zettel before updating.
Since Zettelstore is currently in a development state, every update might fix some of your problems.


If you have problems concerning Zettelstore, do not hesitate to get in [[contact with the main developer|mailto:ds@zettelstore.de]].

=== Reporting errors
If you have encountered an error, please include the content of the following zettel in your mail (if possible):
* [[Zettelstore Version|00000000000001]]: {{00000000000001}}
* [[Zettelstore Operating System|00000000000003]]
* [[Zettelstore Startup Configuration|00000000000096]]
* [[Zettelstore Runtime Configuration|00000000000100]]

Changes to box/manager/box.go.

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
	if err := mgr.checkContinue(ctx); err != nil {
		return id.Invalid, err
	}
	mgr.mgrMx.RLock()
	defer mgr.mgrMx.RUnlock()
	if box, isWriteBox := mgr.boxes[0].(box.WriteBox); isWriteBox {
		ztl.Meta = mgr.cleanMetaProperties(ztl.Meta)
		zidO, err := box.CreateZettel(ctx, ztl)
		if err == nil {
			mgr.idxUpdateZettel(ctx, ztl)
		}
		return zidO, err
	}
	return id.Invalid, box.ErrReadOnly
}

// GetZettel retrieves a specific zettel.
func (mgr *Manager) GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) {
	mgr.mgrLog.Debug().Zid(zid).Msg("GetZettel")







|



|







61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
	if err := mgr.checkContinue(ctx); err != nil {
		return id.Invalid, err
	}
	mgr.mgrMx.RLock()
	defer mgr.mgrMx.RUnlock()
	if box, isWriteBox := mgr.boxes[0].(box.WriteBox); isWriteBox {
		ztl.Meta = mgr.cleanMetaProperties(ztl.Meta)
		zid, err := box.CreateZettel(ctx, ztl)
		if err == nil {
			mgr.idxUpdateZettel(ctx, ztl)
		}
		return zid, err
	}
	return id.Invalid, box.ErrReadOnly
}

// GetZettel retrieves a specific zettel.
func (mgr *Manager) GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) {
	mgr.mgrLog.Debug().Zid(zid).Msg("GetZettel")
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
		if err != nil {
			return nil, err
		}
	}
	return result, nil
}

// FetchZidsO returns the set of all old-style zettel identifer managed by the box.
func (mgr *Manager) FetchZidsO(ctx context.Context) (*id.Set, error) {
	mgr.mgrLog.Debug().Msg("FetchZidsO")
	return mgr.fetchZids(ctx)
}

func (mgr *Manager) hasZettel(ctx context.Context, zid id.Zid) bool {
	mgr.mgrLog.Debug().Zid(zid).Msg("HasZettel")
	if err := mgr.checkContinue(ctx); err != nil {
		return false
	}
	mgr.mgrMx.RLock()
	defer mgr.mgrMx.RUnlock()







<
<
<
<
<
<







138
139
140
141
142
143
144






145
146
147
148
149
150
151
		if err != nil {
			return nil, err
		}
	}
	return result, nil
}







func (mgr *Manager) hasZettel(ctx context.Context, zid id.Zid) bool {
	mgr.mgrLog.Debug().Zid(zid).Msg("HasZettel")
	if err := mgr.checkContinue(ctx); err != nil {
		return false
	}
	mgr.mgrMx.RLock()
	defer mgr.mgrMx.RUnlock()
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
			return true
		}
	}
	return false
}

// DeleteZettel removes the zettel from the box.
func (mgr *Manager) DeleteZettel(ctx context.Context, zidO id.Zid) error {
	mgr.mgrLog.Debug().Zid(zidO).Msg("DeleteZettel")
	if err := mgr.checkContinue(ctx); err != nil {
		return err
	}
	mgr.mgrMx.RLock()
	defer mgr.mgrMx.RUnlock()
	for _, p := range mgr.boxes {
		err := p.DeleteZettel(ctx, zidO)
		if err == nil {
			mgr.idxDeleteZettel(ctx, zidO)
			return err
		}
		var errZNF box.ErrZettelNotFound
		if !errors.As(err, &errZNF) && !errors.Is(err, box.ErrReadOnly) {
			return err
		}
	}
	return box.ErrZettelNotFound{Zid: zidO}
}

// Remove all (computed) properties from metadata before storing the zettel.
func (mgr *Manager) cleanMetaProperties(m *meta.Meta) *meta.Meta {
	result := m.Clone()
	for _, p := range result.ComputedPairsRest() {
		if mgr.propertyKeys.Has(p.Key) {
			result.Delete(p.Key)
		}
	}
	return result
}







|
|






|

|







|












272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
			return true
		}
	}
	return false
}

// DeleteZettel removes the zettel from the box.
func (mgr *Manager) DeleteZettel(ctx context.Context, zid id.Zid) error {
	mgr.mgrLog.Debug().Zid(zid).Msg("DeleteZettel")
	if err := mgr.checkContinue(ctx); err != nil {
		return err
	}
	mgr.mgrMx.RLock()
	defer mgr.mgrMx.RUnlock()
	for _, p := range mgr.boxes {
		err := p.DeleteZettel(ctx, zid)
		if err == nil {
			mgr.idxDeleteZettel(ctx, zid)
			return err
		}
		var errZNF box.ErrZettelNotFound
		if !errors.As(err, &errZNF) && !errors.Is(err, box.ErrReadOnly) {
			return err
		}
	}
	return box.ErrZettelNotFound{Zid: zid}
}

// Remove all (computed) properties from metadata before storing the zettel.
func (mgr *Manager) cleanMetaProperties(m *meta.Meta) *meta.Meta {
	result := m.Clone()
	for _, p := range result.ComputedPairsRest() {
		if mgr.propertyKeys.Has(p.Key) {
			result.Delete(p.Key)
		}
	}
	return result
}

Changes to box/manager/manager.go.

245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
	cache[zid] = destutterData{
		deadAt: now.Add(500 * time.Millisecond),
		reason: reason,
	}
	return false
}

func (mgr *Manager) idxEnqueue(reason box.UpdateReason, zidO id.Zid) {
	switch reason {
	case box.OnReady:
		return
	case box.OnReload:
		mgr.idxAr.Reset()
	case box.OnZettel:
		mgr.idxAr.EnqueueZettel(zidO)
	case box.OnDelete:
		mgr.idxAr.EnqueueZettel(zidO)
	default:
		mgr.mgrLog.Error().Uint("reason", uint64(reason)).Zid(zidO).Msg("Unknown notification reason")
		return
	}
	select {
	case mgr.idxReady <- struct{}{}:
	default:
	}
}







|






|

|

|







245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
	cache[zid] = destutterData{
		deadAt: now.Add(500 * time.Millisecond),
		reason: reason,
	}
	return false
}

func (mgr *Manager) idxEnqueue(reason box.UpdateReason, zid id.Zid) {
	switch reason {
	case box.OnReady:
		return
	case box.OnReload:
		mgr.idxAr.Reset()
	case box.OnZettel:
		mgr.idxAr.EnqueueZettel(zid)
	case box.OnDelete:
		mgr.idxAr.EnqueueZettel(zid)
	default:
		mgr.mgrLog.Error().Uint("reason", uint64(reason)).Zid(zid).Msg("Unknown notification reason")
		return
	}
	select {
	case mgr.idxReady <- struct{}{}:
	default:
	}
}

Changes to docs/manual/00001007031140.zettel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
id: 00001007031140
title: Zettelmarkup: Query Transclusion
role: manual
tags: #manual #search #zettelmarkup #zettelstore
syntax: zmk
created: 20220809132350
modified: 20240219161800

A query transclusion is specified by the following sequence, starting at the first position in a line: ''{{{query:query-expression}}}''.
The line must literally start with the sequence ''{{{query:''.
Everything after this prefix is interpreted as a [[query expression|00001007700000]].

When evaluated, the query expression is evaluated, often resulting in a list of [[links|00001007040310]] to zettel, matching the query expression.
The result replaces the query transclusion element.






|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
id: 00001007031140
title: Zettelmarkup: Query Transclusion
role: manual
tags: #manual #search #zettelmarkup #zettelstore
syntax: zmk
created: 20220809132350
modified: 20241213153229

A query transclusion is specified by the following sequence, starting at the first position in a line: ''{{{query:query-expression}}}''.
The line must literally start with the sequence ''{{{query:''.
Everything after this prefix is interpreted as a [[query expression|00001007700000]].

When evaluated, the query expression is evaluated, often resulting in a list of [[links|00001007040310]] to zettel, matching the query expression.
The result replaces the query transclusion element.
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
: The resulting list will be a numbered list.
; ''MINn'' (parameter)
: Emit only those values with at least __n__ aggregated values.
  __n__ must be a positive integer, ''MIN'' must be given in upper-case letters.
; ''MAXn'' (parameter)
: Emit only those values with at most __n__ aggregated values.
  __n__ must be a positive integer, ''MAX'' must be given in upper-case letters.
; ''TITLE'' (parameter)
: All words following ''TITLE'' are joined together to form a title.
  It is used for the ''ATOM'' and ''RSS'' action.
; ''ATOM'' (aggregate)
: Transform the zettel list into an [[Atom 1.0|https://www.rfc-editor.org/rfc/rfc4287]]-conformant document / feed.
  The document is embedded into the referencing zettel.
; ''RSS'' (aggregate)
: Transform the zettel list into a [[RSS 2.0|https://www.rssboard.org/rss-specification]]-conformant document / feed.
  The document is embedded into the referencing zettel.
; ''KEYS'' (aggregate)
: Emit a list of all metadata keys, together with the number of zettel having the key.
; ''REDIRECT'', ''REINDEX'' (aggregate)
: Will be ignored.
  These actions may have been copied from an existing [[API query call|00001012051400]] (or from a WebUI query), but are here superfluous (and possibly harmful).
; Any [[metadata key|00001006020000]] of type [[Word|00001006035500]] or of type [[TagSet|00001006034000]] (aggregates)
: Emit an aggregate of the given metadata key.







<
<
<
<
<
<
<
<
<







34
35
36
37
38
39
40









41
42
43
44
45
46
47
: The resulting list will be a numbered list.
; ''MINn'' (parameter)
: Emit only those values with at least __n__ aggregated values.
  __n__ must be a positive integer, ''MIN'' must be given in upper-case letters.
; ''MAXn'' (parameter)
: Emit only those values with at most __n__ aggregated values.
  __n__ must be a positive integer, ''MAX'' must be given in upper-case letters.









; ''KEYS'' (aggregate)
: Emit a list of all metadata keys, together with the number of zettel having the key.
; ''REDIRECT'', ''REINDEX'' (aggregate)
: Will be ignored.
  These actions may have been copied from an existing [[API query call|00001012051400]] (or from a WebUI query), but are here superfluous (and possibly harmful).
; Any [[metadata key|00001006020000]] of type [[Word|00001006035500]] or of type [[TagSet|00001006034000]] (aggregates)
: Emit an aggregate of the given metadata key.

Changes to docs/manual/00001007780000.zettel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
id: 00001007780000
title: Formal syntax of query expressions
role: manual
tags: #manual #reference #search #zettelstore
syntax: zmk
created: 20220810144539
modified: 20240219155949

```
QueryExpression   := ZettelList? QueryDirective* SearchExpression ActionExpression?
ZettelList        := (ZID (SPACE+ ZID)*).
ZID               := '0'+ ('1' .. '9'') DIGIT*
                   | ('1' .. '9') DIGIT*.
QueryDirective    := ContextDirective






|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
id: 00001007780000
title: Formal syntax of query expressions
role: manual
tags: #manual #reference #search #zettelstore
syntax: zmk
created: 20220810144539
modified: 20241213153200

```
QueryExpression   := ZettelList? QueryDirective* SearchExpression ActionExpression?
ZettelList        := (ZID (SPACE+ ZID)*).
ZID               := '0'+ ('1' .. '9'') DIGIT*
                   | ('1' .. '9') DIGIT*.
QueryDirective    := ContextDirective
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
                   | ('!')? ('~' | ':' | '[' | '}').
ExistOperator     := '?'
                   | '!' '?'.
PosInt            := '0'
                   | ('1' .. '9') DIGIT*.
ActionExpression  := '|' (Word (SPACE+ Word)*)?
Action            := Word
                   | 'ATOM'
                   | 'KEYS'
                   | 'N' NO-SPACE*
                   | 'MAX' PosInt
                   | 'MIN' PosInt
                   | 'REDIRECT'
                   | 'REINDEX'
                   | 'RSS'
                   | 'TITLE' (SPACE Word)* .
Word              := NO-SPACE NO-SPACE*
```







<





|
<
<


40
41
42
43
44
45
46

47
48
49
50
51
52


53
54
                   | ('!')? ('~' | ':' | '[' | '}').
ExistOperator     := '?'
                   | '!' '?'.
PosInt            := '0'
                   | ('1' .. '9') DIGIT*.
ActionExpression  := '|' (Word (SPACE+ Word)*)?
Action            := Word

                   | 'KEYS'
                   | 'N' NO-SPACE*
                   | 'MAX' PosInt
                   | 'MIN' PosInt
                   | 'REDIRECT'
                   | 'REINDEX'.


Word              := NO-SPACE NO-SPACE*
```

Changes to docs/manual/00001012051200.zettel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
id: 00001012051200
title: API: List all zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20210126175322
modified: 20230807170810

To list all zettel just send a HTTP GET request to the [[endpoint|00001012920000]] ''/z''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].
Always use the endpoint ''/z'' to work with a list of zettel.

Without further specifications, a plain text document is returned, with one line per zettel.
Each line contains in the first 14 characters the [[zettel identifier|00001006050000]].
Separated by a space character, the title of the zettel follows:

```sh






|

|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
id: 00001012051200
title: API: List all zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20210126175322
modified: 20241216104355

To list all zettel just send a HTTP GET request to the [[endpoint|00001012920000]] ''/z''[^If [[authentication is enabled|00001010040100]], you must include a valid [[access token|00001012050200]] in the ''Authorization'' header].
Always use the endpoint ''/z'' to work with a list of zettel.

Without further specifications, a plain text document is returned, with one line per zettel.
Each line contains in the first 14 characters the [[zettel identifier|00001006050000]].
Separated by a space character, the title of the zettel follows:

```sh

Changes to docs/manual/00001012051400.zettel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
id: 00001012051400
title: API: Query the list of all zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20220912111111
modified: 20240711161320
precursor: 00001012051200

The [[endpoint|00001012920000]] ''/z'' also allows you to filter the list of all zettel[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header] and optionally to provide some actions.

A [[query|00001007700000]] is an optional [[search expression|00001007700000#search-expression]], together with an optional [[list of actions|00001007700000#action-list]] (described below).
An empty search expression will select all zettel.
An empty list of action, or no valid action, returns the list of all selected zettel metadata.

Search expression and action list are separated by a vertical bar character (""''|''"", U+007C), and must be given with the query parameter ''q''.







|


|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
id: 00001012051400
title: API: Query the list of all zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20220912111111
modified: 20241216104329
precursor: 00001012051200

The [[endpoint|00001012920000]] ''/z'' also allows you to filter the list of all zettel[^If [[authentication is enabled|00001010040100]], you must include a valid [[access token|00001012050200]] in the ''Authorization'' header] and optionally to provide some actions.

A [[query|00001007700000]] is an optional [[search expression|00001007700000#search-expression]], together with an optional [[list of actions|00001007700000#action-list]] (described below).
An empty search expression will select all zettel.
An empty list of action, or no valid action, returns the list of all selected zettel metadata.

Search expression and action list are separated by a vertical bar character (""''|''"", U+007C), and must be given with the query parameter ''q''.

Changes to docs/manual/00001012053300.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
id: 00001012053300
title: API: Retrieve metadata and content of an existing zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20211004093206
modified: 20230807170259

The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].

For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/z/00001012053300''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].

````sh
# curl 'http://127.0.0.1:23123/z/00001012053300'
The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].

For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/z/00001012053300''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].

```sh
...
````

Optionally, you may provide which parts of the zettel you are requesting.
In this case, add an additional query parameter ''part=PART''.






|



|





|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
id: 00001012053300
title: API: Retrieve metadata and content of an existing zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20211004093206
modified: 20241216104429

The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].

For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/z/00001012053300''[^If [[authentication is enabled|00001010040100]], you must include a valid [[access token|00001012050200]] in the ''Authorization'' header].

````sh
# curl 'http://127.0.0.1:23123/z/00001012053300'
The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].

For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/z/00001012053300''[^If [[authentication is enabled|00001010040100]], you must include a valid [[access token|00001012050200]] in the ''Authorization'' header].

```sh
...
````

Optionally, you may provide which parts of the zettel you are requesting.
In this case, add an additional query parameter ''part=PART''.

Changes to docs/manual/00001012053400.zettel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
id: 00001012053400
title: API: Retrieve metadata of an existing zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20210726174524
modified: 20230807170155

The [[endpoint|00001012920000]] to work with metadata of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]][^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].

To retrieve the plain metadata of a zettel, use the query parameter ''part=meta''

````sh
# curl 'http://127.0.0.1:23123/z/00001012053400?part=meta'
title: API: Retrieve metadata of an existing zettel
role: manual






|

|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
id: 00001012053400
title: API: Retrieve metadata of an existing zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20210726174524
modified: 20241216104120

The [[endpoint|00001012920000]] to work with metadata of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]][^If [[authentication is enabled|00001010040100]], you must include a valid [[access token|00001012050200]] in the ''Authorization'' header].

To retrieve the plain metadata of a zettel, use the query parameter ''part=meta''

````sh
# curl 'http://127.0.0.1:23123/z/00001012053400?part=meta'
title: API: Retrieve metadata of an existing zettel
role: manual

Changes to docs/manual/00001012053500.zettel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
id: 00001012053500
title: API: Retrieve evaluated metadata and content of an existing zettel in various encodings
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20210726174524
modified: 20240620171057

The [[endpoint|00001012920000]] to work with evaluated metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].

For example, to retrieve some evaluated data about this zettel you are currently viewing in [[Sz encoding|00001012920516]], just send a HTTP GET request to the endpoint ''/z/00001012053500''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header] with the query parameter ''enc=sz''.
If successful, the output is a symbolic expression value:
```sh
# curl 'http://127.0.0.1:23123/z/00001012053500?enc=sz'
(BLOCK (PARA (TEXT "The ") (LINK-ZETTEL () "00001012920000" (TEXT "endpoint")) (TEXT " to work with parsed metadata and content of a specific zettel is ") (LITERAL-INPUT () "/z/{ID}") (TEXT ", where ") (LITERAL-INPUT () "{ID}") (TEXT " is a placeholder for the ") ...
```

To select another encoding, you must provide the query parameter ''enc=ENCODING''.






|



|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
id: 00001012053500
title: API: Retrieve evaluated metadata and content of an existing zettel in various encodings
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20210726174524
modified: 20241216104145

The [[endpoint|00001012920000]] to work with evaluated metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].

For example, to retrieve some evaluated data about this zettel you are currently viewing in [[Sz encoding|00001012920516]], just send a HTTP GET request to the endpoint ''/z/00001012053500''[^If [[authentication is enabled|00001010040100]], you must include a valid [[access token|00001012050200]] in the ''Authorization'' header] with the query parameter ''enc=sz''.
If successful, the output is a symbolic expression value:
```sh
# curl 'http://127.0.0.1:23123/z/00001012053500?enc=sz'
(BLOCK (PARA (TEXT "The ") (LINK-ZETTEL () "00001012920000" (TEXT "endpoint")) (TEXT " to work with parsed metadata and content of a specific zettel is ") (LITERAL-INPUT () "/z/{ID}") (TEXT ", where ") (LITERAL-INPUT () "{ID}") (TEXT " is a placeholder for the ") ...
```

To select another encoding, you must provide the query parameter ''enc=ENCODING''.

Changes to docs/manual/00001012053600.zettel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
id: 00001012053600
title: API: Retrieve parsed metadata and content of an existing zettel in various encodings
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20210126175322
modified: 20240620170909

The [[endpoint|00001012920000]] to work with parsed metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].

A __parsed__ zettel is basically an [[unevaluated|00001012053500]] zettel: the zettel is read and analyzed, but its content is not __evaluated__.
By using this endpoint, you are able to retrieve the structure of a zettel before it is evaluated.

For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/z/00001012053600''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header] with the query parameter ''parseonly'' (and other appropriate query parameter).
For example:
```sh
# curl 'http://127.0.0.1:23123/z/00001012053600?enc=sz&parseonly'
(BLOCK (PARA (TEXT "The ") (LINK-ZETTEL () "00001012920000" (TEXT "endpoint")) (TEXT " to work with parsed metadata and content of a specific zettel is ") (LITERAL-INPUT () "/z/{ID}") (TEXT ", where ") ...
```

Similar to [[retrieving an encoded zettel|00001012053500]], you can specify an [[encoding|00001012920500]] and state which [[part|00001012920800]] of a zettel you are interested in.






|






|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
id: 00001012053600
title: API: Retrieve parsed metadata and content of an existing zettel in various encodings
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20210126175322
modified: 20241216104306

The [[endpoint|00001012920000]] to work with parsed metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].

A __parsed__ zettel is basically an [[unevaluated|00001012053500]] zettel: the zettel is read and analyzed, but its content is not __evaluated__.
By using this endpoint, you are able to retrieve the structure of a zettel before it is evaluated.

For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/z/00001012053600''[^If [[authentication is enabled|00001010040100]], you must include a valid [[access token|00001012050200]] in the ''Authorization'' header] with the query parameter ''parseonly'' (and other appropriate query parameter).
For example:
```sh
# curl 'http://127.0.0.1:23123/z/00001012053600?enc=sz&parseonly'
(BLOCK (PARA (TEXT "The ") (LINK-ZETTEL () "00001012920000" (TEXT "endpoint")) (TEXT " to work with parsed metadata and content of a specific zettel is ") (LITERAL-INPUT () "/z/{ID}") (TEXT ", where ") ...
```

Similar to [[retrieving an encoded zettel|00001012053500]], you can specify an [[encoding|00001012920500]] and state which [[part|00001012920800]] of a zettel you are interested in.

Changes to docs/manual/00001012080200.zettel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
id: 00001012080200
title: API: Check for authentication
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20220103224858
modified: 20220908163156

API clients typically wants to know, whether [[authentication is enabled|00001010040100]] or not.
If authentication is enabled, they present some form of user interface to get user name and password for the actual authentication.
Then they try to [[obtain an access token|00001012050200]].
If authentication is disabled, these steps are not needed.

To check for enabled authentication, you must send a HTTP POST request to the [[endpoint|00001012920000]] ''/x'' and you must specify the query parameter ''cmd=authenticated''.

```sh






|

|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
id: 00001012080200
title: API: Check for authentication
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20220103224858
modified: 20241216104549

API clients typically want to know, whether [[authentication is enabled|00001010040100]] or not.
If authentication is enabled, they present some form of user interface to get user name and password for the actual authentication.
Then they try to [[obtain an access token|00001012050200]].
If authentication is disabled, these steps are not needed.

To check for enabled authentication, you must send a HTTP POST request to the [[endpoint|00001012920000]] ''/x'' and you must specify the query parameter ''cmd=authenticated''.

```sh

Changes to docs/manual/00001012080500.zettel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
id: 00001012080500
title: API: Refresh internal data
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20211230230441
modified: 20220923104836

Zettelstore maintains some internal data to allow faster operations.

One example is the [[content search|00001012051400]] for a term: Zettelstore does not need to scan all zettel to find all occurrences for the term.
Instead, all word are stored internally, with a list of zettel where they occur.

Another example is the way to determine which zettel are stored in a [[ZIP file|00001004011200]].
Scanning a ZIP file is a lengthy operation, therefore Zettelstore maintains a directory of zettel for each ZIP file.

All these internal data may become stale.
This should not happen, but when it comes e.g. to file handling, every operating systems behaves differently in very subtle ways.







|




|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
id: 00001012080500
title: API: Refresh internal data
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20211230230441
modified: 20241216104624

Zettelstore maintains some internal data to allow faster operations.

One example is the [[content search|00001012051400]] for a term: Zettelstore does not need to scan all zettel to find all occurrences for the term.
Instead, all words are stored internally, with a list of zettel where they occur.

Another example is the way to determine which zettel are stored in a [[ZIP file|00001004011200]].
Scanning a ZIP file is a lengthy operation, therefore Zettelstore maintains a directory of zettel for each ZIP file.

All these internal data may become stale.
This should not happen, but when it comes e.g. to file handling, every operating systems behaves differently in very subtle ways.

Changes to docs/manual/00001012931400.zettel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
id: 00001012931400
title: Encoding of Sz Block Elements
role: manual
tags: #api #manual #reference #zettelstore
syntax: zmk
created: 20230403161803
modified: 20240123120132

=== ''PARA''
:::syntax
__Paragraph__ **=** ''(PARA'' [[__InlineElement__|00001012931600]] &hellip; '')''.
:::
A paragraph is just a list of inline elements.







|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
id: 00001012931400
title: Encoding of Sz Block Elements
role: manual
tags: #api #manual #reference #zettelstore
syntax: zmk
created: 20230403161803
modified: 20241216104739

=== ''PARA''
:::syntax
__Paragraph__ **=** ''(PARA'' [[__InlineElement__|00001012931600]] &hellip; '')''.
:::
A paragraph is just a list of inline elements.

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
:::
A list element is either a block or an inline.
If it is a block, it may contain a nested list.
=== ''DESCRIPTION''
:::syntax
__Description__ **=** ''(DESCRIPTION'' __DescriptionTerm__ __DescriptionValues__ __DescriptionTerm__ __DescriptionValues__ &hellip; '')''.
:::
A description is a sequence of one ore more terms and values.

:::syntax
__DescriptionTerm__ **=** ''('' [[__InlineElement__|00001012931600]] &hellip; '')''.
:::
A description term is just an inline-structured value.

:::syntax







|







43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
:::
A list element is either a block or an inline.
If it is a block, it may contain a nested list.
=== ''DESCRIPTION''
:::syntax
__Description__ **=** ''(DESCRIPTION'' __DescriptionTerm__ __DescriptionValues__ __DescriptionTerm__ __DescriptionValues__ &hellip; '')''.
:::
A description is a sequence of one or more terms and values.

:::syntax
__DescriptionTerm__ **=** ''('' [[__InlineElement__|00001012931600]] &hellip; '')''.
:::
A description term is just an inline-structured value.

:::syntax
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
Attributes may further specify the quotation.
The inline typically describes author / source of the quotation.

:::syntax
__VerseRegion__ **=** ''(REGION-VERSE'' [[__Attributes__|00001012931000#attribute]] ''('' [[__BlockElement__|00001012931400]] &hellip; '')'' [[__InlineElement__|00001012931600]] &hellip; '')''.
:::
A block region just treats the block to contain a verse.
Soft line break are transformed into hard line breaks to save the structure of the verse / poem.
Attributes may further specify something.
The inline typically describes author / source of the verse.

=== ''VERBATIM-*''
The following lists specifies some literal text of more than one line.
The structure is always the same, the initial symbol denotes the actual usage.
The content is encoded as a string, most likely to contain control characters that signals the end of a line.







|







122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
Attributes may further specify the quotation.
The inline typically describes author / source of the quotation.

:::syntax
__VerseRegion__ **=** ''(REGION-VERSE'' [[__Attributes__|00001012931000#attribute]] ''('' [[__BlockElement__|00001012931400]] &hellip; '')'' [[__InlineElement__|00001012931600]] &hellip; '')''.
:::
A block region just treats the block to contain a verse.
Soft line breaks are transformed into hard line breaks to save the structure of the verse / poem.
Attributes may further specify something.
The inline typically describes author / source of the verse.

=== ''VERBATIM-*''
The following lists specifies some literal text of more than one line.
The structure is always the same, the initial symbol denotes the actual usage.
The content is encoded as a string, most likely to contain control characters that signals the end of a line.

Changes to docs/manual/00001012931600.zettel.

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: 20240620170546

=== ''TEXT''
:::syntax
__Text__ **=** ''(TEXT'' String '')''.
:::
Specifies the string as some text content, including white space characters.







|







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: 20241216104906

=== ''TEXT''
:::syntax
__Text__ **=** ''(TEXT'' String '')''.
:::
Specifies the string as some text content, including white space characters.

92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
=== ''EMBED-BLOB''
:::syntax
__EmbedBLOB__ **=** ''(EMBED-BLOB'' [[__Attributes__|00001012931000#attribute]] String,,1,, String,,2,, '')''.
:::
If used if some processed image has to be embedded inside some inline material.
The first string specifies the syntax of the image content.
The second string contains the image content.
If the syntax is ""SVG"", the image content is not further encoded.
Otherwise a base64 encoding is used.

=== ''CITE''
:::syntax
__CiteBLOB__ **=** ''(CITE'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] &hellip; '')''.
:::
The string contains the citation key.







|







92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
=== ''EMBED-BLOB''
:::syntax
__EmbedBLOB__ **=** ''(EMBED-BLOB'' [[__Attributes__|00001012931000#attribute]] String,,1,, String,,2,, '')''.
:::
If used if some processed image has to be embedded inside some inline material.
The first string specifies the syntax of the image content.
The second string contains the image content.
If the syntax is ""SVG"", the image content is not encoded further.
Otherwise a base64 encoding is used.

=== ''CITE''
:::syntax
__CiteBLOB__ **=** ''(CITE'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] &hellip; '')''.
:::
The string contains the citation key.
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
__InsertFormat__ **=** ''(FORMAT-INSERT'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] &hellip; '')''.
:::
The inline text should be treated as inserted.

:::syntax
__MarkFormat__ **=** ''(FORMAT-MARK'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] &hellip; '')''.
:::
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]] &hellip; '')''.
:::
The inline text should be treated as quoted text.

:::syntax







|







138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
__InsertFormat__ **=** ''(FORMAT-INSERT'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] &hellip; '')''.
:::
The inline text should be treated as inserted.

:::syntax
__MarkFormat__ **=** ''(FORMAT-MARK'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] &hellip; '')''.
:::
The inline text should be treated as highlighted for the reader (but was not important to the original author).

:::syntax
__QuoteFormat__ **=** ''(FORMAT-QUOTE'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] &hellip; '')''.
:::
The inline text should be treated as quoted text.

:::syntax

Changes to docs/manual/00001017000000.zettel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
id: 00001017000000
title: Tips and Tricks
role: manual
tags: #manual #zettelstore
syntax: zmk
created: 20220803170112
modified: 20231012154803

=== Welcome Zettel
* **Problem:** You want to put your Zettelstore into the public and need a starting zettel for your users.
  In addition, you still want a ""home zettel"", with all your references to internal, non-public zettel.
  Zettelstore only allows to specify one [[''home-zettel''|00001004020000#home-zettel]].
* **Solution 1:**
*# Create a new zettel with all your references to internal, non-public zettel.






|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
id: 00001017000000
title: Tips and Tricks
role: manual
tags: #manual #zettelstore
syntax: zmk
created: 20220803170112
modified: 20241216105111

=== Welcome Zettel
* **Problem:** You want to put your Zettelstore into the public and need a starting zettel for your users.
  In addition, you still want a ""home zettel"", with all your references to internal, non-public zettel.
  Zettelstore only allows to specify one [[''home-zettel''|00001004020000#home-zettel]].
* **Solution 1:**
*# Create a new zettel with all your references to internal, non-public zettel.
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
* **Solution 2:** If you typically start your Zettelstore on the command line, you could use the ''-d DIR'' option for the [[''run''|00001004051000#d]] sub-command.
  In this case you are allowed to use the character ""''~''"".

  ''zettelstore run -d ~/Library/Mobile\\ Documents/com\\~apple\\~CloudDocs/zettel''

  (The ""''\\''"" is needed by the command line processor to mask the following character to be processed in unintended ways.)
* **Discussion:** Zettel files are synchronized between your computers via iCloud.
  Is does not matter, if one of your computer is offline / switched off.
  iCloud will synchronize the zettel files if it later comes online.

  However, if you use more than one computer simultaneously, you must be aware that synchronization takes some time.
  It might take several seconds, maybe longer, that new new version of a zettel appears on the other computer.
  If you update the same zettel on multiple computers at nearly the same time, iCloud will not be able to synchronize the different versions in a safe manner.
  Zettelstore is intentionally not aware of any synchronization within its zettel boxes.

  If Zettelstore behaves strangely after a synchronization took place, the page about [[Troubleshooting|00001018000000#working-with-files]] might contain some useful information.







|



|




65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
* **Solution 2:** If you typically start your Zettelstore on the command line, you could use the ''-d DIR'' option for the [[''run''|00001004051000#d]] sub-command.
  In this case you are allowed to use the character ""''~''"".

  ''zettelstore run -d ~/Library/Mobile\\ Documents/com\\~apple\\~CloudDocs/zettel''

  (The ""''\\''"" is needed by the command line processor to mask the following character to be processed in unintended ways.)
* **Discussion:** Zettel files are synchronized between your computers via iCloud.
  Is does not matter, if one of your computers is offline or switched off.
  iCloud will synchronize the zettel files if it later comes online.

  However, if you use more than one computer simultaneously, you must be aware that synchronization takes some time.
  It might take several seconds, maybe longer, that the new version of a zettel appears on the other computer.
  If you update the same zettel on multiple computers at nearly the same time, iCloud will not be able to synchronize the different versions in a safe manner.
  Zettelstore is intentionally not aware of any synchronization within its zettel boxes.

  If Zettelstore behaves strangely after a synchronization took place, the page about [[Troubleshooting|00001018000000#working-with-files]] might contain some useful information.

Deleted encoding/atom/atom.go.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//-----------------------------------------------------------------------------
// Copyright (c) 2022-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: 2022-present Detlef Stern
//-----------------------------------------------------------------------------

// Package atom provides an Atom encoding.
package atom

import (
	"bytes"
	"time"

	"t73f.de/r/zsc/api"
	"zettelstore.de/z/config"
	"zettelstore.de/z/encoding"
	"zettelstore.de/z/encoding/xml"
	"zettelstore.de/z/kernel"
	"zettelstore.de/z/query"
	"zettelstore.de/z/strfun"
	"zettelstore.de/z/zettel/id"
	"zettelstore.de/z/zettel/meta"
)

// ContentType specifies the HTTP content type for Atom.
const ContentType = "application/atom+xml"

// Configuration contains data to configure the Atom encoding.
type Configuration struct {
	Title            string
	Generator        string
	NewURLBuilderAbs func() *api.URLBuilder
}

// Setup initializes the Configuration.
func (c *Configuration) Setup(cfg config.Config) {
	baseURL := kernel.Main.GetConfig(kernel.WebService, kernel.WebBaseURL).(string)

	c.Title = cfg.GetSiteName()
	c.Generator = (kernel.Main.GetConfig(kernel.CoreService, kernel.CoreProgname).(string) +
		" " +
		kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string))
	c.NewURLBuilderAbs = func() *api.URLBuilder { return api.NewURLBuilder(baseURL, 'h') }
}

// Marshal encodes the result of a query as Atom.
func (c *Configuration) Marshal(q *query.Query, ml []*meta.Meta) []byte {
	atomUpdated := encoding.LastUpdated(ml, time.RFC3339)
	feedLink := c.NewURLBuilderAbs().String()

	var buf bytes.Buffer
	buf.WriteString(`<feed xmlns="http://www.w3.org/2005/Atom">` + "\n")
	xml.WriteTag(&buf, "  ", "title", c.Title)
	xml.WriteTag(&buf, "  ", "id", feedLink)
	buf.WriteString(`  <link rel="self" href="`)
	if s := q.String(); s != "" {
		strfun.XMLEscape(&buf, c.NewURLBuilderAbs().AppendQuery(s).String())
	} else {
		strfun.XMLEscape(&buf, feedLink)
	}
	buf.WriteString(`"/>` + "\n")
	if atomUpdated != "" {
		xml.WriteTag(&buf, "  ", "updated", atomUpdated)
	}
	xml.WriteTag(&buf, "  ", "generator", c.Generator)
	buf.WriteString("  <author><name>Unknown</name></author>\n")

	for _, m := range ml {
		c.marshalMeta(&buf, m)
	}

	buf.WriteString("</feed>")
	return buf.Bytes()
}

func (c *Configuration) marshalMeta(buf *bytes.Buffer, m *meta.Meta) {
	entryUpdated := ""
	if val, found := m.Get(api.KeyPublished); found {
		if published, err := time.ParseInLocation(id.TimestampLayout, val, time.Local); err == nil {
			entryUpdated = published.UTC().Format(time.RFC3339)
		}
	}

	link := c.NewURLBuilderAbs().SetZid(m.Zid.ZettelID()).String()

	buf.WriteString("  <entry>\n")
	xml.WriteTag(buf, "    ", "title", encoding.TitleAsText(m))
	xml.WriteTag(buf, "    ", "id", link)
	buf.WriteString(`    <link rel="self" href="`)
	strfun.XMLEscape(buf, link)
	buf.WriteString(`"/>` + "\n")
	buf.WriteString(`    <link rel="alternate" type="text/html" href="`)
	strfun.XMLEscape(buf, link)
	buf.WriteString(`"/>` + "\n")

	if entryUpdated != "" {
		xml.WriteTag(buf, "    ", "updated", entryUpdated)
	}
	marshalTags(buf, m)
	buf.WriteString("  </entry>\n")
}

func marshalTags(buf *bytes.Buffer, m *meta.Meta) {
	if tags, found := m.GetList(api.KeyTags); found && len(tags) > 0 {
		for _, tag := range tags {
			for len(tag) > 0 && tag[0] == '#' {
				tag = tag[1:]
			}
			if tag != "" {
				buf.WriteString(`    <category term="`)
				strfun.XMLEscape(buf, tag)
				buf.WriteString("\"/>\n")
			}
		}
	}
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






















































































































































































































































Deleted encoding/encoding.go.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//-----------------------------------------------------------------------------
// Copyright (c) 2022-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: 2022-present Detlef Stern
//-----------------------------------------------------------------------------

// Package encoding provides helper functions for encodings.
package encoding

import (
	"time"

	"t73f.de/r/zsc/api"
	"zettelstore.de/z/parser"
	"zettelstore.de/z/zettel/id"
	"zettelstore.de/z/zettel/meta"
)

// LastUpdated returns the formated time of the zettel which was updated at the latest time.
func LastUpdated(ml []*meta.Meta, timeFormat string) string {
	maxPublished := time.Date(1, time.January, 1, 0, 0, 0, 0, time.Local)
	for _, m := range ml {
		if val, found := m.Get(api.KeyPublished); found {
			if published, err := time.ParseInLocation(id.TimestampLayout, val, time.Local); err == nil {
				if maxPublished.Before(published) {
					maxPublished = published
				}
			}
		}
	}
	if maxPublished.Year() > 1 {
		return maxPublished.UTC().Format(timeFormat)
	}
	return ""
}

// TitleAsText returns the title of a zettel as plain text
func TitleAsText(m *meta.Meta) string { return parser.NormalizedSpacedText(m.GetTitle()) }
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


























































































Deleted encoding/rss/rss.go.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//-----------------------------------------------------------------------------
// Copyright (c) 2022-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: 2022-present Detlef Stern
//-----------------------------------------------------------------------------

// Package rss provides a RSS encoding.
package rss

import (
	"bytes"
	"context"
	"time"

	"t73f.de/r/zsc/api"
	"zettelstore.de/z/config"
	"zettelstore.de/z/encoding"
	"zettelstore.de/z/encoding/xml"
	"zettelstore.de/z/kernel"
	"zettelstore.de/z/query"
	"zettelstore.de/z/strfun"
	"zettelstore.de/z/zettel/id"
	"zettelstore.de/z/zettel/meta"
)

// ContentType specifies the HTTP content type for RSS.
const ContentType = "application/rss+xml"

// Configuration contains data to configure the RSS encoding.
type Configuration struct {
	Title            string
	Language         string
	Copyright        string
	Generator        string
	NewURLBuilderAbs func() *api.URLBuilder
}

// Setup initializes the Configuration.
func (c *Configuration) Setup(ctx context.Context, cfg config.Config) {
	baseURL := kernel.Main.GetConfig(kernel.WebService, kernel.WebBaseURL).(string)
	defVals := cfg.AddDefaultValues(ctx, &meta.Meta{})

	c.Title = cfg.GetSiteName()
	c.Language = defVals.GetDefault(api.KeyLang, "")
	c.Copyright = defVals.GetDefault(api.KeyCopyright, "")
	c.Generator = (kernel.Main.GetConfig(kernel.CoreService, kernel.CoreProgname).(string) +
		" " +
		kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string))
	c.NewURLBuilderAbs = func() *api.URLBuilder { return api.NewURLBuilder(baseURL, 'h') }
}

// Marshal encodes the result of a query as Atom.
func (c *Configuration) Marshal(q *query.Query, ml []*meta.Meta) []byte {
	rssPublished := encoding.LastUpdated(ml, time.RFC1123Z)

	atomLink := ""
	if s := q.String(); s != "" {
		atomLink = c.NewURLBuilderAbs().AppendQuery(s).String()
	}
	var buf bytes.Buffer
	buf.WriteString(`<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">` + "\n<channel>\n")
	xml.WriteTag(&buf, "  ", "title", c.Title)
	xml.WriteTag(&buf, "  ", "link", c.NewURLBuilderAbs().String())
	xml.WriteTag(&buf, "  ", "description", "")
	xml.WriteTag(&buf, "  ", "language", c.Language)
	xml.WriteTag(&buf, "  ", "copyright", c.Copyright)
	if rssPublished != "" {
		xml.WriteTag(&buf, "  ", "pubDate", rssPublished)
		xml.WriteTag(&buf, "  ", "lastBuildDate", rssPublished)
	}
	xml.WriteTag(&buf, "  ", "generator", c.Generator)
	buf.WriteString("  <docs>https://www.rssboard.org/rss-specification</docs>\n")
	if atomLink != "" {
		buf.WriteString(`  <atom:link href="`)
		strfun.XMLEscape(&buf, atomLink)
		buf.WriteString(`" rel="self" type="application/rss+xml"></atom:link>` + "\n")
	}
	for _, m := range ml {
		c.marshalMeta(&buf, m)
	}

	buf.WriteString("</channel>\n</rss>")
	return buf.Bytes()
}

func (c *Configuration) marshalMeta(buf *bytes.Buffer, m *meta.Meta) {
	itemPublished := ""
	if val, found := m.Get(api.KeyPublished); found {
		if published, err := time.ParseInLocation(id.TimestampLayout, val, time.Local); err == nil {
			itemPublished = published.UTC().Format(time.RFC1123Z)
		}
	}

	link := c.NewURLBuilderAbs().SetZid(m.Zid.ZettelID()).String()

	buf.WriteString("  <item>\n")
	xml.WriteTag(buf, "    ", "title", encoding.TitleAsText(m))
	xml.WriteTag(buf, "    ", "link", link)
	xml.WriteTag(buf, "    ", "guid", link)
	if itemPublished != "" {
		xml.WriteTag(buf, "    ", "pubDate", itemPublished)
	}
	marshalTags(buf, m)
	buf.WriteString("  </item>\n")
}

func marshalTags(buf *bytes.Buffer, m *meta.Meta) {
	if tags, found := m.GetList(api.KeyTags); found && len(tags) > 0 {
		for _, tag := range tags {
			for len(tag) > 0 && tag[0] == '#' {
				tag = tag[1:]
			}
			if tag != "" {
				xml.WriteTag(buf, "    ", "category", tag)
			}
		}
	}
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


























































































































































































































































Deleted encoding/xml/xml.go.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//-----------------------------------------------------------------------------
// Copyright (c) 2022-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: 2022-present Detlef Stern
//-----------------------------------------------------------------------------

// Package xml provides helper for a XML-based encoding.
package xml

import (
	"bytes"

	"zettelstore.de/z/strfun"
)

// Header contains the string that should start all XML documents.
const Header = `<?xml version="1.0" encoding="UTF-8"?>` + "\n"

// WriteTag writes a simple XML tag with a given prefix and a specific value.
func WriteTag(buf *bytes.Buffer, prefix, tag, value string) {
	buf.WriteString(prefix)
	buf.WriteByte('<')
	buf.WriteString(tag)
	buf.WriteByte('>')
	strfun.XMLEscape(buf, value)
	buf.WriteString("</")
	buf.WriteString(tag)
	buf.WriteString(">\n")
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<








































































Changes to evaluator/evaluator.go.

292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
	ml, err := e.port.QueryMeta(e.ctx, q)
	if err != nil {
		if errors.Is(err, &box.ErrNotAllowed{}) {
			return nil
		}
		return makeBlockNode(createInlineErrorText(nil, "Unable", "to", "search", "zettel"))
	}
	result, _ := QueryAction(e.ctx, q, ml, e.rtConfig)
	if result != nil {
		ast.Walk(e, result)
	}
	return result
}

func (e *evaluator) checkMaxTransclusions(ref *ast.Reference) ast.InlineNode {







|







292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
	ml, err := e.port.QueryMeta(e.ctx, q)
	if err != nil {
		if errors.Is(err, &box.ErrNotAllowed{}) {
			return nil
		}
		return makeBlockNode(createInlineErrorText(nil, "Unable", "to", "search", "zettel"))
	}
	result, _ := QueryAction(e.ctx, q, ml)
	if result != nil {
		ast.Walk(e, result)
	}
	return result
}

func (e *evaluator) checkMaxTransclusions(ref *ast.Reference) ast.InlineNode {

Changes to evaluator/list.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
	"slices"
	"strconv"
	"strings"

	"t73f.de/r/zsc/api"
	"t73f.de/r/zsc/attrs"
	"zettelstore.de/z/ast"
	"zettelstore.de/z/config"
	"zettelstore.de/z/encoding/atom"
	"zettelstore.de/z/encoding/rss"
	"zettelstore.de/z/parser"
	"zettelstore.de/z/query"
	"zettelstore.de/z/zettel/meta"
)

// QueryAction transforms a list of metadata according to query actions into a AST nested list.
func QueryAction(ctx context.Context, q *query.Query, ml []*meta.Meta, rtConfig config.Config) (ast.BlockNode, int) {
	ap := actionPara{
		ctx:    ctx,
		q:      q,
		ml:     ml,
		kind:   ast.NestedListUnordered,
		minVal: -1,
		maxVal: -1,
		title:  rtConfig.GetSiteName(),
	}
	actions := q.Actions()
	if len(actions) == 0 {
		return ap.createBlockNodeMeta("")
	}

	acts := make([]string, 0, len(actions))
	for i, act := range actions {
		if strings.HasPrefix(act, api.NumberedAction[0:1]) {
			ap.kind = ast.NestedListOrdered
			continue
		}
		if strings.HasPrefix(act, api.MinAction) {
			if num, err := strconv.Atoi(act[3:]); err == nil && num > 0 {
				ap.minVal = num
				continue
			}
		}
		if strings.HasPrefix(act, api.MaxAction) {
			if num, err := strconv.Atoi(act[3:]); err == nil && num > 0 {
				ap.maxVal = num
				continue
			}
		}
		if act == api.TitleAction && i+1 < len(actions) {
			ap.title = strings.Join(actions[i+1:], " ")
			break
		}
		if act == api.ReIndexAction {
			continue
		}
		acts = append(acts, act)
	}
	var firstUnknowAct string
	for _, act := range acts {
		switch act {
		case api.AtomAction:
			return ap.createBlockNodeAtom(rtConfig)
		case api.RSSAction:
			return ap.createBlockNodeRSS(rtConfig)
		case api.KeysAction:
			return ap.createBlockNodeMetaKeys()
		}
		key := strings.ToLower(act)
		switch meta.Type(key) {
		case meta.TypeWord:
			return ap.createBlockNodeWord(key)







<
<
<






|







<







|
















<
<
<
<








<
<
<
<







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
	"slices"
	"strconv"
	"strings"

	"t73f.de/r/zsc/api"
	"t73f.de/r/zsc/attrs"
	"zettelstore.de/z/ast"



	"zettelstore.de/z/parser"
	"zettelstore.de/z/query"
	"zettelstore.de/z/zettel/meta"
)

// QueryAction transforms a list of metadata according to query actions into a AST nested list.
func QueryAction(ctx context.Context, q *query.Query, ml []*meta.Meta) (ast.BlockNode, int) {
	ap := actionPara{
		ctx:    ctx,
		q:      q,
		ml:     ml,
		kind:   ast.NestedListUnordered,
		minVal: -1,
		maxVal: -1,

	}
	actions := q.Actions()
	if len(actions) == 0 {
		return ap.createBlockNodeMeta("")
	}

	acts := make([]string, 0, len(actions))
	for _, act := range actions {
		if strings.HasPrefix(act, api.NumberedAction[0:1]) {
			ap.kind = ast.NestedListOrdered
			continue
		}
		if strings.HasPrefix(act, api.MinAction) {
			if num, err := strconv.Atoi(act[3:]); err == nil && num > 0 {
				ap.minVal = num
				continue
			}
		}
		if strings.HasPrefix(act, api.MaxAction) {
			if num, err := strconv.Atoi(act[3:]); err == nil && num > 0 {
				ap.maxVal = num
				continue
			}
		}




		if act == api.ReIndexAction {
			continue
		}
		acts = append(acts, act)
	}
	var firstUnknowAct string
	for _, act := range acts {
		switch act {




		case api.KeysAction:
			return ap.createBlockNodeMetaKeys()
		}
		key := strings.ToLower(act)
		switch meta.Type(key) {
		case meta.TypeWord:
			return ap.createBlockNodeWord(key)
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
type actionPara struct {
	ctx    context.Context
	q      *query.Query
	ml     []*meta.Meta
	kind   ast.NestedListKind
	minVal int
	maxVal int
	title  string
}

func (ap *actionPara) createBlockNodeWord(key string) (ast.BlockNode, int) {
	var buf bytes.Buffer
	ccs, bufLen := ap.prepareCatAction(key, &buf)
	if len(ccs) == 0 {
		return nil, 0







<







94
95
96
97
98
99
100

101
102
103
104
105
106
107
type actionPara struct {
	ctx    context.Context
	q      *query.Query
	ml     []*meta.Meta
	kind   ast.NestedListKind
	minVal int
	maxVal int

}

func (ap *actionPara) createBlockNodeWord(key string) (ast.BlockNode, int) {
	var buf bytes.Buffer
	ccs, bufLen := ap.prepareCatAction(key, &buf)
	if len(ccs) == 0 {
		return nil, 0
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
			}
		}
	}
	return result
}

func calcBudget(total, curSize float64) float64 { return math.Round(total / (fontSizes64 - curSize)) }

func (ap *actionPara) createBlockNodeRSS(cfg config.Config) (ast.BlockNode, int) {
	var rssConfig rss.Configuration
	rssConfig.Setup(ap.ctx, cfg)
	rssConfig.Title = ap.title
	data := rssConfig.Marshal(ap.q, ap.ml)

	return &ast.VerbatimNode{
		Kind:    ast.VerbatimProg,
		Attrs:   attrs.Attributes{"lang": "xml"},
		Content: data,
	}, len(ap.ml)
}

func (ap *actionPara) createBlockNodeAtom(cfg config.Config) (ast.BlockNode, int) {
	var atomConfig atom.Configuration
	atomConfig.Setup(cfg)
	atomConfig.Title = ap.title
	data := atomConfig.Marshal(ap.q, ap.ml)

	return &ast.VerbatimNode{
		Kind:    ast.VerbatimProg,
		Attrs:   attrs.Attributes{"lang": "xml"},
		Content: data,
	}, len(ap.ml)
}







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
333
334
335
336
337
338
339


























			}
		}
	}
	return result
}

func calcBudget(total, curSize float64) float64 { return math.Round(total / (fontSizes64 - curSize)) }


























Changes to go.mod.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module zettelstore.de/z

go 1.23

require (
	github.com/fsnotify/fsnotify v1.8.0
	github.com/yuin/goldmark v1.7.8
	golang.org/x/crypto v0.31.0
	golang.org/x/term v0.27.0
	golang.org/x/text v0.21.0
	t73f.de/r/sx v0.0.0-20240814083626-4df0ec6454b5
	t73f.de/r/sxwebs v0.0.0-20241031144449-53c3b2ed1a6f
	t73f.de/r/zsc v0.0.0-20241213121052-fd15ae683e82
)

require (
	golang.org/x/sys v0.28.0 // indirect
	t73f.de/r/webs v0.0.0-20241031141359-cd4f76a622cd // indirect
)












|






1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module zettelstore.de/z

go 1.23

require (
	github.com/fsnotify/fsnotify v1.8.0
	github.com/yuin/goldmark v1.7.8
	golang.org/x/crypto v0.31.0
	golang.org/x/term v0.27.0
	golang.org/x/text v0.21.0
	t73f.de/r/sx v0.0.0-20240814083626-4df0ec6454b5
	t73f.de/r/sxwebs v0.0.0-20241031144449-53c3b2ed1a6f
	t73f.de/r/zsc v0.0.0-20241213141447-7902e0d34990
)

require (
	golang.org/x/sys v0.28.0 // indirect
	t73f.de/r/webs v0.0.0-20241031141359-cd4f76a622cd // indirect
)

Changes to go.sum.

12
13
14
15
16
17
18
19
20
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
t73f.de/r/sx v0.0.0-20240814083626-4df0ec6454b5 h1:ug4hohM6pK28M8Uo0o3+XvjBure2wfEtuCnHVIdqBZY=
t73f.de/r/sx v0.0.0-20240814083626-4df0ec6454b5/go.mod h1:VRvsWoBErPKvMieDMMk1hsh1tb9sA4ijEQWGw/TbtQ0=
t73f.de/r/sxwebs v0.0.0-20241031144449-53c3b2ed1a6f h1:VJ4S7YWy9tCJuFz5MckqUjjktPaf0kpnTkNBVRVXpo4=
t73f.de/r/sxwebs v0.0.0-20241031144449-53c3b2ed1a6f/go.mod h1:IaM+U+LvYTYeuiIS5cwZW6kcEpdwoKBYVCU7LZr4Sgk=
t73f.de/r/webs v0.0.0-20241031141359-cd4f76a622cd h1:+7cqJonXKDso+uPvsvOPl7BiLkhj8VQT/Has8qC5VIQ=
t73f.de/r/webs v0.0.0-20241031141359-cd4f76a622cd/go.mod h1:NSoOON8be62MfQZzlCApK27Jt2zhIa6Vrmo9RJ4tOnQ=
t73f.de/r/zsc v0.0.0-20241213121052-fd15ae683e82 h1:Bl3iCW9bHx9CeIyWUMLi9xqRV7mHh/1HS6rBnt5QQQU=
t73f.de/r/zsc v0.0.0-20241213121052-fd15ae683e82/go.mod h1:sQWKzNp0I18aSFnKJjAyhL1zTWITIj1v2acWv4GvuIY=







|
|
12
13
14
15
16
17
18
19
20
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
t73f.de/r/sx v0.0.0-20240814083626-4df0ec6454b5 h1:ug4hohM6pK28M8Uo0o3+XvjBure2wfEtuCnHVIdqBZY=
t73f.de/r/sx v0.0.0-20240814083626-4df0ec6454b5/go.mod h1:VRvsWoBErPKvMieDMMk1hsh1tb9sA4ijEQWGw/TbtQ0=
t73f.de/r/sxwebs v0.0.0-20241031144449-53c3b2ed1a6f h1:VJ4S7YWy9tCJuFz5MckqUjjktPaf0kpnTkNBVRVXpo4=
t73f.de/r/sxwebs v0.0.0-20241031144449-53c3b2ed1a6f/go.mod h1:IaM+U+LvYTYeuiIS5cwZW6kcEpdwoKBYVCU7LZr4Sgk=
t73f.de/r/webs v0.0.0-20241031141359-cd4f76a622cd h1:+7cqJonXKDso+uPvsvOPl7BiLkhj8VQT/Has8qC5VIQ=
t73f.de/r/webs v0.0.0-20241031141359-cd4f76a622cd/go.mod h1:NSoOON8be62MfQZzlCApK27Jt2zhIa6Vrmo9RJ4tOnQ=
t73f.de/r/zsc v0.0.0-20241213141447-7902e0d34990 h1:iUDHrc4tjfq3UZT3HLrqx2HMN4fZpFCdpNqgr73vTOw=
t73f.de/r/zsc v0.0.0-20241213141447-7902e0d34990/go.mod h1:sQWKzNp0I18aSFnKJjAyhL1zTWITIj1v2acWv4GvuIY=

Changes to query/query.go.

332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
	if q == nil {
		return false
	}
	if len(q.zids) > 0 {
		return true
	}
	if len(q.actions) > 0 {
		// Unknown, what an action will use. Example: RSS needs api.KeyPublished.
		return true
	}
	for _, term := range q.terms {
		for key := range term.keys {
			if meta.IsProperty(key) {
				return true
			}







|







332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
	if q == nil {
		return false
	}
	if len(q.zids) > 0 {
		return true
	}
	if len(q.actions) > 0 {
		// Unknown, what an action may use. For examples: KEYS action uses all metadata.
		return true
	}
	for _, term := range q.terms {
		for key := range term.keys {
			if meta.IsProperty(key) {
				return true
			}

Changes to web/adapter/webui/create_zettel.go.

105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
	var sb strings.Builder
	for _, p := range m.PairsRest() {
		sb.WriteString(p.Key)
		sb.WriteString(": ")
		sb.WriteString(p.Value)
		sb.WriteByte('\n')
	}
	env, rb := wui.createRenderEnv(ctx, "form", wui.rtConfig.Get(ctx, nil, api.KeyLang), title, user)
	rb.bindString("heading", sx.MakeString(title))
	rb.bindString("form-action-url", sx.MakeString(formActionURL))
	rb.bindString("role-data", makeStringList(roleData))
	rb.bindString("syntax-data", makeStringList(syntaxData))
	rb.bindString("meta", sx.MakeString(sb.String()))
	if !ztl.Content.IsBinary() {
		rb.bindString("content", sx.MakeString(ztl.Content.AsString()))







|







105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
	var sb strings.Builder
	for _, p := range m.PairsRest() {
		sb.WriteString(p.Key)
		sb.WriteString(": ")
		sb.WriteString(p.Value)
		sb.WriteByte('\n')
	}
	env, rb := wui.createRenderEnv(ctx, "form", wui.getUserLang(ctx), title, user)
	rb.bindString("heading", sx.MakeString(title))
	rb.bindString("form-action-url", sx.MakeString(formActionURL))
	rb.bindString("role-data", makeStringList(roleData))
	rb.bindString("syntax-data", makeStringList(syntaxData))
	rb.bindString("meta", sx.MakeString(sb.String()))
	if !ztl.Content.IsBinary() {
		rb.bindString("content", sx.MakeString(ztl.Content.AsString()))
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
		q := adapter.GetQuery(r.URL.Query())
		ctx := r.Context()
		metaSeq, err := queryMeta.Run(box.NoEnrichQuery(ctx, q), q)
		if err != nil {
			wui.reportError(ctx, w, err)
			return
		}
		entries, _ := evaluator.QueryAction(ctx, q, metaSeq, wui.rtConfig)
		bns := evaluate.RunBlockNode(ctx, entries)
		enc := zmkenc.Create()
		var zmkContent bytes.Buffer
		_, err = enc.WriteBlocks(&zmkContent, &bns)
		if err != nil {
			wui.reportError(ctx, w, err)
			return







|







169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
		q := adapter.GetQuery(r.URL.Query())
		ctx := r.Context()
		metaSeq, err := queryMeta.Run(box.NoEnrichQuery(ctx, q), q)
		if err != nil {
			wui.reportError(ctx, w, err)
			return
		}
		entries, _ := evaluator.QueryAction(ctx, q, metaSeq)
		bns := evaluate.RunBlockNode(ctx, entries)
		enc := zmkenc.Create()
		var zmkContent bytes.Buffer
		_, err = enc.WriteBlocks(&zmkContent, &bns)
		if err != nil {
			wui.reportError(ctx, w, err)
			return

Changes to web/adapter/webui/delete_zettel.go.

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
			wui.reportError(ctx, w, err)
			return
		}
		m := zs[0].Meta

		user := server.GetUser(ctx)
		env, rb := wui.createRenderEnv(
			ctx, "delete",
			wui.rtConfig.Get(ctx, nil, api.KeyLang), "Delete Zettel "+m.Zid.String(), user)
		if len(zs) > 1 {
			rb.bindString("shadowed-box", sx.MakeString(zs[1].Meta.GetDefault(api.KeyBoxNumber, "???")))
			rb.bindString("incoming", nil)
		} else {
			rb.bindString("shadowed-box", nil)
			rb.bindString("incoming", wui.encodeIncoming(m, wui.makeGetTextTitle(ctx, getZettel)))
		}







<
|







47
48
49
50
51
52
53

54
55
56
57
58
59
60
61
			wui.reportError(ctx, w, err)
			return
		}
		m := zs[0].Meta

		user := server.GetUser(ctx)
		env, rb := wui.createRenderEnv(

			ctx, "delete", wui.getUserLang(ctx), "Delete Zettel "+m.Zid.String(), user)
		if len(zs) > 1 {
			rb.bindString("shadowed-box", sx.MakeString(zs[1].Meta.GetDefault(api.KeyBoxNumber, "???")))
			rb.bindString("incoming", nil)
		} else {
			rb.bindString("shadowed-box", nil)
			rb.bindString("incoming", wui.encodeIncoming(m, wui.makeGetTextTitle(ctx, getZettel)))
		}

Changes to web/adapter/webui/get_info.go.

55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

		zn, err := ucParseZettel.Run(ctx, zid, q.Get(api.KeySyntax))
		if err != nil {
			wui.reportError(ctx, w, err)
			return
		}

		enc := wui.getSimpleHTMLEncoder(wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang))
		getTextTitle := wui.makeGetTextTitle(ctx, ucGetZettel)
		evalMeta := func(val string) ast.InlineSlice {
			return ucEvaluate.RunMetadata(ctx, val)
		}
		pairs := zn.Meta.ComputedPairs()
		metadata := sx.Nil()
		for i := len(pairs) - 1; i >= 0; i-- {







|







55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

		zn, err := ucParseZettel.Run(ctx, zid, q.Get(api.KeySyntax))
		if err != nil {
			wui.reportError(ctx, w, err)
			return
		}

		enc := wui.getSimpleHTMLEncoder(wui.getConfig(ctx, zn.InhMeta, api.KeyLang))
		getTextTitle := wui.makeGetTextTitle(ctx, ucGetZettel)
		evalMeta := func(val string) ast.InlineSlice {
			return ucEvaluate.RunMetadata(ctx, val)
		}
		pairs := zn.Meta.ComputedPairs()
		metadata := sx.Nil()
		for i := len(pairs) - 1; i >= 0; i-- {
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
		}
		unlinkedMeta, err := ucQuery.Run(ctx, createUnlinkedQuery(zid, phrase))
		if err != nil {
			wui.reportError(ctx, w, err)
			return
		}

		entries, _ := evaluator.QueryAction(ctx, nil, unlinkedMeta, wui.rtConfig)
		bns := ucEvaluate.RunBlockNode(ctx, entries)
		unlinkedContent, _, err := enc.BlocksSxn(&bns)
		if err != nil {
			wui.reportError(ctx, w, err)
			return
		}
		encTexts := encodingTexts()
		shadowLinks := getShadowLinks(ctx, zid, ucGetAllZettel)

		user := server.GetUser(ctx)
		env, rb := wui.createRenderEnv(ctx, "info", wui.rtConfig.Get(ctx, nil, api.KeyLang), title, user)
		rb.bindString("metadata", metadata)
		rb.bindString("local-links", locLinks)
		rb.bindString("query-links", queryLinks)
		rb.bindString("ext-links", extLinks)
		rb.bindString("unlinked-content", unlinkedContent)
		rb.bindString("phrase", sx.MakeString(phrase))
		rb.bindString("query-key-phrase", sx.MakeString(api.QueryKeyPhrase))







|










|







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
		}
		unlinkedMeta, err := ucQuery.Run(ctx, createUnlinkedQuery(zid, phrase))
		if err != nil {
			wui.reportError(ctx, w, err)
			return
		}

		entries, _ := evaluator.QueryAction(ctx, nil, unlinkedMeta)
		bns := ucEvaluate.RunBlockNode(ctx, entries)
		unlinkedContent, _, err := enc.BlocksSxn(&bns)
		if err != nil {
			wui.reportError(ctx, w, err)
			return
		}
		encTexts := encodingTexts()
		shadowLinks := getShadowLinks(ctx, zid, ucGetAllZettel)

		user := server.GetUser(ctx)
		env, rb := wui.createRenderEnv(ctx, "info", wui.getUserLang(ctx), title, user)
		rb.bindString("metadata", metadata)
		rb.bindString("local-links", locLinks)
		rb.bindString("query-links", queryLinks)
		rb.bindString("ext-links", extLinks)
		rb.bindString("unlinked-content", unlinkedContent)
		rb.bindString("phrase", sx.MakeString(phrase))
		rb.bindString("query-key-phrase", sx.MakeString(api.QueryKeyPhrase))

Changes to web/adapter/webui/get_zettel.go.

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
		q := r.URL.Query()
		zn, err := evaluate.Run(ctx, zid, q.Get(api.KeySyntax))
		if err != nil {
			wui.reportError(ctx, w, err)
			return
		}


		enc := wui.getSimpleHTMLEncoder(wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang))
		metaObj := enc.MetaSxn(zn.InhMeta, createEvalMetadataFunc(ctx, evaluate))
		content, endnotes, err := enc.BlocksSxn(&zn.Ast)
		if err != nil {
			wui.reportError(ctx, w, err)
			return
		}

		user := server.GetUser(ctx)
		getTextTitle := wui.makeGetTextTitle(ctx, getZettel)

		title := parser.NormalizedSpacedText(zn.InhMeta.GetTitle())
		env, rb := wui.createRenderEnv(ctx, "zettel", wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang), title, user)
		rb.bindSymbol(symMetaHeader, metaObj)
		rb.bindString("heading", sx.MakeString(title))
		if role, found := zn.InhMeta.Get(api.KeyRole); found && role != "" {
			rb.bindString("role-url", sx.MakeString(wui.NewURLBuilder('h').AppendQuery(api.KeyRole+api.SearchOperatorHas+role).String()))
		}
		if folgeRole, found := zn.InhMeta.Get(api.KeyFolgeRole); found && folgeRole != "" {
			rb.bindString("folge-role-url", sx.MakeString(wui.NewURLBuilder('h').AppendQuery(api.KeyRole+api.SearchOperatorHas+folgeRole).String()))







>
|











|







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
		q := r.URL.Query()
		zn, err := evaluate.Run(ctx, zid, q.Get(api.KeySyntax))
		if err != nil {
			wui.reportError(ctx, w, err)
			return
		}

		zettelLang := wui.getConfig(ctx, zn.InhMeta, api.KeyLang)
		enc := wui.getSimpleHTMLEncoder(zettelLang)
		metaObj := enc.MetaSxn(zn.InhMeta, createEvalMetadataFunc(ctx, evaluate))
		content, endnotes, err := enc.BlocksSxn(&zn.Ast)
		if err != nil {
			wui.reportError(ctx, w, err)
			return
		}

		user := server.GetUser(ctx)
		getTextTitle := wui.makeGetTextTitle(ctx, getZettel)

		title := parser.NormalizedSpacedText(zn.InhMeta.GetTitle())
		env, rb := wui.createRenderEnv(ctx, "zettel", zettelLang, title, user)
		rb.bindSymbol(symMetaHeader, metaObj)
		rb.bindString("heading", sx.MakeString(title))
		if role, found := zn.InhMeta.Get(api.KeyRole); found && role != "" {
			rb.bindString("role-url", sx.MakeString(wui.NewURLBuilder('h').AppendQuery(api.KeyRole+api.SearchOperatorHas+role).String()))
		}
		if folgeRole, found := zn.InhMeta.Get(api.KeyFolgeRole); found && folgeRole != "" {
			rb.bindString("folge-role-url", sx.MakeString(wui.NewURLBuilder('h').AppendQuery(api.KeyRole+api.SearchOperatorHas+folgeRole).String()))
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
	}
	return result.List()
}

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 = shtml.SymAttrOpen
	}







|







119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
	}
	return result.List()
}

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.getConfig(ctx, m, configKey) {
	case "false":
		rb.bindString(varLinks, sx.Nil())
		return
	case "close":
	default:
		symOpen = shtml.SymAttrOpen
	}

Changes to web/adapter/webui/home.go.

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
func (wui *WebUI) MakeGetRootHandler(s getRootPort) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()
		if p := r.URL.Path; p != "/" {
			wui.reportError(ctx, w, adapter.ErrResourceNotFound{Path: p})
			return
		}
		homeZid, _ := id.Parse(wui.rtConfig.Get(ctx, nil, config.KeyHomeZettel))
		apiHomeZid := homeZid.ZettelID()
		if homeZid != id.DefaultHomeZid {
			if _, err := s.GetZettel(ctx, homeZid); err == nil {
				wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(apiHomeZid))
				return
			}
			homeZid = id.DefaultHomeZid







|







34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
func (wui *WebUI) MakeGetRootHandler(s getRootPort) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()
		if p := r.URL.Path; p != "/" {
			wui.reportError(ctx, w, adapter.ErrResourceNotFound{Path: p})
			return
		}
		homeZid, _ := id.Parse(wui.getConfig(ctx, nil, config.KeyHomeZettel))
		apiHomeZid := homeZid.ZettelID()
		if homeZid != id.DefaultHomeZid {
			if _, err := s.GetZettel(ctx, homeZid); err == nil {
				wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(apiHomeZid))
				return
			}
			homeZid = id.DefaultHomeZid

Changes to web/adapter/webui/lists.go.

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// SPDX-FileCopyrightText: 2020-present Detlef Stern
//-----------------------------------------------------------------------------

package webui

import (
	"context"
	"io"
	"net/http"
	"net/url"
	"slices"
	"strconv"
	"strings"

	"t73f.de/r/sx"
	"t73f.de/r/sxwebs/sxhtml"
	"t73f.de/r/zsc/api"
	"t73f.de/r/zsc/shtml"
	"zettelstore.de/z/ast"
	"zettelstore.de/z/encoding/atom"
	"zettelstore.de/z/encoding/rss"
	"zettelstore.de/z/encoding/xml"
	"zettelstore.de/z/evaluator"
	"zettelstore.de/z/query"
	"zettelstore.de/z/usecase"
	"zettelstore.de/z/web/adapter"
	"zettelstore.de/z/web/server"
	"zettelstore.de/z/zettel/id"
	"zettelstore.de/z/zettel/meta"
)








<











<
<
<

<







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
// SPDX-FileCopyrightText: 2020-present Detlef Stern
//-----------------------------------------------------------------------------

package webui

import (
	"context"

	"net/http"
	"net/url"
	"slices"
	"strconv"
	"strings"

	"t73f.de/r/sx"
	"t73f.de/r/sxwebs/sxhtml"
	"t73f.de/r/zsc/api"
	"t73f.de/r/zsc/shtml"
	"zettelstore.de/z/ast"



	"zettelstore.de/z/evaluator"

	"zettelstore.de/z/usecase"
	"zettelstore.de/z/web/adapter"
	"zettelstore.de/z/web/server"
	"zettelstore.de/z/zettel/id"
	"zettelstore.de/z/zettel/meta"
)

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
			return
		}
		actions, err := adapter.TryReIndex(ctx, q.Actions(), metaSeq, reIndex)
		if err != nil {
			wui.reportError(ctx, w, err)
			return
		}
		if len(actions) > 0 {
			if len(metaSeq) > 0 {
				for _, act := range actions {
					if act == api.RedirectAction {
						ub := wui.NewURLBuilder('h').SetZid(metaSeq[0].Zid.ZettelID())
						wui.redirectFound(w, r, ub)
						return
					}
				}
			}
			switch actions[0] {
			case api.AtomAction:
				wui.renderAtom(w, q, metaSeq)
				return
			case api.RSSAction:
				wui.renderRSS(ctx, w, q, metaSeq)
				return
			}

		}

		var content, endnotes *sx.Pair
		numEntries := 0
		if bn, cnt := evaluator.QueryAction(ctx, q, metaSeq, wui.rtConfig); bn != nil {
			enc := wui.getSimpleHTMLEncoder(wui.rtConfig.Get(ctx, nil, api.KeyLang))
			content, endnotes, err = enc.BlocksSxn(&ast.BlockSlice{bn})
			if err != nil {
				wui.reportError(ctx, w, err)
				return
			}
			numEntries = cnt
		}


		user := server.GetUser(ctx)
		env, rb := wui.createRenderEnv(
			ctx, "list",
			wui.rtConfig.Get(ctx, nil, api.KeyLang),
			wui.rtConfig.GetSiteName(), user)
		if q == nil {
			rb.bindString("heading", sx.MakeString(wui.rtConfig.GetSiteName()))
		} else {
			var sb strings.Builder
			q.PrintHuman(&sb)
			rb.bindString("heading", sx.MakeString(sb.String()))
		}
		rb.bindString("query-value", sx.MakeString(q.String()))
		if tzl := q.GetMetaValues(api.KeyTags, false); len(tzl) > 0 {







|
<
|
|
|
|
|
|
|
|
<
<
<
<
<
<
<
|
>
|
<


|
|








>

|
<
<
<

|







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
			return
		}
		actions, err := adapter.TryReIndex(ctx, q.Actions(), metaSeq, reIndex)
		if err != nil {
			wui.reportError(ctx, w, err)
			return
		}
		if len(actions) > 0 && len(metaSeq) > 0 {

			for _, act := range actions {
				if act == api.RedirectAction {
					ub := wui.NewURLBuilder('h').SetZid(metaSeq[0].Zid.ZettelID())
					wui.redirectFound(w, r, ub)
					return
				}
			}
		}








		userLang := wui.getUserLang(ctx)


		var content, endnotes *sx.Pair
		numEntries := 0
		if bn, cnt := evaluator.QueryAction(ctx, q, metaSeq); bn != nil {
			enc := wui.getSimpleHTMLEncoder(userLang)
			content, endnotes, err = enc.BlocksSxn(&ast.BlockSlice{bn})
			if err != nil {
				wui.reportError(ctx, w, err)
				return
			}
			numEntries = cnt
		}

		siteName := wui.rtConfig.GetSiteName()
		user := server.GetUser(ctx)
		env, rb := wui.createRenderEnv(ctx, "list", userLang, siteName, user)



		if q == nil {
			rb.bindString("heading", sx.MakeString(siteName))
		} else {
			var sb strings.Builder
			q.PrintHuman(&sb)
			rb.bindString("heading", sx.MakeString(sb.String()))
		}
		rb.bindString("query-value", sx.MakeString(q.String()))
		if tzl := q.GetMetaValues(api.KeyTags, false); len(tzl) > 0 {
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
	)
	if sxZtl != nil {
		sxZtl = sxZtl.Cons(sx.MakeString(", "))
	}
	return sxZtl.Cons(link)
}

func (wui *WebUI) renderRSS(ctx context.Context, w http.ResponseWriter, q *query.Query, ml []*meta.Meta) {
	var rssConfig rss.Configuration
	rssConfig.Setup(ctx, wui.rtConfig)
	if actions := q.Actions(); len(actions) > 2 && actions[1] == api.TitleAction {
		rssConfig.Title = strings.Join(actions[2:], " ")
	}
	data := rssConfig.Marshal(q, ml)

	adapter.PrepareHeader(w, rss.ContentType)
	w.WriteHeader(http.StatusOK)
	var err error
	if _, err = io.WriteString(w, xml.Header); err == nil {
		_, err = w.Write(data)
	}
	if err != nil {
		wui.log.Error().Err(err).Msg("unable to write RSS data")
	}
}

func (wui *WebUI) renderAtom(w http.ResponseWriter, q *query.Query, ml []*meta.Meta) {
	var atomConfig atom.Configuration
	atomConfig.Setup(wui.rtConfig)
	if actions := q.Actions(); len(actions) > 2 && actions[1] == api.TitleAction {
		atomConfig.Title = strings.Join(actions[2:], " ")
	}
	data := atomConfig.Marshal(q, ml)

	adapter.PrepareHeader(w, atom.ContentType)
	w.WriteHeader(http.StatusOK)
	var err error
	if _, err = io.WriteString(w, xml.Header); err == nil {
		_, err = w.Write(data)
	}
	if err != nil {
		wui.log.Error().Err(err).Msg("unable to write Atom data")
	}
}

func (wui *WebUI) handleTagZettel(w http.ResponseWriter, r *http.Request, tagZettel *usecase.TagZettel, vals url.Values) bool {
	tag := vals.Get(api.QueryKeyTag)
	if tag == "" {
		return false
	}
	ctx := r.Context()
	z, err := tagZettel.Run(ctx, tag)







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







182
183
184
185
186
187
188






































189
190
191
192
193
194
195
	)
	if sxZtl != nil {
		sxZtl = sxZtl.Cons(sx.MakeString(", "))
	}
	return sxZtl.Cons(link)
}







































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)

Changes to web/adapter/webui/login.go.

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package webui

import (
	"context"
	"net/http"

	"t73f.de/r/sx"
	"t73f.de/r/zsc/api"
	"zettelstore.de/z/auth"
	"zettelstore.de/z/usecase"
	"zettelstore.de/z/web/adapter"
	"zettelstore.de/z/zettel/id"
)

// MakeGetLoginOutHandler creates a new HTTP handler to display the HTML login view,







<







14
15
16
17
18
19
20

21
22
23
24
25
26
27
package webui

import (
	"context"
	"net/http"

	"t73f.de/r/sx"

	"zettelstore.de/z/auth"
	"zettelstore.de/z/usecase"
	"zettelstore.de/z/web/adapter"
	"zettelstore.de/z/zettel/id"
)

// MakeGetLoginOutHandler creates a new HTTP handler to display the HTML login view,
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
			return
		}
		wui.renderLoginForm(wui.clearToken(r.Context(), w), w, false)
	})
}

func (wui *WebUI) renderLoginForm(ctx context.Context, w http.ResponseWriter, retry bool) {
	env, rb := wui.createRenderEnv(ctx, "login", wui.rtConfig.Get(ctx, nil, api.KeyLang), "Login", nil)
	rb.bindString("retry", sx.MakeBoolean(retry))
	if rb.err == nil {
		rb.err = wui.renderSxnTemplate(ctx, w, id.LoginTemplateZid, env)
	}
	if err := rb.err; err != nil {
		wui.reportError(ctx, w, err)
	}







|







35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
			return
		}
		wui.renderLoginForm(wui.clearToken(r.Context(), w), w, false)
	})
}

func (wui *WebUI) renderLoginForm(ctx context.Context, w http.ResponseWriter, retry bool) {
	env, rb := wui.createRenderEnv(ctx, "login", wui.getUserLang(ctx), "Login", nil)
	rb.bindString("retry", sx.MakeBoolean(retry))
	if rb.err == nil {
		rb.err = wui.renderSxnTemplate(ctx, w, id.LoginTemplateZid, env)
	}
	if err := rb.err; err != nil {
		wui.reportError(ctx, w, err)
	}

Changes to web/adapter/webui/template.go.

312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
			AppendKVQuery(queryKeyAction, valueActionNew).String())

		lst = lst.Cons(sx.Cons(text, link))
	}
	return lst
}
func (wui *WebUI) calculateFooterSxn(ctx context.Context) *sx.Pair {
	if footerZid, err := id.Parse(wui.rtConfig.Get(ctx, nil, config.KeyFooterZettel)); err == nil {
		if zn, err2 := wui.evalZettel.Run(ctx, footerZid, ""); err2 == nil {
			htmlEnc := wui.getSimpleHTMLEncoder(wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang)).SetUnique("footer-")
			if content, endnotes, err3 := htmlEnc.BlocksSxn(&zn.Ast); err3 == nil {
				if content != nil && endnotes != nil {
					content.LastPair().SetCdr(sx.Cons(endnotes, nil))
				}
				return content
			}
		}







|

|







312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
			AppendKVQuery(queryKeyAction, valueActionNew).String())

		lst = lst.Cons(sx.Cons(text, link))
	}
	return lst
}
func (wui *WebUI) calculateFooterSxn(ctx context.Context) *sx.Pair {
	if footerZid, err := id.Parse(wui.getConfig(ctx, nil, config.KeyFooterZettel)); err == nil {
		if zn, err2 := wui.evalZettel.Run(ctx, footerZid, ""); err2 == nil {
			htmlEnc := wui.getSimpleHTMLEncoder(wui.getConfig(ctx, zn.InhMeta, api.KeyLang)).SetUnique("footer-")
			if content, endnotes, err3 := htmlEnc.BlocksSxn(&zn.Ast); err3 == nil {
				if content != nil && endnotes != nil {
					content.LastPair().SetCdr(sx.Cons(endnotes, nil))
				}
				return content
			}
		}

Changes to web/adapter/webui/webui.go.

122
123
124
125
126
127
128







129
130
131
132
133
134
135
		genHTML:       sxhtml.NewGenerator().SetNewline(),
	}
	wui.rootBinding = wui.createRenderBinding()
	wui.observe(box.UpdateInfo{Box: mgr, Reason: box.OnReload, Zid: id.Invalid})
	mgr.RegisterObserver(wui.observe)
	return wui
}








var (
	symDetail     = sx.MakeSymbol("DETAIL")
	symMetaHeader = sx.MakeSymbol("META-HEADER")
)

func (wui *WebUI) observe(ci box.UpdateInfo) {







>
>
>
>
>
>
>







122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
		genHTML:       sxhtml.NewGenerator().SetNewline(),
	}
	wui.rootBinding = wui.createRenderBinding()
	wui.observe(box.UpdateInfo{Box: mgr, Reason: box.OnReload, Zid: id.Invalid})
	mgr.RegisterObserver(wui.observe)
	return wui
}

func (wui *WebUI) getConfig(ctx context.Context, m *meta.Meta, key string) string {
	return wui.rtConfig.Get(ctx, m, key)
}
func (wui *WebUI) getUserLang(ctx context.Context) string {
	return wui.getConfig(ctx, nil, api.KeyLang)
}

var (
	symDetail     = sx.MakeSymbol("DETAIL")
	symMetaHeader = sx.MakeSymbol("META-HEADER")
)

func (wui *WebUI) observe(ci box.UpdateInfo) {

Changes to www/changes.wiki.

1
2
3
4




5
6
7
8
9
10
11
<title>Change Log</title>

<a id="0_20"></a>
<h2>Changes for Version 0.20.0 (pending)</h2>





<a id="0_19"></a>
<h2>Changes for Version 0.19.0 (2024-12-13)</h2>
  *  Remove support for renaming zettel, i.e. changing zettel identifier. Was
     announced as deprecated in version 0.18.
     (breaking: api, webui)
  *  Format of zettel identifier will be not changed. The deprecation message




>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<title>Change Log</title>

<a id="0_20"></a>
<h2>Changes for Version 0.20.0 (pending)</h2>
  *  Query aggregates <code>ATOM</code> and <code>RSS</code> are removed, as
     well as the accompanying <code>TITLE</code> query action (parameter).
     Were announced as deprecated in version 0.19.
     (major)

<a id="0_19"></a>
<h2>Changes for Version 0.19.0 (2024-12-13)</h2>
  *  Remove support for renaming zettel, i.e. changing zettel identifier. Was
     announced as deprecated in version 0.18.
     (breaking: api, webui)
  *  Format of zettel identifier will be not changed. The deprecation message