ADDED .deepsource.toml
Index: .deepsource.toml
==================================================================
--- /dev/null
+++ .deepsource.toml
@@ -0,0 +1,8 @@
+version = 1
+
+[[analyzers]]
+name = "go"
+enabled = true
+
+ [analyzers.meta]
+import_paths = ["github.com/zettelstore/zettelstore"]
Index: Makefile
==================================================================
--- Makefile
+++ Makefile
@@ -1,46 +1,25 @@
-## Copyright (c) 2020 Detlef Stern
+## Copyright (c) 2020-2021 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.
-.PHONY: test check validate race run build build-dev release clean
-
-PACKAGE := zettelstore.de/z/cmd/zettelstore
-
-GO_LDFLAG_VERSION := -X main.buildVersion=$(shell go run tools/version.go || echo unknown)
-GOFLAGS_DEVELOP := -ldflags "$(GO_LDFLAG_VERSION)" -tags osusergo,netgo
-GOFLAGS_RELEASE := -ldflags "$(GO_LDFLAG_VERSION) -w" -tags osusergo,netgo
-
-test:
- go test ./...
+.PHONY: check build release clean
check:
- go vet ./...
- ~/go/bin/golint ./...
-
-validate: test check
-
-race:
- go test -race ./...
-
-build-dev:
- mkdir -p bin
- go build $(GOFLAGS_DEVELOP) -o bin/zettelstore $(PACKAGE)
+ go run tools/build.go check
+
+version:
+ @echo $(shell go run tools/build.go version)
build:
- mkdir -p bin
- go build $(GOFLAGS_RELEASE) -o bin/zettelstore $(PACKAGE)
+ go run tools/build.go build
release:
- mkdir -p releases
- GOARCH=amd64 GOOS=linux go build $(GOFLAGS_RELEASE) -o releases/zettelstore $(PACKAGE)
- GOARCH=arm GOARM=6 GOOS=linux go build $(GOFLAGS_RELEASE) -o releases/zettelstore-arm6 $(PACKAGE)
- GOARCH=amd64 GOOS=darwin go build $(GOFLAGS_RELEASE) -o releases/iZettelstore $(PACKAGE)
- GOARCH=amd64 GOOS=windows go build $(GOFLAGS_RELEASE) -o releases/zettelstore.exe $(PACKAGE)
+ go run tools/build.go release
clean:
- rm -rf bin releases
+ go run tools/build.go clean
Index: VERSION
==================================================================
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
-0.0.9
+0.0.10
Index: ast/ast.go
==================================================================
--- ast/ast.go
+++ ast/ast.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -80,13 +80,14 @@
// RefState indicates the state of the reference.
type RefState int
// Constants for RefState
const (
- RefStateInvalid RefState = iota // Invalid URL
- RefStateZettel // Valid reference to an internal zettel
- RefStateZettelSelf // Valid reference to same zettel with a fragment
- RefStateZettelFound // Valid reference to an existing internal zettel
- RefStateZettelBroken // Valid reference to a non-existing internal zettel
- RefStateLocal // Valid reference to a non-zettel, but local hosted
- RefStateExternal // Valid reference to external material
+ RefStateInvalid RefState = iota // Invalid Referende
+ RefStateZettel // Reference to an internal zettel
+ RefStateSelf // Reference to same zettel with a fragment
+ RefStateFound // Reference to an existing internal zettel
+ RefStateBroken // Reference to a non-existing internal zettel
+ RefStateHosted // Reference to local hosted non-Zettel, without URL change
+ RefStateBased // Reference to local non-Zettel, to be prefixed
+ RefStateExternal // Reference to external material
)
Index: ast/attr.go
==================================================================
--- ast/attr.go
+++ ast/attr.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -54,11 +54,11 @@
}
return &Attributes{attrs}
}
// Set changes the attribute that a given key has now a given value.
-func (a *Attributes) Set(key string, value string) *Attributes {
+func (a *Attributes) Set(key, value string) *Attributes {
if a == nil {
return &Attributes{map[string]string{key: value}}
}
if a.Attrs == nil {
a.Attrs = make(map[string]string)
Index: ast/ref.go
==================================================================
--- ast/ref.go
+++ ast/ref.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -17,12 +17,21 @@
"zettelstore.de/z/domain/id"
)
// ParseReference parses a string and returns a reference.
func ParseReference(s string) *Reference {
- if len(s) == 0 {
+ if s == "" {
return &Reference{URL: nil, Value: s, State: RefStateInvalid}
+ }
+ if state, ok := localState(s); ok {
+ if state == RefStateBased {
+ s = s[1:]
+ }
+ u, err := url.Parse(s)
+ if err == nil {
+ return &Reference{URL: u, Value: s, State: state}
+ }
}
u, err := url.Parse(s)
if err != nil {
return &Reference{URL: nil, Value: s, State: RefStateInvalid}
}
@@ -29,30 +38,30 @@
if len(u.Scheme)+len(u.Opaque)+len(u.Host) == 0 && u.User == nil {
if _, err := id.Parse(u.Path); err == nil {
return &Reference{URL: u, Value: s, State: RefStateZettel}
}
if u.Path == "" && u.Fragment != "" {
- return &Reference{URL: u, Value: s, State: RefStateZettelSelf}
- }
- if isLocalPath(u.Path) {
- return &Reference{URL: u, Value: s, State: RefStateLocal}
+ return &Reference{URL: u, Value: s, State: RefStateSelf}
}
}
return &Reference{URL: u, Value: s, State: RefStateExternal}
}
-func isLocalPath(path string) bool {
+func localState(path string) (RefState, bool) {
if len(path) > 0 && path[0] == '/' {
- return true
+ if len(path) > 1 && path[1] == '/' {
+ return RefStateBased, true
+ }
+ return RefStateHosted, true
}
if len(path) > 1 && path[0] == '.' {
if len(path) > 2 && path[1] == '.' && path[2] == '/' {
- return true
+ return RefStateHosted, true
}
- return path[1] == '/'
+ return RefStateHosted, path[1] == '/'
}
- return false
+ return RefStateInvalid, false
}
// String returns the string representation of a reference.
func (r Reference) String() string {
if r.URL != nil {
@@ -65,16 +74,18 @@
func (r *Reference) IsValid() bool { return r.State != RefStateInvalid }
// IsZettel returns true if it is a referencen to a local zettel.
func (r *Reference) IsZettel() bool {
switch r.State {
- case RefStateZettel, RefStateZettelSelf, RefStateZettelFound, RefStateZettelBroken:
+ case RefStateZettel, RefStateSelf, RefStateFound, RefStateBroken:
return true
}
return false
}
// IsLocal returns true if reference is local
-func (r *Reference) IsLocal() bool { return r.State == RefStateLocal }
+func (r *Reference) IsLocal() bool {
+ return r.State == RefStateHosted || r.State == RefStateBased
+}
// IsExternal returns true if it is a referencen to external material.
func (r *Reference) IsExternal() bool { return r.State == RefStateExternal }
Index: ast/ref_test.go
==================================================================
--- ast/ref_test.go
+++ ast/ref_test.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -53,10 +53,11 @@
{"12345678901234#local", true, false, false},
{"http://12345678901234", false, true, false},
{"http://zettelstore.de/z/12345678901234", false, true, false},
{"http://zettelstore.de/12345678901234", false, true, false},
{"/12345678901234", false, false, true},
+ {"//12345678901234", false, false, true},
{"./12345678901234", false, false, true},
{"../12345678901234", false, false, true},
{".../12345678901234", false, true, false},
}
Index: auth/cred/cred.go
==================================================================
--- auth/cred/cred.go
+++ auth/cred/cred.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -17,11 +17,11 @@
"golang.org/x/crypto/bcrypt"
"zettelstore.de/z/domain/id"
)
// HashCredential returns a hashed vesion of the given credential
-func HashCredential(zid id.Zid, ident string, credential string) (string, error) {
+func HashCredential(zid id.Zid, ident, credential string) (string, error) {
fullCredential := createFullCredential(zid, ident, credential)
res, err := bcrypt.GenerateFromPassword(fullCredential, bcrypt.DefaultCost)
if err != nil {
return "", err
}
@@ -28,11 +28,11 @@
return string(res), nil
}
// CompareHashAndCredential checks, whether the hashed credential is a possible
// value when hashing the credential.
-func CompareHashAndCredential(hashed string, zid id.Zid, ident string, credential string) (bool, error) {
+func CompareHashAndCredential(hashed string, zid id.Zid, ident, credential string) (bool, error) {
fullCredential := createFullCredential(zid, ident, credential)
err := bcrypt.CompareHashAndPassword([]byte(hashed), fullCredential)
if err == nil {
return true, nil
}
@@ -40,14 +40,14 @@
return false, nil
}
return false, err
}
-func createFullCredential(zid id.Zid, ident string, credential string) []byte {
+func createFullCredential(zid id.Zid, ident, credential string) []byte {
var buf bytes.Buffer
buf.WriteString(zid.String())
buf.WriteByte(' ')
buf.WriteString(ident)
buf.WriteByte(' ')
buf.WriteString(credential)
return buf.Bytes()
}
Index: auth/policy/anon.go
==================================================================
--- auth/policy/anon.go
+++ auth/policy/anon.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -20,31 +20,27 @@
expertMode func() bool
getVisibility func(*meta.Meta) meta.Visibility
pre Policy
}
-func (ap *anonPolicy) CanReload(user *meta.Meta) bool {
- return ap.pre.CanReload(user)
-}
-
-func (ap *anonPolicy) CanCreate(user *meta.Meta, newMeta *meta.Meta) bool {
+func (ap *anonPolicy) CanCreate(user, newMeta *meta.Meta) bool {
return ap.pre.CanCreate(user, newMeta)
}
-func (ap *anonPolicy) CanRead(user *meta.Meta, m *meta.Meta) bool {
+func (ap *anonPolicy) CanRead(user, m *meta.Meta) bool {
return ap.pre.CanRead(user, m) && ap.checkVisibility(m)
}
-func (ap *anonPolicy) CanWrite(user *meta.Meta, oldMeta, newMeta *meta.Meta) bool {
+func (ap *anonPolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool {
return ap.pre.CanWrite(user, oldMeta, newMeta) && ap.checkVisibility(oldMeta)
}
-func (ap *anonPolicy) CanRename(user *meta.Meta, m *meta.Meta) bool {
+func (ap *anonPolicy) CanRename(user, m *meta.Meta) bool {
return ap.pre.CanRename(user, m) && ap.checkVisibility(m)
}
-func (ap *anonPolicy) CanDelete(user *meta.Meta, m *meta.Meta) bool {
+func (ap *anonPolicy) CanDelete(user, m *meta.Meta) bool {
return ap.pre.CanDelete(user, m) && ap.checkVisibility(m)
}
func (ap *anonPolicy) checkVisibility(m *meta.Meta) bool {
switch ap.getVisibility(m) {
Index: auth/policy/default.go
==================================================================
--- auth/policy/default.go
+++ auth/policy/default.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -16,35 +16,19 @@
"zettelstore.de/z/domain/meta"
)
type defaultPolicy struct{}
-func (d *defaultPolicy) CanReload(user *meta.Meta) bool {
- return true
-}
-
-func (d *defaultPolicy) CanCreate(user *meta.Meta, newMeta *meta.Meta) bool {
- return true
-}
-
-func (d *defaultPolicy) CanRead(user *meta.Meta, m *meta.Meta) bool {
- return true
-}
-
-func (d *defaultPolicy) CanWrite(user *meta.Meta, oldMeta, newMeta *meta.Meta) bool {
+func (d *defaultPolicy) CanCreate(user, newMeta *meta.Meta) bool { return true }
+func (d *defaultPolicy) CanRead(user, m *meta.Meta) bool { return true }
+func (d *defaultPolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool {
return d.canChange(user, oldMeta)
}
-
-func (d *defaultPolicy) CanRename(user *meta.Meta, m *meta.Meta) bool {
- return d.canChange(user, m)
-}
-
-func (d *defaultPolicy) CanDelete(user *meta.Meta, m *meta.Meta) bool {
- return d.canChange(user, m)
-}
-
-func (d *defaultPolicy) canChange(user *meta.Meta, m *meta.Meta) bool {
+func (d *defaultPolicy) CanRename(user, m *meta.Meta) bool { return d.canChange(user, m) }
+func (d *defaultPolicy) CanDelete(user, m *meta.Meta) bool { return d.canChange(user, m) }
+
+func (d *defaultPolicy) canChange(user, m *meta.Meta) bool {
metaRo, ok := m.Get(meta.KeyReadOnly)
if !ok {
return true
}
if user == nil {
Index: auth/policy/owner.go
==================================================================
--- auth/policy/owner.go
+++ auth/policy/owner.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -22,46 +22,38 @@
isOwner func(id.Zid) bool
getVisibility func(*meta.Meta) meta.Visibility
pre Policy
}
-func (o *ownerPolicy) CanReload(user *meta.Meta) bool {
- // No need to call o.pre.CanReload(user), because it will always return true.
- // Both the default and the readonly policy allow to reload a place.
-
- // Only the owner is allowed to reload a place
- return o.userIsOwner(user)
-}
-
-func (o *ownerPolicy) CanCreate(user *meta.Meta, newMeta *meta.Meta) bool {
+func (o *ownerPolicy) CanCreate(user, newMeta *meta.Meta) bool {
if user == nil || !o.pre.CanCreate(user, newMeta) {
return false
}
return o.userIsOwner(user) || o.userCanCreate(user, newMeta)
}
-func (o *ownerPolicy) userCanCreate(user *meta.Meta, newMeta *meta.Meta) bool {
+func (o *ownerPolicy) userCanCreate(user, newMeta *meta.Meta) bool {
if runtime.GetUserRole(user) == meta.UserRoleReader {
return false
}
if role, ok := newMeta.Get(meta.KeyRole); ok && role == meta.ValueRoleUser {
return false
}
return true
}
-func (o *ownerPolicy) CanRead(user *meta.Meta, m *meta.Meta) bool {
+func (o *ownerPolicy) CanRead(user, m *meta.Meta) bool {
// No need to call o.pre.CanRead(user, meta), because it will always return true.
// Both the default and the readonly policy allow to read a zettel.
vis := o.getVisibility(m)
if res, ok := o.checkVisibility(user, vis); ok {
return res
}
return o.userIsOwner(user) || o.userCanRead(user, m, vis)
}
-func (o *ownerPolicy) userCanRead(user *meta.Meta, m *meta.Meta, vis meta.Visibility) bool {
+func (o *ownerPolicy) userCanRead(user, m *meta.Meta, vis meta.Visibility) bool {
switch vis {
case meta.VisibilityOwner, meta.VisibilitySimple, meta.VisibilityExpert:
return false
case meta.VisibilityPublic:
return true
@@ -81,11 +73,11 @@
meta.KeyRole,
meta.KeyUserID,
meta.KeyUserRole,
}
-func (o *ownerPolicy) CanWrite(user *meta.Meta, oldMeta, newMeta *meta.Meta) bool {
+func (o *ownerPolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool {
if user == nil || !o.pre.CanWrite(user, oldMeta, newMeta) {
return false
}
vis := o.getVisibility(oldMeta)
if res, ok := o.checkVisibility(user, vis); ok {
@@ -111,21 +103,21 @@
return false
}
return o.userCanCreate(user, newMeta)
}
-func (o *ownerPolicy) CanRename(user *meta.Meta, m *meta.Meta) bool {
+func (o *ownerPolicy) CanRename(user, m *meta.Meta) bool {
if user == nil || !o.pre.CanRename(user, m) {
return false
}
if res, ok := o.checkVisibility(user, o.getVisibility(m)); ok {
return res
}
return o.userIsOwner(user)
}
-func (o *ownerPolicy) CanDelete(user *meta.Meta, m *meta.Meta) bool {
+func (o *ownerPolicy) CanDelete(user, m *meta.Meta) bool {
if user == nil || !o.pre.CanDelete(user, m) {
return false
}
if res, ok := o.checkVisibility(user, o.getVisibility(m)); ok {
return res
Index: auth/policy/place.go
==================================================================
--- auth/policy/place.go
+++ auth/policy/place.go
@@ -55,12 +55,11 @@
func (pp *polPlace) CanCreateZettel(ctx context.Context) bool {
return pp.place.CanCreateZettel(ctx)
}
-func (pp *polPlace) CreateZettel(
- ctx context.Context, zettel domain.Zettel) (id.Zid, error) {
+func (pp *polPlace) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) {
user := session.GetUser(ctx)
if pp.policy.CanCreate(user, zettel.Meta) {
return pp.place.CreateZettel(ctx, zettel)
}
return id.Invalid, place.NewErrNotAllowed("Create", user, id.Invalid)
@@ -88,11 +87,11 @@
return m, nil
}
return nil, place.NewErrNotAllowed("GetMeta", user, zid)
}
-func (pp *polPlace) FetchZids(ctx context.Context) (map[id.Zid]bool, error) {
+func (pp *polPlace) FetchZids(ctx context.Context) (id.Set, error) {
return nil, place.NewErrNotAllowed("fetch-zids", session.GetUser(ctx), id.Invalid)
}
func (pp *polPlace) SelectMeta(
ctx context.Context, f *place.Filter, s *place.Sorter) ([]*meta.Meta, error) {
@@ -163,15 +162,8 @@
return pp.place.DeleteZettel(ctx, zid)
}
return place.NewErrNotAllowed("Delete", user, zid)
}
-func (pp *polPlace) Reload(ctx context.Context) error {
- user := session.GetUser(ctx)
- if pp.policy.CanReload(user) {
- return pp.place.Reload(ctx)
- }
- return place.NewErrNotAllowed("Reload", user, id.Invalid)
-}
func (pp *polPlace) ReadStats(st *place.Stats) {
pp.place.ReadStats(st)
}
Index: auth/policy/policy.go
==================================================================
--- auth/policy/policy.go
+++ auth/policy/policy.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -16,27 +16,24 @@
"zettelstore.de/z/domain/meta"
)
// Policy is an interface for checking access authorization.
type Policy interface {
- // User is allowed to reload a place.
- CanReload(user *meta.Meta) bool
-
// User is allowed to create a new zettel.
- CanCreate(user *meta.Meta, newMeta *meta.Meta) bool
+ CanCreate(user, newMeta *meta.Meta) bool
// User is allowed to read zettel
- CanRead(user *meta.Meta, m *meta.Meta) bool
+ CanRead(user, m *meta.Meta) bool
// User is allowed to write zettel.
- CanWrite(user *meta.Meta, oldMeta, newMeta *meta.Meta) bool
+ CanWrite(user, oldMeta, newMeta *meta.Meta) bool
// User is allowed to rename zettel
- CanRename(user *meta.Meta, m *meta.Meta) bool
+ CanRename(user, m *meta.Meta) bool
// User is allowed to delete zettel
- CanDelete(user *meta.Meta, m *meta.Meta) bool
+ CanDelete(user, m *meta.Meta) bool
}
// newPolicy creates a policy based on given constraints.
func newPolicy(
simpleMode bool,
@@ -72,29 +69,25 @@
type prePolicy struct {
post Policy
}
-func (p *prePolicy) CanReload(user *meta.Meta) bool {
- return p.post.CanReload(user)
-}
-
-func (p *prePolicy) CanCreate(user *meta.Meta, newMeta *meta.Meta) bool {
+func (p *prePolicy) CanCreate(user, newMeta *meta.Meta) bool {
return newMeta != nil && p.post.CanCreate(user, newMeta)
}
-func (p *prePolicy) CanRead(user *meta.Meta, m *meta.Meta) bool {
+func (p *prePolicy) CanRead(user, m *meta.Meta) bool {
return m != nil && p.post.CanRead(user, m)
}
-func (p *prePolicy) CanWrite(user *meta.Meta, oldMeta, newMeta *meta.Meta) bool {
+func (p *prePolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool {
return oldMeta != nil && newMeta != nil && oldMeta.Zid == newMeta.Zid &&
p.post.CanWrite(user, oldMeta, newMeta)
}
-func (p *prePolicy) CanRename(user *meta.Meta, m *meta.Meta) bool {
+func (p *prePolicy) CanRename(user, m *meta.Meta) bool {
return m != nil && p.post.CanRename(user, m)
}
-func (p *prePolicy) CanDelete(user *meta.Meta, m *meta.Meta) bool {
+func (p *prePolicy) CanDelete(user, m *meta.Meta) bool {
return m != nil && p.post.CanDelete(user, m)
}
Index: auth/policy/policy_test.go
==================================================================
--- auth/policy/policy_test.go
+++ auth/policy/policy_test.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -58,11 +58,10 @@
}
pol := newPolicy(ts.simple, authFunc, ts.readonly, expertFunc, isOwner, getVisibility)
name := fmt.Sprintf("simple=%v/readonly=%v/withauth=%v/expert=%v",
ts.simple, ts.readonly, ts.withAuth, ts.expert)
t.Run(name, func(tt *testing.T) {
- testReload(tt, pol, ts.simple, ts.withAuth, ts.readonly, ts.expert)
testCreate(tt, pol, ts.simple, ts.withAuth, ts.readonly, ts.expert)
testRead(tt, pol, ts.simple, ts.withAuth, ts.readonly, ts.expert)
testWrite(tt, pol, ts.simple, ts.withAuth, ts.readonly, ts.expert)
testRename(tt, pol, ts.simple, ts.withAuth, ts.readonly, ts.expert)
testDelete(tt, pol, ts.simple, ts.withAuth, ts.readonly, ts.expert)
@@ -89,32 +88,11 @@
}
}
return meta.VisibilityLogin
}
-func testReload(t *testing.T, pol Policy, simple bool, withAuth bool, readonly bool, isExpert bool) {
- t.Helper()
- testCases := []struct {
- user *meta.Meta
- exp bool
- }{
- {newAnon(), !withAuth},
- {newReader(), !withAuth},
- {newWriter(), !withAuth},
- {newOwner(), true},
- }
- for _, tc := range testCases {
- t.Run("Reload", func(tt *testing.T) {
- got := pol.CanReload(tc.user)
- if tc.exp != got {
- tt.Errorf("exp=%v, but got=%v", tc.exp, got)
- }
- })
- }
-}
-
-func testCreate(t *testing.T, pol Policy, simple bool, withAuth bool, readonly bool, isExpert bool) {
+func testCreate(t *testing.T, pol Policy, simple, withAuth, readonly, isExpert bool) {
t.Helper()
anonUser := newAnon()
reader := newReader()
writer := newWriter()
owner := newOwner()
@@ -153,11 +131,11 @@
}
})
}
}
-func testRead(t *testing.T, pol Policy, simple bool, withAuth bool, readonly bool, expert bool) {
+func testRead(t *testing.T, pol Policy, simple, withAuth, readonly, expert bool) {
t.Helper()
anonUser := newAnon()
reader := newReader()
writer := newWriter()
owner := newOwner()
@@ -238,11 +216,11 @@
}
})
}
}
-func testWrite(t *testing.T, pol Policy, simple bool, withAuth bool, readonly bool, expert bool) {
+func testWrite(t *testing.T, pol Policy, simple, withAuth, readonly, expert bool) {
t.Helper()
anonUser := newAnon()
reader := newReader()
writer := newWriter()
owner := newOwner()
@@ -379,11 +357,11 @@
}
})
}
}
-func testRename(t *testing.T, pol Policy, simple bool, withAuth bool, readonly bool, expert bool) {
+func testRename(t *testing.T, pol Policy, simple, withAuth, readonly, expert bool) {
t.Helper()
anonUser := newAnon()
reader := newReader()
writer := newWriter()
owner := newOwner()
@@ -464,11 +442,11 @@
}
})
}
}
-func testDelete(t *testing.T, pol Policy, simple bool, withAuth bool, readonly bool, expert bool) {
+func testDelete(t *testing.T, pol Policy, simple, withAuth, readonly, expert bool) {
t.Helper()
anonUser := newAnon()
reader := newReader()
writer := newWriter()
owner := newOwner()
Index: auth/policy/readonly.go
==================================================================
--- auth/policy/readonly.go
+++ auth/policy/readonly.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -13,28 +13,10 @@
import "zettelstore.de/z/domain/meta"
type roPolicy struct{}
-func (p *roPolicy) CanReload(user *meta.Meta) bool {
- return true
-}
-
-func (p *roPolicy) CanCreate(user *meta.Meta, newMeta *meta.Meta) bool {
- return false
-}
-
-func (p *roPolicy) CanRead(user *meta.Meta, m *meta.Meta) bool {
- return true
-}
-
-func (p *roPolicy) CanWrite(user *meta.Meta, oldMeta, newMeta *meta.Meta) bool {
- return false
-}
-
-func (p *roPolicy) CanRename(user *meta.Meta, m *meta.Meta) bool {
- return false
-}
-
-func (p *roPolicy) CanDelete(user *meta.Meta, m *meta.Meta) bool {
- return false
-}
+func (p *roPolicy) CanCreate(user, newMeta *meta.Meta) bool { return false }
+func (p *roPolicy) CanRead(user, m *meta.Meta) bool { return true }
+func (p *roPolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool { return false }
+func (p *roPolicy) CanRename(user, m *meta.Meta) bool { return false }
+func (p *roPolicy) CanDelete(user, m *meta.Meta) bool { return false }
Index: auth/token/token.go
==================================================================
--- auth/token/token.go
+++ auth/token/token.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -50,11 +50,11 @@
func GetToken(ident *meta.Meta, d time.Duration, kind Kind) ([]byte, error) {
if role, ok := ident.Get(meta.KeyRole); !ok || role != meta.ValueRoleUser {
return nil, ErrNoUser
}
subject, ok := ident.Get(meta.KeyUserID)
- if !ok || len(subject) == 0 {
+ if !ok || subject == "" {
return nil, ErrNoIdent
}
now := time.Now().Round(time.Second)
claims := jwt.Claims{
@@ -102,11 +102,11 @@
expires := claims.Expires.Time()
if expires.Before(now) {
return Data{}, ErrTokenExpired
}
ident := claims.Subject
- if len(ident) == 0 {
+ if ident == "" {
return Data{}, ErrNoIdent
}
if zidS, ok := claims.Set["zid"].(string); ok {
if zid, err := id.Parse(zidS); err == nil {
if kind, ok := claims.Set["_tk"].(float64); ok {
Index: cmd/cmd_run.go
==================================================================
--- cmd/cmd_run.go
+++ cmd/cmd_run.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -81,25 +81,25 @@
ucGetZettel := usecase.NewGetZettel(pp)
ucParseZettel := usecase.NewParseZettel(ucGetZettel)
ucListMeta := usecase.NewListMeta(pp)
ucListRoles := usecase.NewListRole(pp)
ucListTags := usecase.NewListTags(pp)
- listHTMLMetaHandler := webui.MakeListHTMLMetaHandler(te, ucListMeta)
- getHTMLZettelHandler := webui.MakeGetHTMLZettelHandler(te, ucParseZettel, ucGetMeta)
+ ucZettelContext := usecase.NewZettelContext(pp)
router := router.NewRouter()
- router.Handle("/", webui.MakeGetRootHandler(
- pp, listHTMLMetaHandler, getHTMLZettelHandler))
+ router.Handle("/", webui.MakeGetRootHandler(pp))
router.AddListRoute('a', http.MethodGet, webui.MakeGetLoginHandler(te))
router.AddListRoute('a', http.MethodPost, adapter.MakePostLoginHandler(
api.MakePostLoginHandlerAPI(ucAuthenticate),
webui.MakePostLoginHandlerHTML(te, ucAuthenticate)))
router.AddListRoute('a', http.MethodPut, api.MakeRenewAuthHandler())
router.AddZettelRoute('a', http.MethodGet, webui.MakeGetLogoutHandler())
- router.AddListRoute('c', http.MethodGet, adapter.MakeReloadHandler(
- usecase.NewReload(pp), api.ReloadHandlerAPI, webui.ReloadHandlerHTML))
if !readonlyMode {
+ router.AddZettelRoute('b', http.MethodGet, webui.MakeGetRenameZettelHandler(
+ te, ucGetMeta))
+ router.AddZettelRoute('b', http.MethodPost, webui.MakePostRenameZettelHandler(
+ usecase.NewRenameZettel(pp)))
router.AddZettelRoute('c', http.MethodGet, webui.MakeGetCopyZettelHandler(
te, ucGetZettel, usecase.NewCopyZettel()))
router.AddZettelRoute('c', http.MethodPost, webui.MakePostCreateZettelHandler(
usecase.NewCreateZettel(pp)))
router.AddZettelRoute('d', http.MethodGet, webui.MakeGetDeleteZettelHandler(
@@ -112,35 +112,32 @@
usecase.NewUpdateZettel(pp)))
router.AddZettelRoute('f', http.MethodGet, webui.MakeGetFolgeZettelHandler(
te, ucGetZettel, usecase.NewFolgeZettel()))
router.AddZettelRoute('f', http.MethodPost, webui.MakePostCreateZettelHandler(
usecase.NewCreateZettel(pp)))
- }
- router.AddListRoute('h', http.MethodGet, listHTMLMetaHandler)
- router.AddZettelRoute('h', http.MethodGet, getHTMLZettelHandler)
- router.AddZettelRoute('i', http.MethodGet, webui.MakeGetInfoHandler(
- te, ucParseZettel, ucGetMeta))
- router.AddZettelRoute('k', http.MethodGet, webui.MakeWebUIListsHandler(
- te, ucListMeta, ucListRoles, ucListTags))
- router.AddZettelRoute('l', http.MethodGet, api.MakeGetLinksHandler(ucParseZettel))
- if !readonlyMode {
- router.AddZettelRoute('n', http.MethodGet, webui.MakeGetNewZettelHandler(
+ router.AddZettelRoute('g', http.MethodGet, webui.MakeGetNewZettelHandler(
te, ucGetZettel, usecase.NewNewZettel()))
- router.AddZettelRoute('n', http.MethodPost, webui.MakePostCreateZettelHandler(
+ router.AddZettelRoute('g', http.MethodPost, webui.MakePostCreateZettelHandler(
usecase.NewCreateZettel(pp)))
}
+ router.AddListRoute('f', http.MethodGet, webui.MakeSearchHandler(
+ te, usecase.NewSearch(pp), ucGetMeta, ucGetZettel))
+ router.AddListRoute('h', http.MethodGet, webui.MakeListHTMLMetaHandler(
+ te, ucListMeta, ucListRoles, ucListTags))
+ router.AddZettelRoute('h', http.MethodGet, webui.MakeGetHTMLZettelHandler(
+ te, ucParseZettel, ucGetMeta))
+ router.AddZettelRoute('i', http.MethodGet, webui.MakeGetInfoHandler(
+ te, ucParseZettel, ucGetMeta))
+ router.AddZettelRoute('j', http.MethodGet, webui.MakeZettelContextHandler(te, ucZettelContext))
+
+ router.AddZettelRoute('l', http.MethodGet, api.MakeGetLinksHandler(ucParseZettel))
+ router.AddZettelRoute('o', http.MethodGet, api.MakeGetOrderHandler(
+ usecase.NewZettelOrder(pp, ucParseZettel)))
router.AddListRoute('r', http.MethodGet, api.MakeListRoleHandler(ucListRoles))
- if !readonlyMode {
- router.AddZettelRoute('r', http.MethodGet, webui.MakeGetRenameZettelHandler(
- te, ucGetMeta))
- router.AddZettelRoute('r', http.MethodPost, webui.MakePostRenameZettelHandler(
- usecase.NewRenameZettel(pp)))
- }
router.AddListRoute('t', http.MethodGet, api.MakeListTagsHandler(ucListTags))
- router.AddListRoute('s', http.MethodGet, webui.MakeSearchHandler(
- te, usecase.NewSearch(pp), ucGetMeta, ucGetZettel))
+ router.AddZettelRoute('y', http.MethodGet, api.MakeZettelContextHandler(ucZettelContext))
router.AddListRoute('z', http.MethodGet, api.MakeListMetaHandler(
usecase.NewListMeta(pp), ucGetMeta, ucParseZettel))
router.AddZettelRoute('z', http.MethodGet, api.MakeGetZettelHandler(
ucParseZettel, ucGetMeta))
return session.NewHandler(router, usecase.NewGetUserByZid(up))
}
Index: cmd/cmd_run_simple.go
==================================================================
--- cmd/cmd_run_simple.go
+++ cmd/cmd_run_simple.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -9,38 +9,25 @@
//-----------------------------------------------------------------------------
package cmd
import (
- "context"
"flag"
"fmt"
"log"
"os"
"strings"
- "zettelstore.de/z/domain"
- "zettelstore.de/z/place"
- "zettelstore.de/z/web/server"
-
"zettelstore.de/z/config/startup"
- "zettelstore.de/z/domain/id"
- "zettelstore.de/z/domain/meta"
+ "zettelstore.de/z/web/server"
)
func flgSimpleRun(fs *flag.FlagSet) {
fs.String("d", "", "zettel directory")
}
func runSimpleFunc(*flag.FlagSet) (int, error) {
- p := startup.PlaceManager()
- if _, err := p.GetMeta(context.Background(), id.WelcomeZid); err != nil {
- if err == place.ErrNotFound {
- updateWelcomeZettel(p)
- }
- }
-
listenAddr := startup.ListenAddress()
readonlyMode := startup.IsReadOnlyMode()
logBeforeRun(listenAddr, readonlyMode)
if idx := strings.LastIndexByte(listenAddr, ':'); idx >= 0 {
log.Println()
@@ -60,74 +47,11 @@
// runSimple is called, when the user just starts the software via a double click
// or via a simple call ``./zettelstore`` on the command line.
func runSimple() {
dir := "./zettel"
- if err := os.MkdirAll(dir, 0755); err != nil {
+ if err := os.MkdirAll(dir, 0750); err != nil {
fmt.Fprintf(os.Stderr, "Unable to create zettel directory %q (%s)\n", dir, err)
os.Exit(1)
}
executeCommand("run-simple", "-d", dir)
}
-
-func updateWelcomeZettel(p place.Place) {
- m := meta.New(id.WelcomeZid)
- m.Set(meta.KeyTitle, "Welcome to Zettelstore")
- m.Set(meta.KeyRole, meta.ValueRoleZettel)
- m.Set(meta.KeySyntax, meta.ValueSyntaxZmk)
- zid, err := p.CreateZettel(
- context.Background(),
- domain.Zettel{Meta: m, Content: domain.NewContent(welcomeZettelContent)},
- )
- if err == nil {
- p.RenameZettel(context.Background(), zid, id.WelcomeZid)
- }
-}
-
-var welcomeZettelContent = `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.
-To check for versions, there is a zettel with the [[current version|00000000000001]] of your Zettelstore.
-
-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:
-* [[Zettelstore Version|00000000000001]]
-* [[Zettelstore Operating System|00000000000003]]
-* [[Zettelstore Startup Configuration|00000000000096]]
-* [[Zettelstore Startup Values|00000000000098]]
-* [[Zettelstore Runtime Configuration|00000000000100]]
-
-Additionally, you have to describe, what you have done before that error occurs
-and what you have expected instead.
-Please do not forget to include the error message, if there is one.
-
-Some of above Zettelstore zettel can only be retrieved if you enabled ""expert mode"".
-Otherwise, only some zettel are linked.
-To enable expert mode, edit the zettel [[Zettelstore Runtime Configuration|00000000000100]]:
-please set the metadata value of the key ''expert-mode'' to true.
-To do you, enter the string ''expert-mode:true'' inside the edit view of the metadata.
-
-=== Information about this zettel
-This zettel was generated automatically.
-Every time you start Zettelstore by double clicking in your graphical user interface,
-or by just starting it in a command line via something like ''zettelstore'', and this zettel
-does not exist, it will be generated.
-This allows you to edit this zettel for your own needs.
-
-If you don't need it anymore, you can delete this zettel by clicking on ""Info"" and then
-on ""Delete"".
-However, by starting Zettelstore as described above, the original version of this zettel
-will be restored.
-
-If you start Zettelstore with the ''run'' command, e.g. as a service or via command line,
-this zettel will not be generated.
-But if it exists before, it will not be deleted.
-In this case, Zettelstore assumes that you have enough knowledge and that you do not need
-zettel.
-`
Index: cmd/main.go
==================================================================
--- cmd/main.go
+++ cmd/main.go
@@ -122,11 +122,11 @@
}
})
return cfg
}
-func setupOperations(cfg *meta.Meta, withPlaces bool, simple bool) error {
+func setupOperations(cfg *meta.Meta, withPlaces, simple bool) error {
var mgr place.Manager
var idx index.Indexer
if withPlaces {
idx = indexer.New()
filter := index.NewMetaFilter(idx)
Index: cmd/zettelstore/main.go
==================================================================
--- cmd/zettelstore/main.go
+++ cmd/zettelstore/main.go
@@ -14,10 +14,10 @@
import (
"zettelstore.de/z/cmd"
)
// Version variable. Will be filled by build process.
-var buildVersion string = ""
+var version string = ""
func main() {
- cmd.Main("Zettelstore", buildVersion)
+ cmd.Main("Zettelstore", version)
}
Index: collect/collect.go
==================================================================
--- collect/collect.go
+++ collect/collect.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
Index: collect/collect_test.go
==================================================================
--- collect/collect_test.go
+++ collect/collect_test.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
ADDED collect/order.go
Index: collect/order.go
==================================================================
--- /dev/null
+++ collect/order.go
@@ -0,0 +1,69 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2021 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 collect provides functions to collect items from a syntax tree.
+package collect
+
+import "zettelstore.de/z/ast"
+
+// Order of internal reference within the given zettel.
+func Order(zn *ast.ZettelNode) (result []*ast.Reference) {
+ for _, bn := range zn.Ast {
+ if ln, ok := bn.(*ast.NestedListNode); ok {
+ switch ln.Code {
+ case ast.NestedListOrdered, ast.NestedListUnordered:
+ for _, is := range ln.Items {
+ if ref := firstItemZettelReference(is); ref != nil {
+ result = append(result, ref)
+ }
+ }
+ }
+ }
+ }
+ return result
+}
+
+func firstItemZettelReference(is ast.ItemSlice) *ast.Reference {
+ for _, in := range is {
+ if pn, ok := in.(*ast.ParaNode); ok {
+ if ref := firstInlineZettelReference(pn.Inlines); ref != nil {
+ return ref
+ }
+ }
+ }
+ return nil
+}
+
+func firstInlineZettelReference(ins ast.InlineSlice) (result *ast.Reference) {
+ for _, inl := range ins {
+ switch in := inl.(type) {
+ case *ast.LinkNode:
+ if ref := in.Ref; ref.IsZettel() {
+ return ref
+ }
+ result = firstInlineZettelReference(in.Inlines)
+ case *ast.ImageNode:
+ result = firstInlineZettelReference(in.Inlines)
+ case *ast.CiteNode:
+ result = firstInlineZettelReference(in.Inlines)
+ case *ast.FootnoteNode:
+ // Ignore references in footnotes
+ continue
+ case *ast.FormatNode:
+ result = firstInlineZettelReference(in.Inlines)
+ default:
+ continue
+ }
+ if result != nil {
+ return result
+ }
+ }
+ return nil
+}
Index: collect/split.go
==================================================================
--- collect/split.go
+++ collect/split.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -9,13 +9,11 @@
//-----------------------------------------------------------------------------
// Package collect provides functions to collect items from a syntax tree.
package collect
-import (
- "zettelstore.de/z/ast"
-)
+import "zettelstore.de/z/ast"
// DivideReferences divides the given list of rederences into zettel, local, and external References.
func DivideReferences(all []*ast.Reference, duplicates bool) (zettel, local, external []*ast.Reference) {
if len(all) == 0 {
return nil, nil, nil
@@ -23,40 +21,37 @@
mapZettel := make(map[string]bool)
mapLocal := make(map[string]bool)
mapExternal := make(map[string]bool)
for _, ref := range all {
- if ref.State == ast.RefStateZettelSelf {
+ if ref.State == ast.RefStateSelf {
continue
}
- s := ref.String()
if ref.IsZettel() {
- if duplicates {
- zettel = append(zettel, ref)
- } else {
- if _, ok := mapZettel[s]; !ok {
- zettel = append(zettel, ref)
- mapZettel[s] = true
- }
- }
+ zettel = appendRefToList(zettel, mapZettel, ref, duplicates)
} else if ref.IsExternal() {
- if duplicates {
- external = append(external, ref)
- } else {
- if _, ok := mapExternal[s]; !ok {
- external = append(external, ref)
- mapExternal[s] = true
- }
- }
- } else {
- if duplicates {
- local = append(local, ref)
- } else {
- if _, ok := mapLocal[s]; !ok {
- local = append(local, ref)
- mapLocal[s] = true
- }
- }
+ external = appendRefToList(external, mapExternal, ref, duplicates)
+ } else {
+ local = appendRefToList(local, mapLocal, ref, duplicates)
}
}
return zettel, local, external
}
+
+func appendRefToList(
+ reflist []*ast.Reference,
+ refSet map[string]bool,
+ ref *ast.Reference,
+ duplicates bool,
+) []*ast.Reference {
+ if duplicates {
+ reflist = append(reflist, ref)
+ } else {
+ s := ref.String()
+ if _, ok := refSet[s]; !ok {
+ reflist = append(reflist, ref)
+ refSet[s] = true
+ }
+ }
+
+ return reflist
+}
Index: config/runtime/runtime.go
==================================================================
--- config/runtime/runtime.go
+++ config/runtime/runtime.go
@@ -10,12 +10,10 @@
// Package runtime provides functions to retrieve runtime configuration data.
package runtime
import (
- "strconv"
-
"zettelstore.de/z/domain/id"
"zettelstore.de/z/domain/meta"
"zettelstore.de/z/place"
"zettelstore.de/z/place/stock"
)
@@ -132,20 +130,20 @@
}
}
return "Zettelstore"
}
-// GetStart returns the value of the "start" key.
-func GetStart() id.Zid {
+// GetHomeZettel returns the value of the "home-zettel" key.
+func GetHomeZettel() id.Zid {
if config := getConfigurationMeta(); config != nil {
- if start, ok := config.Get(meta.KeyStart); ok {
+ if start, ok := config.Get(meta.KeyHomeZettel); ok {
if startID, err := id.Parse(start); err == nil {
return startID
}
}
}
- return id.Invalid
+ return id.DefaultHomeZid
}
// GetDefaultVisibility returns the default value for zettel visibility.
func GetDefaultVisibility() meta.Visibility {
if config := getConfigurationMeta(); config != nil {
@@ -179,11 +177,11 @@
if config := getConfigurationMeta(); config != nil {
if html, ok := config.Get(meta.KeyMarkerExternal); ok {
return html
}
}
- return "↗︎"
+ return "➚"
}
// GetFooterHTML returns HTML code that should be embedded into the footer
// of each WebUI page.
func GetFooterHTML() string {
@@ -197,13 +195,11 @@
// GetListPageSize returns the maximum length of a list to be returned in WebUI.
// A value less or equal to zero signals no limit.
func GetListPageSize() int {
if config := getConfigurationMeta(); config != nil {
- if data, ok := config.Get(meta.KeyListPageSize); ok {
- if value, err := strconv.Atoi(data); err == nil {
- return value
- }
+ if value, ok := config.GetNumber(meta.KeyListPageSize); ok && value > 0 {
+ return value
}
}
return 0
}
Index: config/startup/startup.go
==================================================================
--- config/startup/startup.go
+++ config/startup/startup.go
@@ -102,11 +102,10 @@
io.WriteString(h, secret)
}
io.WriteString(h, version.Prog)
io.WriteString(h, version.Build)
io.WriteString(h, version.Hostname)
- io.WriteString(h, version.GoVersion)
io.WriteString(h, version.Os)
io.WriteString(h, version.Arch)
return h.Sum(nil)
}
Index: docs/manual/00000000000100.zettel
==================================================================
--- docs/manual/00000000000100.zettel
+++ docs/manual/00000000000100.zettel
@@ -4,10 +4,8 @@
syntax: none
default-copyright: (c) 2020-2021 by Detlef Stern ... ")
v.acceptInlineSlice(pn.Inlines)
- v.b.WriteString("
-modified: 20210111182407
site-name: Zettelstore Manual
-start: 00001000000000
visibility: owner
DELETED docs/manual/00001000000000.zettel
Index: docs/manual/00001000000000.zettel
==================================================================
--- docs/manual/00001000000000.zettel
+++ /dev/null
@@ -1,22 +0,0 @@
-id: 00001000000000
-title: Zettelstore Manual
-role: manual
-tags: #manual #zettelstore
-syntax: zmk
-modified: 20210126174156
-
-* [[Introduction|00001001000000]]
-* [[Design goals|00001002000000]]
-* [[Installation|00001003000000]]
-* [[Configuration|00001004000000]]
-* [[Structure of Zettelstore|00001005000000]]
-* [[Layout of a zettel|00001006000000]]
-* [[Zettelmarkup|00001007000000]]
-* [[Other markup languages|00001008000000]]
-* [[Security|00001010000000]]
-* [[API|00001012000000]]
-* [[Web user interface|00001014000000]]
-* Troubleshooting
-* Frequently asked questions
-
-Licensed under the EUPL-1.2-or-later.
Index: docs/manual/00001001000000.zettel
==================================================================
--- docs/manual/00001001000000.zettel
+++ docs/manual/00001001000000.zettel
@@ -1,11 +1,10 @@
id: 00001001000000
title: Introduction to the Zettelstore
role: manual
tags: #introduction #manual #zettelstore
syntax: zmk
-modified: 20210126170856
[[Personal knowledge
management|https://en.wikipedia.org/wiki/Personal_knowledge_management]] is
about collecting, classifying, storing, searching, retrieving, assessing,
evaluating, and sharing knowledge as a daily activity. Personal knowledge
Index: docs/manual/00001002000000.zettel
==================================================================
--- docs/manual/00001002000000.zettel
+++ docs/manual/00001002000000.zettel
@@ -1,5 +1,6 @@
+id: 00001002000000
title: Design goals for the Zettelstore
tags: #design #goal #manual #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001003000000.zettel
==================================================================
--- docs/manual/00001003000000.zettel
+++ docs/manual/00001003000000.zettel
@@ -1,10 +1,10 @@
+id: 00001003000000
title: Installation of the Zettelstore software
role: manual
tags: #installation #manual #zettelstore
syntax: zmk
-modified: 20201221142822
=== The curious user
You just want to check out the Zettelstore software
* Grab the appropriate executable and copy it into any directory
@@ -11,13 +11,13 @@
* Start the Zettelstore software, e.g. with a double click
* A sub-directory ""zettel"" will be created in the directory where you placed the executable.
It will contain your future zettel.
* Open the URI [[http://localhost:23123]] with your web browser.
It will present you a mostly empty Zettelstore.
- There will be a zettel titled ""Welcome to Zettelstore"" that contains some helpful information.
+ There will be a zettel titled ""[[Home|00010000000000]]"" that contains some helpful information.
* Please read the instructions for the web-based user interface and learn about the various ways to write zettel.
-* If you restart your device, please make sure to start your Zettelstore.
+* If you restart your device, please make sure to start your Zettelstore again.
=== The intermediate user
You already tried the Zettelstore software and now you want to use it permanently.
* Grab the appropriate executable and copy it into the appropriate directory
Index: docs/manual/00001004000000.zettel
==================================================================
--- docs/manual/00001004000000.zettel
+++ docs/manual/00001004000000.zettel
@@ -1,11 +1,10 @@
id: 00001004000000
title: Configuration of Zettelstore
role: manual
tags: #configuration #manual #zettelstore
syntax: zmk
-modified: 20210125195740
There are two levels to change the behavior and/or the appearance of Zettelstore.
The first level is the configuration that is needed to start the services provided by Zettelstore.
For example, this includes the URI under which your Zettelstore is accessible.
* [[Zettelstore start-up configuration|00001004010000]]
Index: docs/manual/00001004010000.zettel
==================================================================
--- docs/manual/00001004010000.zettel
+++ docs/manual/00001004010000.zettel
@@ -1,11 +1,10 @@
id: 00001004010000
title: Zettelstore start-up configuration
role: manual
tags: #configuration #manual #zettelstore
syntax: zmk
-modified: 20201226183537
The configuration file, as specified by the ''-c CONFIGFILE'' [[command line option|00001004051000]], allows you to specify some start-up options.
These options cannot be stored in a [[configuration zettel|00001004020000]] because either they are needed before Zettelstore can start or because of security reasons.
For example, Zettelstore need to know in advance, on which network address is must listen or where zettel are placed.
An attacker that is able to change the owner can do anything.
@@ -40,11 +39,11 @@
Its lifetime exceeds the lifetime of the authentication token (see option ''token-lifetime-html'') by 30 seconds.
Default: ''false''
; [!place-X-uri]''place-//X//-uri'', where //X// is a number greater or equal to one
: Specifies a [[place|00001004011200]] where zettel are stored.
- During startup //X// is counted, starting with one, until no key is found.
+ During start-up //X// is counted, starting with one, until no key is found.
This allows to configure more than one place.
If no ''place-1-uri'' key is given, the overall effect will be the same as if only ''place-1-uri'' was specified with the value ''dir://.zettel''.
In this case, even a key ''place-2-uri'' will be ignored.
; [!read-only-mode]''read-only-mode''
@@ -61,14 +60,14 @@
''token-lifetime-html'' specifies the lifetime for the HTML views.
Default: 60.
It is automatically extended, when a new HTML view is rendered.
; [!url-prefix]''url-prefix''
: Add the given string as a prefix to the local part of a Zettelstore local URL/URI when rendering zettel representations.
- Must start and end with a slash character (""''/''"", ''U+002F'').
+ Must begin and end with a slash character (""''/''"", ''U+002F'').
Default: ''"/"''.
This allows to use a forwarding proxy [[server|00001010090100]] in front of the Zettelstore.
; ''verbose''
: Be more verbose inf logging data.
Default: false
Other keys will be ignored.
Index: docs/manual/00001004011200.zettel
==================================================================
--- docs/manual/00001004011200.zettel
+++ docs/manual/00001004011200.zettel
@@ -1,5 +1,6 @@
+id: 00001004011200
title: Zettelstore places
tags: #configuration #manual #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001004011400.zettel
==================================================================
--- docs/manual/00001004011400.zettel
+++ docs/manual/00001004011400.zettel
@@ -1,5 +1,6 @@
+id: 00001004011400
title: Configure file directory places
tags: #configuration #manual #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001004020000.zettel
==================================================================
--- docs/manual/00001004020000.zettel
+++ docs/manual/00001004020000.zettel
@@ -1,11 +1,10 @@
id: 00001004020000
title: Configure the running Zettelstore
role: manual
tags: #configuration #manual #zettelstore
syntax: zmk
-modified: 20201231131204
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:
@@ -47,24 +46,24 @@
; [!footer-html]''footer-html''
: Contains some HTML code that will be included into the footer of each Zettelstore web page.
It only affects the [[web user interface|00001014000000]].
Zettel content, delivered via the [[API|00001012000000]] as JSON, etc. is not affected.
Default: (the empty string).
+; [!home-zettel]''home-zettel''
+: Specifies the identifier of the zettel, that should be presented for the default view / home view.
+ If not given or if the identifier does not identify a zettel, the zettel with the identifier ''00010000000000'' is shown.
; [!marker-external]''marker-external''
: Some HTML code that is displayed after a reference to external material.
- Default: ''&\#8599;&\#xfe0e;'', to display a ""↗︎"" sign[^The string ''&\#xfe0e;'' is needed to enforce the sign on all platforms.].
+ Default: ''&\#10138;'', to display a ""➚"" sign.
; [!list-page-size]''list-page-size''
: If set to a value greater than zero, specifies the number of items shown in WebUI lists.
Basically, this is the list of all zettel (possibly restricted) and the list of search results.
Default: ''0''.
; [!site-name]''site-name''
: Name of the Zettelstore instance.
Will be used when displaying some lists.
Default: ''Zettelstore''.
-; [!start]''start''
-: Specifies the ID of the zettel, that should be presented for the default view.
- If not given or if the ID does not identify a zettel, the list of all zettel is shown.
; [!yaml-header]''yaml-header''
: If true, 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
Index: docs/manual/00001004050000.zettel
==================================================================
--- docs/manual/00001004050000.zettel
+++ docs/manual/00001004050000.zettel
@@ -1,11 +1,10 @@
id: 00001004050000
title: Command line parameters
role: manual
tags: #command #configuration #manual #zettelstore
syntax: zmk
-modified: 20210104115555
Zettelstore is not just a web service that provides services of a zettelkasten.
It allows to some tasks to be executed at the command line.
Typically, the task (""sub-command"") will be given at the command line as the first parameter.
Index: docs/manual/00001004050200.zettel
==================================================================
--- docs/manual/00001004050200.zettel
+++ docs/manual/00001004050200.zettel
@@ -1,11 +1,10 @@
id: 00001004050200
title: The ''help'' sub-command
role: manual
tags: #command #configuration #manual #zettelstore
syntax: zmk
-modified: 20210104115646
precursor: 00001004050000
Lists all implemented sub-commands.
Example:
Index: docs/manual/00001004050400.zettel
==================================================================
--- docs/manual/00001004050400.zettel
+++ docs/manual/00001004050400.zettel
@@ -1,11 +1,10 @@
id: 00001004050400
title: The ''version'' sub-command
role: manual
tags: #command #configuration #manual #zettelstore
syntax: zmk
-modified: 20210104115659
precursor: 00001004050000
Emits some information about the Zettelstore's version.
This allows you to check, whether your installed Zettelstore is
Index: docs/manual/00001004050600.zettel
==================================================================
--- docs/manual/00001004050600.zettel
+++ docs/manual/00001004050600.zettel
@@ -1,11 +1,10 @@
id: 00001004050600
title: The ''config'' sub-command
role: manual
tags: #command #configuration #manual #zettelstore
syntax: zmk
-modified: 20210104115712
precursor: 00001004050000
Shows the Zettelstore configuration, for debugging purposes.
Currently, only the [[start-up configuration|00001004010000]] is shown.
Index: docs/manual/00001004051000.zettel
==================================================================
--- docs/manual/00001004051000.zettel
+++ docs/manual/00001004051000.zettel
@@ -1,11 +1,10 @@
id: 00001004051000
title: The ''run'' sub-command
role: manual
tags: #command #configuration #manual #zettelstore
syntax: zmk
-modified: 20210104115719
precursor: 00001004050000
=== ``zettelstore run``
This starts the web service.
@@ -40,8 +39,8 @@
No changes are possible via the web interface / via the API.
This allows to publish your content without any risks of unauthorized changes.
; ''-v''
: Be more verbose in writing logs.
- Writes the startup configuration to stderr.
+ Writes the start-up configuration to stderr.
Command line options take precedence over configuration file options.
Index: docs/manual/00001004051100.zettel
==================================================================
--- docs/manual/00001004051100.zettel
+++ docs/manual/00001004051100.zettel
@@ -1,11 +1,10 @@
id: 00001004051100
title: The ''run-simple'' sub-command
role: manual
tags: #command #configuration #manual #zettelstore
syntax: zmk
-modified: 20210104115448
precursor: 00001004050000
=== ``zettelstore run-simple``
This sub-command is implicitly called, when an user starts Zettelstore by double-clicking on its GUI icon.
It is s simplified variant of the [[''run'' sub-command|00001004051000]].
Index: docs/manual/00001004051200.zettel
==================================================================
--- docs/manual/00001004051200.zettel
+++ docs/manual/00001004051200.zettel
@@ -1,11 +1,10 @@
id: 00001004051200
title: The ''file'' sub-command
role: manual
tags: #command #configuration #manual #zettelstore
syntax: zmk
-modified: 20210104115726
precursor: 00001004050000
Reads zettel data from a file (or from standard input / stdin) and renders it to standard output / stdout.
This allows Zettelstore to render files manually.
```
Index: docs/manual/00001004051400.zettel
==================================================================
--- docs/manual/00001004051400.zettel
+++ docs/manual/00001004051400.zettel
@@ -1,11 +1,10 @@
id: 00001004051400
title: The ''password'' sub-command
role: manual
tags: #command #configuration #manual #zettelstore
syntax: zmk
-modified: 20210104115737
precursor: 00001004050000
This sub-command is used to create a hashed password for to be authenticated users.
It reads a password from standard input (two times, both must be equal) and writes the hashed password to standard output.
Index: docs/manual/00001005000000.zettel
==================================================================
--- docs/manual/00001005000000.zettel
+++ docs/manual/00001005000000.zettel
@@ -1,11 +1,10 @@
id: 00001005000000
title: Structure of Zettelstore
role: manual
tags: #design #manual #zettelstore
syntax: zmk
-modified: 20210125195908
Zettelstore is a software that manages your zettel.
Since every zettel must be readable without any special tool, most zettel has to be stored as ordinary files within specific directories.
Typically, file names and file content must comply to specific rules so that Zettelstore can manage them.
If you add, delete, or change zettel files with other tools, e.g. a text editor, Zettelstore will monitor these actions.
@@ -21,14 +20,14 @@
=== Where zettel are stored
Your zettel are stored as files in a specific directory.
If you have not explicitly specified the directory, a default directory will be used.
-The directory has to be specified at [[startup time|00001004010000]].
+The directory has to be specified at [[start-up time|00001004010000]].
Nested directories are not supported (yet).
-Every file in this directory that should be monitored by Zettelstore must have a file name that starts with 14 digits (0-9), the [[zettel identifier|00001006050000]].
+Every file in this directory that should be monitored by Zettelstore must have a file name that begins with 14 digits (0-9), the [[zettel identifier|00001006050000]].
If you create a new zettel via the web interface or the API, the zettel identifier will be the timestamp of the current date and time (format is ''YYYYMMDDhhmmss'').
This allows zettel to be sorted naturally by creation time.
Since the only restriction on zettel identifiers are the 14 digits, you are free to use other digit sequences.
The [[configuration zettel|00001004020000]] is one prominent example, as well as these manual zettel.
@@ -51,11 +50,11 @@
In case of some textual zettel content you do not want to store the metadata and the zettel content in two different files.
Here the ''.zettel'' extension will signal that the metadata and the zettel content will be placed in the same file, separated by an empty line or a line with three dashes (""''-\-\-''"", also known as ""YAML separator"").
=== Predefined zettel
-Zettelstore contains some predefined zettel to work properly.
+Zettelstore contains some [[predefined zettel|00001005090000]] to work properly.
The [[configuration zettel|00001004020000]] is one example.
To render the builtin web interface, some templates are used, as well as a layout specification in CSS.
The icon that visualizes an external link is a predefined SVG image.
All of these are visible to the Zettelstore as zettel.
Index: docs/manual/00001005090000.zettel
==================================================================
--- docs/manual/00001005090000.zettel
+++ docs/manual/00001005090000.zettel
@@ -1,11 +1,10 @@
id: 00001005090000
title: List of predefined zettel
role: manual
tags: #manual #reference #zettelstore
syntax: zmk
-modified: 20210126114739
The following table lists all predefined zettel with their purpose.
|= Identifier :|= Title | Purpose
| [[00000000000001]] | Zettelstore Version | Contains the version string of the running Zettelstore
@@ -14,12 +13,12 @@
| [[00000000000006]] | Zettelstore Environment Values | Contains environmental data of Zettelstore executable
| [[00000000000008]] | Zettelstore Runtime Values | Contains values that reflect the inner working; see [[here|https://golang.org/pkg/runtime/]] for a technical description of these values
| [[00000000000018]] | Zettelstore Indexer | Provides some statistics about the index process
| [[00000000000020]] | Zettelstore Place Manager | Contains some statistics about zettel places
| [[00000000000090]] | Zettelstore Supported Metadata Keys | Contains all supported metadata keys, their [[types|00001006030000]], and more
-| [[00000000000096]] | Zettelstore Startup Configuration | Contains the effective values of the [[startup configuration|00001004010000]]
-| [[00000000000098]] | Zettelstore Startup Values | Contains all values computed from the [[startup configuration|00001004010000]]
+| [[00000000000096]] | Zettelstore Start-up Configuration | Contains the effective values of the [[start-up configuration|00001004010000]]
+| [[00000000000098]] | Zettelstore Start-up Values | Contains all values computed from the [[start-up configuration|00001004010000]]
| [[00000000000100]] | Zettelstore Runtime Configuration | Allows to [[configure Zettelstore at runtime|00001004020000]]
| [[00000000010100]] | Zettelstore Base HTML Template | Contains the general layout of the HTML view
| [[00000000010200]] | Zettelstore Login Form HTML Template | Layout of the login form, when authentication is [[enabled|00001010040100]]
| [[00000000010300]] | Zettelstore List Meta HTML Template | Used when displaying a list of zettel
| [[00000000010401]] | Zettelstore Detail HTML Template | Layout for the HTML detail view of one zettel
@@ -28,11 +27,13 @@
| [[00000000010404]] | Zettelstore Rename Form HTML Template | View that is displayed to change the [[zettel identifier|00001006050000]]
| [[00000000010405]] | Zettelstore Delete HTML Template | View to confirm the deletion of a zettel
| [[00000000010500]] | Zettelstore List Roles HTML Template | Layout for listing all roles
| [[00000000010600]] | Zettelstore List Tags HTML Template | Layout of tags lists
| [[00000000020001]] | Zettelstore Base CSS | CSS file that is included by the [[Base HTML Template|00000000010100]]
-| [[00000000091001]] | New Zettel | Template for a new zettel with role ""[[zettel|00001006020100]]""
-| [[00000000096001]] | New User | Template for a new zettel with role ""[[user|00001006020100#user]]""
+| [[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]]""
+| [[00000000090002]] | New User | Template for a new zettel with role ""[[user|00001006020100#user]]""
+| [[00010000000000]] | Home | Default home zettel, contains some welcome information
If a zettel is not linked, it is not accessible for the current user.
**Important:** The identifier may change until a stable version of the software is released.
Index: docs/manual/00001006000000.zettel
==================================================================
--- docs/manual/00001006000000.zettel
+++ docs/manual/00001006000000.zettel
@@ -1,5 +1,6 @@
+id: 00001006000000
title: Layout of a Zettel
tags: #design #manual #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001006010000.zettel
==================================================================
--- docs/manual/00001006010000.zettel
+++ docs/manual/00001006010000.zettel
@@ -1,23 +1,24 @@
+id: 00001006010000
title: Syntax of Metadata
tags: #manual #syntax #zettelstore
syntax: zmk
role: manual
The metadata of a zettel is a collection of key-value pairs.
The syntax roughly resembles the internal header of an email ([[RFC5322|https://tools.ietf.org/html/rfc5322]]).
The key is a sequence of alphanumeric characters, a hyphen-minus character (""''-''"") is also allowed.
-It starts at a new line.
+It begins at the first position of a new line.
A key is separated from its value either by
-* a colon character (""'':''""),
-* a non-empty sequence of space characters,
+* a colon character (""'':''""),
+* a non-empty sequence of space characters,
* a sequence of space characters, followed by a colon, followed by a sequence of space characters.
A Value is a sequence of printable characters.
-If the value should be continued in the following line, that following line (//continuation line//) must start with a non-empty sequence of space characters.
+If the value should be continued in the following line, that following line (//continuation line//) must begin with a non-empty sequence of space characters.
The rest of the following line will be interpreted as the next part of the value.
There can be more than one continuation line for a value.
A non-continuation line that contains a possibly empty sequence of characters, followed by the percent sign character (""''%''"") is treated as a comment line.
It will be ignored.
Index: docs/manual/00001006020000.zettel
==================================================================
--- docs/manual/00001006020000.zettel
+++ docs/manual/00001006020000.zettel
@@ -1,11 +1,10 @@
id: 00001006020000
title: Supported Metadata Keys
role: manual
tags: #manual #meta #reference #zettel #zettelstore
syntax: zmk
-modified: 20210123223645
Although you are free to define your own metadata, by using any key (according to the [[syntax|00001006010000]]), some keys have a special meaning that is enforced by Zettelstore.
See the [[computed list of supported metadata keys|00000000000090]] for details.
Most keys conform to a [[type|00001006030000]].
@@ -74,11 +73,11 @@
: Specifies the syntax that should be used for interpreting the zettel.
The zettel about [[other markup languages|00001008000000]] defines supported values.
If not given, the value ''default-syntax'' from the [[configuration zettel|00001004020000#default-syntax]] will be used.
; [!tags]''tags''
: Contains a space separated list of tags to describe the zettel further.
- Each Tag must start with the number sign character (""''#''"", ''U+0023'').
+ Each Tag must begin with the number sign character (""''#''"", ''U+0023'').
; [!title]''title''
: Specifies the title of the zettel.
If not given, the value ''default-title'' from the [[configuration zettel|00001004020000#default-title]] will be used.
You can use all [[inline-structured elements|00001007040000]] of Zettelmarkup.
@@ -99,11 +98,5 @@
; [!visibility]''visibility''
: When you work with authentication, you can give every zettel a value to decide, who can see the zettel.
Its default value can be set with [[''default-visibility''|00001004020000#default-visibility]] of the configuration zettel.
See [[visibility rules for zettel|00001010070200]] for more details.
-
----
-Not yet supported, but planned:
-
-; [!folge]''folge''
-: The IDs of zettel that acts as a [[Folgezettel|https://zettelkasten.de/posts/tags/folgezettel/]].
Index: docs/manual/00001006020100.zettel
==================================================================
--- docs/manual/00001006020100.zettel
+++ docs/manual/00001006020100.zettel
@@ -1,20 +1,18 @@
+id: 00001006020100
title: Supported Zettel Roles
+role: manual
tags: #manual #meta #reference #zettel #zettelstore
syntax: zmk
-role: manual
The [[''role'' key|00001006020000#role]] defines what kind of zettel you are writing.
-The following values are used by Zettelstore:
+The following values are used internally by Zettelstore and must exist:
-; [!new-template]''new-template''
-: Zettel with this role are used as templates for creating new zettel.
- Within such a zettel, the metadata key [[''new-role''|00001006020000#new-role]] is used to specify the role of the new zettel.
; [!user]''user''
: If you want to use [[authentication|00001010000000]], all zettel that identify users of the zettel store must have this role.
-Beside this, you are free to define your own roles.
+Beside of this, you are free to define your own roles.
The role ''zettel'' is predefined as the default role, but you can [[change this|00001004020000#default-role]].
Some roles are defined for technical reasons:
Index: docs/manual/00001006020400.zettel
==================================================================
--- docs/manual/00001006020400.zettel
+++ docs/manual/00001006020400.zettel
@@ -1,30 +1,40 @@
+id: 00001006020400
title: Supported values for metadata key ''read-only''
role: manual
tags: #manual #meta #reference #zettel #zettelstore
syntax: zmk
-A zettel can be marked as read-only, if it contains a metadata value for key [[''read-only''|00001006020000#read-only]].
-If user authentication is [[enabled|00001010040100]], it is possible to allow some users to change the zettel, depending on their [[user role|00001010070300]].
+A zettel can be marked as read-only, if it contains a metadata value for key
+[[''read-only''|00001006020000#read-only]].
+If user authentication is [[enabled|00001010040100]], it is possible to allow some users to change the zettel,
+depending on their [[user role|00001010070300]].
Otherwise, the read-only mark is just a binary value.
=== No authentication
-If there is no metadata value for key ''read-only'' or if its [[boolean value|00001006030000]] is interpreted as ""false"", anybody can modify the zettel.
+If there is no metadata value for key ''read-only'' or if its [[boolean value|00001006030500]]
+is interpreted as ""false"", anybody can modify the zettel.
-If the metadata value is something else (the value ""true"" is recommended), the user cannot modify the zettel through the web interface.
-However, if the zettel is stored as a file in a [[directory place|00001004011400]], the zettel could be modified using an external editor.
+If the metadata value is something else (the value ""true"" is recommended),
+the user cannot modify the zettel through the web interface.
+However, if the zettel is stored as a file in a [[directory place|00001004011400]],
+the zettel could be modified using an external editor.
=== Authentication enabled
-If there is no metadata value for key ''read-only'' or if its [[boolean value|00001006030000]] is interpreted as ""false"", anybody can modify the zettel.
+If there is no metadata value for key ''read-only'' or if its [[boolean value|00001006030500]]
+is interpreted as ""false"", anybody can modify the zettel.
-If the metadata value is the same as an explicit [[user role|00001010070300]], user with that role (or below) are not allowed to modify the zettel.
+If the metadata value is the same as an explicit [[user role|00001010070300]],
+users with that role (or a role with lower rights) are not allowed to modify the zettel.
-; ''reader''
+; ""reader""
: Neither an unauthenticated user nor a user with role ""reader"" is allowed to modify the zettel.
Users with role ""writer"" or the owner itself still can modify the zettel.
-; ''writer''
+; ""writer""
: Neither an unauthenticated user, nor users with roles ""reader"" or ""writer"" are allowed to modify the zettel.
Only the owner of the Zettelstore can modify the zettel.
-If the metadata value is something else (the value ""owner"" is recommended), no user is allowed modify the zettel through the web interface.
-However, if the zettel is accessible as a file in a [[directory place|00001004011400]], the zettel could be modified using an external editor.
-Typically the owner of a Zettelstore should have such an access.
+If the metadata value is something else (one of the values ""true"" or ""owner"" is recommended),
+no user is allowed modify the zettel through the web interface.
+However, if the zettel is accessible as a file in a [[directory place|00001004011400]],
+the zettel could be modified using an external editor.
+Typically the owner of a Zettelstore have such an access.
Index: docs/manual/00001006030000.zettel
==================================================================
--- docs/manual/00001006030000.zettel
+++ docs/manual/00001006030000.zettel
@@ -1,31 +1,29 @@
id: 00001006030000
title: Supported Key Types
role: manual
tags: #manual #meta #reference #zettel #zettelstore
syntax: zmk
-modified: 20210108184053
Most [[supported metadata keys|00001006020000]] conform to a type.
Every metadata key should conform to a type.
-Every key type is specified by a letter.
-User-defined types are normally strings (type ''e'').
-
-Every key type has an associated validation rule to check values of the given
-type. There is also a rule how values are matched, e.g. against a search term
-when selecting some zettel. And there is a rule, how values compare for
-sorting.
-
-|= Name | Meaning | Match | Sorting
-| Boolean | Boolean value, False if value starts with ""''0''"", ""''F''"", ""''N''"", ""''f''"", or ""''n''"" | Boolean match | False < True
-| Credential | Value is a credential, e.g. an encrypted password (planned) | Never matches | Uses zettel identifier for sorting
-| Timestamp | Timestamp value YYYYMMDDHHmmSS | prefix match | by number
-| EString | Any string, possibly empty | case-insensitive contains | case-sensitive
-| Identifier | Value is a [[zettel identifier|00001006050000]] | prefix match | by number
-| Number | Integer value | exact match | by number
-| String | Any string, must not be empty | case-insensitive contains | case-sensitive
-| TagSet | Value is a space-separated list of tags | exact match for one tag | case sensitive by sorted tags
-| Word | Alfanumeric word, case-insensitive | case-insensitive equality | case-sensitive
-| WordSet | Space-separated list of alfanumeric words, case-insensitive | case-insensitive match for one word | case-sensitive by sorted words
-| URL | URL / URI | case-insensitive contains | case-sensitive
-| Zettelmarkup | Any string, must not be empty, formatted in [[Zettelmarkup|00001007000000]] | case-insensitive contains | case-sensitive
+User-defined metadata keys are of type EString.
+The name of the metadata key is bound to the key type
+
+Every key type has an associated validation rule to check values of the given type.
+There is also a rule how values are matched, e.g. against a search term when selecting some zettel.
+And there is a rule, how values compare for sorting.
+
+* [[Boolean|00001006030500]]
+* [[Credential|00001006031000]]
+* [[EString|00001006031500]]
+* [[Identifier|00001006032000]]
+* [[IdentifierSet|00001006032500]]
+* [[Number|00001006033000]]
+* [[String|00001006033500]]
+* [[TagSet|00001006034000]]
+* [[Timestamp|00001006034500]]
+* [[URL|00001006035000]]
+* [[Word|00001006035500]]
+* [[WordSet|00001006036000]]
+* [[Zettelmarkup|00001006036500]]
ADDED docs/manual/00001006030500.zettel
Index: docs/manual/00001006030500.zettel
==================================================================
--- /dev/null
+++ docs/manual/00001006030500.zettel
@@ -0,0 +1,21 @@
+id: 00001006030500
+title: Boolean Key Type
+role: manual
+tags: #manual #meta #reference #zettel #zettelstore
+syntax: zmk
+
+Values of this type denote a truth value.
+
+=== Allowed values
+Every character sequence that begins with a ""''0''"", ""''F''"", ""''N''"", ""''f''"", or a ""''n''"" is interpreted as the ""false"" boolean value.
+All other metadata value is interpreted as the ""true"" boolean value.
+
+=== Match operator
+The match operator is the equals operator, i.e.
+* ``(true == true) == true``
+* ``(false == false) == true``
+* ``(true == false) == false``
+* ``(false == true) == false``
+
+=== Sorting
+The ""false"" value is less than the ""true"" value: ``false < true``
ADDED docs/manual/00001006031000.zettel
Index: docs/manual/00001006031000.zettel
==================================================================
--- /dev/null
+++ docs/manual/00001006031000.zettel
@@ -0,0 +1,17 @@
+id: 00001006031000
+title: Credential Key Type
+role: manual
+tags: #manual #meta #reference #zettel #zettelstore
+syntax: zmk
+
+Values of this type denote a credential value, e.g. an encrypted password.
+
+=== Allowed values
+All printable characters are allowed.
+Since a credential contains some kind of secret, the sequence of characters might have some hidden syntax to be interpreted by other parts of Zettelstore.
+
+=== Match operator
+A credential never matches to any other value.
+
+=== Sorting
+If a list of zettel should be sorted based on a credential value, the identifier of the respective zettel is used instead.
ADDED docs/manual/00001006031500.zettel
Index: docs/manual/00001006031500.zettel
==================================================================
--- /dev/null
+++ docs/manual/00001006031500.zettel
@@ -0,0 +1,26 @@
+id: 00001006031500
+title: EString Key Type
+role: manual
+tags: #manual #meta #reference #zettel #zettelstore
+syntax: zmk
+
+Values of this type are just a sequence of character, possibly an empty sequence.
+
+An EString is the most general metadata key type, as it places no restrictions to the character sequence.[^Well, there are some minor restrictions that follow from the [[metadata syntax|00001006010000]].]
+
+=== Allowed values
+All printable characters are allowed.
+
+=== Match operator
+A value matches an EString value, if the first value is part of the EString value.
+This check is done case-insensitive.
+
+For example, ""hell"" matches ""Hello"".
+
+=== Sorting
+To sort two values, the underlying encoding is used to determine which value is less than the other.
+
+Uppercase letters are typically interpreted as less than their corresponding lowercase letters, i.e. ``A < a``.
+
+Comparison is done character-wise by finding the first difference in the respective character sequence.
+For example, ``abc > aBc``.
ADDED docs/manual/00001006032000.zettel
Index: docs/manual/00001006032000.zettel
==================================================================
--- /dev/null
+++ docs/manual/00001006032000.zettel
@@ -0,0 +1,20 @@
+id: 00001006032000
+title: Identifier Key Type
+role: manual
+tags: #manual #meta #reference #zettel #zettelstore
+syntax: zmk
+
+Values of this type denote a [[zettel identifier|00001006050000]].
+
+=== Allowed values
+Must be a sequence of 14 digits (""0""--""9"").
+
+=== Match operator
+A value matches an identifier value, if the first value is the prefix of the identifier value.
+
+For example, ""000010"" matches ""[[00001006032000]]"".
+
+=== Sorting
+Sorting is done by comparing the [[String|00001006033500]] values.
+
+If both values are identifiers, this works well because both have the same length.
ADDED docs/manual/00001006032500.zettel
Index: docs/manual/00001006032500.zettel
==================================================================
--- /dev/null
+++ docs/manual/00001006032500.zettel
@@ -0,0 +1,20 @@
+id: 00001006032500
+title: IdentifierSet Key Type
+role: manual
+tags: #manual #meta #reference #zettel #zettelstore
+syntax: zmk
+
+Values of this type denote a (sorted) set of [[zettel identifier|00001006050000]].
+
+A set is different to a list, as no duplicates are allowed.
+
+=== Allowed values
+Must be at least one sequence of 14 digits (""0""--""9""), separated by space characters.
+
+=== Match operator
+A value matches an identifier set value, if the first value is a prefix of one of the identifier value.
+
+For example, ""000010"" matches ""[[00001006032000]] [[00001006032500]]"".
+
+=== Sorting
+Sorting is done by comparing the [[String|00001006033500]] values.
ADDED docs/manual/00001006033000.zettel
Index: docs/manual/00001006033000.zettel
==================================================================
--- /dev/null
+++ docs/manual/00001006033000.zettel
@@ -0,0 +1,18 @@
+id: 00001006033000
+title: Number Key Type
+role: manual
+tags: #manual #meta #reference #zettel #zettelstore
+syntax: zmk
+
+Values of this type denote a numeric integer value.
+
+=== Allowed values
+Must be a sequence of digits (""0""--""9""), optionally prefixed with a ""-"" or a ""+"" character.
+
+=== Match operator
+The match operator is the equals operator, i.e. two values must be numeric equal to match.
+
+This includes that ""+12"" is equal to ""12"", therefore both values match.
+
+=== Sorting
+Sorting is done by comparing the numeric values.
ADDED docs/manual/00001006033500.zettel
Index: docs/manual/00001006033500.zettel
==================================================================
--- /dev/null
+++ docs/manual/00001006033500.zettel
@@ -0,0 +1,25 @@
+id: 00001006033500
+title: String Key Type
+role: manual
+tags: #manual #meta #reference #zettel #zettelstore
+syntax: zmk
+
+Values of this type are just a sequence of character, but not an empty sequence.
+
+=== Allowed values
+All printable characters are allowed.
+There must be at least one such character.
+
+=== Match operator
+A value matches a String value, if the first value is part of the String value.
+This check is done case-insensitive.
+
+For example, ""hell"" matches ""Hello"".
+
+=== Sorting
+To sort two values, the underlying encoding is used to determine which value is less than the other.
+
+Uppercase letters are typically interpreted as less than their corresponding lowercase letters, i.e. ``A < a``.
+
+Comparison is done character-wise by finding the first difference in the respective character sequence.
+For example, ``abc > aBc``.
ADDED docs/manual/00001006034000.zettel
Index: docs/manual/00001006034000.zettel
==================================================================
--- /dev/null
+++ docs/manual/00001006034000.zettel
@@ -0,0 +1,19 @@
+id: 00001006034000
+title: TagSet Key Type
+role: manual
+tags: #manual #meta #reference #zettel #zettelstore
+syntax: zmk
+
+Values of this type denote a (sorted) set of tags.
+
+A set is different to a list, as no duplicates are allowed.
+
+=== Allowed values
+Every tag must must begin with the number sign character (""''#''"", ''U+0023''), followed by at least one printable character.
+Tags are separated by space characters.
+
+=== Match operator
+A value matches a tag set value, if the first value is equal to at least one tag in the tag set.
+
+=== Sorting
+Sorting is done by comparing the [[String|00001006033500]] values.
ADDED docs/manual/00001006034500.zettel
Index: docs/manual/00001006034500.zettel
==================================================================
--- /dev/null
+++ docs/manual/00001006034500.zettel
@@ -0,0 +1,27 @@
+id: 00001006034500
+title: Timestamp Key Type
+role: manual
+tags: #manual #meta #reference #zettel #zettelstore
+syntax: zmk
+
+Values of this type denote a point in time.
+
+=== Allowed values
+Must be a sequence of 14 digits (""0""--""9"") (same as an [[Identifier|00001006032000]]), with the restriction that is conforms to the pattern ""YYYYMMDDhhmmss"".
+
+* YYYY is the year,
+* MM is the month,
+* DD is the day,
+* hh is the hour,
+* mm is the minute,
+* ss is the second.
+
+=== Match operator
+A value matches an timestampvalue, if the first value is the prefix of the timestamp value.
+
+For example, ""202102"" matches ""20210212143200"".
+
+=== Sorting
+Sorting is done by comparing the [[String|00001006033500]] values.
+
+If both values are timestamp values, this works well because both have the same length.
ADDED docs/manual/00001006035000.zettel
Index: docs/manual/00001006035000.zettel
==================================================================
--- /dev/null
+++ docs/manual/00001006035000.zettel
@@ -0,0 +1,19 @@
+id: 00001006035000
+title: URL Key Type
+role: manual
+tags: #manual #meta #reference #zettel #zettelstore
+syntax: zmk
+
+Values of this type denote an URL.
+
+=== Allowed values
+All characters of an URL / URI are allowed.
+
+=== Match operator
+A value matches a URL value, if the first value is part of the URL value.
+This check is done case-insensitive.
+
+For example, ""hell"" matches ""http://example.com/Hello"".
+
+=== Sorting
+Sorting is done by comparing the [[String|00001006033500]] values.
ADDED docs/manual/00001006035500.zettel
Index: docs/manual/00001006035500.zettel
==================================================================
--- /dev/null
+++ docs/manual/00001006035500.zettel
@@ -0,0 +1,16 @@
+id: 00001006035500
+title: Word Key Type
+role: manual
+tags: #manual #meta #reference #zettel #zettelstore
+syntax: zmk
+
+Values of this type denote a single word.
+
+=== Allowed values
+Must be a non-empty sequence of characters, but without the space character.
+
+=== Match operator
+A value matches a word value, if both value are character-wise equal.
+
+=== Sorting
+Sorting is done by comparing the [[String|00001006033500]] values.
ADDED docs/manual/00001006036000.zettel
Index: docs/manual/00001006036000.zettel
==================================================================
--- /dev/null
+++ docs/manual/00001006036000.zettel
@@ -0,0 +1,18 @@
+id: 00001006036000
+title: WordSet Key Type
+role: manual
+tags: #manual #meta #reference #zettel #zettelstore
+syntax: zmk
+
+Values of this type denote a (sorted) set of [[words|00001006035500]].
+
+A set is different to a list, as no duplicates are allowed.
+
+=== Allowed values
+Must be a sequence of at least one word, separated by space characters.
+
+=== Match operator
+A value matches an wordset value, if the first value is equal to one of the word values in the word set.
+
+=== Sorting
+Sorting is done by comparing the [[String|00001006033500]] values.
ADDED docs/manual/00001006036500.zettel
Index: docs/manual/00001006036500.zettel
==================================================================
--- /dev/null
+++ docs/manual/00001006036500.zettel
@@ -0,0 +1,25 @@
+id: 00001006036500
+title: Zettelmarkup Key Type
+role: manual
+tags: #manual #meta #reference #zettel #zettelstore
+syntax: zmk
+
+Values of this type are [[String|00001006033500]] values, interpreted as [[Zettelmarkup|00001007000000]].
+
+=== Allowed values
+All printable characters are allowed.
+There must be at least one such character.
+
+=== Match operator
+A value matches a String value, if the first value is part of the String value.
+This check is done case-insensitive.
+
+For example, ""hell"" matches ""Hello"".
+
+=== Sorting
+To sort two values, the underlying encoding is used to determine which value is less than the other.
+
+Uppercase letters are typically interpreted as less than their corresponding lowercase letters, i.e. ``A < a``.
+
+Comparison is done character-wise by finding the first difference in the respective character sequence.
+For example, ``abc > aBc``.
Index: docs/manual/00001006050000.zettel
==================================================================
--- docs/manual/00001006050000.zettel
+++ docs/manual/00001006050000.zettel
@@ -1,5 +1,6 @@
+id: 00001006050000
title: Zettel identifier
tags: #design #manual #zettelstore
syntax: zmk
role: manual
@@ -6,17 +7,21 @@
Each zettel is given a unique identifier.
To some degree, the zettel identifier is part of the metadata.
Basically, the identifier is given by the [[Zettelstore|00001005000000]] software.
Every zettel identifier consists of 14 digits.
-They resemble a timestamp: the first four digits could represent the year, the next two represent the month, following by day, hour, minute, and second.
+They resemble a timestamp: the first four digits could represent the year, the
+next two represent the month, following by day, hour, minute, and second.
This allows to order zettel chronologically in a canonical way.
In most cases the zettel identifier is the timestamp when the zettel was created.
However, the Zettelstore software just checks for exactly 14 digits.
-Anybody is free to assign a ""non-timestamp"" identifier to a zettel, e.g. with a month part of ""35"" or with ""99"" as the last two digits.
-In fact, all identifiers of zettel initially provided by an empty Zettelstore begin with ""000000"".
-The identifiers of zettel if this manual have be chosen to start with ""000010"".
+Anybody is free to assign a ""non-timestamp"" identifier to a zettel, e.g. with
+a month part of ""35"" or with ""99"" as the last two digits.
+In fact, all identifiers of zettel initially provided by an empty Zettelstore
+begin with ""000000"", except the home zettel ''00010000000000''.
+The identifiers of zettel if this manual have be chosen to begin with ""000010"".
-A zettel can have any identifier that contains 14 digits and that is not in use by another zettel managed by the same Zettelstore.
+A zettel can have any identifier that contains 14 digits and that is not in use
+by another zettel managed by the same Zettelstore.
Index: docs/manual/00001007000000.zettel
==================================================================
--- docs/manual/00001007000000.zettel
+++ docs/manual/00001007000000.zettel
@@ -1,5 +1,6 @@
+id: 00001007000000
title: Zettelmarkup
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001007010000.zettel
==================================================================
--- docs/manual/00001007010000.zettel
+++ docs/manual/00001007010000.zettel
@@ -1,56 +1,57 @@
+id: 00001007010000
title: Zettelmarkup: General Principles
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
Any document can be thought as a sequence of paragraphs and other blocks-structural elements (""blocks""), such as headings, lists, quotations, and code blocks.
Some of these blocks can contain other blocks, for example lists may contain other lists or paragraphs.
Other blocks contain inline-structural elements (""inlines""), such as text, links, emphasized text, and images.
-With the exception of lists and tables, the markup for blocks always starts at the first position of a line and starts with three or more identical characters.
-List blocks also starts at the first position of a line, but may need one or more character, plus a space character.
-Table blocks starts at the first position of a line with the character ""``|``"".
+With the exception of lists and tables, the markup for blocks always begins at the first position of a line with three or more identical characters.
+List blocks also begins at the first position of a line, but may need one or more character, plus a space character.
+Table blocks begins at the first position of a line with the character ""``|``"".
Non-list blocks are either fully specified on that line or they span multiple lines and are delimited with the same three or more character.
It depends on the block kind, whether blocks are specified on one line or on at least two lines.
-If a line does not start with an explicit block element. the line is treated as a (implicit) paragraph block element that contains inline elements.
+If a line does not begin with an explicit block element. the line is treated as a (implicit) paragraph block element that contains inline elements.
This paragraph ends when a block element is detected at the beginning of a next line or when an empty line occurs.
Some blocks may also contain inline elements, e.g. a heading.
-Inline elements mostly starts with two non-space, often identical characters.
-With some exceptions, two identical non-space characters starts a formatting range that is ended with the same two characters.
+Inline elements mostly begins with two non-space, often identical characters.
+With some exceptions, two identical non-space characters begins a formatting range that is ended with the same two characters.
Exceptions are: links, images, edits, comments, and both the ""en-dash"" and the ""horizontal ellipsis"".
A link is given with ``[[...]]``{=zmk}, an images with ``{{...}}``{=zmk}, and an edit formatting with ``((...))``{=zmk}.
-An inline comment, starting with the sequence ``%%``{=zmk}, always ends at the end of the line where it begins.
+An inline comment, beginning with the sequence ``%%``{=zmk}, always ends at the end of the line where it begins.
The ""en-dash"" (""--"") is specified as ``--``{=zmk}, the ""horizontal ellipsis"" (""..."") as ``...``{=zmk}[^If placed at the end of non-space text.].
Some inline elements do not follow the rule of two identical character, especially to specify footnotes, citation keys, and local marks.
-These elements start with one opening square bracket (""``[``""), use a character for specifying the kind of the inline, typically allow to specify some content, and end with one closing square bracket (""``]``"").
+These elements begin with one opening square bracket (""``[``""), use a character for specifying the kind of the inline, typically allow to specify some content, and end with one closing square bracket (""``]``"").
-One inline element that does not start with two characters is the ""entity"".
+One inline element that does not begin with two characters is the ""entity"".
It allows to specify any Unicode character.
The specification of that character is placed between an ampersand character and a semicolon: ``&...;``{=zmk}.
For exmple, an ""n-dash"" could also be specified as ``–``{==zmk}.
The backslash character (""``\\``"") possibly gives the next character a special meaning.
This allows to resolve some left ambiguities.
-For example, list of depth 2 will start a line with ``** Item 2.2``{=zmk}.
-An inline element to strongly emphasize some text start with a space will be specified as ``** Text**``{=zmk}.
-To force the inline element formatting at the start of a line, ``**\\ Text**``{=zmk} should better be specified.
+For example, a list of depth 2 will begin a line with ``** Item 2.2``{=zmk}.
+An inline element to strongly emphasize some text begin with a space will be specified as ``** Text**``{=zmk}.
+To force the inline element formatting at the beginning of a line, ``**\\ Text**``{=zmk} should better be specified.
Many block and inline elements can be refined by additional attributes.
Attributes resemble roughly HTML attributes and are placed near the corresponding elements by using the syntax ``{...}``{=zmk}.
One example is to make space characters visible inside a inline literal element: ``1 + 2 = 3``{-} was specified by using the default attribute: ``\`\`1 + 2 = 3\`\`{-}``.
To summarize:
-* With some exceptions, blocks-structural elements starts at the for position of a line with three identical characters.
+* With some exceptions, blocks-structural elements begins at the for position of a line with three identical characters.
* The most important exception to this rule is the specification of lists.
* If no block element is found, a paragraph with inline elements is assumed.
-* With some exceptions, inline-structural elements starts with two characters, quite often the same two characters.
+* With some exceptions, inline-structural elements begins with two characters, quite often the same two characters.
* The most important exceptions are links.
* The backslash character can help to resolve possible ambiguities.
* Attributes refine some block and inline elements.
* Block elements have a higher priority than inline elements.
Index: docs/manual/00001007020000.zettel
==================================================================
--- docs/manual/00001007020000.zettel
+++ docs/manual/00001007020000.zettel
@@ -1,5 +1,6 @@
+id: 00001007020000
title: Zettelmarkup: Basic Definitions
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001007030000.zettel
==================================================================
--- docs/manual/00001007030000.zettel
+++ docs/manual/00001007030000.zettel
@@ -1,11 +1,12 @@
+id: 00001007030000
title: Zettelmarkup: Blocks-Structured Elements
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
-Every markup for blocks-structured elements (""blocks"") starts at the very first position of a line.
+Every markup for blocks-structured elements (""blocks"") begins at the very first position of a line.
There are five kinds of blocks: lists, one-line blocks, line-range blocks, tables, and paragraphs.
=== Lists
@@ -24,12 +25,12 @@
=== Line-range blocks
This kind of blocks encompass at least two lines.
To be useful, they encompass more lines.
-They start with at least three identical characters at the first position of the beginning line.
-They end at the line, that contains at least the same number of these identical characters, starting at the first position of that line.
+They begin with at least three identical characters at the first position of the beginning line.
+They end at the line, that contains at least the same number of these identical characters, beginning at the first position of that line.
This allows line-range blocks to be nested.
Additionally, all other blocks elements are allowed in line-range blocks.
* [[Verbatim blocks|00001007030500]] do not interpret their content,
* [[Quotation blocks|00001007030600]] specify a block-length quotation,
@@ -43,11 +44,11 @@
A sequence of table rows is considered a [[table|00001007031000]].
A table row itself is a sequence of table cells.
=== Paragraphs
-Any line that does not conform to another blocks-structured element starts a paragraph.
+Any line that does not conform to another blocks-structured element begins a paragraph.
This has the implication that a mistyped syntax element for a block element will be part of the paragraph. For example:
```zmk
= Heading
Some text follows.
```
@@ -60,9 +61,9 @@
A paragraph is essentially a sequence of [[inline-structured elements|00001007040000]].
Inline-structured elements cam span more than one line.
Paragraphs are separated by empty lines.
-If you want to specify a second paragraph inside a list item, or if you want to continue a paragraph on a second and more line within a list item, you must start the paragraph with a certain number of space characters.
+If you want to specify a second paragraph inside a list item, or if you want to continue a paragraph on a second and more line within a list item, you must begin the paragraph with a certain number of space characters.
The number of space characters depends on the kind of a list and the relevant nesting level.
-A line that starts with a space character and which is outside of a list or does not contain the right number of space characters is considered to be part of a paragraph.
+A line that begins with a space character and which is outside of a list or does not contain the right number of space characters is considered to be part of a paragraph.
Index: docs/manual/00001007030100.zettel
==================================================================
--- docs/manual/00001007030100.zettel
+++ docs/manual/00001007030100.zettel
@@ -1,18 +1,19 @@
+id: 00001007030100
title: Zettelmarkup: Description Lists
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
A description list is a sequence of terms to be described together with the descriptions of each term.
Every term can described in multiple ways.
A description term (short: //term//) is specified with one semicolon (""'';''"", ''U+003B'') at the first position, followed by a space character and the described term, specified as a sequence of line elements.
-If the following lines should also be part of the term, exactly two spaces must be given at the start the each following line.
+If the following lines should also be part of the term, exactly two spaces must be given at the beginning of each following line.
The description of a term is given with one colon (""'':''"", ''U+003A'') at the first position, followed by a space character and the description itself, specified as a sequence of inline elements.
-Similar to terms, following lines can also be part of the actual description, if they start at each line with exactly two space characters.
+Similar to terms, following lines can also be part of the actual description, if they begin at each line with exactly two space characters.
In contrast to terms, the actual descriptions are merged into a paragraph.
This is because, an actual description can contain more than one paragraph.
As usual, paragraphs are separated by an empty line.
Every following paragraph of an actual description must be indented by two space characters.
Index: docs/manual/00001007030200.zettel
==================================================================
--- docs/manual/00001007030200.zettel
+++ docs/manual/00001007030200.zettel
@@ -1,5 +1,6 @@
+id: 00001007030200
title: Zettelmarkup: Nested Lists
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
@@ -9,11 +10,12 @@
Let's call these three characters //list characters//.
Any nested list item is specified by a non-empty sequence of list characters, followed by a space character and a sequence of inline elements.
In case of a quotation list as the last list character, the space character followed by a sequence of inline elements is optional.
The number / count of list characters gives the nesting of the lists.
-If the following lines should also be part of the list item, exactly the same number of spaces must be given at the start the each following line as it is the lists are nested, plus one additional space character. In other words: the inline elements must start at the same column as it was on the previous line.
+If the following lines should also be part of the list item, exactly the same number of spaces must be given at the beginning of each of the following lines as it is the lists are nested, plus one additional space character.
+In other words: the inline elements must begin at the same column as it was on the previous line.
The resulting sequence on inline elements is merged into a paragraph.
Appropriately indented paragraphs can specified after the first one.
Since each blocks-structured element has to be specified at the first position of a line, none of the nested list items may contain anything else than paragraphs.
Index: docs/manual/00001007030300.zettel
==================================================================
--- docs/manual/00001007030300.zettel
+++ docs/manual/00001007030300.zettel
@@ -1,12 +1,13 @@
+id: 00001007030300
title: Zettelmarkup: Headings
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
To specify a (sub-) section of a zettel, you should use the headings syntax: at
-the start of a new line type at least three equal signs (""''=''"", ''U+003D''), plus at least one
+the beginning of a new line type at least three equal signs (""''=''"", ''U+003D''), plus at least one
space and enter the text of the heading as inline elements.
```zmk
=== Level 1 Heading
==== Level 2 Heading
Index: docs/manual/00001007030400.zettel
==================================================================
--- docs/manual/00001007030400.zettel
+++ docs/manual/00001007030400.zettel
@@ -1,5 +1,6 @@
+id: 00001007030400
title: Zettelmarkup: Horizontal Rule
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001007030500.zettel
==================================================================
--- docs/manual/00001007030500.zettel
+++ docs/manual/00001007030500.zettel
@@ -1,22 +1,23 @@
+id: 00001007030500
title: Zettelmarkup: Verbatim Blocks
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
Verbatim blocks are used to enter text that should not be interpreted.
-They start with at least three grave accent characters (""''`''"", ''U+0060'') at the first position of a line.
+They begin with at least three grave accent characters (""''`''"", ''U+0060'') at the first position of a line.
Alternatively, a modifier letter grave accent (""''Ë‹''"", ''U+02CB'') is also allowed[^On some devices, such as an iPhone / iPad, a grave accent character is harder to enter and is often confused with a modifier letter grave accent.].
-You can add some [[attributes|00001007050000]] on the start line of a verbatim block, following the initiating characters.
+You can add some [[attributes|00001007050000]] on the beginning line of a verbatim block, following the initiating characters.
The verbatim block supports the default attribute: when given, all spaces in the text are rendered in HTML as open box characters (""''␣''"", ''U+2423'').
If you want to give only one attribute and this attribute is the generic attribute, you can omit the most of the attribute syntax and just specify the value.
It will be interpreted as a (programming) language to support colourizing the text when rendered in HTML.
Any other character in this line will be ignored
-Text following the starting line will not be interpreted, until a line starts with at least the same number of the same characters given at the starting line.
+Text following the beginning line will not be interpreted, until a line begins with at least the same number of the same characters given at the beginning line.
This allows to enter some grave accent characters in the text that should not be interpreted.
For example:
`````zmk
````zmk
Index: docs/manual/00001007030600.zettel
==================================================================
--- docs/manual/00001007030600.zettel
+++ docs/manual/00001007030600.zettel
@@ -1,21 +1,22 @@
+id: 00001007030600
title: Zettelmarkup: Quotation Blocks
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
A simple way to enter a quotation is to use the [[quotation list|00001007030200]].
A quotation list loosely follows the convention of quoting text within emails.
However, if you want to attribute the quotation to seomeone, a quotation block is more appropriately.
-This kind of line-range block starts with at least three less-than characters (""''<''"", ''U+003C'') at the first position of a line.
-You can add some [[attributes|00001007050000]] on the start line of a quotation block, following the initiating characters.
+This kind of line-range block begins with at least three less-than characters (""''<''"", ''U+003C'') at the first position of a line.
+You can add some [[attributes|00001007050000]] on the beginning line of a quotation block, following the initiating characters.
The quotation does not support the default attribute, nor the generic attribute.
Attributes are interpreted on HTML rendering.
Any other character in this line will be ignored
-Text following the starting line will be interpreted, until a line starts with at least the same number of the same characters given at the starting line.
+Text following the beginning line will be interpreted, until a line begins with at least the same number of the same characters given at the beginning line.
This allows to enter a quotation block within a quotation block.
At the ending line, you can enter some [[inline elements|00001007040000]] after the less-than characters.
These will interpreted as some attribution text.
For example:
Index: docs/manual/00001007030700.zettel
==================================================================
--- docs/manual/00001007030700.zettel
+++ docs/manual/00001007030700.zettel
@@ -1,5 +1,6 @@
+id: 00001007030700
title: Zettelmarkup: Verse Blocks
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
@@ -6,17 +7,17 @@
Sometimes, you want to enter text with significant space characters at the beginning of each line and with significant line endings.
Poetry is one typical example.
Of course, you could help yourself with hard space characters and hard line breaks, by entering a backslash character before a space character and at the end of each line.
Using a verse block might be easier.
-This kind of line-range block starts with at least three quotation mark characters (""''"''"", ''U+0022'') at the first position of a line.
-You can add some [[attributes|00001007050000]] on the start line of a verse block, following the initiating characters.
+This kind of line-range block begins with at least three quotation mark characters (""''"''"", ''U+0022'') at the first position of a line.
+You can add some [[attributes|00001007050000]] on the beginning line of a verse block, following the initiating characters.
The verse block does not support the default attribute, nor the generic attribute.
Attributes are interpreted on HTML rendering.
Any other character in this line will be ignored.
-Text following the starting line will be interpreted, until a line starts with at least the same number of the same characters given at the starting line.
+Text following the beginning line will be interpreted, until a line begins with at least the same number of the same characters given at the beginning line.
This allows to enter a verse block within a verse block.
At the ending line, you can enter some [[inline elements|00001007040000]] after the quotation mark characters.
These will interpreted as some attribution text.
For example:
Index: docs/manual/00001007030800.zettel
==================================================================
--- docs/manual/00001007030800.zettel
+++ docs/manual/00001007030800.zettel
@@ -1,5 +1,6 @@
+id: 00001007030800
title: Zettelmarkup: Region Blocks
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
@@ -6,18 +7,18 @@
Region blocks does not directly have a visual representation.
They just group a range of lines.
You can use region blocks to enter [[attributes|00001007050000]] that apply only to this range of lines.
One example is to enter a multi-line warning that should be visible.
-This kind of line-range block starts with at least three colon characters (""'':''"", ''U+003A'') at the first position of a line[^Since a [[description text|00001007030100]] only use exactly one colon character at the first position of a line, there is no possible ambiguity between these elements.].
-You can add some [[attributes|00001007050000]] on the start line of a verse block, following the initiating characters.
+This kind of line-range block begins with at least three colon characters (""'':''"", ''U+003A'') at the first position of a line[^Since a [[description text|00001007030100]] only use exactly one colon character at the first position of a line, there is no possible ambiguity between these elements.].
+You can add some [[attributes|00001007050000]] on the beginning line of a verse block, following the initiating characters.
The region block does not support the default attribute, but it supports the generic attribute.
Some generic attributes, like ``=note``, ``=warning`` will be rendered special.
Attributes are interpreted on HTML rendering.
Any other character in this line will be ignored.
-Text following the starting line will be interpreted, until a line starts with at least the same number of the same characters given at the starting line.
+Text following the beginning line will be interpreted, until a line begins with at least the same number of the same characters given at the beginning line.
This allows to enter a region block within a region block.
At the ending line, you can enter some [[inline elements|00001007040000]] after the colon characters.
These will interpreted as some attribution text.
For example:
Index: docs/manual/00001007030900.zettel
==================================================================
--- docs/manual/00001007030900.zettel
+++ docs/manual/00001007030900.zettel
@@ -1,23 +1,24 @@
+id: 00001007030900
title: Zettelmarkup: Comment Blocks
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
Comment blocks are quite similar to [[verbatim blocks|00001007030500]]: both are used to enter text that should not be interpreted.
While the text entered inside a verbatim block will be processed somehow, text inside a comment block will be ignored[^Well, not completely ignored: text is read, but it will typically not rendered visible.].
Comment blocks are typically used to give some internal comments, e.g. the license of a text or some internal remarks.
-Comment blocks start with at least three percent sign characters (""''%''"", ''U+0025'') at the first position of a line.
-You can add some [[attributes|00001007050000]] on the start line of a comment block, following the initiating characters.
+Comment blocks begin with at least three percent sign characters (""''%''"", ''U+0025'') at the first position of a line.
+You can add some [[attributes|00001007050000]] on the beginning line of a comment block, following the initiating characters.
The comment block supports the default attribute: when given, the text will be rendered, e.g. as an HTML comment.
When rendered to JSON, the comment block will not be ignored but it will output some JSON text.
Same for other renderers.
Any other character in this line will be ignored
-Text following the starting line will not be interpreted, until a line starts with at least the same number of the same characters given at the starting line.
+Text following the beginning line will not be interpreted, until a line begins with at least the same number of the same characters given at the beginning line.
This allows to enter some percent sign characters in the text that should not be interpreted.
For example:
```zmk
%%%
Index: docs/manual/00001007031000.zettel
==================================================================
--- docs/manual/00001007031000.zettel
+++ docs/manual/00001007031000.zettel
@@ -1,5 +1,6 @@
+id: 00001007031000
title: Zettelmarkup: Tables
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
@@ -7,12 +8,12 @@
In zettelmarkup, table are not specified explicitly, but by entering //table rows//.
Therefore, a table can be seen as a sequence of table rows.
A table row is nothing as a sequence of //table cells//.
The length of a table is the number of table rows, the width of a table is the maximum length of its rows.
-The first cell of a row must start with the vertical bar character (""''|''"", ''U+007C'') at the first position of a line.
-The other cells of a row start with the same vertical bar character at later positions in that line.
+The first cell of a row must begin with the vertical bar character (""''|''"", ''U+007C'') at the first position of a line.
+The other cells of a row begin with the same vertical bar character at later positions in that line.
A cell is delimited by the vertical bar character of the next cell or by the end of the current line.
A vertical bar character as the last character of a line will not result in a table cell.
It will be ignored.
Inside a cell, you can specify any [[inline elements|00001007040000]].
Index: docs/manual/00001007040000.zettel
==================================================================
--- docs/manual/00001007040000.zettel
+++ docs/manual/00001007040000.zettel
@@ -1,5 +1,6 @@
+id: 00001007040000
title: Zettelmarkup: Inline-Structured Elements
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
@@ -7,11 +8,11 @@
The content of a zettel contains is many cases just ordinary text, lightly formatted.
Inline-structured elements allow to format your text and add some helpful links or images.
Sometimes, you want to enter characters that have no representation on your keyboard.
=== Text formatting
-Every [[text formatting|00001007040100]] element starts with two same characters at the beginning.
+Every [[text formatting|00001007040100]] element begins with two same characters at the beginning.
It lasts until the same two characters occurred the second time.
Some of these elements explicitly support [[attributes|00001007050000]].
=== Literal-like formatting
Sometime you want to render the text as it is.
@@ -25,29 +26,29 @@
An important aspect of all knowledge work is to reference others work, e.g. with citation keys.
All these elements can be subsumed under [[reference-like text|00001007040300]].
=== Other inline elements
==== Comments
-A comment is started with two consecutive percent sign characters (""''%''"", ''U+0025'').
-It ends at the end of the line where it started.
+A comment begins with two consecutive percent sign characters (""''%''"", ''U+0025'').
+It ends at the end of the line where it begins.
==== Backslash
The backslash character (""''\\''"", ''U+005C'') gives the next character another meaning.
* If a space character follows, it is converted in a non-breaking space (''U+00A0'').
* If a line ending follows the backslash character, the line break is converted from a //soft break// into a //hard break//.
* Every other character is taken as itself, but without the interpretation of a Zettelmarkup element.
For example, if you want to enter a ""'']''"" into a footnote text, you should escape it with a backslash.
==== Tag
-Any text that starts with a number sign character (""''#''"", ''U+0023''), followed by a non-empty sequence of Unicode letters, Unicode digits, the hyphen-minus character (""''-''"", ''U+002D''), or the low line character (""''_''"", ''U+005F'') is interpreted as an //inline tag//.
+Any text that begins with a number sign character (""''#''"", ''U+0023''), followed by a non-empty sequence of Unicode letters, Unicode digits, the hyphen-minus character (""''-''"", ''U+002D''), or the low line character (""''_''"", ''U+005F'') is interpreted as an //inline tag//.
They will be considered equivalent to tags in metadata.
==== Entities & more
Sometimes it is not easy to enter special characters.
If you know the Unicode code point of that character, or its name according to the [[HTML standard|https://html.spec.whatwg.org/multipage/named-characters.html]], you can enter it by number or by name.
-Regardless which method you use, an entity always starts with an ampersand character (""''&''"", ''U+0026'') and ends with a semicolon character (""'';''"", ''U+003B'').
+Regardless which method you use, an entity always begins with an ampersand character (""''&''"", ''U+0026'') and ends with a semicolon character (""'';''"", ''U+003B'').
If you know the HTML name of the character you want to enter, place it between these two character.
Example: ``&`` is rendered as ::&::{=example}.
If you want to enter its numeric code point, a number sign character must follow the ampersand character, followed by digits to base 10.
Example: ``&`` is rendered in HTML as ::&::{=example}.
Index: docs/manual/00001007040100.zettel
==================================================================
--- docs/manual/00001007040100.zettel
+++ docs/manual/00001007040100.zettel
@@ -1,18 +1,19 @@
+id: 00001007040100
title: Zettelmarkup: Text Formatting
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
Text formatting is the way to make your text visually different.
-Every text formatting element starts with two same characters.
+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.
-The following characters start a text formatting:
+The following characters begin a text formatting:
* The slash character (""''/''"", ''U+002F'') emphasizes its text. Often, such text is rendered in italics. If the default attribute is specified, the emphasized text is not just rendered as such, but also internally marked as emphasized.
** Example: ``abc //def// ghi`` is rendered in HTML as: ::abc //def// ghi::{=example}.
** Example: ``abc //def//{-} ghi`` is rendered in HTML as: ::abc //def//{-} ghi::{=example}.
* The asterisk character (""''*''"", ''U+002A'') strongly emphasized its enclosed text. The text is often rendered in bold. Again, the default attribute will force a explicit semantic meaning of strong emphasizing.
Index: docs/manual/00001007040200.zettel
==================================================================
--- docs/manual/00001007040200.zettel
+++ docs/manual/00001007040200.zettel
@@ -1,5 +1,6 @@
+id: 00001007040200
title: Zettelmarkup: Literal-like formatting
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001007040300.zettel
==================================================================
--- docs/manual/00001007040300.zettel
+++ docs/manual/00001007040300.zettel
@@ -1,5 +1,6 @@
+id: 00001007040300
title: Zettelmarkup: Reference-like text
role: manual
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
@@ -14,11 +15,11 @@
* Reference via a citation key.
* Put a mark within your zettel that you can reference later with a link.
=== Links
There are two kinds of links, regardless of links to (internal) other zettel or to (external) material.
-Both kinds starts with two consecutive left square bracket characters (""''[''"", ''U+005B'') and ends with two consecutive right square bracket characters (""'']''"", ''U+005D'').
+Both kinds begin with two consecutive left square bracket characters (""''[''"", ''U+005B'') and ends with two consecutive right square bracket characters (""'']''"", ''U+005D'').
The first form provides some text plus the link specification, delimited by a vertical bar character (""''|''"", ''U+007C''): ``[[text|linkspecification]]``.
The second form just provides a link specification between the square brackets.
Its text is derived from the link specification, e.g. by interpreting the link specification as text: ``[[linkspecification]]``.
@@ -26,21 +27,22 @@
The link specification for another zettel within the same Zettelstore is just the [[zettel identifier|00001006050000]].
To reference some content within a zettel, you can append a number sign character (""''#''"", ''U+0023'') and the name of the mark to the zettel identifier.
The resulting reference is called ""zettel reference"".
To specify some material outside the Zettelstore, just use an normal Uniform Resource Identifier (URI) as defined by [[RFC\ 3986|https://tools.ietf.org/html/rfc3986]].
-If the URL starts with the slash character (""/"", ''U+002F''), i.e. without scheme, user info, and host name, or if it starts with ""./"" or with ""../"", the reference will be treated as a ""local reference"", otherwise as an ""external reference"".
+If the URL begins with the slash character (""/"", ''U+002F''), or if it begins with ""./"" or with ""../"", i.e. without scheme, user info, and host name, the reference will be treated as a ""local reference"", otherwise as an ""external reference"".
+If the URL begins with two slash characters, it will be interpreted relative to the value of [[''url-prefix''|00001004010000#url-prefix]].
The text in the second form is just a sequence of inline elements.
=== Images
To some degree, an image specification is conceptually not too far away from a link specification.
Both contain a link specification and optionally some text.
In contrast to a link, the link specification of an image must resolve to actual graphical image data.
That data is read when rendered as HTML, and is embedded inside the zettel as an inline image.
-An image specification starts with two consecutive left curly bracket characters (""''{''"", ''U+007B'') and ends with two consecutive right curly bracket characters (""''}''"", ''U+007D'').
+An image specification begins with two consecutive left curly bracket characters (""''{''"", ''U+007B'') and ends with two consecutive right curly bracket characters (""''}''"", ''U+007D'').
The curly brackets delimits either a link specification or some text, a vertical bar character and the link specification, similar to a link.
One difference to a link: if the text was not given, an empty string is assumed.
The link specification must reference a graphical image representation if the image is about to be rendered.
@@ -61,11 +63,11 @@
%%``{{External link|00000000030001}}{title=External width=30}`` is rendered as ::{{External link|00000000030001}}{title=External width=30}::{=example}.
=== Footnotes
-A footnote starts with a left square bracket, followed by a circumflex accent (""''^''"", ''U+005E''), followed by some text, and ends with a right square bracket.
+A footnote begins with a left square bracket, followed by a circumflex accent (""''^''"", ''U+005E''), followed by some text, and ends with a right square bracket.
Example:
``Main text[^Footnote text.].`` is rendered in HTML as: ::Main text[^Footnote text.].::{=example}.
@@ -73,22 +75,22 @@
A citation key references some external material that is part of a bibliografical collection.
Currently, Zettelstore implements this only partially, it is ""work in progress"".
-However, the syntax is: starting with a left square bracket and followed by an at sign character (""''@''"", ''U+0040''), a the citation key is given.
+However, the syntax is: beginning with a left square bracket and followed by an at sign character (""''@''"", ''U+0040''), a the citation key is given.
The key is typically a sequence of letters and digits.
If a comma character (""'',''"", ''U+002C'') or a vertical bar character is given, the following is interpreted as inline elements.
A right square bracket ends the text and the citation key element.
=== Mark
A mark allows to name a point within a zettel.
This is useful if you want to reference some content in a bigger-sized zettel[^Other uses of marks will be given, if Zettelmarkup is extended by a concept called //transclusion//.].
-A mark starts with a left square bracket, followed by an exclamation mark character (""''!''"", ''U+0021'').
+A mark begins with a left square bracket, followed by an exclamation mark character (""''!''"", ''U+0021'').
Now the optional mark name follows.
It is a (possibly empty) sequence of Unicode letters, Unicode digits, the hyphen-minus character (""''-''"", ''U+002D''), or the low-line character (""''_''"", ''U+005F'').
The mark element ends with a right square bracket.
Examples:
* ``[!]`` is a mark without a name, the empty mark.
* ``[!mark]`` is a mark with the name ""mark"".
Index: docs/manual/00001007050000.zettel
==================================================================
--- docs/manual/00001007050000.zettel
+++ docs/manual/00001007050000.zettel
@@ -1,5 +1,6 @@
+id: 00001007050000
title: Zettelmarkup: Attributes
tags: #manual #zettelmarkup #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001007050100.zettel
==================================================================
--- docs/manual/00001007050100.zettel
+++ docs/manual/00001007050100.zettel
@@ -1,5 +1,6 @@
+id: 00001007050100
title: Zettelmarkup: Supported Attribute Values for Natural Languages
tags: #manual #reference #zettelmarkup #zettelstore
syntax: zmk
role: manual
@@ -16,13 +17,13 @@
* ``{lang=de}`` for the german language
* ``{lang=de-at}`` for the german language dialect spoken in Austria
* ``{lang=de-de}`` for the german language dialect spoken in Germany
The actual [[typographic quotations marks|00001007040100]] (``""...""``) are derived from the current language.
-The language of a zettel (meta key ''lang'') or of the whole Zettelstore (''default-lang'' of the [[configuration zettel|00001004020000]]) can be overwritten by an attribute: ``""...""{lang=fr}``{=zmk}.
+The language of a zettel (meta key ''lang'') or of the whole Zettelstore (''default-lang'' of the [[configuration zettel|00001004020000#default-lang]]) can be overwritten by an attribute: ``""...""{lang=fr}``{=zmk}.
Currently, Zettelstore supports the following primary languages:
* ''de''
* ''en''
* ''fr''
These are used, even if a dialect was specified.
Index: docs/manual/00001007050200.zettel
==================================================================
--- docs/manual/00001007050200.zettel
+++ docs/manual/00001007050200.zettel
@@ -1,5 +1,6 @@
+id: 00001007050200
title: Zettelmarkup: Supported Attribute Values for Programming Languages
tags: #manual #reference #zettelmarkup #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001007060000.zettel
==================================================================
--- docs/manual/00001007060000.zettel
+++ docs/manual/00001007060000.zettel
@@ -1,11 +1,12 @@
+id: 00001007060000
title: Zettelmarkup: Summary of Formatting Characters
tags: #manual #reference #zettelmarkup #zettelstore
syntax: zmk
role: manual
-The following table gives an overview about the use of all characters that start a markup element.
+The following table gives an overview about the use of all characters that begin a markup element.
|= Character :|= Blocks <|= Inlines <
| ''!'' | (free) | (free)
| ''"'' | [[Verse block|00001007030700]] | [[Typographic quotation mark|00001007040100]]
| ''#'' | [[Ordered list|00001007030200]] | [[Tag|00001007040000]]
Index: docs/manual/00001008000000.zettel
==================================================================
--- docs/manual/00001008000000.zettel
+++ docs/manual/00001008000000.zettel
@@ -1,11 +1,10 @@
id: 00001008000000
title: Other Markup Languages
role: manual
tags: #manual #zettelstore
syntax: zmk
-modified: 20210111182215
[[Zettelmarkup|00001007000000]] is not the only markup language you can use to define your content.
Zettelstore is quite agnostic with respect to markup languages.
Of course, Zettelmarkup plays an important role.
However, with the exception of zettel titles, you can use any (markup) language that is supported:
@@ -15,11 +14,11 @@
* CSS
* HTML template data
* Plain text, not further interpreted
The [[metadata key|00001006020000#syntax]] ""''syntax''"" specifies which language should be used.
-If it is not given, the key ""''default-syntax''"" will be used (specified in the [[configuration zettel|00001004020000]]).
+If it is not given, the key ""''default-syntax''"" will be used (specified in the [[configuration zettel|00001004020000#default-syntax]]).
The following syntax values are supported:
; [!css]''css''
: A [[Cascading Style Sheet|https://www.w3.org/Style/CSS/]], to be used when rendering a zettel as HTML.
; [!gif]''gif''; [!jpeg]''jpeg''; [!jpg]''jpg''; [!png]''png''
Index: docs/manual/00001008010000.zettel
==================================================================
--- docs/manual/00001008010000.zettel
+++ docs/manual/00001008010000.zettel
@@ -1,5 +1,6 @@
+id: 00001008010000
title: Use Markdown as the main markup language of Zettelstore
tags: #manual #markdown #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001010000000.zettel
==================================================================
--- docs/manual/00001010000000.zettel
+++ docs/manual/00001010000000.zettel
@@ -1,5 +1,6 @@
+id: 00001010000000
title: Security
tags: #configuration #manual #security #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001010040100.zettel
==================================================================
--- docs/manual/00001010040100.zettel
+++ docs/manual/00001010040100.zettel
@@ -1,8 +1,9 @@
+id: 00001010040100
title: Enable authentication
tags: #authentication #configuration #manual #security #zettelstore
syntax: zmk
role: manual
To enable authentication, you must create a zettel that stores [[authentication data|00001010040200]] for the owner.
-Then you must reference this zettel within the [[start-up configuration|00001004010000]] under the key ''owner''.
+Then you must reference this zettel within the [[start-up configuration|00001004010000#owner]] under the key ''owner''.
Once the start-up configuration contains a valid [[zettel identifier|00001006050000]] under that key, authentication is enabled.
Index: docs/manual/00001010040200.zettel
==================================================================
--- docs/manual/00001010040200.zettel
+++ docs/manual/00001010040200.zettel
@@ -1,5 +1,6 @@
+id: 00001010040200
title: Creating an user zettel
tags: #authentication #configuration #manual #security #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001010040400.zettel
==================================================================
--- docs/manual/00001010040400.zettel
+++ docs/manual/00001010040400.zettel
@@ -1,5 +1,6 @@
+id: 00001010040400
title: Authentication process
tags: #authentication #configuration #manual #security #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001010040700.zettel
==================================================================
--- docs/manual/00001010040700.zettel
+++ docs/manual/00001010040700.zettel
@@ -1,5 +1,6 @@
+id: 00001010040700
title: Access token
tags: #authentication #configuration #manual #security #zettelstore
syntax: zmk
role: manual
@@ -6,19 +7,19 @@
If an user is authenticated, an ""access token"" is created that must be sent with every request to prove the identity of the caller.
Otherwise the user will not be recognized by Zettelstore.
If the user was authenticated via the web interface, the access token is stored in a [[""session cookie""|https://en.wikipedia.org/wiki/HTTP_cookie#Session_cookie]].
When the web browser is closed, theses cookies are not saved.
-If you want web browser to store the cookie as long as lifetime of that token, the owner must set ''persistent-cookie'' of the [[startup configuration|00001004010000]] to ''true''.
+If you want web browser to store the cookie as long as lifetime of that token, the owner must set ''persistent-cookie'' of the [[start-up configuration|00001004010000]] to ''true''.
If the web browser remains inactive for a period, the user will be automatically logged off, because each access token has a limited lifetime.
-The maximum length of this period is specified by the ''token-lifetime-html'' value of the startup configuration.
+The maximum length of this period is specified by the ''token-lifetime-html'' value of the start-up configuration.
Every time a web page is displayed, a fresh token is created and stored inside the cookie.
If the user was authenticated via the API, the access token will be returned as the content of the response.
Typically, the lifetime of this token is more short term, e.g. 10 minutes.
-It is specified by the ''token-timeout-api'' value of the startup configuration.
+It is specified by the ''token-timeout-api'' value of the start-up configuration.
If you need more time, you can either [[re-authenticate|00001012050200]] the user or use an API call to [[renew the access token|00001012050400]].
-If you remotely access your Zettelstore via HTTP (not via HTTPS, which allows encrypted communication), your must set the ''insecure-cookie'' value of the startup configuration to ''true''.
+If you remotely access your Zettelstore via HTTP (not via HTTPS, which allows encrypted communication), your must set the ''insecure-cookie'' value of the start-up configuration to ''true''.
In most cases, such a scenario is not recommended, because user name and password will be transferred as plain text.
You could make use of such scenario if you know all parties that access the local network where you access the Zettelstore.
Index: docs/manual/00001010070200.zettel
==================================================================
--- docs/manual/00001010070200.zettel
+++ docs/manual/00001010070200.zettel
@@ -1,10 +1,10 @@
+id: 00001010070200
title: Visibility rules for zettel
role: manual
tags: #authorization #configuration #manual #security #zettelstore
syntax: zmk
-modified: 20201221174224
For every zettel you can specify under which condition the zettel is visible to others.
This is controlled with the metadata key [[''visibility''|00001006020000#visibility]].
The following values are supported:
@@ -22,11 +22,11 @@
: Only the owner of the Zettelstore can access the zettel, if runtime configuration [[''expert-mode''|00001004020000#expert-mode]] is set to a boolean true value.
This is for zettel with sensitive content that might irritate the owner.
Computed zettel with internal runtime information are examples for such a zettel.
; [!simple-expert]""simple-expert""
-: The owner of the Zettelstore cab access the zettel, if expert mode is enabled, or if authentication is disabled and the Zettelstore is started without any command.
+: The owner of the Zettelstore can access the zettel, if expert mode is enabled, or if authentication is disabled and the Zettelstore is started without any command.
The reason for this is to show all computed zettel to an user that started the Zettestore in simple mode.
Many computed zettel should be given in error reporting and a new user might not be able to enable expert mode.
When you install a Zettelstore, only two zettel have visibility ""public"".
@@ -35,7 +35,7 @@
The other zettel is the zettel containing the [[version|00000000000001]] of the Zettelstore.
Please note: if authentication is not enabled, every user has the same rights as the owner of a Zettelstore.
This is also true, if the Zettelstore runs additionally in [[read-only mode|00001004010000#read-only-mode]].
In this case, the [[runtime configuration zettel|00001004020000]] is shown (its visibility is ""owner"").
-The [[startup configuration|00001004010000]] is not shown, because the associated computed zettel with identifier ''00000000000099'' is stored with the visibility ""expert"".
+The [[start-up configuration|00001004010000]] is not shown, because the associated computed zettel with identifier ''00000000000099'' is stored with the visibility ""expert"".
If you want to show such a zettel, you must set ''expert-mode'' to true.
Index: docs/manual/00001010070300.zettel
==================================================================
--- docs/manual/00001010070300.zettel
+++ docs/manual/00001010070300.zettel
@@ -1,5 +1,6 @@
+id: 00001010070300
title: User roles
role: manual
tags: #authorization #configuration #manual #security #zettelstore
syntax: zmk
Index: docs/manual/00001010070400.zettel
==================================================================
--- docs/manual/00001010070400.zettel
+++ docs/manual/00001010070400.zettel
@@ -1,5 +1,6 @@
+id: 00001010070400
title: Authorization and read-only mode
tags: #authorization #configuration #manual #security #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001010070600.zettel
==================================================================
--- docs/manual/00001010070600.zettel
+++ docs/manual/00001010070600.zettel
@@ -1,5 +1,6 @@
+id: 00001010070600
title: Access rules
tags: #authorization #configuration #manual #security #zettelstore
syntax: zmk
role: manual
@@ -44,9 +45,5 @@
Only the owner of the Zettelstore is currently allowed to give a new identifier for a zettel.
* Delete a zettel
** Reject the access.
Only the owner of the Zettelstore is allowed to delete a zettel.
This may change in the future.
-* Reload internal values
-** Reject the access.
- Only the owner of the Zettelstore is allowed to perform a reload operation.
- This may change in the future.
Index: docs/manual/00001010090100.zettel
==================================================================
--- docs/manual/00001010090100.zettel
+++ docs/manual/00001010090100.zettel
@@ -1,11 +1,10 @@
id: 00001010090100
title: External server to encrypt message transport
role: manual
tags: #configuration #encryption #manual #security #zettelstore
syntax: zmk
-modified: 20210125195546
Since Zettelstore does not encrypt the messages it exchanges with its clients, you may need some additional software to enable encryption.
=== Public-key encryption
To enable encryption, you probably use some kind of encryption keys.
@@ -65,7 +64,7 @@
}
```
This will forwards requests with the prefix ""/manual"" to the running Zettelstore.
All other requests will be handled by Caddy itself.
-In this case you must specify the start-tp configuration key ''url-prefix'' with the value ""/manual"".
+In this case you must specify the start-up configuration key ''url-prefix'' with the value ""/manual"".
This is to allow the Zettelstore to give you the correct URLs with the given prefix.
Index: docs/manual/00001012000000.zettel
==================================================================
--- docs/manual/00001012000000.zettel
+++ docs/manual/00001012000000.zettel
@@ -1,11 +1,10 @@
id: 00001012000000
title: API
role: manual
tags: #api #manual #zettelstore
syntax: zmk
-modified: 20210112113014
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.
@@ -31,15 +30,17 @@
* [[List metadata of all zettel|00001012051200]]
* [[List all zettel, but in different encoding formats|00001012051400]]
* [[List all zettel, but include different parts of a zettel|00001012051600]]
* [[Shape the list of zettel metadata with filter options|00001012051800]]
* [[Sort the list of zettel metadata|00001012052000]]
-* List all [[tags|00001006020000]] used in a Zettelstore
-* List all [[roles|00001006020100]] used in a Zettelstore.
+* [[List all tags|00001012052200]]
+* [[List all roles|00001012052400]]
=== Working with zettel
* Create a new zettel
* [[Retrieve metadata and content of an existing zettel|00001012053400]]
* [[Retrieve references of an existing zettel|00001012053600]]
+* [[Retrieve context of an existing zettel|00001012053800]]
+* [[Retrieve zettel order within an existing zettel|00001012054000]]
* Update metadata and content of a zettel
* Rename a zettel
* Delete a zettel
Index: docs/manual/00001012050200.zettel
==================================================================
--- docs/manual/00001012050200.zettel
+++ docs/manual/00001012050200.zettel
@@ -1,17 +1,16 @@
id: 00001012050200
title: API: Authenticate a client
role: manual
tags: #api #manual #zettelstore
syntax: zmk
-modified: 20210111190943
Authentication for future API calls is done by sending a [[user identification|00001010040200]] and a password to the Zettelstore to obtain an [[access token|00001010040700]].
This token has to be used for other API calls.
It is valid for a relatively short amount of time, as configured with the key ''token-timeout-api'' of the [[start-up configuration|00001004010000]] (typically 10 minutes).
-The simplest way is to send user identification (''IDENT'') and password (''PASSWORD'') via [[HTTP Basic Authentication|https://tools.ietf.org/html/rfc7617]] and send them to the endpoint ''/a'' with a POST request:
+The simplest way is to send user identification (''IDENT'') and password (''PASSWORD'') via [[HTTP Basic Authentication|https://tools.ietf.org/html/rfc7617]] and send them to the [[endpoint|00001012920000]] ''/a'' with a POST request:
```sh
# curl -X POST -u IDENT:PASSWORD http://127.0.0.1:23123/a
{"access_token":"eyJhbGciOiJIUzUxMiJ9.eyJfdGsiOjEsImV4cCI6MTYwMTczMTI3NSwiaWF0IjoxNjAxNzMwNjc1LCJzdWIiOiJhYmMiLCJ6aWQiOiIyMDIwMTAwMzE1MDEwMCJ9.ekhXkvn146P2bMKFQcU-bNlvgbeO6sS39hs6U5EKfjIqnSInkuHYjYAIfUqf_clYRfr6YBlX5izii8XfxV8jhg","token_type":"Bearer","expires_in":600}
```
Index: docs/manual/00001012050400.zettel
==================================================================
--- docs/manual/00001012050400.zettel
+++ docs/manual/00001012050400.zettel
@@ -1,14 +1,15 @@
+id: 00001012050400
title: API: Renew an access token
+role: manual
tags: #api #manual #zettelstore
syntax: zmk
-role: manual
An access token is only valid for a certain duration.
Since the [[authentication process|00001012050200]] will need some processing time, there is a way to renew the token without providing full authentication data.
-Send a HTTP PUT request to the endpoint ''/a'' and include the current access token in the ''Authorization'' header:
+Send a HTTP PUT request to the [[endpoint|00001012920000]] ''/a'' and include the current access token in the ''Authorization'' header:
```sh
# curl -X PUT -H 'Authorization: Bearer TOKEN' http://127.0.0.1:23123/a
{"access_token":"eyJhbGciOiJIUzUxMiJ9.eyJfdGsiOjEsImV4cCI6MTYwMTczMTI3NSwiaWF0IjoxNjAxNzMwNjc1LCJzdWIiOiJhYmMiLCJ6aWQiOiIyMDIwMTAwMzE1MDEwMCJ9.ekhXkvn146P2bMKFQcU-bNlvgbeO6sS39hs6U5EKfjIqnSInkuHYjYAIfUqf_clYRfr6YBlX5izii8XfxV8jhg","token_type":"Bearer","expires_in":456}
```
Index: docs/manual/00001012050600.zettel
==================================================================
--- docs/manual/00001012050600.zettel
+++ docs/manual/00001012050600.zettel
@@ -1,5 +1,6 @@
+id: 00001012050600
title: API: Provide an access token
tags: #api #manual #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001012051200.zettel
==================================================================
--- docs/manual/00001012051200.zettel
+++ docs/manual/00001012051200.zettel
@@ -1,11 +1,12 @@
+id: 00001012051200
title: API: List metadata of all zettel
+role: manual
tags: #api #manual #zettelstore
syntax: zmk
-role: manual
-To list the metadata of all zettel just send a HTTP GET request to the endpoint ''/z''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].
+To list the metadata of 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].
If successful, the output is a JSON object:
```sh
# curl http://127.0.0.1:23123/z
{"list":[{"id":"00001012051200","url":"/z/00001012051200","meta":{"title":"API: Renew an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012050600","url":"/z/00001012050600","meta":{"title":"API: Provide an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012050400","url":"/z/00001012050400","meta":{"title":"API: Renew an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012050200","url":"/z/00001012050200","meta":{"title":"API: Authenticate a client","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012000000","url":"/z/00001012000000","meta":{"title":"API","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"}}]}
Index: docs/manual/00001012051400.zettel
==================================================================
--- docs/manual/00001012051400.zettel
+++ docs/manual/00001012051400.zettel
@@ -1,5 +1,6 @@
+id: 00001012051400
title: API: List all zettel, but in different encoding formats
tags: #api #manual #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001012051600.zettel
==================================================================
--- docs/manual/00001012051600.zettel
+++ docs/manual/00001012051600.zettel
@@ -1,5 +1,6 @@
+id: 00001012051600
title: API: List all zettel, but include different parts of a zettel
tags: #api #manual #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001012051800.zettel
==================================================================
--- docs/manual/00001012051800.zettel
+++ docs/manual/00001012051800.zettel
@@ -1,18 +1,17 @@
id: 00001012051800
title: API: Shape the list of zettel metadata with filter options
role: manual
tags: #api #manual #zettelstore
syntax: zmk
-modified: 20210112114019
In most cases, it is not essential to list //all// zettel.
Typically, you are interested only in a subset of the zettel maintained by your Zettelstore.
This is done by adding some query parameters to the general ''GET /z'' request.
=== Filter
-Every query parameter that does //not// start with the low line character (""_"", ''U+005F'') is treated as the name of a [[metadata|00001006010000]] key.
+Every query parameter that does //not// begin with the low line character (""_"", ''U+005F'') is treated as the name of a [[metadata|00001006010000]] key.
According to the [[type|00001006030000]] of a metadata key, zettel are matched and therefore filtered.
All [[supported|00001006020000]] metadata keys have a well-defined type.
User-defined keys have the type ''e'' (string, possibly empty).
For example, if you want to retrieve all zettel that contain the string ""API"" in its title, your request will be:
@@ -40,11 +39,11 @@
By using the query parameter ""''_limit''"", which must have an integer value, you specifying an upper limit of list elements:
```sh
# curl 'http://192.168.17.7:23121/z?title=API&_part=id&_sort=id&_limit=2'
{"list":[{"id":"00001012000000","url":"/z/00001012000000"},{"id":"00001012050200","url":"/z/00001012050200"}]}
```
-The query parameter ""''_offset''"" allows to list not only the first elements, but start at a specific element:
+The query parameter ""''_offset''"" allows to list not only the first elements, but to begin at a specific element:
```sh
# curl 'http://192.168.17.7:23121/z?title=API&_part=id&_sort=id&_limit=2&_offset=1'
{"list":[{"id":"00001012050200","url":"/z/00001012050200"},{"id":"00001012050400","url":"/z/00001012050400"}]}
```
Index: docs/manual/00001012052000.zettel
==================================================================
--- docs/manual/00001012052000.zettel
+++ docs/manual/00001012052000.zettel
@@ -1,11 +1,10 @@
id: 00001012052000
title: API: Sort the list of zettel metadata
role: manual
tags: #api #manual #zettelstore
syntax: zmk
-modified: 20210112113839
If not specified, the list of zettel is sorted descending by the value of the zettel identifier.
The highest zettel identifier, which is a number, comes first.
You change that with the ""''_sort''"" query parameter.
Alternatively, you can also use the ""''_order''"" query parameter.
ADDED docs/manual/00001012052200.zettel
Index: docs/manual/00001012052200.zettel
==================================================================
--- /dev/null
+++ docs/manual/00001012052200.zettel
@@ -0,0 +1,18 @@
+id: 00001012052200
+title: API: List all tags
+role: manual
+tags: #api #manual #zettelstore
+syntax: zmk
+
+To list all [[tags|00001006020000#tags]] used in the Zettelstore just send a HTTP GET request to the [[endpoint|00001012920000]] ''/t''.
+If successful, the output is a JSON object:
+
+```sh
+# curl http://127.0.0.1:23123/t
+{"tags":{"#api":[:["00001012921000","00001012920800","00001012920522",...],"#authorization":["00001010040700","00001010040400",...],...,"#zettelstore":["00010000000000","00001014000000",...,"00001001000000"]}}
+```
+
+The JSON object only contains the key ''"tags"'' with the value of another object.
+This second object contains all tags as keys and the list of identifier of those zettel with this tag as a value.
+
+Please note that this structure will likely change in the future to be more compliant with other API calls.
ADDED docs/manual/00001012052400.zettel
Index: docs/manual/00001012052400.zettel
==================================================================
--- /dev/null
+++ docs/manual/00001012052400.zettel
@@ -0,0 +1,18 @@
+id: 00001012052400
+title: API: List all roles
+role: manual
+tags: #api #manual #zettelstore
+syntax: zmk
+
+To list all [[roles|00001006020100]] used in the Zettelstore just send a HTTP GET request to the [[endpoint|00001012920000]] ''/r''.
+If successful, the output is a JSON object:
+
+```sh
+# curl http://127.0.0.1:23123/r
+{"role-list":["configuration","manual","user","zettel"]}
+```
+
+The JSON object only contains the key ''"role-list"'' with the value of a sorted string list.
+Each string names one role.
+
+Please note that this structure will likely change in the future to be more compliant with other API calls.
Index: docs/manual/00001012053400.zettel
==================================================================
--- docs/manual/00001012053400.zettel
+++ docs/manual/00001012053400.zettel
@@ -1,11 +1,12 @@
+id: 00001012053400
title: API: Retrieve metadata and content of an existing zettel
+role: manual
tags: #api #manual #zettelstore
syntax: zmk
-role: manual
-The endpoint to work with metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the zettel identifier (14 digits).
+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 (14 digits).
For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/z/00001012053400''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header].
If successful, the output is a JSON object:
```sh
# curl http://127.0.0.1:23123/z/00001012053400
Index: docs/manual/00001012053600.zettel
==================================================================
--- docs/manual/00001012053600.zettel
+++ docs/manual/00001012053600.zettel
@@ -1,5 +1,6 @@
+id: 00001012053600
title: API: Retrieve references of an existing zettel
tags: #api #manual #zettelstore
syntax: zmk
role: manual
ADDED docs/manual/00001012053800.zettel
Index: docs/manual/00001012053800.zettel
==================================================================
--- /dev/null
+++ docs/manual/00001012053800.zettel
@@ -0,0 +1,80 @@
+id: 00001012053800
+title: API: Retrieve context of an existing zettel
+role: manual
+tags: #api #manual #zettelstore
+syntax: zmk
+
+The context of an origin zettel consists of those zettel that are somehow connected to the origin zettel.
+Direct connections of an origin zettel to other zettel are visible via [[metadata values|00001006020000]], such as ''backward'', ''forward'' or other values with type [[identifier|00001006032000]] or [[set of identifier|00001006032500]].
+Zettel are also connected by using same [[tags|00001006020000#tags]].
+
+The context is defined by a //direction//, a //depth//, and a /limit//:
+* Direction: connections are directed.
+ For example, the metadata value of ''backward'' lists all zettel that link to the current zettel, while ''formward'' list all zettel to which the current zettel links.
+ When you are only interested in one direction, set the parameter ''dir'' either to the value ""backward"" or ""forward"".
+ All other values, including a missing value, is interpreted as ""both"".
+* Depth: a direct connection has depth 1, an indirect connection is the length of the shortest path between two zettel.
+ You should limit the depth by using the parameter ''depth''.
+ Its default value is ""5"".
+ A value of ""0"" does disable any depth check.
+* Limit: to set an upper bound for the returned context, you should use the parameter ''limit''.
+ Its default value is ""200"".
+ A value of ""0"" disables does not limit the number of elements returned.
+
+Zettel with same tags as the origin zettel are considered depth 1.
+Only for the origin zettel, tags are used to calculate a connection.
+Currently, only some of the newest zettel with a given tag are considered a connection.[^The number of zettel is given by the value of parameter ''depth''.]
+Otherwise the context would become too big and therefore unusable.
+
+To retrieve the context of an existing zettel, use the [[endpoint|00001012920000]] ''/y/{ID}''.
+
+````
+# curl 'http://127.0.0.1:23123/y/00001012053800?limit=3&dir=forward&depth=2'
+{"id": "00001012053800","url": "/z/00001012053800","meta": {...},"list": [{"id": "00001012921000","url": "/z/00001012921000","meta": {...}},{"id": "00001012920800","url": "/z/00001012920800","meta": {...}},{"id": "00010000000000","url": "/z/00010000000000","meta": {...}}]}
+````
+Formatted, this translates into:[^Metadata (key ''meta'') are hidden to make the overall structure easier to read.]
+````json
+{
+ "id": "00001012053800",
+ "url": "/z/00001012053800",
+ "meta": {...},
+ "list": [
+ {
+ "id": "00001012921000",
+ "url": "/z/00001012921000",
+ "meta": {...}
+ },
+ {
+ "id": "00001012920800",
+ "url": "/z/00001012920800",
+ "meta": {...}
+ },
+ {
+ "id": "00010000000000",
+ "url": "/z/00010000000000",
+ "meta": {...}
+ }
+ ]
+}
+````
+=== Keys
+The following top-level JSON keys are returned:
+; ''id''
+: The zettel identifier for which the context was requested.
+; ''url''
+: The API endpoint to fetch more information about the zettel.
+; ''meta'':
+: The metadata of the zettel, encoded as a JSON object.
+; ''list''
+: A list of JSON objects with keys ''id'', ''url'' and ''meta'' that contains the zettel of the context.
+
+=== HTTP Status codes
+; ''200''
+: Retrieval was successful, the body contains an appropriate JSON object.
+; ''400''
+: Request was not valid.
+; ''403''
+: You are not allowed to retrieve data of the given zettel.
+; ''404''
+: Zettel not found.
+ You probably used a zettel identifier that is not used in the Zettelstore.
ADDED docs/manual/00001012054000.zettel
Index: docs/manual/00001012054000.zettel
==================================================================
--- /dev/null
+++ docs/manual/00001012054000.zettel
@@ -0,0 +1,82 @@
+id: 00001012054000
+title: API: Retrieve zettel order within an existing zettel
+role: manual
+tags: #api #manual #zettelstore
+syntax: zmk
+
+Some zettel act as a ""table of contents"" for other zettel.
+The [[Home zettel|00010000000000]] of this manual is one example, the [[general API description|00001012000000]] is another.
+Every zettel with a certain internal structure can act as the ""table of contents"" for others.
+
+What is a ""table of contents""?
+Basically, it is just a list of references to other zettel.
+
+To retrieve the ""table of contents"", the software looks at first level [[list items|00001007030200]].
+If an item contains a valid reference to a zettel, this reference will be interpreted as an item in the table of contents.
+
+This applies only to first level list items (ordered or unordered list), but not to deeper levels.
+Only the first reference to a valid zettel is collected for the table of contents.
+Following references to zettel within such an list item are ignored.
+
+To retrieve the zettel order of an existing zettel, use the [[endpoint|00001012920000]] ''/o/{ID}''.
+
+````
+# curl http://127.0.0.1:23123/o/00010000000000
+{"id":"00010000000000","url":"/z/00010000000000","meta":{...},"list":[{"id":"00001001000000","url":"/z/00001001000000","meta":{...}},{"id":"00001002000000","url":"/z/00001002000000","meta":{...}},{"id":"00001003000000","url":"/z/00001003000000","meta":{...}},{"id":"00001004000000","url":"/z/00001004000000","meta":{...}},...,{"id":"00001014000000","url":"/z/00001014000000","meta":{...}}]}
+````
+Formatted, this translates into:[^Metadata (key ''meta'') are hidden to make the overall structure easier to read.]
+````json
+{
+ "id": "00010000000000",
+ "url": "/z/00010000000000",
+ "order": [
+ {
+ "id": "00001001000000",
+ "url": "/z/00001001000000",
+ "meta": {...}
+ },
+ {
+ "id": "00001002000000",
+ "url": "/z/00001002000000",
+ "meta": {...}
+ },
+ {
+ "id": "00001003000000",
+ "url": "/z/00001003000000",
+ "meta": {...}
+ },
+ {
+ "id": "00001004000000",
+ "url": "/z/00001004000000",
+ "meta": {...}
+ },
+ ...
+ {
+ "id": "00001014000000",
+ "url": "/z/00001014000000",
+ "meta": {...}
+ }
+ ]
+}
+````
+=== Kind
+The following top-level JSON keys are returned:
+; ''id''
+: The zettel identifier for which the references were requested.
+; ''url''
+: The API endpoint to fetch more information about the zettel.
+; ''meta'':
+: The metadata of the zettel, encoded as a JSON object.
+; ''list''
+: A list of JSON objects with keys ''id'', ''url'', and ''meta'' that describe other zettel in the defined order.
+
+=== HTTP Status codes
+; ''200''
+: Retrieval was successful, the body contains an appropriate JSON object.
+; ''400''
+: Request was not valid.
+; ''403''
+: You are not allowed to retrieve data of the given zettel.
+; ''404''
+: Zettel not found.
+ You probably used a zettel identifier that is not used in the Zettelstore.
Index: docs/manual/00001012920000.zettel
==================================================================
--- docs/manual/00001012920000.zettel
+++ docs/manual/00001012920000.zettel
@@ -1,13 +1,14 @@
+id: 00001012920000
title: Endpoints used by the API
+role: manual
tags: #api #manual #reference #zettelstore
syntax: zmk
-role: manual
All API endpoints conform to the pattern ''[PREFIX]/LETTER[/ZETTEL-ID]'', where:
; ''PREFIX''
-: is an optional URL prefix, configured via the ''url-prefix'' [[startup configuration|00001004010000]],
+: is an optional URL prefix, configured via the ''url-prefix'' [[start-up configuration|00001004010000]],
; ''LETTER''
: is a single letter that specifies the ressource type,
; ''ZETTEL-ID''
: is an optional 14 digits string that uniquely [[identify a zettel|00001006050000]].
@@ -15,13 +16,17 @@
|= Letter:| Without zettel identifier | With [[zettel identifier|00001006050000]]
| ''a'' | POST: [[Client authentication|00001012050200]] |
| | PUT: [[renew access token|00001012050400]] |
| ''l'' | | GET: [[list references|00001012053600]]
+| ''o'' | | GET: [[list zettel order|00001012054000]]
+| ''r'' | GET: [[list roles|00001012052400]]
+| ''t'' | GET: [[list tags|00001012052200]]
+| ''y'' | | GET: [[list zettel context|00001012053800]]
| ''z'' | GET: [[list zettel|00001012051200]] | GET: [[retrieve zettel|00001012053400]]
| | POST: add new zettel | PUT: change a zettel
| | | DELETE: delete the zettel
The full URL will contain either the ''http'' oder ''https'' scheme, a host name, and an optional port number.
The API examples will assume the ''http'' schema, the local host ''127.0.0.1'', the default port ''23123'', and the default empty ''PREFIX''.
-Therefore, all URLs will start with ''http://127.0.0.1:23123''.
+Therefore, all URLs in the API documentation will begin with ''http://127.0.0.1:23123''.
Index: docs/manual/00001012920500.zettel
==================================================================
--- docs/manual/00001012920500.zettel
+++ docs/manual/00001012920500.zettel
@@ -1,5 +1,6 @@
+id: 00001012920500
title: Formats available by the API
tags: #api #manual #reference #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001012920501.zettel
==================================================================
--- docs/manual/00001012920501.zettel
+++ docs/manual/00001012920501.zettel
@@ -1,5 +1,6 @@
+id: 00001012920501
title: JSON Format
role: manual
tags: #api #manual #reference #zettelstore
syntax: zmk
@@ -17,14 +18,14 @@
''"id"'' and ''"url"'' are always sent to the client.
It depends on the value of the required [[zettel part|00001012920800]], whether ''"meta"'' or ''"content"'' or both are sent.
For an example, take a look at the JSON encoding of this page, which is available via the ""Info"" sub-page of this zettel:
-* [[../z/00001012920501?_part=id]],
-* [[../z/00001012920501?_part=zettel]],
-* [[../z/00001012920501?_part=meta]],
-* [[../z/00001012920501?_part=content]].
+* [[//z/00001012920501?_part=id]],
+* [[//z/00001012920501?_part=zettel]],
+* [[//z/00001012920501?_part=meta]],
+* [[//z/00001012920501?_part=content]].
If transferred via HTTP, the content type will be ''application/json''.
=== Metadata
This ia a JSON object, that maps [[metadata keys|00001006010000]] to their values.
Index: docs/manual/00001012920503.zettel
==================================================================
--- docs/manual/00001012920503.zettel
+++ docs/manual/00001012920503.zettel
@@ -1,5 +1,6 @@
+id: 00001012920503
title: DJSON Format
role: manual
tags: #api #manual #reference #zettelstore
syntax: zmk
Index: docs/manual/00001012920510.zettel
==================================================================
--- docs/manual/00001012920510.zettel
+++ docs/manual/00001012920510.zettel
@@ -1,5 +1,6 @@
+id: 00001012920510
title: HTML Format
tags: #api #manual #reference #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001012920513.zettel
==================================================================
--- docs/manual/00001012920513.zettel
+++ docs/manual/00001012920513.zettel
@@ -1,5 +1,6 @@
+id: 00001012920513
title: Native Format
tags: #api #manual #reference #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001012920516.zettel
==================================================================
--- docs/manual/00001012920516.zettel
+++ docs/manual/00001012920516.zettel
@@ -1,5 +1,6 @@
+id: 00001012920516
title: Raw Format
tags: #api #manual #reference #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001012920519.zettel
==================================================================
--- docs/manual/00001012920519.zettel
+++ docs/manual/00001012920519.zettel
@@ -1,5 +1,6 @@
+id: 00001012920519
title: Text Format
tags: #api #manual #reference #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001012920522.zettel
==================================================================
--- docs/manual/00001012920522.zettel
+++ docs/manual/00001012920522.zettel
@@ -1,5 +1,6 @@
+id: 00001012920522
title: Zmk Format
tags: #api #manual #reference #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001012920800.zettel
==================================================================
--- docs/manual/00001012920800.zettel
+++ docs/manual/00001012920800.zettel
@@ -1,5 +1,6 @@
+id: 00001012920800
title: Values to specify zettel parts
tags: #api #manual #reference #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001012921000.zettel
==================================================================
--- docs/manual/00001012921000.zettel
+++ docs/manual/00001012921000.zettel
@@ -1,5 +1,6 @@
+id: 00001012921000
title: API: JSON structure of an access token
tags: #api #manual #reference #zettelstore
syntax: zmk
role: manual
Index: docs/manual/00001014000000.zettel
==================================================================
--- docs/manual/00001014000000.zettel
+++ docs/manual/00001014000000.zettel
@@ -1,5 +1,6 @@
+id: 00001014000000
title: Web user interface
tags: #manual #webui #zettelstore
syntax: zmk
role: manual
ADDED docs/manual/00010000000000.zettel
Index: docs/manual/00010000000000.zettel
==================================================================
--- /dev/null
+++ docs/manual/00010000000000.zettel
@@ -0,0 +1,21 @@
+id: 00001000000000
+title: Zettelstore Manual
+role: manual
+tags: #manual #zettelstore
+syntax: zmk
+
+* [[Introduction|00001001000000]]
+* [[Design goals|00001002000000]]
+* [[Installation|00001003000000]]
+* [[Configuration|00001004000000]]
+* [[Structure of Zettelstore|00001005000000]]
+* [[Layout of a zettel|00001006000000]]
+* [[Zettelmarkup|00001007000000]]
+* [[Other markup languages|00001008000000]]
+* [[Security|00001010000000]]
+* [[API|00001012000000]]
+* [[Web user interface|00001014000000]]
+* Troubleshooting
+* Frequently asked questions
+
+Licensed under the EUPL-1.2-or-later.
Index: domain/id/id.go
==================================================================
--- domain/id/id.go
+++ domain/id/id.go
@@ -11,11 +11,10 @@
// Package id provides domain specific types, constants, and functions about
// zettel identifier.
package id
import (
- "sort"
"strconv"
"time"
)
// Zid is the internal identifier of a zettel. Typically, it is a
@@ -22,31 +21,42 @@
// time stamp of the form "YYYYMMDDHHmmSS" converted to an unsigned integer.
// A zettelstore implementation should try to set the last two digits to zero,
// e.g. the seconds should be zero,
type Zid uint64
-// Some important ZettelIDs
+// Some important ZettelIDs.
+// Note: if you change some values, ensure that you also change them in the
+// constant place. They are mentioned there literally, because these
+// constants are not available there.
const (
- Invalid = Zid(0) // Invalid is a Zid that will never be valid
- ConfigurationZid = Zid(100)
- BaseTemplateZid = Zid(10100)
- LoginTemplateZid = Zid(10200)
- ListTemplateZid = Zid(10300)
- DetailTemplateZid = Zid(10401)
- InfoTemplateZid = Zid(10402)
- FormTemplateZid = Zid(10403)
- RenameTemplateZid = Zid(10404)
- DeleteTemplateZid = Zid(10405)
- RolesTemplateZid = Zid(10500)
- TagsTemplateZid = Zid(10600)
- BaseCSSZid = Zid(20001)
+ Invalid = Zid(0) // Invalid is a Zid that will never be valid
+ ConfigurationZid = Zid(100)
+
+ // WebUI HTML templates are in the range 10000..19999
+ BaseTemplateZid = Zid(10100)
+ LoginTemplateZid = Zid(10200)
+ ListTemplateZid = Zid(10300)
+ DetailTemplateZid = Zid(10401)
+ InfoTemplateZid = Zid(10402)
+ FormTemplateZid = Zid(10403)
+ RenameTemplateZid = Zid(10404)
+ DeleteTemplateZid = Zid(10405)
+ ContextTemplateZid = Zid(10406)
+ RolesTemplateZid = Zid(10500)
+ TagsTemplateZid = Zid(10600)
+
+ // WebUI CSS pages are in the range 20000..29999
+ BaseCSSZid = Zid(20001)
+
+ // WebUI JS pages are in the range 30000..39999
// Range 90000...99999 is reserved for zettel templates
- TemplateNewZettelZid = Zid(91001)
- TemplateNewUserZid = Zid(96001)
+ TOCNewTemplateZid = Zid(90000)
+ TemplateNewZettelZid = Zid(90001)
+ TemplateNewUserZid = Zid(90002)
- WelcomeZid = Zid(19700101000000)
+ DefaultHomeZid = Zid(10000000000)
)
const maxZid = 99999999999999
// Parse interprets a string as a zettel identification and
@@ -100,16 +110,5 @@
if err != nil {
panic(err)
}
return res
}
-
-// Sort a slice of Zids.
-func Sort(zids []Zid) {
- sort.Sort(zidSlice(zids))
-}
-
-type zidSlice []Zid
-
-func (zs zidSlice) Len() int { return len(zs) }
-func (zs zidSlice) Less(i, j int) bool { return zs[i] < zs[j] }
-func (zs zidSlice) Swap(i, j int) { zs[i], zs[j] = zs[j], zs[i] }
Index: domain/id/id_test.go
==================================================================
--- domain/id/id_test.go
+++ domain/id/id_test.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -15,13 +15,10 @@
"testing"
"zettelstore.de/z/domain/id"
)
-func TestParseZettelID(t *testing.T) {
-}
-
func TestIsValid(t *testing.T) {
validIDs := []string{
"00000000000001",
"00000000000020",
"00000000000300",
ADDED domain/id/set.go
Index: domain/id/set.go
==================================================================
--- /dev/null
+++ domain/id/set.go
@@ -0,0 +1,58 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2021 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 id provides domain specific types, constants, and functions about
+// zettel identifier.
+package id
+
+// Set is a set of zettel identifier
+type Set map[Zid]bool
+
+// NewSet returns a new set of identifier with the given initial values.
+func NewSet(zids ...Zid) Set {
+ l := len(zids)
+ if l < 8 {
+ l = 8
+ }
+ result := make(Set, l)
+ for _, zid := range zids {
+ result[zid] = true
+ }
+ return result
+}
+
+// NewSetCap returns a new set of identifier with the given capacity and initial values.
+func NewSetCap(c int, zids ...Zid) Set {
+ l := len(zids)
+ if c < l {
+ c = l
+ }
+ if c < 8 {
+ c = 8
+ }
+ result := make(Set, c)
+ for _, zid := range zids {
+ result[zid] = true
+ }
+ return result
+}
+
+// Sort returns the set as a sorted slice of zettel identifier.
+func (s Set) Sort() Slice {
+ if l := len(s); l > 0 {
+ result := make(Slice, 0, l)
+ for zid := range s {
+ result = append(result, zid)
+ }
+ result.Sort()
+ return result
+ }
+ return nil
+}
ADDED domain/id/slice.go
Index: domain/id/slice.go
==================================================================
--- /dev/null
+++ domain/id/slice.go
@@ -0,0 +1,52 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2021 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 id provides domain specific types, constants, and functions about
+// zettel identifier.
+package id
+
+import (
+ "sort"
+ "strings"
+)
+
+// Slice is a sequence of zettel identifier. A special case is a sorted slice.
+type Slice []Zid
+
+func (zs Slice) Len() int { return len(zs) }
+func (zs Slice) Less(i, j int) bool { return zs[i] < zs[j] }
+func (zs Slice) Swap(i, j int) { zs[i], zs[j] = zs[j], zs[i] }
+
+// Sort a slice of Zids.
+func (zs Slice) Sort() { sort.Sort(zs) }
+
+// Copy a zettel identifier slice
+func (zs Slice) Copy() Slice {
+ if zs == nil {
+ return nil
+ }
+ result := make(Slice, len(zs))
+ copy(result, zs)
+ return result
+}
+
+func (zs Slice) String() string {
+ if len(zs) == 0 {
+ return ""
+ }
+ var sb strings.Builder
+ for i, zid := range zs {
+ if i > 0 {
+ sb.WriteByte(' ')
+ }
+ sb.WriteString(zid.String())
+ }
+ return sb.String()
+}
ADDED domain/id/slice_test.go
Index: domain/id/slice_test.go
==================================================================
--- /dev/null
+++ domain/id/slice_test.go
@@ -0,0 +1,57 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2021 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 id provides domain specific types, constants, and functions about
+// zettel identifier.
+package id_test
+
+import (
+ "testing"
+
+ "zettelstore.de/z/domain/id"
+)
+
+func TestSort(t *testing.T) {
+ zs := id.Slice{9, 4, 6, 1, 7}
+ zs.Sort()
+ if zs[0] != 1 || zs[1] != 4 || zs[2] != 6 || zs[3] != 7 || zs[4] != 9 {
+ t.Errorf("Slice.Sort did not work. Expected %v, got %v", id.Slice{1, 4, 6, 7, 9}, zs)
+ }
+}
+
+func TestCopy(t *testing.T) {
+ var orig id.Slice
+ got := orig.Copy()
+ if got != nil {
+ t.Errorf("Nil copy resulted in %v", got)
+ }
+ orig = id.Slice{9, 4, 6, 1, 7}
+ got = orig.Copy()
+ if len(got) != len(orig) || got[0] != 9 || got[1] != 4 || got[2] != 6 || got[3] != 1 || got[4] != 7 {
+ t.Errorf("Slice.Copy did not work. Expected %v, got %v", orig, got)
+ }
+}
+func TestString(t *testing.T) {
+ testcases := []struct {
+ in id.Slice
+ exp string
+ }{
+ {nil, ""},
+ {id.Slice{}, ""},
+ {id.Slice{1}, "00000000000001"},
+ {id.Slice{1, 2}, "00000000000001 00000000000002"},
+ }
+ for i, tc := range testcases {
+ got := tc.in.String()
+ if got != tc.exp {
+ t.Errorf("%d/%v: expected %q, but got %q", i, tc.in, tc.exp, got)
+ }
+ }
+}
Index: domain/meta/meta.go
==================================================================
--- domain/meta/meta.go
+++ domain/meta/meta.go
@@ -73,10 +73,18 @@
if kd, ok := registeredKeys[name]; ok {
return kd.IsComputed()
}
return false
}
+
+// Inverse returns the name of the inverse key.
+func Inverse(name string) string {
+ if kd, ok := registeredKeys[name]; ok {
+ return kd.Inverse
+ }
+ return ""
+}
// GetDescription returns the key description object of the given key name.
func GetDescription(name string) DescriptionKey {
if d, ok := registeredKeys[name]; ok {
return *d
@@ -120,21 +128,20 @@
KeyDuplicates = registerKey("duplicates", TypeBool, usageUser, "")
KeyExpertMode = registerKey("expert-mode", TypeBool, usageUser, "")
KeyFolge = registerKey("folge", TypeIDSet, usageProperty, "")
KeyFooterHTML = registerKey("footer-html", TypeString, usageUser, "")
KeyForward = registerKey("forward", TypeIDSet, usageProperty, "")
+ KeyHomeZettel = registerKey("home-zettel", TypeID, usageUser, "")
KeyLang = registerKey("lang", TypeWord, usageUser, "")
KeyLicense = registerKey("license", TypeEmpty, usageUser, "")
KeyListPageSize = registerKey("list-page-size", TypeNumber, usageUser, "")
- KeyNewRole = registerKey("new-role", TypeWord, usageUser, "")
KeyMarkerExternal = registerKey("marker-external", TypeEmpty, usageUser, "")
KeyModified = registerKey("modified", TypeTimestamp, usageComputed, "")
KeyPrecursor = registerKey("precursor", TypeIDSet, usageUser, KeyFolge)
KeyPublished = registerKey("published", TypeTimestamp, usageProperty, "")
KeyReadOnly = registerKey("read-only", TypeWord, usageUser, "")
KeySiteName = registerKey("site-name", TypeString, usageUser, "")
- KeyStart = registerKey("start", TypeID, usageUser, "")
KeyURL = registerKey("url", TypeURL, usageUser, "")
KeyUserID = registerKey("user-id", TypeWord, usageUser, "")
KeyUserRole = registerKey("user-role", TypeWord, usageUser, "")
KeyVisibility = registerKey("visibility", TypeWord, usageUser, "")
KeyYAMLHeader = registerKey("yaml-header", TypeBool, usageUser, "")
@@ -143,11 +150,10 @@
// Important values for some keys.
const (
ValueRoleConfiguration = "configuration"
ValueRoleUser = "user"
- ValueRoleNewTemplate = "new-template"
ValueRoleZettel = "zettel"
ValueSyntaxNone = "none"
ValueSyntaxZmk = "zmk"
ValueTrue = "true"
ValueFalse = "false"
@@ -235,11 +241,11 @@
return value, ok
}
// GetDefault retrieves the string value of the given key. If no value was
// stored, the given default value is returned.
-func (m *Meta) GetDefault(key string, def string) string {
+func (m *Meta) GetDefault(key, def string) string {
if value, ok := m.Get(key); ok {
return value
}
return def
}
@@ -255,11 +261,11 @@
// predefined keys. The pairs are ordered by key.
func (m *Meta) PairsRest(allowComputed bool) []Pair {
return m.doPairs(false, allowComputed)
}
-func (m *Meta) doPairs(first bool, allowComputed bool) []Pair {
+func (m *Meta) doPairs(first, allowComputed bool) []Pair {
result := make([]Pair, 0, len(m.pairs))
if first {
for _, key := range firstKeys {
if value, ok := m.pairs[key]; ok {
result = append(result, Pair{key, value})
Index: domain/meta/parse.go
==================================================================
--- domain/meta/parse.go
+++ domain/meta/parse.go
@@ -179,11 +179,9 @@
})
case TypeTimestamp:
if _, ok := TimeValue(v); ok {
m.Set(key, v)
}
- case TypeEmpty:
- fallthrough
default:
addData(m, key, v)
}
}
Index: domain/meta/type.go
==================================================================
--- domain/meta/type.go
+++ domain/meta/type.go
@@ -10,10 +10,11 @@
// Package meta provides the domain specific type 'meta'.
package meta
import (
+ "strconv"
"strings"
"time"
)
// DescriptionType is a description of a specific key type.
@@ -76,10 +77,18 @@
values[i] = trimValue(val)
}
m.pairs[key] = strings.Join(values, " ")
}
}
+
+// CleanTag removes the number charachter ('#') from a tag value.
+func CleanTag(tag string) string {
+ if len(tag) > 1 && tag[0] == '#' {
+ return tag[1:]
+ }
+ return tag
+}
// SetNow stores the current timestamp under the given key.
func (m *Meta) SetNow(key string) {
m.Set(key, time.Now().Format("20060102150405"))
}
@@ -131,14 +140,37 @@
if !ok {
return nil, false
}
return ListFromValue(value), true
}
+
+// GetTags returns the list of tags as a string list. Each tag does not begin
+// with the '#' character, in contrast to `GetList`.
+func (m *Meta) GetTags(key string) ([]string, bool) {
+ tags, ok := m.GetList(key)
+ if !ok {
+ return nil, false
+ }
+ for i, tag := range tags {
+ tags[i] = CleanTag(tag)
+ }
+ return tags, len(tags) > 0
+}
// GetListOrNil retrieves the string list value of a given key. If there was
// nothing stores, a nil list is returned.
func (m *Meta) GetListOrNil(key string) []string {
if value, ok := m.GetList(key); ok {
return value
}
return nil
}
+
+// GetNumber retrieves the numeric value of a given key.
+func (m *Meta) GetNumber(key string) (int, bool) {
+ if value, ok := m.Get(key); ok {
+ if num, err := strconv.Atoi(value); err == nil {
+ return num, true
+ }
+ }
+ return 0, false
+}
Index: encoder/encoder.go
==================================================================
--- encoder/encoder.go
+++ encoder/encoder.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -33,15 +33,15 @@
WriteInlines(io.Writer, ast.InlineSlice) (int, error)
}
// Some errors to signal when encoder methods are not implemented.
var (
- ErrNoWriteZettel = errors.New("Method WriteZettel is not implemented")
- 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")
+ ErrNoWriteZettel = errors.New("method WriteZettel is not implemented")
+ 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")
)
// Option allows to configure an encoder
type Option interface {
Name() string
Index: encoder/htmlenc/block.go
==================================================================
--- encoder/htmlenc/block.go
+++ encoder/htmlenc/block.go
@@ -1,7 +1,7 @@
//-----------------------------------------------------------------------------
-// Copyright (c) 2020 Detlef Stern
+// Copyright (c) 2020-2021 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
@@ -21,11 +21,11 @@
// VisitPara emits HTML code for a paragraph:
Unable to display BLOB with syntax '", bn.Syntax, "'.
\n") } } + +func (v *visitor) writeEndPara() { + v.b.WriteString("\n") +} Index: encoder/htmlenc/htmlenc.go ================================================================== --- encoder/htmlenc/htmlenc.go +++ encoder/htmlenc/htmlenc.go @@ -54,12 +54,11 @@ he.newWindow = opt.Value case "xhtml": he.xhtml = opt.Value } case *encoder.StringsOption: - switch opt.Key { - case "no-meta": + if opt.Key == "no-meta" { he.ignoreMeta = make(map[string]bool, len(opt.Value)) for _, v := range opt.Value { he.ignoreMeta[v] = true } } Index: encoder/htmlenc/inline.go ================================================================== --- encoder/htmlenc/inline.go +++ encoder/htmlenc/inline.go @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020 Detlef Stern +// Copyright (c) 2020-2021 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 @@ -25,11 +25,11 @@ } // VisitTag writes tag content. func (v *visitor) VisitTag(tn *ast.TagNode) { // TODO: erst mal als span. Link wäre gut, muss man vermutlich via Callback lösen. - v.b.WriteString("") + v.b.WriteString("#") v.writeHTMLEscaped(tn.Tag) v.b.WriteString("") } // VisitSpace emits a white space. @@ -66,13 +66,13 @@ } v.lang.push(ln.Attrs) defer v.lang.pop() switch ln.Ref.State { - case ast.RefStateZettelSelf, ast.RefStateZettelFound, ast.RefStateLocal: + case ast.RefStateSelf, ast.RefStateFound, ast.RefStateHosted, ast.RefStateBased: v.writeAHref(ln.Ref, ln.Attrs, ln.Inlines) - case ast.RefStateZettelBroken: + case ast.RefStateBroken: attrs := ln.Attrs.Clone() attrs = attrs.Set("class", "zs-broken") attrs = attrs.Set("title", "Zettel not found") // l10n v.writeAHref(ln.Ref, attrs, ln.Inlines) case ast.RefStateExternal: Index: encoder/htmlenc/visitor.go ================================================================== --- encoder/htmlenc/visitor.go +++ encoder/htmlenc/visitor.go @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020 Detlef Stern +// Copyright (c) 2020-2021 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 @@ -47,35 +47,41 @@ meta.KeyLicense: "license", } func (v *visitor) acceptMeta(m *meta.Meta, withTitle bool) { for i, pair := range m.Pairs(true) { + if v.enc.ignoreMeta[pair.Key] { + continue + } if i == 0 { // "title" is number 0... - if withTitle && !v.enc.ignoreMeta[pair.Key] { + if withTitle { + // TODO: title value may contain zmk elements v.b.WriteStrings("") } continue } - if !v.enc.ignoreMeta[pair.Key] { - if pair.Key == meta.KeyTags { - v.b.WriteString("\n 0 { - v.b.WriteString(", ") - } - v.writeQuotedEscaped(strings.TrimPrefix(val, "#")) - } - v.b.WriteString("\">") - } else if key, ok := mapMetaKey[pair.Key]; ok { - v.writeMeta("", key, pair.Value) - } else { - v.writeMeta("zs-", pair.Key, pair.Value) - } - } - } + if pair.Key == meta.KeyTags { + v.writeTags(pair.Value) + } else if key, ok := mapMetaKey[pair.Key]; ok { + v.writeMeta("", key, pair.Value) + } else { + v.writeMeta("zs-", pair.Key, pair.Value) + } + } +} + +func (v *visitor) writeTags(tags string) { + v.b.WriteString("\n 0 { + v.b.WriteString(", ") + } + v.writeQuotedEscaped(strings.TrimPrefix(val, "#")) + } + v.b.WriteString("\">") } func (v *visitor) writeMeta(prefix, key, value string) { v.b.WriteStrings("\n 0 { - v.b.WriteByte(',') - } - v.b.WriteByte('"') - v.b.Write(Escape(val)) - v.b.WriteByte('"') - } - v.b.WriteByte(']') + v.writeSetValue(p.Value) } else { v.b.WriteByte('"') v.b.Write(Escape(p.Value)) v.b.WriteByte('"') } } } + +func (v *detailVisitor) writeSetValue(value string) { + v.b.WriteByte('[') + for i, val := range meta.ListFromValue(value) { + if i > 0 { + v.b.WriteByte(',') + } + v.b.WriteByte('"') + v.b.Write(Escape(val)) + v.b.WriteByte('"') + } + v.b.WriteByte(']') +} Index: encoder/jsonenc/jsonenc.go ================================================================== --- encoder/jsonenc/jsonenc.go +++ encoder/jsonenc/jsonenc.go @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020 Detlef Stern +// Copyright (c) 2020-2021 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 @@ -29,11 +29,11 @@ // jsonEncoder is just a stub. It is not implemented. The real implementation // is in file web/adapter/json.go type jsonEncoder struct{} -// SetOption sets an option for the encoder +// SetOption does nothing because this encoder does not recognize any option. func (je *jsonEncoder) SetOption(option encoder.Option) {} // WriteZettel writes the encoded zettel to the writer. func (je *jsonEncoder) WriteZettel( w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) { Index: encoder/nativeenc/nativeenc.go ================================================================== --- encoder/nativeenc/nativeenc.go +++ encoder/nativeenc/nativeenc.go @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020 Detlef Stern +// Copyright (c) 2020-2021 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 @@ -133,17 +133,17 @@ v.level-- v.b.WriteByte(']') } } -func (v *visitor) writeMetaString(m *meta.Meta, key string, native string) { +func (v *visitor) writeMetaString(m *meta.Meta, key, native string) { if val, ok := m.Get(key); ok && len(val) > 0 { v.b.WriteStrings("\n[", native, " \"", val, "\"]") } } -func (v *visitor) writeMetaList(m *meta.Meta, key string, native string) { +func (v *visitor) writeMetaList(m *meta.Meta, key, native string) { if vals, ok := m.GetList(key); ok && len(vals) > 0 { v.b.WriteStrings("\n[", native) for _, val := range vals { v.b.WriteByte(' ') v.b.WriteString(val) @@ -380,17 +380,18 @@ v.b.WriteString("Space") } } var mapRefState = map[ast.RefState]string{ - ast.RefStateInvalid: "INVALID", - ast.RefStateZettel: "ZETTEL", - ast.RefStateZettelSelf: "SELF", - ast.RefStateZettelFound: "ZETTEL", - ast.RefStateZettelBroken: "BROKEN", - ast.RefStateLocal: "LOCAL", - ast.RefStateExternal: "EXTERNAL", + ast.RefStateInvalid: "INVALID", + ast.RefStateZettel: "ZETTEL", + ast.RefStateSelf: "SELF", + ast.RefStateFound: "ZETTEL", + ast.RefStateBroken: "BROKEN", + ast.RefStateHosted: "LOCAL", + ast.RefStateBased: "BASED", + ast.RefStateExternal: "EXTERNAL", } // VisitLink writes native code for links. func (v *visitor) VisitLink(ln *ast.LinkNode) { if adapt := v.enc.adaptLink; adapt != nil { Index: encoder/rawenc/rawenc.go ================================================================== --- encoder/rawenc/rawenc.go +++ encoder/rawenc/rawenc.go @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020 Detlef Stern +// Copyright (c) 2020-2021 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 @@ -25,11 +25,11 @@ }) } type rawEncoder struct{} -// SetOption sets an option for the encoder +// SetOption does nothing because this encoder does not recognize any option. func (re *rawEncoder) SetOption(option encoder.Option) {} // WriteZettel writes the encoded zettel to the writer. func (re *rawEncoder) WriteZettel( w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) { Index: encoder/textenc/textenc.go ================================================================== --- encoder/textenc/textenc.go +++ encoder/textenc/textenc.go @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020 Detlef Stern +// Copyright (c) 2020-2021 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 @@ -25,11 +25,11 @@ }) } type textEncoder struct{} -// SetOption sets an option for this encoder +// SetOption does nothing because this encoder does not recognize any option. func (te *textEncoder) SetOption(option encoder.Option) {} // WriteZettel does nothing. func (te *textEncoder) WriteZettel( w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) { Index: encoder/zmkenc/zmkenc.go ================================================================== --- encoder/zmkenc/zmkenc.go +++ encoder/zmkenc/zmkenc.go @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020 Detlef Stern +// Copyright (c) 2020-2021 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 @@ -27,11 +27,11 @@ }) } type zmkEncoder struct{} -// SetOption sets an option for this encoder. +// SetOption does nothing because this encoder does not recognize any option. func (ze *zmkEncoder) SetOption(option encoder.Option) {} // WriteZettel writes the encoded zettel to the writer. func (ze *zmkEncoder) WriteZettel( w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) { Index: go.mod ================================================================== --- go.mod +++ go.mod @@ -3,9 +3,9 @@ go 1.15 require ( github.com/fsnotify/fsnotify v1.4.9 github.com/pascaldekloe/jwt v1.10.0 - github.com/yuin/goldmark v1.3.0 + github.com/yuin/goldmark v1.3.2 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a golang.org/x/text v0.3.0 ) Index: go.sum ================================================================== --- go.sum +++ go.sum @@ -1,11 +1,11 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/pascaldekloe/jwt v1.10.0 h1:ktcIUV4TPvh404R5dIBEnPCsSwj0sqi3/0+XafE5gJs= github.com/pascaldekloe/jwt v1.10.0/go.mod h1:TKhllgThT7TOP5rGr2zMLKEDZRAgJfBbtKyVeRsNB9A= -github.com/yuin/goldmark v1.3.0 h1:DRvEHivhJ1fQhZbpmttnonfC674RycyZGE/5IJzDKgg= -github.com/yuin/goldmark v1.3.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.3.2 h1:YjHC5TgyMmHpicTgEqDN0Q96Xo8K6tLXPnmNOHXCgs0= +github.com/yuin/goldmark v1.3.2/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= Index: index/index.go ================================================================== --- index/index.go +++ index/index.go @@ -47,11 +47,11 @@ } // Port contains all the used functions to access zettel to be indexed. type Port interface { RegisterObserver(func(place.ChangeInfo)) - FetchZids(context.Context) (map[id.Zid]bool, error) + FetchZids(context.Context) (id.Set, error) GetMeta(context.Context, id.Zid) (*meta.Meta, error) GetZettel(context.Context, id.Zid) (domain.Zettel, error) } // Indexer contains all the functions of an index. @@ -89,19 +89,21 @@ // memory-based, file-based, based on SQLite, ... type Store interface { Enricher // UpdateReferences for a specific zettel. - UpdateReferences(context.Context, *ZettelIndex) + // Returns set of zettel identifier that must also be checked for changes. + UpdateReferences(context.Context, *ZettelIndex) id.Set // DeleteZettel removes index data for given zettel. - DeleteZettel(context.Context, id.Zid) + // Returns set of zettel identifier that must also be checked for changes. + DeleteZettel(context.Context, id.Zid) id.Set // ReadStats populates st with store statistics. ReadStats(st *StoreStats) - // Write the content to a Writer + // Write the content to a Writer. Write(io.Writer) } // StoreStats records statistics about the store. type StoreStats struct { Index: index/indexer/anteroom.go ================================================================== --- index/indexer/anteroom.go +++ index/indexer/anteroom.go @@ -14,14 +14,23 @@ import ( "sync" "zettelstore.de/z/domain/id" ) + +type arAction int + +const ( + arNothing arAction = iota + arReload + arUpdate + arDelete +) type anteroom struct { next *anteroom - waiting map[id.Zid]bool + waiting map[id.Zid]arAction curLoad int reload bool } type anterooms struct { @@ -33,124 +42,146 @@ func newAnterooms(maxLoad int) *anterooms { return &anterooms{maxLoad: maxLoad} } -func (ar *anterooms) Enqueue(zid id.Zid, val bool) { - if !zid.IsValid() { +func (ar *anterooms) Enqueue(zid id.Zid, action arAction) { + if !zid.IsValid() || action == arNothing || action == arReload { return } ar.mx.Lock() defer ar.mx.Unlock() if ar.first == nil { - ar.first = ar.makeAnteroom(zid, val) + ar.first = ar.makeAnteroom(zid, action) ar.last = ar.first return } for room := ar.first; room != nil; room = room.next { if room.reload { continue // Do not place zettel in reload room } - if v, ok := room.waiting[zid]; ok { - if val == v { - return - } - room.waiting[zid] = val + a, ok := room.waiting[zid] + if !ok { + continue + } + switch action { + case a: return + case arUpdate: + room.waiting[zid] = action + case arDelete: + room.waiting[zid] = action } + return } if room := ar.last; !room.reload && (ar.maxLoad == 0 || room.curLoad < ar.maxLoad) { - room.waiting[zid] = val + room.waiting[zid] = action room.curLoad++ return } - room := ar.makeAnteroom(zid, val) + room := ar.makeAnteroom(zid, action) ar.last.next = room ar.last = room } -func (ar *anterooms) makeAnteroom(zid id.Zid, val bool) *anteroom { - cap := ar.maxLoad - if cap == 0 { - cap = 100 +func (ar *anterooms) makeAnteroom(zid id.Zid, action arAction) *anteroom { + c := ar.maxLoad + if c == 0 { + c = 100 } - waiting := make(map[id.Zid]bool, cap) - waiting[zid] = val + waiting := make(map[id.Zid]arAction, c) + waiting[zid] = action return &anteroom{next: nil, waiting: waiting, curLoad: 1, reload: false} } func (ar *anterooms) Reset() { ar.mx.Lock() defer ar.mx.Unlock() - ar.first = ar.makeAnteroom(id.Invalid, true) + ar.first = ar.makeAnteroom(id.Invalid, arReload) ar.last = ar.first } -func (ar *anterooms) Reload(delZids []id.Zid, newZids map[id.Zid]bool) { +func (ar *anterooms) Reload(delZids id.Slice, newZids id.Set) { ar.mx.Lock() defer ar.mx.Unlock() - delWaiting := make(map[id.Zid]bool, len(delZids)) - for _, zid := range delZids { - if zid.IsValid() { - delWaiting[zid] = false - } - } - newWaiting := make(map[id.Zid]bool, len(newZids)) - for zid := range newZids { - if zid.IsValid() { - newWaiting[zid] = true - } - } - - // Delete previous reload rooms - room := ar.first - for ; room != nil && room.reload; room = room.next { - } - ar.first = room - if room == nil { - ar.last = nil - } + delWaiting := createWaitingSlice(delZids, arDelete) + newWaiting := createWaitingSet(newZids, arUpdate) + ar.deleteReloadedRooms() if ds := len(delWaiting); ds > 0 { if ns := len(newWaiting); ns > 0 { roomNew := &anteroom{next: ar.first, waiting: newWaiting, curLoad: ns, reload: true} ar.first = &anteroom{next: roomNew, waiting: delWaiting, curLoad: ds, reload: true} if roomNew.next == nil { ar.last = roomNew } - } else { - ar.first = &anteroom{next: ar.first, waiting: delWaiting, curLoad: ds} - if ar.first.next == nil { - ar.last = ar.first - } - } - } else { - if ns := len(newWaiting); ns > 0 { - ar.first = &anteroom{next: ar.first, waiting: newWaiting, curLoad: ns} - if ar.first.next == nil { - ar.last = ar.first - } - } else { - ar.first = nil - ar.last = nil - } - } -} - -func (ar *anterooms) Dequeue() (id.Zid, bool) { - ar.mx.Lock() - defer ar.mx.Unlock() - if ar.first == nil { - return id.Invalid, false - } - for zid, val := range ar.first.waiting { + return + } + + ar.first = &anteroom{next: ar.first, waiting: delWaiting, curLoad: ds} + if ar.first.next == nil { + ar.last = ar.first + } + return + } + + if ns := len(newWaiting); ns > 0 { + ar.first = &anteroom{next: ar.first, waiting: newWaiting, curLoad: ns} + if ar.first.next == nil { + ar.last = ar.first + } + return + } + + ar.first = nil + ar.last = nil +} + +func createWaitingSlice(zids id.Slice, action arAction) map[id.Zid]arAction { + waitingSet := make(map[id.Zid]arAction, len(zids)) + for _, zid := range zids { + if zid.IsValid() { + waitingSet[zid] = action + } + } + return waitingSet +} + +func createWaitingSet(zids id.Set, action arAction) map[id.Zid]arAction { + waitingSet := make(map[id.Zid]arAction, len(zids)) + for zid := range zids { + if zid.IsValid() { + waitingSet[zid] = action + } + } + return waitingSet +} + +func (ar *anterooms) deleteReloadedRooms() { + room := ar.first + for room != nil && room.reload { + room = room.next + } + ar.first = room + if room == nil { + ar.last = nil + } +} + +func (ar *anterooms) Dequeue() (arAction, id.Zid) { + ar.mx.Lock() + defer ar.mx.Unlock() + if ar.first == nil { + return arNothing, id.Invalid + } + for zid, action := range ar.first.waiting { delete(ar.first.waiting, zid) if len(ar.first.waiting) == 0 { ar.first = ar.first.next if ar.first == nil { ar.last = nil } } - return zid, val + return action, zid } - return id.Invalid, false + return arNothing, id.Invalid } Index: index/indexer/anteroom_test.go ================================================================== --- index/indexer/anteroom_test.go +++ index/indexer/anteroom_test.go @@ -17,33 +17,33 @@ "zettelstore.de/z/domain/id" ) func TestSimple(t *testing.T) { ar := newAnterooms(2) - ar.Enqueue(id.Zid(1), true) - zid, val := ar.Dequeue() - if zid != id.Zid(1) || val != true { - t.Errorf("Expected 1/true, but got %v/%v", zid, val) + ar.Enqueue(id.Zid(1), arUpdate) + action, zid := ar.Dequeue() + if zid != id.Zid(1) || action != arUpdate { + t.Errorf("Expected 1/arUpdate, but got %v/%v", zid, action) } - zid, val = ar.Dequeue() - if zid != id.Invalid && val != false { + action, zid = ar.Dequeue() + if zid != id.Invalid && action != arDelete { t.Errorf("Expected invalid Zid, but got %v", zid) } - ar.Enqueue(id.Zid(1), true) - ar.Enqueue(id.Zid(2), true) + ar.Enqueue(id.Zid(1), arUpdate) + ar.Enqueue(id.Zid(2), arUpdate) if ar.first != ar.last { t.Errorf("Expected one room, but got more") } - ar.Enqueue(id.Zid(3), true) + ar.Enqueue(id.Zid(3), arUpdate) if ar.first == ar.last { t.Errorf("Expected more than one room, but got only one") } count := 0 - for ; ; count++ { - zid, val := ar.Dequeue() - if zid == id.Invalid && val == false { + for ; count < 1000; count++ { + action, _ := ar.Dequeue() + if action == arNothing { break } } if count != 3 { t.Errorf("Expected 3 dequeues, but got %v", count) @@ -50,73 +50,73 @@ } } func TestReset(t *testing.T) { ar := newAnterooms(1) - ar.Enqueue(id.Zid(1), true) + ar.Enqueue(id.Zid(1), arUpdate) ar.Reset() - zid, val := ar.Dequeue() - if zid != id.Invalid && val != true { - t.Errorf("Expected invalid Zid, but got %v/%v", zid, val) - } - ar.Reload([]id.Zid{id.Zid(2)}, map[id.Zid]bool{id.Zid(3): true, id.Zid(4): false}) - ar.Enqueue(id.Zid(5), true) - ar.Enqueue(id.Zid(5), false) - ar.Enqueue(id.Zid(5), false) - ar.Enqueue(id.Zid(5), true) + action, zid := ar.Dequeue() + if action != arReload || zid != id.Invalid { + t.Errorf("Expected reload & invalid Zid, but got %v/%v", action, zid) + } + ar.Reload(id.Slice{2}, id.NewSet(3, 4)) + ar.Enqueue(id.Zid(5), arUpdate) + ar.Enqueue(id.Zid(5), arDelete) + ar.Enqueue(id.Zid(5), arDelete) + ar.Enqueue(id.Zid(5), arUpdate) if ar.first == ar.last || ar.first.next == ar.last || ar.first.next.next != ar.last { t.Errorf("Expected 3 rooms") } - zid, val = ar.Dequeue() - if zid != id.Zid(2) || val != false { - t.Errorf("Expected 2/false, but got %v/%v", zid, val) - } - zid1, val := ar.Dequeue() - if val != true { - t.Errorf("Expected true, but got %v", val) - } - zid2, val := ar.Dequeue() - if val != true { - t.Errorf("Expected true, but got %v", val) + action, zid = ar.Dequeue() + if zid != id.Zid(2) || action != arDelete { + t.Errorf("Expected 2/arDelete, but got %v/%v", zid, action) + } + action, zid1 := ar.Dequeue() + if action != arUpdate { + t.Errorf("Expected arUpdate, but got %v", action) + } + action, zid2 := ar.Dequeue() + if action != arUpdate { + t.Errorf("Expected arUpdate, but got %v", action) } if !(zid1 == id.Zid(3) && zid2 == id.Zid(4) || zid1 == id.Zid(4) && zid2 == id.Zid(3)) { t.Errorf("Zids must be 3 or 4, but got %v/%v", zid1, zid2) } - zid, val = ar.Dequeue() - if zid != id.Zid(5) || val != true { - t.Errorf("Expected 5/true, but got %v/%v", zid, val) - } - zid, val = ar.Dequeue() - if zid != id.Invalid && val != false { - t.Errorf("Expected invalid Zid, but got %v", zid) - } - - ar = newAnterooms(1) - ar.Reload(nil, map[id.Zid]bool{id.Zid(6): true}) - zid, val = ar.Dequeue() - if zid != id.Zid(6) || val != true { - t.Errorf("Expected 6/true, but got %v/%v", zid, val) - } - zid, val = ar.Dequeue() - if zid != id.Invalid && val != false { - t.Errorf("Expected invalid Zid, but got %v", zid) - } - - ar = newAnterooms(1) - ar.Reload([]id.Zid{id.Zid(7)}, nil) - zid, val = ar.Dequeue() - if zid != id.Zid(7) || val != false { - t.Errorf("Expected 7/false, but got %v/%v", zid, val) - } - zid, val = ar.Dequeue() - if zid != id.Invalid && val != false { - t.Errorf("Expected invalid Zid, but got %v", zid) - } - - ar = newAnterooms(1) - ar.Enqueue(id.Zid(8), true) - ar.Reload(nil, nil) - zid, val = ar.Dequeue() - if zid != id.Invalid && val != false { - t.Errorf("Expected invalid Zid, but got %v", zid) + action, zid = ar.Dequeue() + if zid != id.Zid(5) || action != arUpdate { + t.Errorf("Expected 5/arUpdate, but got %v/%v", zid, action) + } + action, zid = ar.Dequeue() + if action != arNothing || zid != id.Invalid { + t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid) + } + + ar = newAnterooms(1) + ar.Reload(nil, id.NewSet(id.Zid(6))) + action, zid = ar.Dequeue() + if zid != id.Zid(6) || action != arUpdate { + t.Errorf("Expected 6/arUpdate, but got %v/%v", zid, action) + } + action, zid = ar.Dequeue() + if action != arNothing || zid != id.Invalid { + t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid) + } + + ar = newAnterooms(1) + ar.Reload(id.Slice{7}, nil) + action, zid = ar.Dequeue() + if zid != id.Zid(7) || action != arDelete { + t.Errorf("Expected 7/arDelete, but got %v/%v", zid, action) + } + action, zid = ar.Dequeue() + if action != arNothing || zid != id.Invalid { + t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid) + } + + ar = newAnterooms(1) + ar.Enqueue(id.Zid(8), arUpdate) + ar.Reload(nil, nil) + action, zid = ar.Dequeue() + if action != arNothing || zid != id.Invalid { + t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid) } } Index: index/indexer/indexer.go ================================================================== --- index/indexer/indexer.go +++ index/indexer/indexer.go @@ -54,13 +54,13 @@ func (idx *indexer) observer(ci place.ChangeInfo) { switch ci.Reason { case place.OnReload: idx.ar.Reset() case place.OnUpdate: - idx.ar.Enqueue(ci.Zid, true) + idx.ar.Enqueue(ci.Zid, arUpdate) case place.OnDelete: - idx.ar.Enqueue(ci.Zid, false) + idx.ar.Enqueue(ci.Zid, arDelete) default: return } select { case idx.ready <- struct{}{}: @@ -109,15 +109,16 @@ idx.store.ReadStats(&st.Store) } type indexerPort interface { getMetaPort - FetchZids(ctx context.Context) (map[id.Zid]bool, error) + FetchZids(ctx context.Context) (id.Set, error) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) } // indexer runs in the background and updates the index data structures. +// This is the main service of the indexer. func (idx *indexer) indexer(p indexerPort) { // Something may panic. Ensure a running indexer. defer func() { if err := recover(); err != nil { go idx.indexer(p) @@ -127,69 +128,77 @@ timerDuration := 15 * time.Second timer := time.NewTimer(timerDuration) ctx := index.NoEnrichContext(context.Background()) for { start := time.Now() - changed := false - for { - zid, val := idx.ar.Dequeue() - if zid.IsValid() { - changed = true - idx.mx.Lock() - idx.sinceReload++ - idx.mx.Unlock() - if !val { - idx.deleteZettel(zid) - continue - } - - zettel, err := p.GetZettel(ctx, zid) - if err != nil { - // TODO: on some errors put the zid into a "try later" set - continue - } - idx.updateZettel(ctx, zettel, p) - continue - } - - if val == false { - break - } + if idx.workService(ctx, p) { + idx.mx.Lock() + idx.durLastIndex = time.Since(start) + idx.mx.Unlock() + } + if !idx.sleepService(timer, timerDuration) { + return + } + } +} + +func (idx *indexer) workService(ctx context.Context, p indexerPort) bool { + changed := false + for { + switch action, zid := idx.ar.Dequeue(); action { + case arNothing: + return changed + case arReload: zids, err := p.FetchZids(ctx) if err == nil { idx.ar.Reload(nil, zids) idx.mx.Lock() idx.lastReload = time.Now() idx.sinceReload = 0 idx.mx.Unlock() } - } - if changed { - idx.mx.Lock() - idx.durLastIndex = time.Now().Sub(start) - idx.mx.Unlock() - } - - select { - case _, ok := <-idx.ready: - if !ok { - return - } - case _, ok := <-timer.C: - if !ok { - return - } - timer.Reset(timerDuration) - case _, ok := <-idx.done: - if !ok { - if !timer.Stop() { - <-timer.C - } - return - } - } - } + case arUpdate: + changed = true + idx.mx.Lock() + idx.sinceReload++ + idx.mx.Unlock() + zettel, err := p.GetZettel(ctx, zid) + if err != nil { + // TODO: on some errors put the zid into a "try later" set + continue + } + idx.updateZettel(ctx, zettel, p) + case arDelete: + changed = true + idx.mx.Lock() + idx.sinceReload++ + idx.mx.Unlock() + idx.deleteZettel(zid) + } + } +} + +func (idx *indexer) sleepService(timer *time.Timer, timerDuration time.Duration) bool { + select { + case _, ok := <-idx.ready: + if !ok { + return false + } + case _, ok := <-timer.C: + if !ok { + return false + } + timer.Reset(timerDuration) + case _, ok := <-idx.done: + if !ok { + if !timer.Stop() { + <-timer.C + } + return false + } + } + return true } type getMetaPort interface { GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) } @@ -213,15 +222,15 @@ } zn := parser.ParseZettel(zettel, "") refs := collect.References(zn) updateReferences(ctx, refs.Links, p, zi) updateReferences(ctx, refs.Images, p, zi) - idx.store.UpdateReferences(ctx, zi) + toCheck := idx.store.UpdateReferences(ctx, zi) + idx.checkZettel(toCheck) } -func updateValue( - ctx context.Context, inverse string, value string, p getMetaPort, zi *index.ZettelIndex) { +func updateValue(ctx context.Context, inverse string, value string, p getMetaPort, zi *index.ZettelIndex) { zid, err := id.Parse(value) if err != nil { return } if _, err := p.GetMeta(ctx, zid); err != nil { @@ -233,20 +242,18 @@ return } zi.AddMetaRef(inverse, zid) } -func updateReferences( - ctx context.Context, refs []*ast.Reference, p getMetaPort, zi *index.ZettelIndex) { +func updateReferences(ctx context.Context, refs []*ast.Reference, p getMetaPort, zi *index.ZettelIndex) { zrefs, _, _ := collect.DivideReferences(refs, false) for _, ref := range zrefs { - updateReference(ctx, ref.Value, p, zi) + updateReference(ctx, ref.URL.Path, p, zi) } } -func updateReference( - ctx context.Context, value string, p getMetaPort, zi *index.ZettelIndex) { +func updateReference(ctx context.Context, value string, p getMetaPort, zi *index.ZettelIndex) { zid, err := id.Parse(value) if err != nil { return } if _, err := p.GetMeta(ctx, zid); err != nil { @@ -255,7 +262,14 @@ } zi.AddBackRef(zid) } func (idx *indexer) deleteZettel(zid id.Zid) { - idx.store.DeleteZettel(context.Background(), zid) + toCheck := idx.store.DeleteZettel(context.Background(), zid) + idx.checkZettel(toCheck) +} + +func (idx *indexer) checkZettel(s id.Set) { + for zid := range s { + idx.ar.Enqueue(zid, arUpdate) + } } Index: index/memstore/memstore.go ================================================================== --- index/memstore/memstore.go +++ index/memstore/memstore.go @@ -21,40 +21,42 @@ "zettelstore.de/z/domain/meta" "zettelstore.de/z/index" ) type metaRefs struct { - forward []id.Zid - backward []id.Zid + forward id.Slice + backward id.Slice } type zettelIndex struct { - dead string - forward []id.Zid - backward []id.Zid + dead id.Slice + forward id.Slice + backward id.Slice meta map[string]metaRefs } func (zi *zettelIndex) isEmpty() bool { - if len(zi.forward) > 0 || len(zi.backward) > 0 || zi.dead != "" { + if len(zi.forward) > 0 || len(zi.backward) > 0 || len(zi.dead) > 0 { return false } return zi.meta == nil || len(zi.meta) == 0 } type memStore struct { - mx sync.RWMutex - idx map[id.Zid]*zettelIndex + mx sync.RWMutex + idx map[id.Zid]*zettelIndex + dead map[id.Zid]id.Slice // map dead refs where they occur // Stats updates uint64 } // New returns a new memory-based index store. func New() index.Store { return &memStore{ - idx: make(map[id.Zid]*zettelIndex), + idx: make(map[id.Zid]*zettelIndex), + dead: make(map[id.Zid]id.Slice), } } func (ms *memStore) Enrich(ctx context.Context, m *meta.Meta) { ms.mx.RLock() @@ -62,80 +64,126 @@ zi, ok := ms.idx[m.Zid] if !ok { return } var updated bool - if zi.dead != "" { - m.Set(meta.KeyDead, zi.dead) + if len(zi.dead) > 0 { + m.Set(meta.KeyDead, zi.dead.String()) updated = true } - back := zi.backward + back := removeOtherMetaRefs(m, zi.backward.Copy()) if len(zi.backward) > 0 { - m.Set(meta.KeyBackward, refsToString(zi.backward)) + m.Set(meta.KeyBackward, zi.backward.String()) updated = true } if len(zi.forward) > 0 { - m.Set(meta.KeyForward, refsToString(zi.forward)) + m.Set(meta.KeyForward, zi.forward.String()) back = remRefs(back, zi.forward) updated = true } if len(zi.meta) > 0 { for k, refs := range zi.meta { if len(refs.backward) > 0 { - m.Set(k, refsToString(refs.backward)) + m.Set(k, refs.backward.String()) back = remRefs(back, refs.backward) updated = true } } } if len(back) > 0 { - m.Set(meta.KeyBack, refsToString(back)) + m.Set(meta.KeyBack, back.String()) updated = true } if updated { ms.updates++ } } -func (ms *memStore) UpdateReferences(ctx context.Context, zidx *index.ZettelIndex) { +func removeOtherMetaRefs(m *meta.Meta, back id.Slice) id.Slice { + for _, p := range m.PairsRest(false) { + switch meta.Type(p.Key) { + case meta.TypeID: + if zid, err := id.Parse(p.Value); err == nil { + back = remRef(back, zid) + } + case meta.TypeIDSet: + for _, val := range meta.ListFromValue(p.Value) { + if zid, err := id.Parse(val); err == nil { + back = remRef(back, zid) + } + } + } + } + return back +} + +func (ms *memStore) UpdateReferences(ctx context.Context, zidx *index.ZettelIndex) id.Set { ms.mx.Lock() defer ms.mx.Unlock() zi, ziExist := ms.idx[zidx.Zid] if !ziExist || zi == nil { zi = &zettelIndex{} ziExist = false } - // Update dead references - if drefs := zidx.GetDeadRefs(); len(drefs) > 0 { - zi.dead = refsToString(drefs) - } else { - zi.dead = "" - } - - // Update forward and backward references + // Is this zettel an old dead reference mentioned in other zettel? + var toCheck id.Set + if refs, ok := ms.dead[zidx.Zid]; ok { + // These must be checked later again + toCheck = id.NewSet(refs...) + delete(ms.dead, zidx.Zid) + } + + ms.updateDeadReferences(zidx, zi) + ms.updateForwardBackwardReferences(zidx, zi) + ms.updateMetadataReferences(zidx, zi) + + // Check if zi must be inserted into ms.idx + if !ziExist && !zi.isEmpty() { + ms.idx[zidx.Zid] = zi + } + + return toCheck +} + +func (ms *memStore) updateDeadReferences(zidx *index.ZettelIndex, zi *zettelIndex) { + // Must only be called if ms.mx is write-locked! + drefs := zidx.GetDeadRefs() + newRefs, remRefs := refsDiff(drefs, zi.dead) + zi.dead = drefs + for _, ref := range remRefs { + ms.dead[ref] = remRef(ms.dead[ref], zidx.Zid) + } + for _, ref := range newRefs { + ms.dead[ref] = addRef(ms.dead[ref], zidx.Zid) + } +} + +func (ms *memStore) updateForwardBackwardReferences(zidx *index.ZettelIndex, zi *zettelIndex) { + // Must only be called if ms.mx is write-locked! brefs := zidx.GetBackRefs() newRefs, remRefs := refsDiff(brefs, zi.forward) zi.forward = brefs - for _, ref := range newRefs { - bzi := ms.getEntry(ref) - bzi.backward = addRef(bzi.backward, zidx.Zid) - } for _, ref := range remRefs { bzi := ms.getEntry(ref) bzi.backward = remRef(bzi.backward, zidx.Zid) } + for _, ref := range newRefs { + bzi := ms.getEntry(ref) + bzi.backward = addRef(bzi.backward, zidx.Zid) + } +} - // Update metadata references +func (ms *memStore) updateMetadataReferences(zidx *index.ZettelIndex, zi *zettelIndex) { + // Must only be called if ms.mx is write-locked! metarefs := zidx.GetMetaRefs() for key, mr := range zi.meta { if _, ok := metarefs[key]; ok { continue } ms.removeInverseMeta(zidx.Zid, key, mr.forward) } - if zi.meta == nil { zi.meta = make(map[string]metaRefs) } for key, mrefs := range metarefs { mr := zi.meta[key] @@ -152,69 +200,94 @@ bmr.backward = addRef(bmr.backward, zidx.Zid) bzi.meta[key] = bmr } ms.removeInverseMeta(zidx.Zid, key, remRefs) } - - // Check if zi must be inserted into ms.idx - if !ziExist && !zi.isEmpty() { - ms.idx[zidx.Zid] = zi - } } func (ms *memStore) getEntry(zid id.Zid) *zettelIndex { + // Must only be called if ms.mx is write-locked! if zi, ok := ms.idx[zid]; ok { return zi } zi := &zettelIndex{} ms.idx[zid] = zi return zi } -func (ms *memStore) DeleteZettel(ctx context.Context, zid id.Zid) { +func (ms *memStore) DeleteZettel(ctx context.Context, zid id.Zid) id.Set { ms.mx.Lock() defer ms.mx.Unlock() zi, ok := ms.idx[zid] if !ok { - return + return nil + } + + ms.deleteDeadSources(zid, zi) + toCheck := ms.deleteForwardBackward(zid, zi) + if len(zi.meta) > 0 { + for key, mrefs := range zi.meta { + ms.removeInverseMeta(zid, key, mrefs.forward) + } + } + delete(ms.idx, zid) + return toCheck +} + +func (ms *memStore) deleteDeadSources(zid id.Zid, zi *zettelIndex) { + // Must only be called if ms.mx is write-locked! + for _, ref := range zi.dead { + if drefs, ok := ms.dead[ref]; ok { + drefs = remRef(drefs, zid) + if len(drefs) > 0 { + ms.dead[ref] = drefs + } else { + delete(ms.dead, ref) + } + } } +} +func (ms *memStore) deleteForwardBackward(zid id.Zid, zi *zettelIndex) id.Set { + // Must only be called if ms.mx is write-locked! + var toCheck id.Set for _, ref := range zi.forward { if fzi, ok := ms.idx[ref]; ok { fzi.backward = remRef(fzi.backward, zid) } } for _, ref := range zi.backward { if bzi, ok := ms.idx[ref]; ok { bzi.forward = remRef(bzi.forward, zid) - } - } - if len(zi.meta) > 0 { - for key, mrefs := range zi.meta { - ms.removeInverseMeta(zid, key, mrefs.forward) + if toCheck == nil { + toCheck = id.NewSet() + } + toCheck[ref] = true } } - delete(ms.idx, zid) + return toCheck } -func (ms *memStore) removeInverseMeta(zid id.Zid, key string, forward []id.Zid) { +func (ms *memStore) removeInverseMeta(zid id.Zid, key string, forward id.Slice) { // Must only be called if ms.mx is write-locked! for _, ref := range forward { - if bzi, ok := ms.idx[ref]; ok { - if bzi.meta != nil { - if bmr, ok := bzi.meta[key]; ok { - bmr.backward = remRef(bmr.backward, zid) - if len(bmr.backward) > 0 || len(bmr.forward) > 0 { - bzi.meta[key] = bmr - } else { - delete(bzi.meta, key) - if len(bzi.meta) == 0 { - bzi.meta = nil - } - } - } + bzi, ok := ms.idx[ref] + if !ok || bzi.meta == nil { + continue + } + bmr, ok := bzi.meta[key] + if !ok { + continue + } + bmr.backward = remRef(bmr.backward, zid) + if len(bmr.backward) > 0 || len(bmr.forward) > 0 { + bzi.meta[key] = bmr + } else { + delete(bzi.meta, key) + if len(bzi.meta) == 0 { + bzi.meta = nil } } } } @@ -225,15 +298,17 @@ ms.mx.RUnlock() } func (ms *memStore) Write(w io.Writer) { ms.mx.RLock() - zids := make([]id.Zid, 0, len(ms.idx)) + defer ms.mx.RUnlock() + + zids := make(id.Slice, 0, len(ms.idx)) for id := range ms.idx { zids = append(zids, id) } - id.Sort(zids) + zids.Sort() for _, id := range zids { fmt.Fprintln(w, id) zi := ms.idx[id] fmt.Fprintln(w, "-", zi.dead) writeZidsLn(w, ">", zi.forward) @@ -248,16 +323,24 @@ writeZidsLn(w, "]", fb.forward) writeZidsLn(w, "[", fb.backward) } } } - ms.mx.RUnlock() + + zids = make(id.Slice, 0, len(ms.dead)) + for id := range ms.dead { + zids = append(zids, id) + } + zids.Sort() + for _, id := range zids { + fmt.Fprintln(w, "~", id, ms.dead[id]) + } } -func writeZidsLn(w io.Writer, prefix string, zids []id.Zid) { +func writeZidsLn(w io.Writer, prefix string, zids id.Slice) { io.WriteString(w, prefix) for _, zid := range zids { io.WriteString(w, " ") w.Write(zid.Bytes()) } fmt.Fprintln(w) } Index: index/memstore/refs.go ================================================================== --- index/memstore/refs.go +++ index/memstore/refs.go @@ -10,27 +10,14 @@ // Package memstore stored the index in main memory. package memstore import ( - "bytes" - "zettelstore.de/z/domain/id" ) -func refsToString(refs []id.Zid) string { - var buf bytes.Buffer - for i, dref := range refs { - if i > 0 { - buf.WriteByte(' ') - } - buf.Write(dref.Bytes()) - } - return buf.String() -} - -func refsDiff(refsN, refsO []id.Zid) (newRefs, remRefs []id.Zid) { +func refsDiff(refsN, refsO id.Slice) (newRefs, remRefs id.Slice) { npos, opos := 0, 0 for npos < len(refsN) && opos < len(refsO) { rn, ro := refsN[npos], refsO[opos] if rn == ro { npos++ @@ -52,30 +39,30 @@ remRefs = append(remRefs, refsO[opos:]...) } return newRefs, remRefs } -func addRef(refs []id.Zid, ref id.Zid) []id.Zid { +func addRef(refs id.Slice, ref id.Zid) id.Slice { if len(refs) == 0 { return append(refs, ref) } for i, r := range refs { if r == ref { return refs } if r > ref { - return append(refs[:i], append([]id.Zid{ref}, refs[i:]...)...) + return append(refs[:i], append(id.Slice{ref}, refs[i:]...)...) } } return append(refs, ref) } -func remRefs(refs []id.Zid, rem []id.Zid) []id.Zid { +func remRefs(refs id.Slice, rem id.Slice) id.Slice { if len(refs) == 0 || len(rem) == 0 { return refs } - result := make([]id.Zid, 0, len(refs)) + result := make(id.Slice, 0, len(refs)) rpos, dpos := 0, 0 for rpos < len(refs) && dpos < len(rem) { rr, dr := refs[rpos], rem[dpos] if rr < dr { result = append(result, rr) @@ -93,18 +80,16 @@ result = append(result, refs[rpos:]...) } return result } -func remRef(refs []id.Zid, ref id.Zid) []id.Zid { - if refs != nil { - for i, r := range refs { - if r == ref { - return append(refs[:i], refs[i+1:]...) - } - if r > ref { - return refs - } +func remRef(refs id.Slice, ref id.Zid) id.Slice { + for i, r := range refs { + if r == ref { + return append(refs[:i], refs[i+1:]...) + } + if r > ref { + return refs } } return refs } Index: index/memstore/refs_test.go ================================================================== --- index/memstore/refs_test.go +++ index/memstore/refs_test.go @@ -15,22 +15,11 @@ "testing" "zettelstore.de/z/domain/id" ) -func numsToRefs(nums []uint) []id.Zid { - if nums == nil { - return nil - } - refs := make([]id.Zid, 0, len(nums)) - for _, n := range nums { - refs = append(refs, id.Zid(n)) - } - return refs -} - -func assertRefs(t *testing.T, i int, got []id.Zid, exp []uint) { +func assertRefs(t *testing.T, i int, got, exp id.Slice) { t.Helper() if got == nil && exp != nil { t.Errorf("%d: got nil, but expected %v", i, exp) return } @@ -47,116 +36,99 @@ t.Errorf("%d: pos %d: expected %d, but got %d", i, p, n, got) } } } -func TestRefsToString(t *testing.T) { - testcases := []struct { - in []uint - exp string - }{ - {nil, ""}, - {[]uint{}, ""}, - {[]uint{1}, "00000000000001"}, - {[]uint{1, 2}, "00000000000001 00000000000002"}, - } - for i, tc := range testcases { - got := refsToString(numsToRefs(tc.in)) - if got != tc.exp { - t.Errorf("%d/%v: expected %q, but got %q", i, tc.in, tc.exp, got) - } - } -} - -func TestRefsDiff(t *testing.T) { - testcases := []struct { - in1, in2 []uint - exp1, exp2 []uint - }{ - {nil, nil, nil, nil}, - {[]uint{1}, nil, []uint{1}, nil}, - {nil, []uint{1}, nil, []uint{1}}, - {[]uint{1}, []uint{1}, nil, nil}, - {[]uint{1, 2}, []uint{1}, []uint{2}, nil}, - {[]uint{1, 2}, []uint{1, 3}, []uint{2}, []uint{3}}, - {[]uint{1, 4}, []uint{1, 3}, []uint{4}, []uint{3}}, - } - for i, tc := range testcases { - got1, got2 := refsDiff(numsToRefs(tc.in1), numsToRefs(tc.in2)) +func TestRefsDiff(t *testing.T) { + testcases := []struct { + in1, in2 id.Slice + exp1, exp2 id.Slice + }{ + {nil, nil, nil, nil}, + {id.Slice{1}, nil, id.Slice{1}, nil}, + {nil, id.Slice{1}, nil, id.Slice{1}}, + {id.Slice{1}, id.Slice{1}, nil, nil}, + {id.Slice{1, 2}, id.Slice{1}, id.Slice{2}, nil}, + {id.Slice{1, 2}, id.Slice{1, 3}, id.Slice{2}, id.Slice{3}}, + {id.Slice{1, 4}, id.Slice{1, 3}, id.Slice{4}, id.Slice{3}}, + } + for i, tc := range testcases { + got1, got2 := refsDiff(tc.in1, tc.in2) assertRefs(t, i, got1, tc.exp1) assertRefs(t, i, got2, tc.exp2) } } func TestAddRef(t *testing.T) { testcases := []struct { - ref []uint + ref id.Slice zid uint - exp []uint + exp id.Slice }{ - {nil, 5, []uint{5}}, - {[]uint{1}, 5, []uint{1, 5}}, - {[]uint{10}, 5, []uint{5, 10}}, - {[]uint{5}, 5, []uint{5}}, - {[]uint{1, 10}, 5, []uint{1, 5, 10}}, - {[]uint{1, 5, 10}, 5, []uint{1, 5, 10}}, + {nil, 5, id.Slice{5}}, + {id.Slice{1}, 5, id.Slice{1, 5}}, + {id.Slice{10}, 5, id.Slice{5, 10}}, + {id.Slice{5}, 5, id.Slice{5}}, + {id.Slice{1, 10}, 5, id.Slice{1, 5, 10}}, + {id.Slice{1, 5, 10}, 5, id.Slice{1, 5, 10}}, } for i, tc := range testcases { - got := addRef(numsToRefs(tc.ref), id.Zid(tc.zid)) + got := addRef(tc.ref, id.Zid(tc.zid)) assertRefs(t, i, got, tc.exp) } } func TestRemRefs(t *testing.T) { testcases := []struct { - in1, in2 []uint - exp []uint + in1, in2 id.Slice + exp id.Slice }{ {nil, nil, nil}, - {nil, []uint{}, nil}, - {[]uint{}, nil, []uint{}}, - {[]uint{}, []uint{}, []uint{}}, - {[]uint{1}, []uint{5}, []uint{1}}, - {[]uint{10}, []uint{5}, []uint{10}}, - {[]uint{1, 5}, []uint{5}, []uint{1}}, - {[]uint{5, 10}, []uint{5}, []uint{10}}, - {[]uint{1, 10}, []uint{5}, []uint{1, 10}}, - {[]uint{1}, []uint{2, 5}, []uint{1}}, - {[]uint{10}, []uint{2, 5}, []uint{10}}, - {[]uint{1, 5}, []uint{2, 5}, []uint{1}}, - {[]uint{5, 10}, []uint{2, 5}, []uint{10}}, - {[]uint{1, 2, 5}, []uint{2, 5}, []uint{1}}, - {[]uint{2, 5, 10}, []uint{2, 5}, []uint{10}}, - {[]uint{1, 10}, []uint{2, 5}, []uint{1, 10}}, - {[]uint{1}, []uint{5, 9}, []uint{1}}, - {[]uint{10}, []uint{5, 9}, []uint{10}}, - {[]uint{1, 5}, []uint{5, 9}, []uint{1}}, - {[]uint{5, 10}, []uint{5, 9}, []uint{10}}, - {[]uint{1, 5, 9}, []uint{5, 9}, []uint{1}}, - {[]uint{5, 9, 10}, []uint{5, 9}, []uint{10}}, - {[]uint{1, 10}, []uint{5, 9}, []uint{1, 10}}, + {nil, id.Slice{}, nil}, + {id.Slice{}, nil, id.Slice{}}, + {id.Slice{}, id.Slice{}, id.Slice{}}, + {id.Slice{1}, id.Slice{5}, id.Slice{1}}, + {id.Slice{10}, id.Slice{5}, id.Slice{10}}, + {id.Slice{1, 5}, id.Slice{5}, id.Slice{1}}, + {id.Slice{5, 10}, id.Slice{5}, id.Slice{10}}, + {id.Slice{1, 10}, id.Slice{5}, id.Slice{1, 10}}, + {id.Slice{1}, id.Slice{2, 5}, id.Slice{1}}, + {id.Slice{10}, id.Slice{2, 5}, id.Slice{10}}, + {id.Slice{1, 5}, id.Slice{2, 5}, id.Slice{1}}, + {id.Slice{5, 10}, id.Slice{2, 5}, id.Slice{10}}, + {id.Slice{1, 2, 5}, id.Slice{2, 5}, id.Slice{1}}, + {id.Slice{2, 5, 10}, id.Slice{2, 5}, id.Slice{10}}, + {id.Slice{1, 10}, id.Slice{2, 5}, id.Slice{1, 10}}, + {id.Slice{1}, id.Slice{5, 9}, id.Slice{1}}, + {id.Slice{10}, id.Slice{5, 9}, id.Slice{10}}, + {id.Slice{1, 5}, id.Slice{5, 9}, id.Slice{1}}, + {id.Slice{5, 10}, id.Slice{5, 9}, id.Slice{10}}, + {id.Slice{1, 5, 9}, id.Slice{5, 9}, id.Slice{1}}, + {id.Slice{5, 9, 10}, id.Slice{5, 9}, id.Slice{10}}, + {id.Slice{1, 10}, id.Slice{5, 9}, id.Slice{1, 10}}, } for i, tc := range testcases { - got := remRefs(numsToRefs(tc.in1), numsToRefs(tc.in2)) + got := remRefs(tc.in1, tc.in2) assertRefs(t, i, got, tc.exp) } } func TestRemRef(t *testing.T) { testcases := []struct { - ref []uint + ref id.Slice zid uint - exp []uint + exp id.Slice }{ {nil, 5, nil}, - {[]uint{}, 5, []uint{}}, - {[]uint{1}, 5, []uint{1}}, - {[]uint{10}, 5, []uint{10}}, - {[]uint{1, 5}, 5, []uint{1}}, - {[]uint{5, 10}, 5, []uint{10}}, - {[]uint{1, 5, 10}, 5, []uint{1, 10}}, + {id.Slice{}, 5, id.Slice{}}, + {id.Slice{5}, 5, id.Slice{}}, + {id.Slice{1}, 5, id.Slice{1}}, + {id.Slice{10}, 5, id.Slice{10}}, + {id.Slice{1, 5}, 5, id.Slice{1}}, + {id.Slice{5, 10}, 5, id.Slice{10}}, + {id.Slice{1, 5, 10}, 5, id.Slice{1, 10}}, } for i, tc := range testcases { - got := remRef(numsToRefs(tc.ref), id.Zid(tc.zid)) + got := remRef(tc.ref, id.Zid(tc.zid)) assertRefs(t, i, got, tc.exp) } } Index: index/zettel.go ================================================================== --- index/zettel.go +++ index/zettel.go @@ -15,23 +15,23 @@ "zettelstore.de/z/domain/id" ) // ZettelIndex contains all index data of a zettel. type ZettelIndex struct { - Zid id.Zid // zid of the indexed zettel - backrefs map[id.Zid]bool // set of back references - metarefs map[string]map[id.Zid]bool // references to inverse keys - deadrefs map[id.Zid]bool // set of dead references + Zid id.Zid // zid of the indexed zettel + backrefs id.Set // set of back references + metarefs map[string]id.Set // references to inverse keys + deadrefs id.Set // set of dead references } // NewZettelIndex creates a new zettel index. func NewZettelIndex(zid id.Zid) *ZettelIndex { return &ZettelIndex{ Zid: zid, - backrefs: make(map[id.Zid]bool), - metarefs: make(map[string]map[id.Zid]bool), - deadrefs: make(map[id.Zid]bool), + backrefs: id.NewSet(), + metarefs: make(map[string]id.Set), + deadrefs: id.NewSet(), } } // AddBackRef adds a reference to a zettel where the current zettel links to // without any more information. @@ -44,46 +44,34 @@ func (zi *ZettelIndex) AddMetaRef(key string, zid id.Zid) { if zids, ok := zi.metarefs[key]; ok { zids[zid] = true return } - zi.metarefs[key] = map[id.Zid]bool{zid: true} + zi.metarefs[key] = id.NewSet(zid) } // AddDeadRef adds a dead reference to a zettel. func (zi *ZettelIndex) AddDeadRef(zid id.Zid) { zi.deadrefs[zid] = true } // GetDeadRefs returns all dead references as a sorted list. -func (zi *ZettelIndex) GetDeadRefs() []id.Zid { - return sortedZids(zi.deadrefs) +func (zi *ZettelIndex) GetDeadRefs() id.Slice { + return zi.deadrefs.Sort() } // GetBackRefs returns all back references as a sorted list. -func (zi *ZettelIndex) GetBackRefs() []id.Zid { - return sortedZids(zi.backrefs) +func (zi *ZettelIndex) GetBackRefs() id.Slice { + return zi.backrefs.Sort() } // GetMetaRefs returns all meta references as a map of strings to a sorted list of references -func (zi *ZettelIndex) GetMetaRefs() map[string][]id.Zid { +func (zi *ZettelIndex) GetMetaRefs() map[string]id.Slice { if len(zi.metarefs) == 0 { return nil } - result := make(map[string][]id.Zid, len(zi.metarefs)) - for key, refs := range zi.metarefs { - result[key] = sortedZids(refs) - } - return result -} - -func sortedZids(refmap map[id.Zid]bool) []id.Zid { - if l := len(refmap); l > 0 { - result := make([]id.Zid, 0, l) - for zid := range refmap { - result = append(result, zid) - } - id.Sort(result) - return result - } - return nil + result := make(map[string]id.Slice, len(zi.metarefs)) + for key, refs := range zi.metarefs { + result[key] = refs.Sort() + } + return result } Index: input/input.go ================================================================== --- input/input.go +++ input/input.go @@ -87,11 +87,10 @@ inp.Ch = '\n' inp.Next() case '\n': inp.Next() } - return } // SetPos allows to reset the read position. func (inp *Input) SetPos(pos int) { inp.readPos = pos Index: parser/cleanup.go ================================================================== --- parser/cleanup.go +++ parser/cleanup.go @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020 Detlef Stern +// Copyright (c) 2020-2021 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 @@ -114,11 +114,11 @@ } if !cv.doMark { cv.hasMark = true return } - if len(mn.Text) == 0 { + if mn.Text == "" { mn.Text = cv.addIdentifier("*", mn) return } mn.Text = cv.addIdentifier(mn.Text, mn) } Index: parser/markdown/markdown.go ================================================================== --- parser/markdown/markdown.go +++ parser/markdown/markdown.go @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020 Detlef Stern +// Copyright (c) 2020-2021 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 @@ -282,11 +282,11 @@ return result } // splitText transform the text into a sequence of TextNode and SpaceNode func splitText(text string) ast.InlineSlice { - if len(text) == 0 { + if text == "" { return ast.InlineSlice{} } result := make(ast.InlineSlice, 0, 1) state := 0 // 0=unknown,1=non-spaces,2=spaces @@ -317,41 +317,37 @@ return result } // cleanText removes backslashes from TextNodes and expands entities func cleanText(text string, cleanBS bool) string { - if len(text) == 0 { + if text == "" { return "" } lastPos := 0 var sb strings.Builder for pos, ch := range text { if pos < lastPos { continue } - switch ch { - case '\\': - if cleanBS && pos < len(text)-1 { - switch b := text[pos+1]; b { - case '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', - ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', - '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~': - sb.WriteString(text[lastPos:pos]) - sb.WriteByte(b) - lastPos = pos + 2 - default: - } - } - case '&': + if ch == '&' { inp := input.NewInput(text[pos:]) - s, ok := inp.ScanEntity() - if ok { + if s, ok := inp.ScanEntity(); ok { sb.WriteString(text[lastPos:pos]) sb.WriteString(s) lastPos = pos + inp.Pos } - default: + continue + } + if cleanBS && ch == '\\' && pos < len(text)-1 { + switch b := text[pos+1]; b { + case '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', + ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', + '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~': + sb.WriteString(text[lastPos:pos]) + sb.WriteByte(b) + lastPos = pos + 2 + } } } if lastPos == 0 { return text } @@ -370,18 +366,17 @@ }, } } func cleanCodeSpan(text string) string { - if len(text) == 0 { + if text == "" { return "" } lastPos := 0 var sb strings.Builder for pos, ch := range text { - switch ch { - case '\n': + if ch == '\n' { sb.WriteString(text[lastPos:pos]) if pos < len(text)-1 { sb.WriteByte(' ') } lastPos = pos + 1 Index: parser/parser.go ================================================================== --- parser/parser.go +++ parser/parser.go @@ -81,11 +81,11 @@ // ParseZettel parses the zettel based on the syntax. func ParseZettel(zettel domain.Zettel, syntax string) *ast.ZettelNode { m := zettel.Meta inhMeta := runtime.AddDefaultValues(zettel.Meta) - if len(syntax) == 0 { + if syntax == "" { syntax, _ = inhMeta.Get(meta.KeySyntax) } title, _ := inhMeta.Get(meta.KeyTitle) parseMeta := inhMeta if syntax == meta.ValueSyntaxNone { Index: parser/plain/plain.go ================================================================== --- parser/plain/plain.go +++ parser/plain/plain.go @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020 Detlef Stern +// Copyright (c) 2020-2021 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 @@ -57,12 +57,11 @@ func readLines(inp *input.Input) (lines []string) { for { inp.EatEOL() posL := inp.Pos - switch inp.Ch { - case input.EOS: + if inp.Ch == input.EOS { return lines } inp.SkipToEOL() lines = append(lines, inp.Src[posL:inp.Pos]) } Index: parser/zettelmark/block.go ================================================================== --- parser/zettelmark/block.go +++ parser/zettelmark/block.go @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020 Detlef Stern +// Copyright (c) 2020-2021 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 @@ -369,13 +369,11 @@ } if prevItems := cp.lists[parentPos].Items; len(prevItems) > 0 { lastItem := len(prevItems) - 1 prevItems[lastItem] = append(prevItems[lastItem], cp.lists[childPos]) } else { - cp.lists[parentPos].Items = []ast.ItemSlice{ - ast.ItemSlice{cp.lists[childPos]}, - } + cp.lists[parentPos].Items = []ast.ItemSlice{{cp.lists[childPos]}} } } return nil, true } @@ -454,64 +452,70 @@ break } cnt++ } if cp.lists != nil { - // Identation for a list? - if len(cp.lists) < cnt { - cnt = len(cp.lists) - } - cp.lists = cp.lists[:cnt] - if cnt == 0 { - return nil, false - } - ln := cp.lists[cnt-1] - pn := cp.parseLinePara() - lbn := ln.Items[len(ln.Items)-1] - if lpn, ok := lbn[len(lbn)-1].(*ast.ParaNode); ok { - lpn.Inlines = append(lpn.Inlines, pn.Inlines...) - } else { - ln.Items[len(ln.Items)-1] = append(ln.Items[len(ln.Items)-1], pn) - } - return nil, true + return nil, cp.parseIndentForList(cnt) } if cp.descrl != nil { - // Indentation for definition list - defPos := len(cp.descrl.Descriptions) - 1 - if cnt < 1 || defPos < 0 { - return nil, false - } - if len(cp.descrl.Descriptions[defPos].Descriptions) == 0 { - // Continuation of a definition term - for { - in := cp.parseInline() - if in == nil { - return nil, true - } - cp.descrl.Descriptions[defPos].Term = append(cp.descrl.Descriptions[defPos].Term, in) - if _, ok := in.(*ast.BreakNode); ok { - return nil, true - } - } - } else { - // Continuation of a definition description - pn := cp.parseLinePara() - if pn == nil { - return nil, false - } - descrPos := len(cp.descrl.Descriptions[defPos].Descriptions) - 1 - lbn := cp.descrl.Descriptions[defPos].Descriptions[descrPos] - if lpn, ok := lbn[len(lbn)-1].(*ast.ParaNode); ok { - lpn.Inlines = append(lpn.Inlines, pn.Inlines...) - } else { - descrPos := len(cp.descrl.Descriptions[defPos].Descriptions) - 1 - cp.descrl.Descriptions[defPos].Descriptions[descrPos] = append(cp.descrl.Descriptions[defPos].Descriptions[descrPos], pn) - } - return nil, true - } - } - return nil, false + return nil, cp.parseIndentForDescription(cnt) + } + return nil, false +} + +func (cp *zmkP) parseIndentForList(cnt int) bool { + if len(cp.lists) < cnt { + cnt = len(cp.lists) + } + cp.lists = cp.lists[:cnt] + if cnt == 0 { + return false + } + ln := cp.lists[cnt-1] + pn := cp.parseLinePara() + lbn := ln.Items[len(ln.Items)-1] + if lpn, ok := lbn[len(lbn)-1].(*ast.ParaNode); ok { + lpn.Inlines = append(lpn.Inlines, pn.Inlines...) + } else { + ln.Items[len(ln.Items)-1] = append(ln.Items[len(ln.Items)-1], pn) + } + return true +} + +func (cp *zmkP) parseIndentForDescription(cnt int) bool { + defPos := len(cp.descrl.Descriptions) - 1 + if cnt < 1 || defPos < 0 { + return false + } + if len(cp.descrl.Descriptions[defPos].Descriptions) == 0 { + // Continuation of a definition term + for { + in := cp.parseInline() + if in == nil { + return true + } + cp.descrl.Descriptions[defPos].Term = append(cp.descrl.Descriptions[defPos].Term, in) + if _, ok := in.(*ast.BreakNode); ok { + return true + } + } + } + + // Continuation of a definition description + pn := cp.parseLinePara() + if pn == nil { + return false + } + descrPos := len(cp.descrl.Descriptions[defPos].Descriptions) - 1 + lbn := cp.descrl.Descriptions[defPos].Descriptions[descrPos] + if lpn, ok := lbn[len(lbn)-1].(*ast.ParaNode); ok { + lpn.Inlines = append(lpn.Inlines, pn.Inlines...) + } else { + descrPos := len(cp.descrl.Descriptions[defPos].Descriptions) - 1 + cp.descrl.Descriptions[defPos].Descriptions[descrPos] = append(cp.descrl.Descriptions[defPos].Descriptions[descrPos], pn) + } + return true } // parseLinePara parses one line of inline material. func (cp *zmkP) parseLinePara() *ast.ParaNode { pn := &ast.ParaNode{} Index: parser/zettelmark/inline.go ================================================================== --- parser/zettelmark/inline.go +++ parser/zettelmark/inline.go @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020 Detlef Stern +// Copyright (c) 2020-2021 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 @@ -61,12 +61,11 @@ case '!': in, success = cp.parseMark() } case '{': inp.Next() - switch inp.Ch { - case '{': + if inp.Ch == '{' { in, success = cp.parseImage() } case '#': return cp.parseTag() case '%': Index: parser/zettelmark/node.go ================================================================== --- parser/zettelmark/node.go +++ parser/zettelmark/node.go @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020 Detlef Stern +// Copyright (c) 2020-2021 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 @@ -16,26 +16,20 @@ ) // Internal nodes for parsing zettelmark. These will be removed in // post-processing. -// nullItemNode specifies a removable placeholder for an item block. +// nullItemNode specifies a removable placeholder for an item node. type nullItemNode struct { ast.ItemNode } -func (nn *nullItemNode) blockNode() {} -func (nn *nullItemNode) itemNode() {} - // Accept a visitor and visit the node. func (nn *nullItemNode) Accept(v ast.Visitor) {} // nullDescriptionNode specifies a removable placeholder. type nullDescriptionNode struct { ast.DescriptionNode } -func (nn *nullDescriptionNode) blockNode() {} -func (nn *nullDescriptionNode) descriptionNode() {} - // Accept a visitor and visit the node. func (nn *nullDescriptionNode) Accept(v ast.Visitor) {} Index: parser/zettelmark/post-processor.go ================================================================== --- parser/zettelmark/post-processor.go +++ parser/zettelmark/post-processor.go @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020 Detlef Stern +// Copyright (c) 2020-2021 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 @@ -39,11 +39,11 @@ if pn != nil { pn.Inlines = pp.processInlineSlice(pn.Inlines) } } -// VisitVerbatim post-processes a verbatim block. +// VisitVerbatim does nothing, no post-processing needed. func (pp *postProcessor) VisitVerbatim(vn *ast.VerbatimNode) {} // VisitRegion post-processes a region. func (pp *postProcessor) VisitRegion(rn *ast.RegionNode) { oldVerse := pp.inVerse @@ -58,11 +58,11 @@ // VisitHeading post-processes a heading. func (pp *postProcessor) VisitHeading(hn *ast.HeadingNode) { hn.Inlines = pp.processInlineSlice(hn.Inlines) } -// VisitHRule post-processes a horizontal rule. +// VisitHRule does nothing, no post-processing needed. func (pp *postProcessor) VisitHRule(hn *ast.HRuleNode) {} // VisitList post-processes a list. func (pp *postProcessor) VisitNestedList(ln *ast.NestedListNode) { for i, item := range ln.Items { @@ -91,13 +91,11 @@ tn.Header = tn.Rows[0] tn.Rows = tn.Rows[1:] for pos, cell := range tn.Header { if inlines := cell.Inlines; len(inlines) > 0 { if textNode, ok := inlines[0].(*ast.TextNode); ok { - if strings.HasPrefix(textNode.Text, "=") { - textNode.Text = textNode.Text[1:] - } + textNode.Text = strings.TrimPrefix(textNode.Text, "=") } if textNode, ok := inlines[len(inlines)-1].(*ast.TextNode); ok { if tnl := len(textNode.Text); tnl > 0 { if align := getAlignment(textNode.Text[tnl-1]); align != ast.AlignDefault { tn.Align[pos] = align @@ -439,15 +437,14 @@ return toPos } func (pp *postProcessor) processInlineSliceInplace(ins ast.InlineSlice) { for _, in := range ins { - switch n := in.(type) { - case *ast.TextNode: + if n, ok := in.(*ast.TextNode); ok { if n.Text == "..." { n.Text = "\u2026" } else if len(n.Text) == 4 && strings.IndexByte(",;:!?", n.Text[3]) >= 0 && n.Text[:3] == "..." { n.Text = "\u2026" + n.Text[3:] } } } } Index: parser/zettelmark/zettelmark.go ================================================================== --- parser/zettelmark/zettelmark.go +++ parser/zettelmark/zettelmark.go @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020 Detlef Stern +// Copyright (c) 2020-2021 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 @@ -137,11 +137,11 @@ } inp.Next() } } -func updateAttrs(attrs map[string]string, key string, val string) { +func updateAttrs(attrs map[string]string, key, val string) { if prevVal := attrs[key]; len(prevVal) > 0 { attrs[key] = prevVal + " " + val } else { attrs[key] = val } Index: parser/zettelmark/zettelmark_test.go ================================================================== --- parser/zettelmark/zettelmark_test.go +++ parser/zettelmark/zettelmark_test.go @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2020 Detlef Stern +// Copyright (c) 2020-2021 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 @@ -876,11 +876,11 @@ tv.b.WriteByte(' ') tv.b.WriteString(k) v := a.Attrs[k] if len(v) > 0 { tv.b.WriteByte('=') - if strings.IndexRune(v, ' ') >= 0 { + if strings.ContainsRune(v, ' ') { tv.b.WriteByte('"') tv.b.WriteString(v) tv.b.WriteByte('"') } else { tv.b.WriteString(v) Index: place/constplace/constdata.go ================================================================== --- place/constplace/constdata.go +++ place/constplace/constdata.go @@ -10,53 +10,68 @@ // Package constplace stores zettel inside the executable. package constplace import ( - "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) const ( syntaxTemplate = "mustache" ) var constZettelMap = map[id.Zid]constZettel{ - id.ConfigurationZid: constZettel{ + id.ConfigurationZid: { constHeader{ meta.KeyTitle: "Zettelstore Runtime Configuration", meta.KeyRole: meta.ValueRoleConfiguration, meta.KeyVisibility: meta.ValueVisibilityOwner, meta.KeySyntax: meta.ValueSyntaxNone, }, "", }, - id.BaseTemplateZid: constZettel{ + id.BaseTemplateZid: { constHeader{ meta.KeyTitle: "Zettelstore Base HTML Template", meta.KeyRole: meta.ValueRoleConfiguration, meta.KeyVisibility: meta.ValueVisibilityExpert, meta.KeySyntax: syntaxTemplate, }, - domain.NewContent( - ` + ` {{{MetaHeader}}} -{{{Header}}}