Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From trunk To v0.4
2022-06-24
| ||
16:35 | Refactor usecase 'list role' to return more information ... (Leaf check-in: 0bd22a7fae user: stern tags: trunk) | |
16:28 | Refactor list handling usecases: merge them into same file ... (check-in: d15af56bca user: stern tags: trunk) | |
2022-03-10
| ||
13:20 | Increase version to 0.5-dev to begin next development cycle ... (check-in: 73f6c7eb13 user: stern tags: trunk) | |
2022-03-09
| ||
14:06 | Version 0.4 ... (check-in: 54ed47f372 user: stern tags: trunk, release, v0.4) | |
13:54 | Update release checklist ... (check-in: 711a670d7a user: stern tags: trunk) | |
Changes to VERSION.
|
| | | 1 | 0.4 |
Changes to ast/ast.go.
︙ | ︙ | |||
60 61 62 63 64 65 66 67 68 69 70 71 72 73 | type DescriptionSlice []DescriptionNode // InlineNode is the interface that all inline nodes must implement. type InlineNode interface { Node inlineNode() } // Reference is a reference to external or internal material. type Reference struct { URL *url.URL Value string State RefState } | > > > > > > > | 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | type DescriptionSlice []DescriptionNode // InlineNode is the interface that all inline nodes must implement. type InlineNode interface { Node inlineNode() } // InlineEmbedNode is a node that specifies some embeddings in inline mode. // It is abstract, b/c there are different concrete type implementations. type InlineEmbedNode interface { InlineNode inlineEmbedNode() } // Reference is a reference to external or internal material. type Reference struct { URL *url.URL Value string State RefState } |
︙ | ︙ |
Changes to ast/block.go.
1 2 3 4 5 6 7 8 9 10 11 12 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 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 ast | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 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 ast import "zettelstore.de/c/zjson" // Definition of Block nodes. // BlockSlice is a slice of BlockNodes. type BlockSlice []BlockNode func (*BlockSlice) blockNode() { /* Just a marker */ } |
︙ | ︙ | |||
69 70 71 72 73 74 75 | } //-------------------------------------------------------------------------- // VerbatimNode contains uninterpreted text type VerbatimNode struct { Kind VerbatimKind | | | < < < < < < < | | | 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | } //-------------------------------------------------------------------------- // VerbatimNode contains uninterpreted text type VerbatimNode struct { Kind VerbatimKind Attrs zjson.Attributes Content []byte } // VerbatimKind specifies the format that is applied to code inline nodes. type VerbatimKind uint8 // Constants for VerbatimCode const ( _ VerbatimKind = iota VerbatimZettel // Zettel content VerbatimProg // Program code VerbatimComment // Block comment VerbatimHTML // Block HTML, e.g. for Markdown ) func (*VerbatimNode) blockNode() { /* Just a marker */ } func (*VerbatimNode) itemNode() { /* Just a marker */ } // WalkChildren does nothing. func (*VerbatimNode) WalkChildren(Visitor) { /* No children*/ } //-------------------------------------------------------------------------- // RegionNode encapsulates a region of block nodes. type RegionNode struct { Kind RegionKind Attrs zjson.Attributes Blocks BlockSlice Inlines InlineSlice // Optional text at the end of the region } // RegionKind specifies the actual region type. type RegionKind uint8 // Values for RegionCode const ( _ RegionKind = iota RegionSpan // Just a span of blocks RegionQuote // A longer quotation RegionVerse // Line breaks matter |
︙ | ︙ | |||
133 134 135 136 137 138 139 | } //-------------------------------------------------------------------------- // HeadingNode stores the heading text and level. type HeadingNode struct { Level int | | | | | | 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | } //-------------------------------------------------------------------------- // HeadingNode stores the heading text and level. type HeadingNode struct { Level int Inlines InlineSlice // Heading text, possibly formatted Slug string // Heading text, normalized Fragment string // Heading text, suitable to be used as an unique URL fragment Attrs zjson.Attributes } func (*HeadingNode) blockNode() { /* Just a marker */ } func (*HeadingNode) itemNode() { /* Just a marker */ } // WalkChildren walks the heading text. func (hn *HeadingNode) WalkChildren(v Visitor) { Walk(v, &hn.Inlines) } //-------------------------------------------------------------------------- // HRuleNode specifies a horizontal rule. type HRuleNode struct { Attrs zjson.Attributes } func (*HRuleNode) blockNode() { /* Just a marker */ } func (*HRuleNode) itemNode() { /* Just a marker */ } // WalkChildren does nothing. func (*HRuleNode) WalkChildren(Visitor) { /* No children*/ } //-------------------------------------------------------------------------- // NestedListNode specifies a nestable list, either ordered or unordered. type NestedListNode struct { Kind NestedListKind Items []ItemSlice Attrs zjson.Attributes } // NestedListKind specifies the actual list type. type NestedListKind uint8 // Values for ListCode const ( |
︙ | ︙ |
Changes to ast/inline.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- package ast import ( "unicode/utf8" | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //----------------------------------------------------------------------------- package ast import ( "unicode/utf8" "zettelstore.de/c/zjson" ) // Definitions of inline nodes. // InlineSlice is a list of BlockNodes. type InlineSlice []InlineNode |
︙ | ︙ | |||
96 97 98 99 100 101 102 | // WalkChildren does nothing. func (*BreakNode) WalkChildren(Visitor) { /* No children*/ } // -------------------------------------------------------------------------- // LinkNode contains the specified link. type LinkNode struct { | < | > < < > > | > | < > | > < > | 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | // WalkChildren does nothing. func (*BreakNode) WalkChildren(Visitor) { /* No children*/ } // -------------------------------------------------------------------------- // LinkNode contains the specified link. type LinkNode struct { Ref *Reference Inlines InlineSlice // The text associated with the link. Attrs zjson.Attributes // Optional attributes } func (*LinkNode) inlineNode() { /* Just a marker */ } // WalkChildren walks to the link text. func (ln *LinkNode) WalkChildren(v Visitor) { if len(ln.Inlines) > 0 { Walk(v, &ln.Inlines) } } // -------------------------------------------------------------------------- // EmbedRefNode contains the specified embedded reference material. type EmbedRefNode struct { Ref *Reference // The reference to be embedded. Inlines InlineSlice // Optional text associated with the image. Attrs zjson.Attributes // Optional attributes Syntax string // Syntax of referenced material, if known } func (*EmbedRefNode) inlineNode() { /* Just a marker */ } func (*EmbedRefNode) inlineEmbedNode() { /* Just a marker */ } // WalkChildren walks to the text that describes the embedded material. func (en *EmbedRefNode) WalkChildren(v Visitor) { Walk(v, &en.Inlines) } // -------------------------------------------------------------------------- // EmbedBLOBNode contains the specified embedded BLOB material. type EmbedBLOBNode struct { Blob []byte // BLOB data itself. Syntax string // Syntax of Blob Inlines InlineSlice // Optional text associated with the image. Attrs zjson.Attributes // Optional attributes } func (*EmbedBLOBNode) inlineNode() { /* Just a marker */ } func (*EmbedBLOBNode) inlineEmbedNode() { /* Just a marker */ } // WalkChildren walks to the text that describes the embedded material. func (en *EmbedBLOBNode) WalkChildren(v Visitor) { Walk(v, &en.Inlines) } // -------------------------------------------------------------------------- // CiteNode contains the specified citation. type CiteNode struct { Key string // The citation key Inlines InlineSlice // Optional text associated with the citation. Attrs zjson.Attributes // Optional attributes } func (*CiteNode) inlineNode() { /* Just a marker */ } // WalkChildren walks to the cite text. func (cn *CiteNode) WalkChildren(v Visitor) { Walk(v, &cn.Inlines) |
︙ | ︙ | |||
185 186 187 188 189 190 191 | } } // -------------------------------------------------------------------------- // FootnoteNode contains the specified footnote. type FootnoteNode struct { | < > | | | 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | } } // -------------------------------------------------------------------------- // FootnoteNode contains the specified footnote. type FootnoteNode struct { Inlines InlineSlice // The footnote text. Attrs zjson.Attributes // Optional attributes } func (*FootnoteNode) inlineNode() { /* Just a marker */ } // WalkChildren walks to the footnote text. func (fn *FootnoteNode) WalkChildren(v Visitor) { Walk(v, &fn.Inlines) } // -------------------------------------------------------------------------- // FormatNode specifies some inline formatting. type FormatNode struct { Kind FormatKind Attrs zjson.Attributes // Optional attributes. Inlines InlineSlice } // FormatKind specifies the format that is applied to the inline nodes. type FormatKind uint8 // Constants for FormatCode const ( _ FormatKind = iota FormatEmph // Emphasized text. FormatStrong // Strongly emphasized text. FormatInsert // Inserted text. |
︙ | ︙ | |||
233 234 235 236 237 238 239 | } // -------------------------------------------------------------------------- // LiteralNode specifies some uninterpreted text. type LiteralNode struct { Kind LiteralKind | | | < | 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | } // -------------------------------------------------------------------------- // LiteralNode specifies some uninterpreted text. type LiteralNode struct { Kind LiteralKind Attrs zjson.Attributes // Optional attributes. Content []byte } // LiteralKind specifies the format that is applied to code inline nodes. type LiteralKind uint8 // Constants for LiteralCode const ( _ LiteralKind = iota LiteralZettel // Zettel content LiteralProg // Inline program code LiteralInput // Computer input, e.g. Keyboard strokes LiteralOutput // Computer output LiteralComment // Inline comment LiteralHTML // Inline HTML, e.g. for Markdown ) func (*LiteralNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. func (*LiteralNode) WalkChildren(Visitor) { /* No children*/ } |
Changes to ast/walk_test.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- package ast_test import ( "testing" | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //----------------------------------------------------------------------------- package ast_test import ( "testing" "zettelstore.de/c/zjson" "zettelstore.de/z/ast" ) func BenchmarkWalk(b *testing.B) { root := ast.BlockSlice{ &ast.HeadingNode{ Inlines: ast.CreateInlineSliceFromWords("A", "Simple", "Heading"), |
︙ | ︙ | |||
42 43 44 45 46 47 48 | }, &ast.ParaNode{ Inlines: ast.CreateInlineSliceFromWords("This", "is", "some", "intermediate", "text."), }, ast.CreateParaNode( &ast.FormatNode{ Kind: ast.FormatEmph, | | | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | }, &ast.ParaNode{ Inlines: ast.CreateInlineSliceFromWords("This", "is", "some", "intermediate", "text."), }, ast.CreateParaNode( &ast.FormatNode{ Kind: ast.FormatEmph, Attrs: zjson.Attributes(map[string]string{ "": "class", "color": "green", }), Inlines: ast.CreateInlineSliceFromWords("This", "is", "some", "emphasized", "text."), }, &ast.SpaceNode{Lexeme: " "}, &ast.LinkNode{ |
︙ | ︙ |
Changes to auth/impl/impl.go.
1 | //----------------------------------------------------------------------------- | | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // 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 impl provides services for authentification / authorization. |
︙ | ︙ |
Changes to box/constbox/base.css.
︙ | ︙ | |||
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | clear: both; visibility: hidden; } main form div { margin: .5em 0 0 0 } input { font-family: monospace } input[type="submit"],button,select { font: inherit } label { font-family: sans-serif; font-size:.9rem } textarea { font-family: monospace; resize: vertical; width: 100%; } .zs-input { padding: .5em; display:block; border:none; border-bottom:1px solid #ccc; width:100%; } | > > | < > > | | 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | clear: both; visibility: hidden; } main form div { margin: .5em 0 0 0 } input { font-family: monospace } input[type="submit"],button,select { font: inherit } label { font-family: sans-serif; font-size:.9rem } label::after { content:":" } textarea { font-family: monospace; resize: vertical; width: 100%; } .zs-input { padding: .5em; display:block; border:none; border-bottom:1px solid #ccc; width:100%; } .zs-button { float:right; margin: .5em 0 .5em 1em; } a:not([class]) { text-decoration-skip-ink: auto } a.broken { text-decoration: line-through } img { max-width: 100% } img.right { float: right } ol.endnotes { padding-top: .5rem; border-top: 1px solid; } kbd { font-family:monospace } code,pre { font-family: monospace; font-size: 85%; |
︙ | ︙ |
Changes to box/constbox/base.mustache.
|
| < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="generator" content="Zettelstore"> <meta name="format-detection" content="telephone=no"> {{{MetaHeader}}} <link rel="stylesheet" href="{{{CSSBaseURL}}}"> <link rel="stylesheet" href="{{{CSSUserURL}}}"> <title>{{Title}}</title> </head> <body> <nav class="zs-menu"> <a href="{{{HomeURL}}}">Home</a> {{#WithUser}} <div class="zs-dropdown"> |
︙ | ︙ | |||
56 57 58 59 60 61 62 | <form action="{{{SearchURL}}}"> <input type="text" placeholder="Search.." name="{{QueryKeySearch}}"> </form> </nav> <main class="content"> {{{Content}}} </main> | | < | > | > | 52 53 54 55 56 57 58 59 60 61 62 63 | <form action="{{{SearchURL}}}"> <input type="text" placeholder="Search.." name="{{QueryKeySearch}}"> </form> </nav> <main class="content"> {{{Content}}} </main> {{#FooterHTML}} <footer> {{{FooterHTML}}} </footer> {{/FooterHTML}} |
Changes to box/constbox/constbox.go.
︙ | ︙ | |||
291 292 293 294 295 296 297 | constHeader{ api.KeyTitle: "Zettelstore User CSS", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: "css", api.KeyVisibility: api.ValueVisibilityPublic, }, domain.NewContent([]byte("/* User-defined CSS */"))}, | < < < < < < < < | | 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 | constHeader{ api.KeyTitle: "Zettelstore User CSS", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: "css", api.KeyVisibility: api.ValueVisibilityPublic, }, domain.NewContent([]byte("/* User-defined CSS */"))}, id.EmojiZid: { constHeader{ api.KeyTitle: "Generic Emoji", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: api.ValueSyntaxGif, api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityPublic, }, domain.NewContent(contentEmoji)}, id.TOCNewTemplateZid: { |
︙ | ︙ |
Changes to box/constbox/delete.mustache.
︙ | ︙ | |||
33 34 35 36 37 38 39 | {{/HasUselessFiles}} <dl> {{#MetaPairs}} <dt>{{Key}}:</dt><dd>{{Value}}</dd> {{/MetaPairs}} </dl> <form method="POST"> | | | 33 34 35 36 37 38 39 40 41 42 43 | {{/HasUselessFiles}} <dl> {{#MetaPairs}} <dt>{{Key}}:</dt><dd>{{Value}}</dd> {{/MetaPairs}} </dl> <form method="POST"> <input class="zs-button" type="submit" value="Delete"> </form> </article> {{end}} |
Changes to box/constbox/form.mustache.
1 2 3 4 5 6 | <article> <header> <h1>{{Heading}}</h1> </header> <form method="POST"> <div> | | | | | < < < < < < < | | | | | | | | < | < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | <article> <header> <h1>{{Heading}}</h1> </header> <form method="POST"> <div> <label for="title">Title</label> <input class="zs-input" type="text" id="title" name="title" placeholder="Title.." value="{{MetaTitle}}" autofocus> </div> <div> <div> <label for="role">Role</label> <input class="zs-input" type="text" id="role" name="role" placeholder="role.." value="{{MetaRole}}"> </div> <label for="tags">Tags</label> <input class="zs-input" type="text" id="tags" name="tags" placeholder="#tag" value="{{MetaTags}}"> </div> <div> <label for="meta">Metadata</label> <textarea class="zs-input" id="meta" name="meta" rows="4" placeholder="metakey: metavalue"> {{#MetaPairsRest}} {{Key}}: {{Value}} {{/MetaPairsRest}} </textarea> </div> <div> <label for="syntax">Syntax</label> <input class="zs-input" type="text" id="syntax" name="syntax" placeholder="syntax.." value="{{MetaSyntax}}"> </div> <div> {{#IsTextContent}} <label for="content">Content</label> <textarea class="zs-input zs-content" id="meta" name="content" rows="20" placeholder="Your content..">{{Content}}</textarea> {{/IsTextContent}} </div> <input class="zs-button" type="submit" value="Submit"> </form> </article> |
Changes to box/constbox/login.mustache.
1 2 3 4 5 6 7 8 9 | <article> <header> <h1>{{Title}}</h1> </header> {{#Retry}} <div class="zs-indication zs-error">Wrong user name / password. Try again.</div> {{/Retry}} <form method="POST" action=""> <div> | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <article> <header> <h1>{{Title}}</h1> </header> {{#Retry}} <div class="zs-indication zs-error">Wrong user name / password. Try again.</div> {{/Retry}} <form method="POST" action=""> <div> <label for="username">User name</label> <input class="zs-input" type="text" id="username" name="username" placeholder="Your user name.." autofocus> </div> <div> <label for="password">Password</label> <input class="zs-input" type="password" id="password" name="password" placeholder="Your password.."> </div> <input class="zs-button" type="submit" value="Login"> </form> </article> |
Changes to box/constbox/rename.mustache.
︙ | ︙ | |||
27 28 29 30 31 32 33 | {{/HasUselessFiles}} <form method="POST"> <div> <label for="newid">New zettel id</label> <input class="zs-input" type="text" id="newzid" name="newzid" placeholder="ZID.." value="{{Zid}}" autofocus> </div> <input type="hidden" id="curzid" name="curzid" value="{{Zid}}"> | | | 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | {{/HasUselessFiles}} <form method="POST"> <div> <label for="newid">New zettel id</label> <input class="zs-input" type="text" id="newzid" name="newzid" placeholder="ZID.." value="{{Zid}}" autofocus> </div> <input type="hidden" id="curzid" name="curzid" value="{{Zid}}"> <input class="zs-button" type="submit" value="Rename"> </form> <dl> {{#MetaPairs}} <dt>{{Key}}:</dt><dd>{{Value}}</dd> {{/MetaPairs}} </dl> </article> |
Changes to box/dirbox/dirbox.go.
︙ | ︙ | |||
399 400 401 402 403 404 405 406 | func (dp *dirBox) ReadStats(st *box.ManagedBoxStats) { st.ReadOnly = dp.readonly st.Zettel = dp.dirSrv.NumDirEntries() dp.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats") } func (dp *dirBox) cleanupMeta(m *meta.Meta) { if syntax, ok := m.Get(api.KeySyntax); !ok || syntax == "" { | > > > | | 399 400 401 402 403 404 405 406 407 408 409 410 411 412 | func (dp *dirBox) ReadStats(st *box.ManagedBoxStats) { st.ReadOnly = dp.readonly st.Zettel = dp.dirSrv.NumDirEntries() dp.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats") } func (dp *dirBox) cleanupMeta(m *meta.Meta) { if role, ok := m.Get(api.KeyRole); !ok || role == "" { m.Set(api.KeyRole, dp.cdata.Config.GetDefaultRole()) } if syntax, ok := m.Get(api.KeySyntax); !ok || syntax == "" { m.Set(api.KeySyntax, dp.cdata.Config.GetDefaultSyntax()) } } |
Changes to box/dirbox/service.go.
1 2 3 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 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 dirbox |
︙ | ︙ |
Changes to box/filebox/zipbox.go.
1 2 3 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 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 filebox |
︙ | ︙ |
Changes to box/manager/manager.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 18 19 20 | // Package manager coordinates the various boxes and indexes of a Zettelstore. package manager import ( "context" "io" "net/url" "sync" "time" | > < | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // Package manager coordinates the various boxes and indexes of a Zettelstore. package manager import ( "context" "io" "net/url" "sort" "sync" "time" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/box/manager/memstore" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" |
︙ | ︙ | |||
76 77 78 79 80 81 82 | if _, ok := registry[scheme]; ok { panic(scheme) } registry[scheme] = create } // GetSchemes returns all registered scheme, ordered by scheme string. | | > > > > > > > | 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | if _, ok := registry[scheme]; ok { panic(scheme) } registry[scheme] = create } // GetSchemes returns all registered scheme, ordered by scheme string. func GetSchemes() []string { result := make([]string, 0, len(registry)) for scheme := range registry { result = append(result, scheme) } sort.Strings(result) return result } // Manager is a coordinating box. type Manager struct { mgrLog *logger.Logger mgrMx sync.RWMutex started bool rtConfig config.Config |
︙ | ︙ |
Changes to box/manager/memstore/memstore.go.
︙ | ︙ | |||
16 17 18 19 20 21 22 | "fmt" "io" "sort" "strings" "sync" "zettelstore.de/c/api" | < | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | "fmt" "io" "sort" "strings" "sync" "zettelstore.de/c/api" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) type metaRefs struct { forward id.Slice |
︙ | ︙ | |||
587 588 589 590 591 592 593 | } func dumpStringRefs(w io.Writer, title, preString, postString string, srefs stringRefs) { if len(srefs) == 0 { return } fmt.Fprintln(w, "====", title) | > > > > > | | 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 | } func dumpStringRefs(w io.Writer, title, preString, postString string, srefs stringRefs) { if len(srefs) == 0 { return } fmt.Fprintln(w, "====", title) slice := make([]string, 0, len(srefs)) for s := range srefs { slice = append(slice, s) } sort.Strings(slice) for _, s := range slice { fmt.Fprintf(w, "; %s%s%s\n", preString, s, postString) fmt.Fprintln(w, ":", srefs[s]) } } |
Changes to box/notify/directory_test.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import ( "testing" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. _ "zettelstore.de/z/parser/markdown" // Allow to use markdown parser. _ "zettelstore.de/z/parser/none" // Allow to use none parser. _ "zettelstore.de/z/parser/plain" // Allow to use plain parser. _ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser. ) func TestSeekZid(t *testing.T) { | > | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import ( "testing" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. _ "zettelstore.de/z/parser/draw" // Allow to use draw parser. _ "zettelstore.de/z/parser/markdown" // Allow to use markdown parser. _ "zettelstore.de/z/parser/none" // Allow to use none parser. _ "zettelstore.de/z/parser/plain" // Allow to use plain parser. _ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser. ) func TestSeekZid(t *testing.T) { |
︙ | ︙ | |||
44 45 46 47 48 49 50 | } } } func TestNewExtIsBetter(t *testing.T) { extVals := []string{ // Main Formats | | | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | } } } func TestNewExtIsBetter(t *testing.T) { extVals := []string{ // Main Formats api.ValueSyntaxZmk, api.ValueSyntaxDraw, "markdown", "md", // Other supported text formats "css", "txt", api.ValueSyntaxHTML, api.ValueSyntaxNone, "mustache", api.ValueSyntaxText, "plain", // Supported graphics formats api.ValueSyntaxGif, "png", api.ValueSyntaxSVG, "jpeg", "jpg", // Unsupported syntax values "gz", "cpp", "tar", "cppc", } |
︙ | ︙ |
Changes to box/notify/entry.go.
1 2 3 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 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 notify |
︙ | ︙ | |||
95 96 97 98 99 100 101 | } func calcContentExt(syntax string, yamlSep bool, getZettelFileSyntax func() []string) string { if yamlSep { return extZettel } switch syntax { | | | 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | } func calcContentExt(syntax string, yamlSep bool, getZettelFileSyntax func() []string) string { if yamlSep { return extZettel } switch syntax { case api.ValueSyntaxDraw, api.ValueSyntaxNone, api.ValueSyntaxZmk: return extZettel } for _, s := range getZettelFileSyntax() { if s == syntax { return extZettel } } |
︙ | ︙ |
Changes to cmd/cmd_file.go.
︙ | ︙ | |||
37 38 39 40 41 42 43 | domain.Zettel{ Meta: m, Content: domain.NewContent(inp.Src[inp.Pos:]), }, m.GetDefault(api.KeySyntax, api.ValueSyntaxZmk), nil, ) | | > > | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | domain.Zettel{ Meta: m, Content: domain.NewContent(inp.Src[inp.Pos:]), }, m.GetDefault(api.KeySyntax, api.ValueSyntaxZmk), nil, ) encdr := encoder.Create(api.Encoder(enc), &encoder.Environment{ Lang: m.GetDefault(api.KeyLang, api.ValueLangEN), }) if encdr == nil { fmt.Fprintf(os.Stderr, "Unknown format %q\n", enc) return 2, nil } _, err = encdr.WriteZettel(os.Stdout, z, parser.ParseMetadata) if err != nil { return 2, err |
︙ | ︙ |
Changes to cmd/cmd_run.go.
︙ | ︙ | |||
22 23 24 25 26 27 28 | "zettelstore.de/z/web/adapter/webui" "zettelstore.de/z/web/server" ) // ---------- Subcommand: run ------------------------------------------------ func flgRun(fs *flag.FlagSet) { | | | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | "zettelstore.de/z/web/adapter/webui" "zettelstore.de/z/web/server" ) // ---------- Subcommand: run ------------------------------------------------ func flgRun(fs *flag.FlagSet) { fs.String("c", defConfigfile, "configuration file") fs.Uint("a", 0, "port number kernel service (0=disable)") fs.Uint("p", 23123, "port number web service") fs.String("d", "", "zettel directory") fs.Bool("r", false, "system-wide read-only mode") fs.Bool("v", false, "verbose mode") fs.Bool("debug", false, "debug mode") } |
︙ | ︙ | |||
63 64 65 66 67 68 69 | ucCreateZettel := usecase.NewCreateZettel(ucLog, rtConfig, protectedBoxManager) ucGetMeta := usecase.NewGetMeta(protectedBoxManager) ucGetAllMeta := usecase.NewGetAllMeta(protectedBoxManager) ucGetZettel := usecase.NewGetZettel(protectedBoxManager) ucParseZettel := usecase.NewParseZettel(rtConfig, ucGetZettel) ucEvaluate := usecase.NewEvaluate(rtConfig, ucGetZettel, ucGetMeta) ucListMeta := usecase.NewListMeta(protectedBoxManager) | | | | | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | ucCreateZettel := usecase.NewCreateZettel(ucLog, rtConfig, protectedBoxManager) ucGetMeta := usecase.NewGetMeta(protectedBoxManager) ucGetAllMeta := usecase.NewGetAllMeta(protectedBoxManager) ucGetZettel := usecase.NewGetZettel(protectedBoxManager) ucParseZettel := usecase.NewParseZettel(rtConfig, ucGetZettel) ucEvaluate := usecase.NewEvaluate(rtConfig, ucGetZettel, ucGetMeta) ucListMeta := usecase.NewListMeta(protectedBoxManager) ucListRoles := usecase.NewListRole(protectedBoxManager) ucListTags := usecase.NewListTags(protectedBoxManager) ucZettelContext := usecase.NewZettelContext(protectedBoxManager, rtConfig) ucDelete := usecase.NewDeleteZettel(ucLog, protectedBoxManager) ucUpdate := usecase.NewUpdateZettel(ucLog, protectedBoxManager) ucRename := usecase.NewRenameZettel(ucLog, protectedBoxManager) ucUnlinkedRefs := usecase.NewUnlinkedReferences(protectedBoxManager, rtConfig) ucRefresh := usecase.NewRefresh(ucLog, protectedBoxManager) ucVersion := usecase.NewVersion(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) webSrv.Handle("/", wui.MakeGetRootHandler(protectedBoxManager)) // Web user interface if !authManager.IsReadonly() { webSrv.AddZettelRoute('b', server.MethodGet, wui.MakeGetRenameZettelHandler( ucGetMeta, &ucEvaluate)) webSrv.AddZettelRoute('b', server.MethodPost, wui.MakePostRenameZettelHandler(&ucRename)) webSrv.AddZettelRoute('c', server.MethodGet, wui.MakeGetCreateZettelHandler( ucGetZettel, &ucCreateZettel)) webSrv.AddZettelRoute('c', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel)) webSrv.AddZettelRoute('d', server.MethodGet, wui.MakeGetDeleteZettelHandler( ucGetMeta, ucGetAllMeta, &ucEvaluate)) webSrv.AddZettelRoute('d', server.MethodPost, wui.MakePostDeleteZettelHandler(&ucDelete)) webSrv.AddZettelRoute('e', server.MethodGet, wui.MakeEditGetZettelHandler(ucGetZettel)) webSrv.AddZettelRoute('e', server.MethodPost, wui.MakeEditSetZettelHandler(&ucUpdate)) } webSrv.AddListRoute('g', server.MethodGet, wui.MakeGetGoActionHandler(&ucRefresh)) webSrv.AddListRoute('h', server.MethodGet, wui.MakeListHTMLMetaHandler( ucListMeta, ucListRoles, ucListTags, &ucEvaluate)) webSrv.AddZettelRoute('h', server.MethodGet, wui.MakeGetHTMLZettelHandler( &ucEvaluate, ucGetMeta)) |
︙ | ︙ |
Changes to cmd/command.go.
1 | //----------------------------------------------------------------------------- | | | > < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package cmd import ( "flag" "sort" "zettelstore.de/z/logger" ) // Command stores information about commands / sub-commands. type Command struct { Name string // command name as it appears on the command line Func CommandFunc // function that executes a command |
︙ | ︙ | |||
59 60 61 62 63 64 65 | // Get returns the command identified by the given name and a bool to signal success. func Get(name string) (Command, bool) { cmd, ok := commands[name] return cmd, ok } // List returns a sorted list of all registered command names. | | > > > > > > > | 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | // Get returns the command identified by the given name and a bool to signal success. func Get(name string) (Command, bool) { cmd, ok := commands[name] return cmd, ok } // List returns a sorted list of all registered command names. func List() []string { result := make([]string, 0, len(commands)) for name := range commands { result = append(result, name) } sort.Strings(result) return result } |
Changes to cmd/main.go.
1 2 3 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // | | < | > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 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 cmd import ( "errors" "flag" "fmt" "net" "net/url" "os" "strconv" "strings" "zettelstore.de/c/api" "zettelstore.de/z/auth" "zettelstore.de/z/auth/impl" "zettelstore.de/z/box" "zettelstore.de/z/box/compbox" "zettelstore.de/z/box/manager" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/web/server" ) const ( defConfigfile = ".zscfg" ) func init() { RegisterCommand(Command{ Name: "help", Func: func(*flag.FlagSet) (int, error) { fmt.Println("Available commands:") for _, name := range List() { |
︙ | ︙ | |||
59 60 61 62 63 64 65 | Func: runFunc, Boxes: true, Header: true, LineServer: true, SetFlags: flgRun, }) RegisterCommand(Command{ | | | 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | Func: runFunc, Boxes: true, Header: true, LineServer: true, SetFlags: flgRun, }) RegisterCommand(Command{ Name: "run-simple", Func: runFunc, Simple: true, Boxes: true, Header: true, // LineServer: true, SetFlags: func(fs *flag.FlagSet) { // fs.Uint("a", 0, "port number kernel service (0=disable)") |
︙ | ︙ | |||
83 84 85 86 87 88 89 | }) RegisterCommand(Command{ Name: "password", Func: cmdPassword, }) } | | > | < < | < < | | | < < < < < < < < < < < < | | 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | }) RegisterCommand(Command{ Name: "password", Func: cmdPassword, }) } func readConfig(fs *flag.FlagSet) (cfg *meta.Meta) { var configFile string if configFlag := fs.Lookup("c"); configFlag != nil { configFile = configFlag.Value.String() } else { configFile = defConfigfile } content, err := os.ReadFile(configFile) if err != nil { return meta.New(id.Invalid) } return meta.NewFromInput(id.Invalid, input.NewInput(content)) } func getConfig(fs *flag.FlagSet) *meta.Meta { cfg := readConfig(fs) fs.Visit(func(flg *flag.Flag) { switch flg.Name { case "p": if portStr, err := parsePort(flg.Value.String()); err == nil { cfg.Set(keyListenAddr, net.JoinHostPort("127.0.0.1", portStr)) } case "a": |
︙ | ︙ | |||
170 171 172 173 174 175 176 | const ( keyAdminPort = "admin-port" keyDebug = "debug-mode" keyDefaultDirBoxType = "default-dir-box-type" keyInsecureCookie = "insecure-cookie" keyListenAddr = "listen-addr" keyLogLevel = "log-level" | < | 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | const ( keyAdminPort = "admin-port" keyDebug = "debug-mode" keyDefaultDirBoxType = "default-dir-box-type" keyInsecureCookie = "insecure-cookie" keyListenAddr = "listen-addr" keyLogLevel = "log-level" keyOwner = "owner" keyPersistentCookie = "persistent-cookie" keyBoxOneURI = kernel.BoxURIs + "1" keyReadOnly = "read-only-mode" keyTokenLifetimeHTML = "token-lifetime-html" keyTokenLifetimeAPI = "token-lifetime-api" keyURLPrefix = "url-prefix" |
︙ | ︙ | |||
220 221 222 223 224 225 226 | ok = setConfigValue( ok, kernel.WebService, kernel.WebListenAddress, cfg.GetDefault(keyListenAddr, "127.0.0.1:23123")) ok = setConfigValue(ok, kernel.WebService, kernel.WebURLPrefix, cfg.GetDefault(keyURLPrefix, "/")) ok = setConfigValue(ok, kernel.WebService, kernel.WebSecureCookie, !cfg.GetBool(keyInsecureCookie)) ok = setConfigValue(ok, kernel.WebService, kernel.WebPersistentCookie, cfg.GetBool(keyPersistentCookie)) | < < < | 205 206 207 208 209 210 211 212 213 214 215 216 217 218 | ok = setConfigValue( ok, kernel.WebService, kernel.WebListenAddress, cfg.GetDefault(keyListenAddr, "127.0.0.1:23123")) ok = setConfigValue(ok, kernel.WebService, kernel.WebURLPrefix, cfg.GetDefault(keyURLPrefix, "/")) ok = setConfigValue(ok, kernel.WebService, kernel.WebSecureCookie, !cfg.GetBool(keyInsecureCookie)) ok = setConfigValue(ok, kernel.WebService, kernel.WebPersistentCookie, cfg.GetBool(keyPersistentCookie)) ok = setConfigValue( ok, kernel.WebService, kernel.WebTokenLifetimeAPI, cfg.GetDefault(keyTokenLifetimeAPI, "")) ok = setConfigValue( ok, kernel.WebService, kernel.WebTokenLifetimeHTML, cfg.GetDefault(keyTokenLifetimeHTML, "")) if !ok { return errors.New("unable to set configuration") |
︙ | ︙ | |||
277 278 279 280 281 282 283 | compbox.Setup(cfg) return manager.New(boxURIs, authManager, rtConfig) } } else { createManager = func([]*url.URL, auth.Manager, config.Config) (box.Manager, error) { return nil, nil } } | < < < < < < | | 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | compbox.Setup(cfg) return manager.New(boxURIs, authManager, rtConfig) } } else { createManager = func([]*url.URL, auth.Manager, config.Config) (box.Manager, error) { return nil, nil } } kern.SetCreators( func(readonly bool, owner id.Zid) (auth.Manager, error) { return impl.New(readonly, owner, cfg.GetDefault("secret", "")), nil }, createManager, func(srv server.Server, plMgr box.Manager, authMgr auth.Manager, rtConfig config.Config) error { setupRouting(srv, plMgr, authMgr, rtConfig) return nil }, ) |
︙ | ︙ | |||
309 310 311 312 313 314 315 | kern.Shutdown(true) return exitCode } // 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() int { | < < < | < | < < < < < < < < < < < < < < < < < < < < < < | 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 | kern.Shutdown(true) return exitCode } // 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() int { dir := "./zettel" if err := os.MkdirAll(dir, 0750); err != nil { fmt.Fprintf(os.Stderr, "Unable to create zettel directory %q (%s)\n", dir, err) return 1 } return executeCommand("run-simple", "-d", dir) } var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") var memprofile = flag.String("memprofile", "", "write memory profile to `file`") // Main is the real entrypoint of the zettelstore. func Main(progName, buildVersion string) int { kernel.Main.SetConfig(kernel.CoreService, kernel.CoreProgname, progName) kernel.Main.SetConfig(kernel.CoreService, kernel.CoreVersion, buildVersion) flag.Parse() if *cpuprofile != "" || *memprofile != "" { if *cpuprofile != "" { kernel.Main.StartProfiling(kernel.ProfileCPU, *cpuprofile) } else { kernel.Main.StartProfiling(kernel.ProfileHead, *memprofile) } defer kernel.Main.StopProfiling() } args := flag.Args() if len(args) == 0 { return runSimple() } return executeCommand(args[0], args[1:]...) } |
Changes to cmd/register.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 | import ( _ "zettelstore.de/z/box/compbox" // Allow to use computed box. _ "zettelstore.de/z/box/constbox" // Allow to use global internal box. _ "zettelstore.de/z/box/dirbox" // Allow to use directory box. _ "zettelstore.de/z/box/filebox" // Allow to use file box. _ "zettelstore.de/z/box/membox" // Allow to use in-memory box. _ "zettelstore.de/z/encoder/htmlenc" // Allow to use HTML encoder. | | > | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | import ( _ "zettelstore.de/z/box/compbox" // Allow to use computed box. _ "zettelstore.de/z/box/constbox" // Allow to use global internal box. _ "zettelstore.de/z/box/dirbox" // Allow to use directory box. _ "zettelstore.de/z/box/filebox" // Allow to use file box. _ "zettelstore.de/z/box/membox" // Allow to use in-memory box. _ "zettelstore.de/z/encoder/htmlenc" // Allow to use HTML encoder. _ "zettelstore.de/z/encoder/nativeenc" // Allow to use native encoder. _ "zettelstore.de/z/encoder/textenc" // Allow to use text encoder. _ "zettelstore.de/z/encoder/zjsonenc" // Allow to use ZJSON encoder. _ "zettelstore.de/z/encoder/zmkenc" // Allow to use zmk encoder. _ "zettelstore.de/z/kernel/impl" // Allow kernel implementation to create itself _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. _ "zettelstore.de/z/parser/draw" // Allow to use draw parser. _ "zettelstore.de/z/parser/markdown" // Allow to use markdown parser. _ "zettelstore.de/z/parser/none" // Allow to use none parser. _ "zettelstore.de/z/parser/plain" // Allow to use plain parser. _ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser. ) |
Changes to config/config.go.
1 | //----------------------------------------------------------------------------- | | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- // Package config provides functions to retrieve runtime configuration data. |
︙ | ︙ | |||
22 23 24 25 26 27 28 29 30 31 32 33 34 35 | AuthConfig // AddDefaultValues enriches the given meta data with its default values. AddDefaultValues(m *meta.Meta) *meta.Meta // GetDefaultTitle returns the current value of the "default-title" key. GetDefaultTitle() string // GetDefaultSyntax returns the current value of the "default-syntax" key. GetDefaultSyntax() string // GetDefaultLang returns the current value of the "default-lang" key. GetDefaultLang() string | > > > | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | AuthConfig // AddDefaultValues enriches the given meta data with its default values. AddDefaultValues(m *meta.Meta) *meta.Meta // GetDefaultTitle returns the current value of the "default-title" key. GetDefaultTitle() string // GetDefaultRole returns the current value of the "default-role" key. GetDefaultRole() string // GetDefaultSyntax returns the current value of the "default-syntax" key. GetDefaultSyntax() string // GetDefaultLang returns the current value of the "default-lang" key. GetDefaultLang() string |
︙ | ︙ | |||
80 81 82 83 84 85 86 | if cfg != nil { return cfg.GetDefaultTitle() } return "Untitled" } // GetRole returns the value of the "role" key of the given meta. If there | | | | 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | if cfg != nil { return cfg.GetDefaultTitle() } return "Untitled" } // GetRole returns the value of the "role" key of the given meta. If there // is no such value, GetDefaultRole is returned. func GetRole(m *meta.Meta, cfg Config) string { if val, ok := m.Get(api.KeyRole); ok { return val } return cfg.GetDefaultRole() } // GetSyntax returns the value of the "syntax" key of the given meta. If there // is no such value, GetDefaultSyntax is returned. func GetSyntax(m *meta.Meta, cfg Config) string { if val, ok := m.Get(api.KeySyntax); ok { return val |
︙ | ︙ |
Changes to docs/manual/00001004010000.zettel.
1 2 3 4 5 | id: 00001004010000 title: Zettelstore startup configuration role: manual tags: #configuration #manual #zettelstore syntax: zmk | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001004010000 title: Zettelstore startup configuration role: manual tags: #configuration #manual #zettelstore syntax: zmk modified: 20220304115353 The configuration file, as specified by the ''-c CONFIGFILE'' [[command line option|00001004051000]], allows you to specify some startup 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 stored. An attacker that is able to change the owner can do anything. Therefore only the owner of the computer on which Zettelstore runs can change this information. |
︙ | ︙ | |||
57 58 59 60 61 62 63 | ; [!log-level|''log-level''] : Specify the global [[logging level|00001004059700]] for the whole application, overwriting the level ""debug"" set by configuration [[''debug-mode''|#debug-mode]]. Can be changed at runtime, even for specific internal services, with the ''log-level'' command of the [[administrator console|00001004101000#log-level]]. Default: ""info"". When you are familiar to operate the Zettelstore, you might set the level to ""warn"" or ""error"" to receive less noisy messages from the Zettelstore. | < < < < < < < < < < < < < | 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | ; [!log-level|''log-level''] : Specify the global [[logging level|00001004059700]] for the whole application, overwriting the level ""debug"" set by configuration [[''debug-mode''|#debug-mode]]. Can be changed at runtime, even for specific internal services, with the ''log-level'' command of the [[administrator console|00001004101000#log-level]]. Default: ""info"". When you are familiar to operate the Zettelstore, you might set the level to ""warn"" or ""error"" to receive less noisy messages from the Zettelstore. ; [!owner|''owner''] : [[Identifier|00001006050000]] of a zettel that contains data about the owner of the Zettelstore. The owner has full authorization for the Zettelstore. Only if owner is set to some value, user [[authentication|00001010000000]] is enabled. ; [!persistent-cookie|''persistent-cookie''] : A [[boolean value|00001006030500]] to make the access cookie persistent. This is helpful if you access the Zettelstore via a mobile device. On these devices, the operating system is free to stop the web browser and to remove temporary cookies. Therefore, an authenticated user will be logged off. If ""true"", a persistent cookie is used. Its lifetime exceeds the lifetime of the authentication token (see option ''token-lifetime-html'') by 30 seconds. Default: ""false"" ; [!read-only-mode|''read-only-mode''] : Puts the Zettelstore service into a read-only mode, if set to a [[true value|00001006030500]]. No changes are possible. Default: ""false"". ; [!token-lifetime-api|''token-lifetime-api''], [!token-lifetime-html|''token-lifetime-html''] : Define lifetime of access tokens in minutes. Values are only valid if authentication is enabled, i.e. key ''owner'' is set. ''token-lifetime-api'' is for accessing Zettelstore via its [[API|00001012000000]]. Default: ""10"". |
︙ | ︙ |
Changes to docs/manual/00001004020000.zettel.
1 2 3 4 5 | id: 00001004020000 title: Configure the running Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001004020000 title: Configure the running Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk modified: 20220304114412 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: ; [!default-copyright|''default-copyright''] : Copyright value to be used when rendering content. |
︙ | ︙ | |||
21 22 23 24 25 26 27 28 29 30 31 32 33 34 | This value is also used to specify the language for all non-zettel content, e.g. lists or search results. Use values according to the language definition of [[RFC-5646|https://tools.ietf.org/html/rfc5646]]. ; [!default-license|''default-license''] : License value to be used when rendering content. Can be overwritten in a zettel with [[meta key|00001006020000]] ''license''. Default: (the empty string). ; [!default-syntax|''default-syntax''] : Syntax to be used, if a zettel specifies no ''syntax'' [[meta key|00001006020000]]. Default: ""zmk"" (""[[Zettelmarkup|00001007000000]]""). ; [!default-title|''default-title''] : Title to be used, if a zettel specifies no ''title'' [[meta key|00001006020000]]. Default: ""Untitled"". | > > > | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | This value is also used to specify the language for all non-zettel content, e.g. lists or search results. Use values according to the language definition of [[RFC-5646|https://tools.ietf.org/html/rfc5646]]. ; [!default-license|''default-license''] : License value to be used when rendering content. Can be overwritten in a zettel with [[meta key|00001006020000]] ''license''. Default: (the empty string). ; [!default-role|''default-role''] : Role to be used, if a zettel specifies no ''role'' [[meta key|00001006020000]]. Default: ""zettel"". ; [!default-syntax|''default-syntax''] : Syntax to be used, if a zettel specifies no ''syntax'' [[meta key|00001006020000]]. Default: ""zmk"" (""[[Zettelmarkup|00001007000000]]""). ; [!default-title|''default-title''] : Title to be used, if a zettel specifies no ''title'' [[meta key|00001006020000]]. Default: ""Untitled"". |
︙ | ︙ |
Changes to docs/manual/00001004051200.zettel.
1 2 3 4 5 | id: 00001004051200 title: The ''file'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | id: 00001004051200 title: The ''file'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk modified: 20220209114650 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. ``` zettelstore file [-t FORMAT] [file-1 [file-2]] ``` ; ''-t FORMAT'' : Specifies the output format. Supported values are: [[''html''|00001012920510]] (default), [[''native''|00001012920513]], [[''text''|00001012920519]], [[''zjson''|00001012920503]], and [[''zmk''|00001012920522]]. ; ''file-1'' : Specifies the file name, where at least metadata is read. If ''file-2'' is not given, the zettel content is also read from here. ; ''file-2'' : File name where the zettel content is stored. If neither ''file-1'' nor ''file-2'' are given, metadata and zettel content are read from standard input / stdin. |
Changes to docs/manual/00001005090000.zettel.
1 2 3 4 5 | id: 00001005090000 title: List of predefined zettel role: manual tags: #manual #reference #zettelstore syntax: zmk | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001005090000 title: List of predefined zettel role: manual tags: #manual #reference #zettelstore syntax: zmk modified: 20211229000646 The following table lists all predefined zettel with their purpose. |= Identifier :|= Title | Purpose | [[00000000000001]] | Zettelstore Version | Contains the version string of the running Zettelstore | [[00000000000002]] | Zettelstore Host | Contains the name of the computer running the Zettelstore | [[00000000000003]] | Zettelstore Operating System | Contains the operating system and CPU architecture of the computer running the Zettelstore |
︙ | ︙ | |||
28 29 30 31 32 33 34 | | [[00000000010403]] | Zettelstore Form HTML Template | Form that is used to create a new or to change an existing zettel that contains text | [[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 | System-defined CSS file that is included by the [[Base HTML Template|00000000010100]] | [[00000000025001]] | Zettelstore User CSS | User-defined CSS file that is included by the [[Base HTML Template|00000000010100]] | < | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | | [[00000000010403]] | Zettelstore Form HTML Template | Form that is used to create a new or to change an existing zettel that contains text | [[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 | System-defined CSS file that is included by the [[Base HTML Template|00000000010100]] | [[00000000025001]] | Zettelstore User CSS | User-defined CSS file that is included by the [[Base HTML Template|00000000010100]] | [[00000000040001]] | Generic Emoji | Image that is shown if [[original image reference|00001007040322]] is invalid | [[00000000090000]] | New Menu | Contains items that should contain in the zettel template menu | [[00000000090001]] | New Zettel | Template for a new zettel with role ""[[zettel|00001006020100]]"" | [[00000000090002]] | New User | Template for a new [[user zettel|00001010040200]] | [[00010000000000]] | Home | Default home zettel, contains some welcome information If a zettel is not linked, it is not accessible for the current user. **Important:** All identifier may change until a stable version of the software is released. |
Changes to docs/manual/00001006020000.zettel.
1 2 3 4 5 | id: 00001006020000 title: Supported Metadata Keys role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001006020000 title: Supported Metadata Keys role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk modified: 20220218130146 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]]. ; [!all-tags|''all-tags''] |
︙ | ︙ | |||
68 69 70 71 72 73 74 | ; [!read-only|''read-only''] : Marks a zettel as read-only. The interpretation of [[supported values|00001006020400]] for this key depends, whether authentication is [[enabled|00001010040100]] or not. ; [!role|''role''] : Defines the role of the zettel. Can be used for selecting zettel. See [[supported zettel roles|00001006020100]]. | | | 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | ; [!read-only|''read-only''] : Marks a zettel as read-only. The interpretation of [[supported values|00001006020400]] for this key depends, whether authentication is [[enabled|00001010040100]] or not. ; [!role|''role''] : Defines the role of the zettel. Can be used for selecting zettel. See [[supported zettel roles|00001006020100]]. If not given, the value ''default-role'' from the [[configuration zettel|00001004020000#default-role]] will be used. ; [!syntax|''syntax''] : 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 begin with the number sign character (""''#''"", U+0023). |
︙ | ︙ |
Changes to docs/manual/00001006020100.zettel.
1 2 3 4 5 | id: 00001006020100 title: Supported Zettel Roles role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk | | | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | id: 00001006020100 title: Supported Zettel Roles role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk modified: 20220214174553 The [[''role'' key|00001006020000#role]] defines what kind of zettel you are writing. 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: ; [!configuration|''configuration''] : A zettel that contains some configuration data for the Zettelstore. Most prominent is [[00000000000100]], as described in [[00001004020000]]. ; [!manual|''manual''] |
︙ | ︙ |
Changes to docs/manual/00001006055000.zettel.
1 2 3 4 5 | id: 00001006055000 title: Reserved zettel identifier role: manual tags: #design #manual #zettelstore syntax: zmk | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001006055000 title: Reserved zettel identifier role: manual tags: #design #manual #zettelstore syntax: zmk modified: 20211124182600 [[Zettel identifier|00001006050000]] are typically created by examine the current date and time. By renaming a zettel, you are able to provide any sequence of 14 digits. If no other zettel has the same identifier, you are allowed to rename a zettel. To make things easier, you normally should not use zettel identifier that begin with four zeroes (''0000''). |
︙ | ︙ | |||
37 38 39 40 41 42 43 | | 00009000000000 | 0000999999999 | Reserved for applications This list may change in the future. ==== External Applications |= From | To | Description | 00009000001000 | 00009000001999 | [[Zettel Presenter|https://zettelstore.de/contrib]], an application to display zettel as a HTML-based slideshow | < | 37 38 39 40 41 42 43 | | 00009000000000 | 0000999999999 | Reserved for applications This list may change in the future. ==== External Applications |= From | To | Description | 00009000001000 | 00009000001999 | [[Zettel Presenter|https://zettelstore.de/contrib]], an application to display zettel as a HTML-based slideshow |
Changes to docs/manual/00001007030000.zettel.
1 2 3 4 5 | id: 00001007030000 title: Zettelmarkup: Block-Structured Elements role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007030000 title: Zettelmarkup: Block-Structured Elements role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220201133655 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 |
︙ | ︙ | |||
35 36 37 38 39 40 41 | 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 quotations, * [[Verse blocks|00001007030700]] allow to enter poetry, lyrics and similar text, where line endings are important, * [[Region blocks|00001007030800]] just mark regions, e.g. for common formatting, * [[Comment blocks|00001007030900]] allow to enter text that will be ignored when rendered. | < < | 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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 quotations, * [[Verse blocks|00001007030700]] allow to enter poetry, lyrics and similar text, where line endings are important, * [[Region blocks|00001007030800]] just mark regions, e.g. for common formatting, * [[Comment blocks|00001007030900]] allow to enter text that will be ignored when rendered. * [[Inline-Zettel blocks|00001007031200]] provide a mechanism to specify zettel content with a new syntax without creating a new zettel. === Tables Similar to lists are tables not specified explicitly. A sequence of table rows is considered a [[table|00001007031000]]. A table row itself is a sequence of table cells. |
︙ | ︙ |
Changes to docs/manual/00001007030800.zettel.
1 2 3 4 5 | id: 00001007030800 title: Zettelmarkup: Region Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | id: 00001007030800 title: Zettelmarkup: Region Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218131131 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 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 region 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 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. |
︙ | ︙ | |||
65 66 67 68 69 70 71 | Generic attributes that are result in a special HTML rendering are: * example * note * tip * important * caution * warning | < < < < < < < < < < < < < | 64 65 66 67 68 69 70 | Generic attributes that are result in a special HTML rendering are: * example * note * tip * important * caution * warning |
Changes to docs/manual/00001007031200.zettel.
1 2 3 4 5 | id: 00001007031200 title: Zettelmarkup: Inline-Zettel Block role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | | > | | | > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | id: 00001007031200 title: Zettelmarkup: Inline-Zettel Block role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218172121 An inline-zettel block allows to specify some content with another syntax without creating a new zettel. This is useful, for example, if you want to specify a [[simple drawing|00001008050000]] within your zettel and you are sure that you do not need the drawing in another context. Another example is to embed some [[Markdown|00001008010500]] content, because you are too lazy to translate Markdown into Zettelmarkup.[^However, translating into Zettelmarkup is quite easy with the [[zmk encoder|00001012920522]].] A last example is to specify HTML code to use it for some kind of web frontend framework. As all other [[line-range blocks|00001007030000#line-range-blocks]], an inline-zettel block begins with at least three identical characters, starting at the first position of a line. For inline-zettel blocks, the at-sign character (""''@''"", U+0040) is used. You can add some [[attributes|00001007050000]] on the beginning line of a verbatim block, following the initiating characters. The inline-zettel block uses the attribute key ""syntax"" to specify the [[syntax|00001008000000]] of the inline-zettel. Alternatively, you can use the generic attribute to specify the syntax value. If no value is provided, ""draw"" is assumed. Any other character in this first line will be ignored. Text following the beginning line will not be interpreted, until a line begins with at least the same number of the same at-sign characters given at the beginning line. This allows to enter some at-sign characters in the text that should not be interpreted at this level. Some examples: ```zmk @@@draw +-------+ +-------+ | Box 1 | ---> | Box 2 | +-------+ +-------+ @@@ ``` This will be rendered as: :::example @@@draw +-------+ +-------+ | Box 1 | ---> | Box 2 | +-------+ +-------+ @@@ ::: Using Markdown syntax: ```zmk @@@markdown A link to [this](00001007031200) zettel. @@@ ``` will be rendered as: :::example @@@markdown |
︙ | ︙ |
Deleted docs/manual/00001007031300.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted docs/manual/00001007031400.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to docs/manual/00001007040200.zettel.
1 2 3 4 5 | id: 00001007040200 title: Zettelmarkup: Literal-like formatting role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007040200 title: Zettelmarkup: Literal-like formatting role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218131420 There are some reasons to mark text that should be rendered as uninterpreted: * Mark text as literal, sometimes as part of a program. * Mark text as input you give into a computer via a keyboard. * Mark text as output from some computer, e.g. shown at the command line. === Literal text |
︙ | ︙ | |||
47 48 49 50 51 52 53 | Attributes can be specified, the default attribute has the same semantic as for literal text. === Inline-zettel snippet To specify an inline snippet in a different [[syntax|00001008000000]], delimit your text with two at-sign characters (""''@''"", U+0040) on each side. You can add some [[attributes|00001007050000]] immediate after the two closing at-sign characters to specify the syntax to use. Either use the attribute key ""syntax"" or use the generic attribute to specify the syntax value. | | < < < < < < < < < < < < < < < < < < < | 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | Attributes can be specified, the default attribute has the same semantic as for literal text. === Inline-zettel snippet To specify an inline snippet in a different [[syntax|00001008000000]], delimit your text with two at-sign characters (""''@''"", U+0040) on each side. You can add some [[attributes|00001007050000]] immediate after the two closing at-sign characters to specify the syntax to use. Either use the attribute key ""syntax"" or use the generic attribute to specify the syntax value. If no value is provided, ""draw"" is assumed. Examples: * ``A @@-->@@ B`` renders in HTML as ::A @@-->@@ B::{=example}. * ``@@<small>@@{=html}Small@@</small>@@{=html}`` renders in HTML as ::@@<small>@@{=html}Small@@</small>@@{=html}::{=example}. To some degree, an inline-zettel snippet is the @@<small>@@{=html}smaller@@</small>@@{=html} sibling of the [[inline-zettel block|00001007031200]]. For HTML syntax, the same rules apply. |
Changes to docs/manual/00001007040324.zettel.
1 2 3 4 5 | id: 00001007040324 title: Zettelmarkup: Inline-mode Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | id: 00001007040324 title: Zettelmarkup: Inline-mode Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220131155955 Inline-mode transclusion applies to all zettel that are parsed in a non-trivial way, e.g. as structured textual content. For example, textual content is assumed if the [[syntax|00001006020000#syntax]] of a zettel is ""zmk"" ([[Zettelmarkup|00001007000000]]), or ""markdown"" / ""md"" ([[Markdown|00001008010000]]). If the syntax is ""[[draw|00001008050000]]"", it is also a non-trivial way. Since this type of transclusion is at the level of [[inline-structured elements|00001007040000]], the transclude specification must be replaced with some inline-structured elements. First, the referenced zettel is read. If it contains other transclusions, these will be expanded, recursively. When an endless recursion is detected, expansion does not take place. Instead an error message replaces the transclude specification. |
︙ | ︙ |
Changes to docs/manual/00001007060000.zettel.
1 2 3 4 5 | id: 00001007060000 title: Zettelmarkup: Summary of Formatting Characters role: manual tags: #manual #reference #zettelmarkup #zettelstore syntax: zmk | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007060000 title: Zettelmarkup: Summary of Formatting Characters role: manual tags: #manual #reference #zettelmarkup #zettelstore syntax: zmk modified: 20220218124943 The following table gives an overview about the use of all characters that begin a markup element. |= Character :|= [[Blocks|00001007030000]] <|= [[Inlines|00001007040000]] < | ''!'' | (free) | (free) | ''"'' | [[Verse block|00001007030700]] | [[Short inline quote|00001007040100]] | ''#'' | [[Ordered list|00001007030200]] | [[Tag|00001007040000]] |
︙ | ︙ | |||
25 26 27 28 29 30 31 | | ''/'' | (free) | (free) | '':'' | [[Region block|00001007030800]] / [[description text|00001007030100]] | [[Inline region|00001007040100]] | '';'' | [[Description term|00001007030100]] | [[Small text|00001007040100]] | ''<'' | [[Quotation block|00001007030600]] | (free) | ''='' | [[Headings|00001007030300]] | [[Computer output|00001007040200]] | ''>'' | [[Quotation lists|00001007030200]] | [[Inserted text|00001007040100]] | ''?'' | (free) | (free) | | | | 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | | ''/'' | (free) | (free) | '':'' | [[Region block|00001007030800]] / [[description text|00001007030100]] | [[Inline region|00001007040100]] | '';'' | [[Description term|00001007030100]] | [[Small text|00001007040100]] | ''<'' | [[Quotation block|00001007030600]] | (free) | ''='' | [[Headings|00001007030300]] | [[Computer output|00001007040200]] | ''>'' | [[Quotation lists|00001007030200]] | [[Inserted text|00001007040100]] | ''?'' | (free) | (free) | ''@'' | [[Inline-Zettel blocks|00001007031200]] | [[Inline-zettel snippets|00001007040200#inline-zettel-snippet]] | ''['' | (reserved) | [[Linked material|00001007040300]], [[citation key|00001007040300]], [[footnote|00001007040300]], [[mark|00001007040300]] | ''\\'' | (blocked by inline meaning) | [[Escape character|00001007040000]] | '']'' | (reserved) | End of [[link|00001007040300]], [[citation key|00001007040300]], [[footnote|00001007040300]], [[mark|00001007040300]] | ''^'' | (free) | [[Superscripted text|00001007040100]] | ''_'' | (free) | [[Emphasized text|00001007040100]] | ''`'' | [[Verbatim block|00001007030500]] | [[Literal text|00001007040200]] | ''{'' | [[Transclusion|00001007031100]] | [[Embedded material|00001007040300]], [[Attribute|00001007050000]] | ''|'' | [[Table row / table cell|00001007031000]] | Separator within link and [[embed|00001007040320]] formatting | ''}'' | End of [[Transclusion|00001007031100]] | End of embedded material, End of Attribute | ''~'' | (free) | [[Deleted text|00001007040100]] |
Changes to docs/manual/00001008000000.zettel.
1 2 3 4 5 | id: 00001008000000 title: Other Markup Languages role: manual tags: #manual #zettelstore syntax: zmk | | > > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | id: 00001008000000 title: Other Markup Languages role: manual tags: #manual #zettelstore syntax: zmk modified: 20220214180202 [[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: * CSS * HTML template data * Image formats: GIF, PNG, JPEG, SVG * Markdown * 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#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. ; [!draw|''draw''] : A simple [[language|00001008050000]] to ""draw"" a graphic by using some simple Unicode characters. ; [!gif|''gif'']; [!jpeg|''jpeg'']; [!jpg|''jpg'']; [!png|''png''] : The formats for pixel graphics. Typically the data is stored in a separate file and the syntax is given in the metafile, which has the same name as the zettel identifier and has no file extension.[^Before version 0.2.0, the metafile had the file extension ''.meta''] ; [!html|''html''] : Hypertext Markup Language, will not be parsed further. Instead, it is treated as [[text|#text]], but will be encoded differently for [[HTML format|00001012920510]] (same for the [[web user interface|00001014000000]]). For security reasons, equivocal elements will not be encoded in the HTML format / web user interface, e.g. the ``<script ...`` tag. See [[security aspects of Markdown|00001008010000#security-aspects]] for some details. ; [!markdown|''markdown''], [!md|''md''] |
︙ | ︙ | |||
48 49 50 51 52 53 54 | : Just plain text that must not be interpreted further. ; [!zmk|''zmk''] : [[Zettelmarkup|00001007000000]]. The actual values are also listed in a zettel named [[Zettelstore Supported Parser|00000000000092]]. If you specify something else, your content will be interpreted as plain text. | < < < < < < < | 50 51 52 53 54 55 56 | : Just plain text that must not be interpreted further. ; [!zmk|''zmk''] : [[Zettelmarkup|00001007000000]]. The actual values are also listed in a zettel named [[Zettelstore Supported Parser|00000000000092]]. If you specify something else, your content will be interpreted as plain text. |
Changes to docs/manual/00001008050000.zettel.
1 2 3 4 5 | id: 00001008050000 title: The ""draw"" language role: manual tags: #graphic #manual #zettelstore syntax: zmk | | | < | | < | | | < > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | id: 00001008050000 title: The ""draw"" language role: manual tags: #graphic #manual #zettelstore syntax: zmk modified: 20220217180713 Sometimes, ""a picture is worth a thousand words"". To create some graphical representations, Zettelstore provides a simple mechanism. Characters like ""''|''"" or ""''-''"" already provide some visual feedback. For example, to create a picture containing two boxes that are connected via an arrow, the following representation is possible: ``` +-------+ +-------+ | Box 1 | ----> | Box 2 | +-------+ +-------+ ``` Zettelstore translates this to: @@@draw +-------+ +-------+ | Box 1 | ----> | Box 2 | +-------+ +-------+ @@@ Technically spoken, the drawing is translated to a [[SVG|00001008000000#svg]] element. The following characters are interpreted to create a graphical representation. Some of them will start a path that results in a recognized object. |=Character:|Meaning|Path Start: |
︙ | ︙ |
Changes to docs/manual/00001010040100.zettel.
1 2 3 4 5 | id: 00001010040100 title: Enable authentication role: manual tags: #authentication #configuration #manual #security #zettelstore syntax: zmk | < < < | 1 2 3 4 5 6 7 8 9 | id: 00001010040100 title: Enable authentication role: manual tags: #authentication #configuration #manual #security #zettelstore syntax: zmk To enable authentication, you must create a zettel that stores [[authentication data|00001010040200]] for the owner. Then you must reference this zettel within the [[startup configuration|00001004010000#owner]] under the key ''owner''. Once the startup configuration contains a valid [[zettel identifier|00001006050000]] under that key, authentication is enabled. |
Changes to docs/manual/00001012053200.zettel.
1 2 3 4 5 | id: 00001012053200 title: API: Create a new zettel role: manual tags: #api #manual #zettelstore syntax: zmk | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001012053200 title: API: Create a new zettel role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20211124180030 A zettel is created by adding it to the [[list of zettel|00001012000000#zettel-lists]]. Therefore, the [[endpoint|00001012920000]] to create a new zettel is also ''/j'', but you must send the data of the new zettel via a HTTP POST request. The body of the POST request must contain a JSON object that specifies metadata and content of the zettel to be created. The following keys of the JSON object are used: ; ''"meta"'' |
︙ | ︙ | |||
23 24 25 26 27 28 29 | Typically, text content is not encoded, and binary content is encoded via Base64. Other keys will be ignored. Even these three keys are just optional. The body of the HTTP POST request must not be empty and it must contain a JSON object. Therefore, a body containing just ''{}'' is perfectly valid. | | | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | Typically, text content is not encoded, and binary content is encoded via Base64. Other keys will be ignored. Even these three keys are just optional. The body of the HTTP POST request must not be empty and it must contain a JSON object. Therefore, a body containing just ''{}'' is perfectly valid. The new zettel will have no content, its title will be set to the value of [[''default-title''|00001004020000#default-title]] (default: ""Untitled""), its role is set to the value of [[''default-role''|00001004020000#default-role]] (default: ""zettel""), and its syntax is set to the value of [[''default-syntax''|00001004020000#default-syntax]] (default: ""zmk""). ``` # curl -X POST --data '{}' http://127.0.0.1:23123/j {"id":"20210713161000"} ``` If creating the zettel was successful, the HTTP response will contain a JSON object with one key: ; ''"id"'' |
︙ | ︙ |
Changes to docs/manual/00001012053500.zettel.
1 2 3 4 5 | id: 00001012053500 title: API: Retrieve evaluated metadata and content of an existing zettel in various encodings role: manual tags: #api #manual #zettelstore syntax: zmk | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001012053500 title: API: Retrieve evaluated metadata and content of an existing zettel in various encodings role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220301174012 The [[endpoint|00001012920000]] to work with evaluated metadata and content of a specific zettel is ''/v/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. For example, to retrieve some evaluated data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/v/00001012053500''[^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/v/00001012053500 |
︙ | ︙ | |||
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | <meta name="zs-backward" content="00001012000000"> <meta name="zs-box-number" content="1"> <meta name="copyright" content="(c) 2020-2021 by Detlef Stern <ds@zettelstore.de>"> <meta name="zs-forward" content="00001010040100 00001012050200 00001012920000 00001012920800"> <meta name="zs-lang" content="en"> <meta name="zs-published" content="00001012053500"> ``` === HTTP Status codes ; ''200'' : Retrieval was successful, the body contains an appropriate JSON object. ; ''400'' : Request was not valid. There are several reasons for this. Maybe the zettel identifier did not consist of exactly 14 digits or ''_enc'' / ''_part'' contained illegal values. ; ''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. | > > | 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | <meta name="zs-backward" content="00001012000000"> <meta name="zs-box-number" content="1"> <meta name="copyright" content="(c) 2020-2021 by Detlef Stern <ds@zettelstore.de>"> <meta name="zs-forward" content="00001010040100 00001012050200 00001012920000 00001012920800"> <meta name="zs-lang" content="en"> <meta name="zs-published" content="00001012053500"> ``` The optional query parameter ''_embed'' will embed all images into the returned encoding. === HTTP Status codes ; ''200'' : Retrieval was successful, the body contains an appropriate JSON object. ; ''400'' : Request was not valid. There are several reasons for this. Maybe the zettel identifier did not consist of exactly 14 digits or ''_enc'' / ''_part'' contained illegal values. ; ''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. |
Changes to docs/manual/00001012920500.zettel.
1 2 3 4 5 | id: 00001012920500 title: Encodings available via the [[API|00001012000000]] role: manual tags: #api #manual #reference #zettelstore syntax: zmk | | > | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001012920500 title: Encodings available via the [[API|00001012000000]] role: manual tags: #api #manual #reference #zettelstore syntax: zmk modified: 20220209114459 A zettel representation can be encoded in various formats for further processing. * [[zjson|00001012920503]] (default) * [[html|00001012920510]] * [[native|00001012920513]] * [[text|00001012920519]] * [[zmk|00001012920522]] |
Changes to docs/manual/00001012920503.zettel.
1 2 3 4 5 | id: 00001012920503 title: ZJSON Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | id: 00001012920503 title: ZJSON Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk modified: 20220223185826 A zettel representation that allows to process the syntactic structure of a zettel. It is a JSON-based encoding format, but different to the structures returned by [[endpoint|00001012920000]] ''/j/{ID}''. For an example, take a look at the JSON encoding of this page, which is available via the ""Info"" sub-page of this zettel: * [[//v/00001012920503?_enc=zjson&_part=zettel]], * [[//v/00001012920503?_enc=zjson&_part=meta]], * [[//v/00001012920503?_enc=zjson&_part=content]]. If transferred via HTTP, the content type will be ''application/json''. |
︙ | ︙ |
Added docs/manual/00001012920513.zettel.
> > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001012920513 title: Native Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk modified: 20210726193049 A zettel representation shows the structure of a zettel in a more user-friendly way. Mostly used for debugging. If transferred via HTTP, the content type will be ''text/plain''. TODO: formal description |
Deleted docs/manual/00001012920516.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to domain/id/id.go.
1 | //----------------------------------------------------------------------------- | | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- // Package id provides domain specific types, constants, and functions about |
︙ | ︙ | |||
44 45 46 47 48 49 50 | FormTemplateZid = MustParse(api.ZidFormTemplate) RenameTemplateZid = MustParse(api.ZidRenameTemplate) DeleteTemplateZid = MustParse(api.ZidDeleteTemplate) ContextTemplateZid = MustParse(api.ZidContextTemplate) RolesTemplateZid = MustParse(api.ZidRolesTemplate) TagsTemplateZid = MustParse(api.ZidTagsTemplate) ErrorTemplateZid = MustParse(api.ZidErrorTemplate) | < | 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | FormTemplateZid = MustParse(api.ZidFormTemplate) RenameTemplateZid = MustParse(api.ZidRenameTemplate) DeleteTemplateZid = MustParse(api.ZidDeleteTemplate) ContextTemplateZid = MustParse(api.ZidContextTemplate) RolesTemplateZid = MustParse(api.ZidRolesTemplate) TagsTemplateZid = MustParse(api.ZidTagsTemplate) ErrorTemplateZid = MustParse(api.ZidErrorTemplate) EmojiZid = MustParse(api.ZidEmoji) TOCNewTemplateZid = MustParse(api.ZidTOCNewTemplate) DefaultHomeZid = MustParse(api.ZidDefaultHome) ) const maxZid = 99999999999999 |
︙ | ︙ |
Deleted domain/meta/collection.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to domain/meta/meta.go.
︙ | ︙ | |||
16 17 18 19 20 21 22 | "regexp" "sort" "strings" "unicode" "unicode/utf8" "zettelstore.de/c/api" | < | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | "regexp" "sort" "strings" "unicode" "unicode/utf8" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/input" "zettelstore.de/z/strfun" ) type keyUsage int |
︙ | ︙ | |||
103 104 105 106 107 108 109 | return *d } return DescriptionKey{Type: Type(name)} } // GetSortedKeyDescriptions delivers all metadata key descriptions as a slice, sorted by name. func GetSortedKeyDescriptions() []*DescriptionKey { | > | > > > | | | 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | return *d } return DescriptionKey{Type: Type(name)} } // GetSortedKeyDescriptions delivers all metadata key descriptions as a slice, sorted by name. func GetSortedKeyDescriptions() []*DescriptionKey { names := make([]string, 0, len(registeredKeys)) for n := range registeredKeys { names = append(names, n) } sort.Strings(names) result := make([]*DescriptionKey, 0, len(names)) for _, n := range names { result = append(result, registeredKeys[n]) } return result } // Supported keys. func init() { |
︙ | ︙ | |||
220 221 222 223 224 225 226 | func init() { firstKeySet = strfun.NewSet(firstKeys...) } // Set stores the given string value under the given key. func (m *Meta) Set(key, value string) { if key != api.KeyID { | < < < < < < < < < < | 223 224 225 226 227 228 229 230 231 232 233 234 235 236 | func init() { firstKeySet = strfun.NewSet(firstKeys...) } // Set stores the given string value under the given key. func (m *Meta) Set(key, value string) { if key != api.KeyID { m.pairs[key] = trimValue(value) } } func trimValue(value string) string { return strings.TrimFunc(value, input.IsSpace) } |
︙ | ︙ |
Changes to domain/meta/parse.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 15 16 17 | // under this license. //----------------------------------------------------------------------------- // Package meta provides the domain specific type 'meta'. package meta import ( "strings" "zettelstore.de/c/api" | > < | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // under this license. //----------------------------------------------------------------------------- // Package meta provides the domain specific type 'meta'. package meta import ( "sort" "strings" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/input" "zettelstore.de/z/strfun" ) // NewFromInput parses the meta data of a zettel. func NewFromInput(zid id.Zid, inp *input.Input) *Meta { |
︙ | ︙ | |||
125 126 127 128 129 130 131 | set := make(strfun.Set, len(newElems)+len(oldElems)) addToSet(set, newElems, useElem) if len(set) == 0 { // Nothing to add. Maybe because of rejected elements. return } addToSet(set, oldElems, useElem) | > > > > > > | | 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | set := make(strfun.Set, len(newElems)+len(oldElems)) addToSet(set, newElems, useElem) if len(set) == 0 { // Nothing to add. Maybe because of rejected elements. return } addToSet(set, oldElems, useElem) resultList := make([]string, 0, len(set)) for tag := range set { resultList = append(resultList, tag) } sort.Strings(resultList) m.SetList(key, resultList) } func addData(m *Meta, k, v string) { if o, ok := m.Get(k); !ok || o == "" { m.Set(k, v) } else if v != "" { m.Set(k, o+" "+v) |
︙ | ︙ |
Changes to domain/meta/parse_test.go.
1 2 3 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 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 meta_test provides tests for the domain specific type 'meta'. |
︙ | ︙ |
Changes to domain/meta/type.go.
︙ | ︙ | |||
14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import ( "strconv" "strings" "sync" "time" "zettelstore.de/c/api" ) // DescriptionType is a description of a specific key type. type DescriptionType struct { Name string IsSet bool } | > | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import ( "strconv" "strings" "sync" "time" "zettelstore.de/c/api" "zettelstore.de/c/zjson" ) // DescriptionType is a description of a specific key type. type DescriptionType struct { Name string IsSet bool } |
︙ | ︙ | |||
38 39 40 41 42 43 44 | t := &DescriptionType{name, isSet} registeredTypes[name] = t return t } // Supported key types. var ( | | | | | | | | | | | | | | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | t := &DescriptionType{name, isSet} registeredTypes[name] = t return t } // Supported key types. var ( TypeCredential = registerType(zjson.MetaCredential, false) TypeEmpty = registerType(zjson.MetaEmpty, false) TypeID = registerType(zjson.MetaID, false) TypeIDSet = registerType(zjson.MetaIDSet, true) TypeNumber = registerType(zjson.MetaNumber, false) TypeString = registerType(zjson.MetaString, false) TypeTagSet = registerType(zjson.MetaTagSet, true) TypeTimestamp = registerType(zjson.MetaTimestamp, false) TypeURL = registerType(zjson.MetaURL, false) TypeWord = registerType(zjson.MetaWord, false) TypeWordSet = registerType(zjson.MetaWordSet, true) TypeZettelmarkup = registerType(zjson.MetaZettelmarkup, false) ) // Type returns a type hint for the given key. If no type hint is specified, // TypeUnknown is returned. func (*Meta) Type(key string) *DescriptionType { return Type(key) } |
︙ | ︙ |
Changes to encoder/encoder.go.
︙ | ︙ | |||
41 42 43 44 45 46 47 | ErrNoWriteMeta = errors.New("method WriteMeta is not implemented") ErrNoWriteContent = errors.New("method WriteContent is not implemented") ErrNoWriteBlocks = errors.New("method WriteBlocks is not implemented") ErrNoWriteInlines = errors.New("method WriteInlines is not implemented") ) // Create builds a new encoder with the given options. | | | | | > | > | > | > | > > > > > > | > > > | | 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | ErrNoWriteMeta = errors.New("method WriteMeta is not implemented") ErrNoWriteContent = errors.New("method WriteContent is not implemented") ErrNoWriteBlocks = errors.New("method WriteBlocks is not implemented") ErrNoWriteInlines = errors.New("method WriteInlines is not implemented") ) // Create builds a new encoder with the given options. func Create(enc api.EncodingEnum, env *Environment) Encoder { if info, ok := registry[enc]; ok { return info.Create(env) } return nil } // Info stores some data about an encoder. type Info struct { Create func(*Environment) Encoder Default bool } var registry = map[api.EncodingEnum]Info{} var defEncoding api.EncodingEnum // Register the encoder for later retrieval. func Register(enc api.EncodingEnum, info Info) { if _, ok := registry[enc]; ok { panic(fmt.Sprintf("Encoder %q already registered", enc)) } if info.Default { if defEncoding != api.EncoderUnknown && defEncoding != enc { panic(fmt.Sprintf("Default encoder already set: %q, new encoding: %q", defEncoding, enc)) } defEncoding = enc } registry[enc] = info } // GetEncodings returns all registered encodings, ordered by encoding value. func GetEncodings() []api.EncodingEnum { result := make([]api.EncodingEnum, 0, len(registry)) for enc := range registry { result = append(result, enc) } return result } // GetDefaultEncoding returns the encoding that should be used as default. func GetDefaultEncoding() api.EncodingEnum { if defEncoding != api.EncoderUnknown { return defEncoding } if _, ok := registry[api.EncoderZJSON]; ok { return api.EncoderZJSON } panic("No default encoding given") } |
Changes to encoder/encoder_blob_test.go.
︙ | ︙ | |||
35 36 37 38 39 40 41 | 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x7e, 0x9b, 0x55, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0x62, 0x00, 0x00, 0x00, 0x06, 0x00, 0x03, 0x36, 0x37, 0x7c, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, }, expect: expectMap{ | | | | | | | 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x7e, 0x9b, 0x55, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0x62, 0x00, 0x00, 0x00, 0x06, 0x00, 0x03, 0x36, 0x37, 0x7c, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, }, expect: expectMap{ encoderZJSON: `[{"":"BLOB","q":"PNG","s":"png","o":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg=="}]`, encoderHTML: `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg==" title="PNG">`, encoderNative: `[BLOB "PNG" "png" "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg=="]`, encoderText: "", encoderZmk: `%% Unable to display BLOB with title 'PNG' and syntax 'png'.`, }, }, } func TestBlob(t *testing.T) { m := meta.New(id.Invalid) m.Set(api.KeyTitle, "PNG") for testNum, tc := range pngTestCases { inp := input.NewInput(tc.blob) pe := &peBlocks{bs: parser.ParseBlocks(inp, m, "png")} checkEncodings(t, testNum, pe, tc.descr, tc.expect, "???") } } |
Changes to encoder/encoder_block_test.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 | package encoder_test var tcsBlock = []zmkTestCase{ { descr: "Empty Zettelmarkup should produce near nothing", zmk: "", expect: expectMap{ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > | | | | | | < > > > > > > > > > > > > > > > > > > > > > > > > | | | | > | > > > > | | | | | | | | | | | | | | > > | | | > | > | | | > > | > | | | | | | | | | > | | | | | < | < < < < < < < < < < | | < < < < < < < < < < < | > > > > > > | > > | | > > | < > > > > > > | | > > > > > | < > > > > > > > | < < < < < < < < < < < | | | | | | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 | package encoder_test var tcsBlock = []zmkTestCase{ { descr: "Empty Zettelmarkup should produce near nothing", zmk: "", expect: expectMap{ encoderZJSON: `[]`, encoderHTML: "", encoderNative: ``, encoderText: "", encoderZmk: useZmk, }, }, { descr: "Simple text: Hello, world", zmk: "Hello, world", expect: expectMap{ encoderZJSON: `[{"":"Para","i":[{"":"Text","s":"Hello,"},{"":"Space"},{"":"Text","s":"world"}]}]`, encoderHTML: "<p>Hello, world</p>", encoderNative: `[Para Text "Hello,",Space,Text "world"]`, encoderText: "Hello, world", encoderZmk: useZmk, }, }, { descr: "Simple block comment", zmk: "%%%\nNo\nrender\n%%%", expect: expectMap{ encoderZJSON: `[{"":"CommentBlock","s":"No\nrender"}]`, encoderHTML: ``, encoderNative: `[CommentBlock "No\nrender"]`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Rendered block comment", zmk: "%%%{-}\nRender\n%%%", expect: expectMap{ encoderZJSON: `[{"":"CommentBlock","a":{"-":""},"s":"Render"}]`, encoderHTML: "<!--\nRender\n-->", encoderNative: `[CommentBlock ("",[-]) "Render"]`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Simple Heading", zmk: `=== Top`, expect: expectMap{ encoderZJSON: `[{"":"Heading","n":1,"s":"top","i":[{"":"Text","s":"Top"}]}]`, encoderHTML: "<h2 id=\"top\">Top</h2>", encoderNative: `[Heading 1 #top Text "Top"]`, encoderText: `Top`, encoderZmk: useZmk, }, }, { descr: "Einfache Liste", zmk: "* A\n* B\n* C", expect: expectMap{ encoderZJSON: `[{"":"Bullet","c":[[{"":"Para","i":[{"":"Text","s":"A"}]}],[{"":"Para","i":[{"":"Text","s":"B"}]}],[{"":"Para","i":[{"":"Text","s":"C"}]}]]}]`, encoderHTML: "<ul>\n<li>A</li>\n<li>B</li>\n<li>C</li>\n</ul>", encoderNative: `[BulletList [[Para Text "A"]], [[Para Text "B"]], [[Para Text "C"]]]`, encoderText: "A\nB\nC", encoderZmk: useZmk, }, }, { descr: "Schachtelliste", zmk: "* T1\n** T2\n* T3\n** T4\n* T5", expect: expectMap{ encoderZJSON: `[{"":"Bullet","c":[[{"":"Para","i":[{"":"Text","s":"T1"}]},{"":"Bullet","c":[[{"":"Para","i":[{"":"Text","s":"T2"}]}]]}],[{"":"Para","i":[{"":"Text","s":"T3"}]},{"":"Bullet","c":[[{"":"Para","i":[{"":"Text","s":"T4"}]}]]}],[{"":"Para","i":[{"":"Text","s":"T5"}]}]]}]`, encoderHTML: `<ul> <li> <p>T1</p> <ul> <li>T2</li> </ul> </li> <li> <p>T3</p> <ul> <li>T4</li> </ul> </li> <li> <p>T5</p> </li> </ul>`, encoderNative: `[BulletList [[Para Text "T1"], [BulletList [[Para Text "T2"]]]], [[Para Text "T3"], [BulletList [[Para Text "T4"]]]], [[Para Text "T5"]]]`, encoderText: "T1\nT2\nT3\nT4\nT5", encoderZmk: useZmk, }, }, { descr: "Zwei Listen hintereinander", zmk: "* Item1.1\n* Item1.2\n* Item1.3\n\n* Item2.1\n* Item2.2", expect: expectMap{ encoderZJSON: `[{"":"Bullet","c":[[{"":"Para","i":[{"":"Text","s":"Item1.1"}]}],[{"":"Para","i":[{"":"Text","s":"Item1.2"}]}],[{"":"Para","i":[{"":"Text","s":"Item1.3"}]}],[{"":"Para","i":[{"":"Text","s":"Item2.1"}]}],[{"":"Para","i":[{"":"Text","s":"Item2.2"}]}]]}]`, encoderHTML: "<ul>\n<li>Item1.1</li>\n<li>Item1.2</li>\n<li>Item1.3</li>\n<li>Item2.1</li>\n<li>Item2.2</li>\n</ul>", encoderNative: `[BulletList [[Para Text "Item1.1"]], [[Para Text "Item1.2"]], [[Para Text "Item1.3"]], [[Para Text "Item2.1"]], [[Para Text "Item2.2"]]]`, encoderText: "Item1.1\nItem1.2\nItem1.3\nItem2.1\nItem2.2", encoderZmk: "* Item1.1\n* Item1.2\n* Item1.3\n* Item2.1\n* Item2.2", }, }, { descr: "Simple horizontal rule", zmk: `---`, expect: expectMap{ encoderZJSON: `[{"":"Thematic"}]`, encoderHTML: "<hr>", encoderNative: `[Hrule]`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "No list after paragraph", zmk: "Text\n*abc", expect: expectMap{ encoderZJSON: `[{"":"Para","i":[{"":"Text","s":"Text"},{"":"Soft"},{"":"Text","s":"*abc"}]}]`, encoderHTML: "<p>Text\n*abc</p>", encoderNative: `[Para Text "Text",Space,Text "*abc"]`, encoderText: `Text *abc`, encoderZmk: useZmk, }, }, { descr: "A list after paragraph", zmk: "Text\n# abc", expect: expectMap{ encoderZJSON: `[{"":"Para","i":[{"":"Text","s":"Text"}]},{"":"Ordered","c":[[{"":"Para","i":[{"":"Text","s":"abc"}]}]]}]`, encoderHTML: "<p>Text</p>\n<ol>\n<li>abc</li>\n</ol>", encoderNative: `[Para Text "Text"], [OrderedList [[Para Text "abc"]]]`, encoderText: "Text\nabc", encoderZmk: useZmk, }, }, { descr: "Simple Quote Block", zmk: "<<<\nToBeOrNotToBe\n<<< Romeo", expect: expectMap{ encoderZJSON: `[{"":"Excerpt","b":[{"":"Para","i":[{"":"Text","s":"ToBeOrNotToBe"}]}],"i":[{"":"Text","s":"Romeo"}]}]`, encoderHTML: "<blockquote>\n<p>ToBeOrNotToBe</p>\n<cite>Romeo</cite>\n</blockquote>", encoderNative: `[QuoteBlock [[Para Text "ToBeOrNotToBe"]], [Cite Text "Romeo"]]`, encoderText: "ToBeOrNotToBe\nRomeo", encoderZmk: useZmk, }, }, { descr: "Quote Block with multiple paragraphs", zmk: "<<<\nToBeOr\n\nNotToBe\n<<< Romeo", expect: expectMap{ encoderZJSON: `[{"":"Excerpt","b":[{"":"Para","i":[{"":"Text","s":"ToBeOr"}]},{"":"Para","i":[{"":"Text","s":"NotToBe"}]}],"i":[{"":"Text","s":"Romeo"}]}]`, encoderHTML: "<blockquote>\n<p>ToBeOr</p>\n<p>NotToBe</p>\n<cite>Romeo</cite>\n</blockquote>", encoderNative: `[QuoteBlock [[Para Text "ToBeOr"], [Para Text "NotToBe"]], [Cite Text "Romeo"]]`, encoderText: "ToBeOr\nNotToBe\nRomeo", encoderZmk: useZmk, }, }, { descr: "Verse block", zmk: `""" A line another line Back Paragraph Spacy Para """ Author`, expect: expectMap{ encoderZJSON: "[{\"\":\"Poem\",\"b\":[{\"\":\"Para\",\"i\":[{\"\":\"Text\",\"s\":\"A\"},{\"\":\"Space\",\"s\":\"\u00a0\"},{\"\":\"Text\",\"s\":\"line\"},{\"\":\"Hard\"},{\"\":\"Space\",\"s\":\"\u00a0\u00a0\"},{\"\":\"Text\",\"s\":\"another\"},{\"\":\"Space\",\"s\":\"\u00a0\"},{\"\":\"Text\",\"s\":\"line\"},{\"\":\"Hard\"},{\"\":\"Text\",\"s\":\"Back\"}]},{\"\":\"Para\",\"i\":[{\"\":\"Text\",\"s\":\"Paragraph\"}]},{\"\":\"Para\",\"i\":[{\"\":\"Space\",\"s\":\"\u00a0\u00a0\u00a0\u00a0\"},{\"\":\"Text\",\"s\":\"Spacy\"},{\"\":\"Space\",\"s\":\"\u00a0\u00a0\"},{\"\":\"Text\",\"s\":\"Para\"}]}],\"i\":[{\"\":\"Text\",\"s\":\"Author\"}]}]", encoderHTML: "<div>\n<p>A\u00a0line<br>\n\u00a0\u00a0another\u00a0line<br>\nBack</p>\n<p>Paragraph</p>\n<p>\u00a0\u00a0\u00a0\u00a0Spacy\u00a0\u00a0Para</p>\n<cite>Author</cite>\n</div>", encoderNative: "[VerseBlock\n [[Para Text \"A\",Space,Text \"line\",Break,Space 2,Text \"another\",Space,Text \"line\",Break,Text \"Back\"],\n [Para Text \"Paragraph\"],\n [Para Space 4,Text \"Spacy\",Space 2,Text \"Para\"]],\n [Cite Text \"Author\"]]", encoderText: "A line\n another line\nBack\nParagraph\n Spacy Para\nAuthor", encoderZmk: "\"\"\"\nA\u00a0line\\\n\u00a0\u00a0another\u00a0line\\\nBack\nParagraph\n\u00a0\u00a0\u00a0\u00a0Spacy\u00a0\u00a0Para\n\"\"\" Author", }, }, { descr: "Span Block", zmk: `::: A simple span and much more :::`, expect: expectMap{ encoderZJSON: `[{"":"Block","b":[{"":"Para","i":[{"":"Text","s":"A"},{"":"Space"},{"":"Text","s":"simple"},{"":"Soft"},{"":"Space"},{"":"Text","s":"span"},{"":"Soft"},{"":"Text","s":"and"},{"":"Space"},{"":"Text","s":"much"},{"":"Space"},{"":"Text","s":"more"}]}]}]`, encoderHTML: "<div>\n<p>A simple\n span\nand much more</p>\n</div>", encoderNative: `[SpanBlock [[Para Text "A",Space,Text "simple",Space,Space 3,Text "span",Space,Text "and",Space,Text "much",Space,Text "more"]]]`, encoderText: `A simple span and much more`, encoderZmk: useZmk, }, }, { descr: "Simple Verbatim", zmk: "```\nHello\nWorld\n```", expect: expectMap{ encoderZJSON: `[{"":"CodeBlock","s":"Hello\nWorld"}]`, encoderHTML: "<pre><code>Hello\nWorld</code></pre>", encoderNative: `[CodeBlock "Hello\nWorld"]`, encoderText: "Hello\nWorld", encoderZmk: useZmk, }, }, { descr: "Simple Description List", zmk: "; Zettel\n: Paper\n: Note\n; Zettelkasten\n: Slip box", expect: expectMap{ encoderZJSON: `[{"":"Description","d":[{"i":[{"":"Text","s":"Zettel"}],"e":[[{"":"Para","i":[{"":"Text","s":"Paper"}]}],[{"":"Para","i":[{"":"Text","s":"Note"}]}]]},{"i":[{"":"Text","s":"Zettelkasten"}],"e":[[{"":"Para","i":[{"":"Text","s":"Slip"},{"":"Space"},{"":"Text","s":"box"}]}]]}]}]`, encoderHTML: "<dl>\n<dt>Zettel</dt>\n<dd>Paper</dd>\n<dd>Note</dd>\n<dt>Zettelkasten</dt>\n<dd>Slip box</dd>\n</dl>", encoderNative: `[DescriptionList [Term [Text "Zettel"], [Description [Para Text "Paper"]], [Description [Para Text "Note"]]], [Term [Text "Zettelkasten"], [Description [Para Text "Slip",Space,Text "box"]]]]`, encoderText: "Zettel\nPaper\nNote\nZettelkasten\nSlip box", encoderZmk: useZmk, }, }, { descr: "Simple Table", zmk: "|c1|c2|c3\n|d1||d3", expect: expectMap{ encoderZJSON: `[{"":"Table","p":[[],[[{"i":[{"":"Text","s":"c1"}]},{"i":[{"":"Text","s":"c2"}]},{"i":[{"":"Text","s":"c3"}]}],[{"i":[{"":"Text","s":"d1"}]},{"i":[]},{"i":[{"":"Text","s":"d3"}]}]]]}]`, encoderHTML: `<table> <tbody> <tr><td>c1</td><td>c2</td><td>c3</td></tr> <tr><td>d1</td><td></td><td>d3</td></tr> </tbody> </table>`, encoderNative: `[Table [Row [Cell Default Text "c1"],[Cell Default Text "c2"],[Cell Default Text "c3"]], [Row [Cell Default Text "d1"],[Cell Default],[Cell Default Text "d3"]]]`, encoderText: "c1 c2 c3\nd1 d3", encoderZmk: useZmk, }, }, { descr: "Table with alignment and comment", zmk: `|h1>|=h2|h3:| |%--+---+---+ |<c1|c2|:c3| |f1|f2|=f3`, expect: expectMap{ encoderZJSON: `[{"":"Table","p":[[{"s":">","i":[{"":"Text","s":"h1"}]},{"i":[{"":"Text","s":"h2"}]},{"s":":","i":[{"":"Text","s":"h3"}]}],[[{"s":"<","i":[{"":"Text","s":"c1"}]},{"i":[{"":"Text","s":"c2"}]},{"s":":","i":[{"":"Text","s":"c3"}]}],[{"s":">","i":[{"":"Text","s":"f1"}]},{"i":[{"":"Text","s":"f2"}]},{"s":":","i":[{"":"Text","s":"=f3"}]}]]]}]`, encoderHTML: `<table> <thead> <tr><th class="right">h1</th><th>h2</th><th class="center">h3</th></tr> </thead> <tbody> <tr><td class="left">c1</td><td>c2</td><td class="center">c3</td></tr> <tr><td class="right">f1</td><td>f2</td><td class="center">=f3</td></tr> </tbody> </table>`, encoderNative: `[Table [Header [Cell Right Text "h1"],[Cell Default Text "h2"],[Cell Center Text "h3"]], [Row [Cell Left Text "c1"],[Cell Default Text "c2"],[Cell Center Text "c3"]], [Row [Cell Right Text "f1"],[Cell Default Text "f2"],[Cell Center Text "=f3"]]]`, encoderText: "h1 h2 h3\nc1 c2 c3\nf1 f2 =f3", encoderZmk: `|=h1>|=h2|=h3: |<c1|c2|c3 |f1|f2|=f3`, }, }, { descr: "", zmk: ``, expect: expectMap{ encoderZJSON: `[]`, encoderHTML: ``, encoderNative: ``, encoderText: "", encoderZmk: useZmk, }, }, } // func TestEncoderBlock(t *testing.T) { // executeTestCases(t, tcsBlock) // } |
Changes to encoder/encoder_inline_test.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 | package encoder_test var tcsInline = []zmkTestCase{ { descr: "Empty Zettelmarkup should produce near nothing (inline)", zmk: "", expect: expectMap{ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | < < | < < < < < < < < < | | | | | | | | | | | | | < < < < < < < < < < < | | | | | | | | | | | | | < | < < < < < < < < < < | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 | package encoder_test var tcsInline = []zmkTestCase{ { descr: "Empty Zettelmarkup should produce near nothing (inline)", zmk: "", expect: expectMap{ encoderZJSON: `[]`, encoderHTML: "", encoderNative: ``, encoderText: "", encoderZmk: useZmk, }, }, { descr: "Simple text: Hello, world (inline)", zmk: `Hello, world`, expect: expectMap{ encoderZJSON: `[{"":"Text","s":"Hello,"},{"":"Space"},{"":"Text","s":"world"}]`, encoderHTML: "Hello, world", encoderNative: `Text "Hello,",Space,Text "world"`, encoderText: "Hello, world", encoderZmk: useZmk, }, }, { descr: "Emphasized formatting", zmk: "__emph__", expect: expectMap{ encoderZJSON: `[{"":"Emph","i":[{"":"Text","s":"emph"}]}]`, encoderHTML: "<em>emph</em>", encoderNative: `Emph [Text "emph"]`, encoderText: "emph", encoderZmk: useZmk, }, }, { descr: "Strong formatting", zmk: "**strong**", expect: expectMap{ encoderZJSON: `[{"":"Strong","i":[{"":"Text","s":"strong"}]}]`, encoderHTML: "<strong>strong</strong>", encoderNative: `Strong [Text "strong"]`, encoderText: "strong", encoderZmk: useZmk, }, }, { descr: "Insert formatting", zmk: ">>insert>>", expect: expectMap{ encoderZJSON: `[{"":"Insert","i":[{"":"Text","s":"insert"}]}]`, encoderHTML: "<ins>insert</ins>", encoderNative: `Insert [Text "insert"]`, encoderText: "insert", encoderZmk: useZmk, }, }, { descr: "Delete formatting", zmk: "~~delete~~", expect: expectMap{ encoderZJSON: `[{"":"Delete","i":[{"":"Text","s":"delete"}]}]`, encoderHTML: "<del>delete</del>", encoderNative: `Delete [Text "delete"]`, encoderText: "delete", encoderZmk: useZmk, }, }, { descr: "Update formatting", zmk: "~~old~~>>new>>", expect: expectMap{ encoderZJSON: `[{"":"Delete","i":[{"":"Text","s":"old"}]},{"":"Insert","i":[{"":"Text","s":"new"}]}]`, encoderHTML: "<del>old</del><ins>new</ins>", encoderNative: `Delete [Text "old"],Insert [Text "new"]`, encoderText: "oldnew", encoderZmk: useZmk, }, }, { descr: "Superscript formatting", zmk: "^^superscript^^", expect: expectMap{ encoderZJSON: `[{"":"Super","i":[{"":"Text","s":"superscript"}]}]`, encoderHTML: `<sup>superscript</sup>`, encoderNative: `Super [Text "superscript"]`, encoderText: `superscript`, encoderZmk: useZmk, }, }, { descr: "Subscript formatting", zmk: ",,subscript,,", expect: expectMap{ encoderZJSON: `[{"":"Sub","i":[{"":"Text","s":"subscript"}]}]`, encoderHTML: `<sub>subscript</sub>`, encoderNative: `Sub [Text "subscript"]`, encoderText: `subscript`, encoderZmk: useZmk, }, }, { descr: "Quotes formatting", zmk: `""quotes""`, expect: expectMap{ encoderZJSON: `[{"":"Quote","i":[{"":"Text","s":"quotes"}]}]`, encoderHTML: "<q>quotes</q>", encoderNative: `Quote [Text "quotes"]`, encoderText: `quotes`, encoderZmk: useZmk, }, }, { descr: "Quotes formatting (german)", zmk: `""quotes""{lang=de}`, expect: expectMap{ encoderZJSON: `[{"":"Quote","a":{"lang":"de"},"i":[{"":"Text","s":"quotes"}]}]`, encoderHTML: `<q lang="de">quotes</q>`, encoderNative: `Quote ("",[lang="de"]) [Text "quotes"]`, encoderText: `quotes`, encoderZmk: `""quotes""{lang="de"}`, }, }, { descr: "Span formatting", zmk: `::span::`, expect: expectMap{ encoderZJSON: `[{"":"Span","i":[{"":"Text","s":"span"}]}]`, encoderHTML: `<span>span</span>`, encoderNative: `Span [Text "span"]`, encoderText: `span`, encoderZmk: useZmk, }, }, { descr: "Code formatting", zmk: "``code``", expect: expectMap{ encoderZJSON: `[{"":"Code","s":"code"}]`, encoderHTML: `<code>code</code>`, encoderNative: `Code "code"`, encoderText: `code`, encoderZmk: useZmk, }, }, { descr: "Input formatting", zmk: `''input''`, expect: expectMap{ encoderZJSON: `[{"":"Input","s":"input"}]`, encoderHTML: `<kbd>input</kbd>`, encoderNative: `Input "input"`, encoderText: `input`, encoderZmk: useZmk, }, }, { descr: "Output formatting", zmk: `==output==`, expect: expectMap{ encoderZJSON: `[{"":"Output","s":"output"}]`, encoderHTML: `<samp>output</samp>`, encoderNative: `Output "output"`, encoderText: `output`, encoderZmk: useZmk, }, }, { descr: "Nested Span Quote formatting", zmk: `::""abc""::{lang=fr}`, expect: expectMap{ encoderZJSON: `[{"":"Span","a":{"lang":"fr"},"i":[{"":"Quote","i":[{"":"Text","s":"abc"}]}]}]`, encoderHTML: `<span lang="fr"><q>abc</q></span>`, encoderNative: `Span ("",[lang="fr"]) [Quote [Text "abc"]]`, encoderText: `abc`, encoderZmk: `::""abc""::{lang="fr"}`, }, }, { descr: "Simple Citation", zmk: `[@Stern18]`, expect: expectMap{ encoderZJSON: `[{"":"Cite","s":"Stern18"}]`, encoderHTML: `Stern18`, // TODO encoderNative: `Cite "Stern18"`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "No comment", zmk: `% comment`, expect: expectMap{ encoderZJSON: `[{"":"Text","s":"%"},{"":"Space"},{"":"Text","s":"comment"}]`, encoderHTML: `% comment`, encoderNative: `Text "%",Space,Text "comment"`, encoderText: `% comment`, encoderZmk: useZmk, }, }, { descr: "Line comment", zmk: `%% line comment`, expect: expectMap{ encoderZJSON: `[{"":"Comment","s":"line comment"}]`, encoderHTML: `<!-- line comment -->`, encoderNative: `Comment "line comment"`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Comment after text", zmk: `Text %% comment`, expect: expectMap{ encoderZJSON: `[{"":"Text","s":"Text"},{"":"Comment","s":"comment"}]`, encoderHTML: `Text <!-- comment -->`, encoderNative: `Text "Text",Comment "comment"`, encoderText: `Text`, encoderZmk: useZmk, }, }, { descr: "Simple footnote", zmk: `[^footnote]`, expect: expectMap{ encoderZJSON: `[{"":"Footnote","i":[{"":"Text","s":"footnote"}]}]`, encoderHTML: `<sup id="fnref:0"><a href="#fn:0" class="footnote-ref" role="doc-noteref">0</a></sup>`, encoderNative: `Footnote [Text "footnote"]`, encoderText: `footnote`, encoderZmk: useZmk, }, }, { descr: "Simple mark", zmk: `[!mark]`, expect: expectMap{ encoderZJSON: `[{"":"Mark","s":"mark","q":"mark"}]`, encoderHTML: `<a id="mark"></a>`, encoderNative: `Mark "mark" #mark`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Mark with text", zmk: `[!mark|with text]`, expect: expectMap{ encoderZJSON: `[{"":"Mark","s":"mark","q":"mark","i":[{"":"Text","s":"with"},{"":"Space"},{"":"Text","s":"text"}]}]`, encoderHTML: `<a id="mark">with text</a>`, encoderNative: `Mark "mark" #mark [Text "with",Space,Text "text"]`, encoderText: `with text`, encoderZmk: useZmk, }, }, { descr: "Dummy Link", zmk: `[[abc]]`, expect: expectMap{ encoderZJSON: `[{"":"Link","q":"external","s":"abc"}]`, encoderHTML: `<a href="abc" class="external">abc</a>`, encoderNative: `Link EXTERNAL "abc"`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Simple URL", zmk: `[[https://zettelstore.de]]`, expect: expectMap{ encoderZJSON: `[{"":"Link","q":"external","s":"https://zettelstore.de"}]`, encoderHTML: `<a href="https://zettelstore.de" class="external">https://zettelstore.de</a>`, encoderNative: `Link EXTERNAL "https://zettelstore.de"`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "URL with Text", zmk: `[[Home|https://zettelstore.de]]`, expect: expectMap{ encoderZJSON: `[{"":"Link","q":"external","s":"https://zettelstore.de","i":[{"":"Text","s":"Home"}]}]`, encoderHTML: `<a href="https://zettelstore.de" class="external">Home</a>`, encoderNative: `Link EXTERNAL "https://zettelstore.de" [Text "Home"]`, encoderText: `Home`, encoderZmk: useZmk, }, }, { descr: "Simple Zettel ID", zmk: `[[00000000000100]]`, expect: expectMap{ encoderZJSON: `[{"":"Link","q":"zettel","s":"00000000000100"}]`, encoderHTML: `<a href="00000000000100">00000000000100</a>`, encoderNative: `Link ZETTEL "00000000000100"`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Zettel ID with Text", zmk: `[[Config|00000000000100]]`, expect: expectMap{ encoderZJSON: `[{"":"Link","q":"zettel","s":"00000000000100","i":[{"":"Text","s":"Config"}]}]`, encoderHTML: `<a href="00000000000100">Config</a>`, encoderNative: `Link ZETTEL "00000000000100" [Text "Config"]`, encoderText: `Config`, encoderZmk: useZmk, }, }, { descr: "Simple Zettel ID with fragment", zmk: `[[00000000000100#frag]]`, expect: expectMap{ encoderZJSON: `[{"":"Link","q":"zettel","s":"00000000000100#frag"}]`, encoderHTML: `<a href="00000000000100#frag">00000000000100#frag</a>`, encoderNative: `Link ZETTEL "00000000000100#frag"`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Zettel ID with Text and fragment", zmk: `[[Config|00000000000100#frag]]`, expect: expectMap{ encoderZJSON: `[{"":"Link","q":"zettel","s":"00000000000100#frag","i":[{"":"Text","s":"Config"}]}]`, encoderHTML: `<a href="00000000000100#frag">Config</a>`, encoderNative: `Link ZETTEL "00000000000100#frag" [Text "Config"]`, encoderText: `Config`, encoderZmk: useZmk, }, }, { descr: "Fragment link to self", zmk: `[[#frag]]`, expect: expectMap{ encoderZJSON: `[{"":"Link","q":"self","s":"#frag"}]`, encoderHTML: `<a href="#frag">#frag</a>`, encoderNative: `Link SELF "#frag"`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Hosted link", zmk: `[[H|/hosted]]`, expect: expectMap{ encoderZJSON: `[{"":"Link","q":"local","s":"/hosted","i":[{"":"Text","s":"H"}]}]`, encoderHTML: `<a href="/hosted">H</a>`, encoderNative: `Link LOCAL "/hosted" [Text "H"]`, encoderText: `H`, encoderZmk: useZmk, }, }, { descr: "Based link", zmk: `[[B|/based]]`, expect: expectMap{ encoderZJSON: `[{"":"Link","q":"local","s":"/based","i":[{"":"Text","s":"B"}]}]`, encoderHTML: `<a href="/based">B</a>`, encoderNative: `Link LOCAL "/based" [Text "B"]`, encoderText: `B`, encoderZmk: useZmk, }, }, { descr: "Relative link", zmk: `[[R|../relative]]`, expect: expectMap{ encoderZJSON: `[{"":"Link","q":"local","s":"../relative","i":[{"":"Text","s":"R"}]}]`, encoderHTML: `<a href="../relative">R</a>`, encoderNative: `Link LOCAL "../relative" [Text "R"]`, encoderText: `R`, encoderZmk: useZmk, }, }, { descr: "Dummy Embed", zmk: `{{abc}}`, expect: expectMap{ encoderZJSON: `[{"":"Embed","s":"abc"}]`, encoderHTML: `<img src="abc" alt="">`, encoderNative: `Embed EXTERNAL "abc"`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "", zmk: ``, expect: expectMap{ encoderZJSON: `[]`, encoderHTML: ``, encoderNative: ``, encoderText: ``, encoderZmk: useZmk, }, }, } |
Changes to encoder/encoder_test.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 | package encoder_test import ( "bytes" "fmt" "testing" | < < | | | | | > | | | | | | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | package encoder_test import ( "bytes" "fmt" "testing" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/input" "zettelstore.de/z/parser" _ "zettelstore.de/z/encoder/htmlenc" // Allow to use HTML encoder. _ "zettelstore.de/z/encoder/nativeenc" // Allow to use native encoder. _ "zettelstore.de/z/encoder/textenc" // Allow to use text encoder. _ "zettelstore.de/z/encoder/zjsonenc" // Allow to use ZJSON encoder. _ "zettelstore.de/z/encoder/zmkenc" // Allow to use zmk encoder. "zettelstore.de/z/parser/cleaner" _ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser. ) type zmkTestCase struct { descr string zmk string inline bool expect expectMap } type expectMap map[api.EncodingEnum]string const useZmk = "\000" const ( encoderZJSON = api.EncoderZJSON encoderHTML = api.EncoderHTML encoderNative = api.EncoderNative encoderText = api.EncoderText encoderZmk = api.EncoderZmk ) func TestEncoder(t *testing.T) { for i := range tcsInline { tcsInline[i].inline = true } executeTestCases(t, append(tcsBlock, tcsInline...)) |
︙ | ︙ | |||
66 67 68 69 70 71 72 | is := parser.ParseInlines(inp, api.ValueSyntaxZmk) cleaner.CleanInlineSlice(&is) pe = &peInlines{is: is} } else { pe = &peBlocks{bs: parser.ParseBlocks(inp, nil, api.ValueSyntaxZmk)} } checkEncodings(t, testNum, pe, tc.descr, tc.expect, tc.zmk) | < | | < < < < < < < < < < < < < < < < < < < < < < < < | 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | is := parser.ParseInlines(inp, api.ValueSyntaxZmk) cleaner.CleanInlineSlice(&is) pe = &peInlines{is: is} } else { pe = &peBlocks{bs: parser.ParseBlocks(inp, nil, api.ValueSyntaxZmk)} } checkEncodings(t, testNum, pe, tc.descr, tc.expect, tc.zmk) } } func checkEncodings(t *testing.T, testNum int, pe parserEncoder, descr string, expected expectMap, zmkDefault string) { t.Helper() for enc, exp := range expected { encdr := encoder.Create(enc, nil) got, err := pe.encode(encdr) if err != nil { t.Error(err) continue } if enc == api.EncoderZmk && exp == "\000" { exp = zmkDefault } if got != exp { prefix := fmt.Sprintf("Test #%d", testNum) if d := descr; d != "" { prefix += "\nReason: " + d } prefix += "\nMode: " + pe.mode() t.Errorf("%s\nEncoder: %s\nExpected: %q\nGot: %q", prefix, enc, exp, got) } } } type parserEncoder interface { encode(encoder.Encoder) (string, error) mode() string } type peInlines struct { is ast.InlineSlice |
︙ | ︙ |
Added encoder/env.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 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 encoder import ( "zettelstore.de/z/ast" "zettelstore.de/z/strfun" ) // Environment specifies all data and functions that affects encoding. type Environment struct { // Important for HTML encoder Lang string // default language Interactive bool // Encoded data will be placed in interactive content Xhtml bool // use XHTML syntax instead of HTML syntax MarkerExternal string // Marker after link to (external) material. NewWindow bool // open link in new window IgnoreMeta strfun.Set footnotes []footnoteInfo // Stores footnotes detected while encoding footnoteNum int } // IsInteractive returns true, if Interactive is enabled and currently embedded // interactive encoding will take place. func (env *Environment) IsInteractive(inInteractive bool) bool { return inInteractive && env != nil && env.Interactive } // IsXHTML return true, if XHTML is enabled. func (env *Environment) IsXHTML() bool { return env != nil && env.Xhtml } // HasNewWindow retruns true, if a new browser windows should be opened. func (env *Environment) HasNewWindow() bool { return env != nil && env.NewWindow } type footnoteInfo struct { fn *ast.FootnoteNode num int } // AddFootnote adds a footnote node to the environment and returns the number of that footnote. func (env *Environment) AddFootnote(fn *ast.FootnoteNode) int { if env == nil { return 0 } env.footnoteNum++ env.footnotes = append(env.footnotes, footnoteInfo{fn: fn, num: env.footnoteNum}) return env.footnoteNum } // PopFootnote returns the next footnote and removes it from the list. func (env *Environment) PopFootnote() (*ast.FootnoteNode, int) { if env == nil { return nil, -1 } if len(env.footnotes) == 0 { env.footnotes = nil env.footnoteNum = 0 return nil, -1 } fni := env.footnotes[0] env.footnotes = env.footnotes[1:] return fni.fn, fni.num } |
Added encoder/htmlenc/block.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 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 htmlenc encodes the abstract syntax tree into HTML5. package htmlenc import ( "fmt" "strconv" "strings" "zettelstore.de/c/api" "zettelstore.de/c/html" "zettelstore.de/c/zjson" "zettelstore.de/z/ast" "zettelstore.de/z/strfun" ) func (v *visitor) visitVerbatim(vn *ast.VerbatimNode) { switch vn.Kind { case ast.VerbatimZettel: v.b.WriteString("<!--\n") v.visitAttributes(vn.Attrs) v.writeHTMLEscaped(string(vn.Content)) v.b.WriteString("\n-->") case ast.VerbatimProg: oldVisible := v.visibleSpace if vn.Attrs != nil { v.visibleSpace = vn.Attrs.HasDefault() } v.b.WriteString("<pre><code") v.visitAttributes(vn.Attrs) v.b.WriteByte('>') v.writeHTMLEscaped(string(vn.Content)) v.b.WriteString("</code></pre>") v.visibleSpace = oldVisible case ast.VerbatimComment: if vn.Attrs.HasDefault() { v.b.WriteString("<!--\n") v.writeHTMLEscaped(string(vn.Content)) v.b.WriteString("\n-->") } case ast.VerbatimHTML: if html.IsSave(string(vn.Content)) { v.b.Write(vn.Content) } default: panic(fmt.Sprintf("Unknown verbatim kind %v", vn.Kind)) } } var specialSpanAttr = strfun.NewSet("example", "note", "tip", "important", "caution", "warning") func processSpanAttributes(attrs zjson.Attributes) zjson.Attributes { if attrVal, ok := attrs.Get(""); ok { attrVal = strings.ToLower(attrVal) if specialSpanAttr.Has(attrVal) { attrs = attrs.Clone().Remove("").AddClass("zs-indication").AddClass("zs-" + attrVal) } } return attrs } func (v *visitor) visitRegion(rn *ast.RegionNode) { var code string attrs := rn.Attrs oldVerse := v.inVerse switch rn.Kind { case ast.RegionSpan: code = "div" attrs = processSpanAttributes(attrs) case ast.RegionVerse: v.inVerse = true code = "div" case ast.RegionQuote: code = "blockquote" default: panic(fmt.Sprintf("Unknown region kind %v", rn.Kind)) } v.b.WriteStrings("<", code) v.visitAttributes(attrs) v.b.WriteString(">\n") ast.Walk(v, &rn.Blocks) if len(rn.Inlines) > 0 { v.b.WriteString("\n<cite>") ast.Walk(v, &rn.Inlines) v.b.WriteString("</cite>") } v.b.WriteStrings("\n</", code, ">") v.inVerse = oldVerse } func (v *visitor) visitHeading(hn *ast.HeadingNode) { lvl := hn.Level + 1 if lvl > 6 { lvl = 6 // HTML has H1..H6 } strLvl := strconv.Itoa(lvl) v.b.WriteStrings("<h", strLvl) v.visitAttributes(hn.Attrs) if _, ok := hn.Attrs.Get("id"); !ok { if fragment := hn.Fragment; fragment != "" { v.b.WriteStrings(" id=\"", fragment, "\"") } } v.b.WriteByte('>') ast.Walk(v, &hn.Inlines) v.b.WriteStrings("</h", strLvl, ">") } var mapNestedListKind = map[ast.NestedListKind]string{ ast.NestedListOrdered: "ol", ast.NestedListUnordered: "ul", } func (v *visitor) visitNestedList(ln *ast.NestedListNode) { if ln.Kind == ast.NestedListQuote { // NestedListQuote -> HTML <blockquote> doesn't use <li>...</li> v.writeQuotationList(ln) return } code, ok := mapNestedListKind[ln.Kind] if !ok { panic(fmt.Sprintf("Invalid list kind %v", ln.Kind)) } compact := isCompactList(ln.Items) v.b.WriteStrings("<", code) v.visitAttributes(ln.Attrs) v.b.WriteString(">\n") for _, item := range ln.Items { v.b.WriteString("<li>") v.writeItemSliceOrPara(item, compact) v.b.WriteString("</li>\n") } v.b.WriteStrings("</", code, ">") } func (v *visitor) writeQuotationList(ln *ast.NestedListNode) { v.b.WriteString("<blockquote>\n") inPara := false for _, item := range ln.Items { if pn := getParaItem(item); pn != nil { if inPara { v.b.WriteByte('\n') } else { v.b.WriteString("<p>") inPara = true } ast.Walk(v, &pn.Inlines) } else { if inPara { v.writeEndPara() inPara = false } ast.WalkItemSlice(v, item) } } if inPara { v.writeEndPara() } v.b.WriteString("</blockquote>\n") } func getParaItem(its ast.ItemSlice) *ast.ParaNode { if len(its) != 1 { return nil } if pn, ok := its[0].(*ast.ParaNode); ok { return pn } return nil } func isCompactList(insl []ast.ItemSlice) bool { for _, ins := range insl { if !isCompactSlice(ins) { return false } } return true } func isCompactSlice(ins ast.ItemSlice) bool { if len(ins) < 1 { return true } if len(ins) == 1 { switch ins[0].(type) { case *ast.ParaNode, *ast.VerbatimNode, *ast.HRuleNode: return true case *ast.NestedListNode: return false } } return false } // writeItemSliceOrPara emits the content of a paragraph if the paragraph is // the only element of the block slice and if compact mode is true. Otherwise, // the item slice is emitted normally. func (v *visitor) writeItemSliceOrPara(ins ast.ItemSlice, compact bool) { if compact && len(ins) == 1 { if para, ok := ins[0].(*ast.ParaNode); ok { ast.Walk(v, ¶.Inlines) return } } for i, in := range ins { if i >= 0 { v.b.WriteByte('\n') } ast.Walk(v, in) } v.b.WriteByte('\n') } func (v *visitor) writeDescriptionsSlice(ds ast.DescriptionSlice) { if len(ds) == 1 { if para, ok := ds[0].(*ast.ParaNode); ok { ast.Walk(v, ¶.Inlines) return } } ast.WalkDescriptionSlice(v, ds) } func (v *visitor) visitDescriptionList(dn *ast.DescriptionListNode) { v.b.WriteString("<dl>\n") for _, descr := range dn.Descriptions { v.b.WriteString("<dt>") ast.Walk(v, &descr.Term) v.b.WriteString("</dt>\n") for _, b := range descr.Descriptions { v.b.WriteString("<dd>") v.writeDescriptionsSlice(b) v.b.WriteString("</dd>\n") } } v.b.WriteString("</dl>") } func (v *visitor) visitTable(tn *ast.TableNode) { v.b.WriteString("<table>\n") if len(tn.Header) > 0 { v.b.WriteString("<thead>\n") v.writeRow(tn.Header, "<th", "</th>") v.b.WriteString("</thead>\n") } if len(tn.Rows) > 0 { v.b.WriteString("<tbody>\n") for _, row := range tn.Rows { v.writeRow(row, "<td", "</td>") } v.b.WriteString("</tbody>\n") } v.b.WriteString("</table>") } var alignStyle = map[ast.Alignment]string{ ast.AlignDefault: ">", ast.AlignLeft: " class=\"left\">", ast.AlignCenter: " class=\"center\">", ast.AlignRight: " class=\"right\">", } func (v *visitor) writeRow(row ast.TableRow, cellStart, cellEnd string) { v.b.WriteString("<tr>") for _, cell := range row { v.b.WriteString(cellStart) if len(cell.Inlines) == 0 { v.b.WriteByte('>') } else { v.b.WriteString(alignStyle[cell.Align]) ast.Walk(v, &cell.Inlines) } v.b.WriteString(cellEnd) } v.b.WriteString("</tr>\n") } func (v *visitor) visitBLOB(bn *ast.BLOBNode) { switch bn.Syntax { case api.ValueSyntaxSVG: v.b.Write(bn.Blob) case api.ValueSyntaxGif, "jpeg", "png": v.b.WriteStrings("<img src=\"data:image/", bn.Syntax, ";base64,") v.b.WriteBase64(bn.Blob) v.b.WriteString("\" title=\"") v.writeQuotedEscaped(bn.Title) v.b.WriteString("\">") default: v.b.WriteStrings("<p class=\"zs-error\">Unable to display BLOB with syntax '", bn.Syntax, "'.</p>") } } func (v *visitor) writeEndPara() { v.b.WriteString("</p>") } |
Changes to encoder/htmlenc/htmlenc.go.
1 | //----------------------------------------------------------------------------- | | | < < < < < | > > < < | < < > > > > > > > > > | | < < < < | | < | | < < | < | < | | < > | > > > > > > > > > > | > | | | | < | | < | | | | < | < < < < < < < < < < < | < | < | < < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 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 htmlenc encodes the abstract syntax tree into HTML5. package htmlenc import ( "io" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" ) func init() { encoder.Register(api.EncoderHTML, encoder.Info{ Create: func(env *encoder.Environment) encoder.Encoder { return &htmlEncoder{env: env} }, }) } type htmlEncoder struct { env *encoder.Environment } // WriteZettel encodes a full zettel as HTML5. func (he *htmlEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) { v := newVisitor(he, w) if !he.env.IsXHTML() { v.b.WriteString("<!DOCTYPE html>\n") } if env := he.env; env != nil && env.Lang == "" { v.b.WriteStrings("<html>\n<head>") } else { v.b.WriteStrings("<html lang=\"", env.Lang, "\">") } v.b.WriteString("\n<head>\n<meta charset=\"utf-8\">\n") plainTitle, hasTitle := zn.InhMeta.Get(api.KeyTitle) if hasTitle { v.b.WriteStrings("<title>", v.evalValue(plainTitle, evalMeta), "</title>") } v.acceptMeta(zn.InhMeta, evalMeta) v.b.WriteString("\n</head>\n<body>\n") if hasTitle { if isTitle := evalMeta(plainTitle); len(isTitle) > 0 { v.b.WriteString("<h1>") ast.Walk(v, &isTitle) v.b.WriteString("</h1>\n") } } ast.Walk(v, &zn.Ast) v.writeEndnotes() v.b.WriteString("</body>\n</html>") length, err := v.b.Flush() return length, err } // WriteMeta encodes meta data as HTML5. func (he *htmlEncoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) { v := newVisitor(he, w) // Write title if title, ok := m.Get(api.KeyTitle); ok { v.b.WriteStrings("<meta name=\"zs-", api.KeyTitle, "\" content=\"") v.writeQuotedEscaped(v.evalValue(title, evalMeta)) v.b.WriteString("\">") } // Write other metadata v.acceptMeta(m, evalMeta) length, err := v.b.Flush() return length, err } func (he *htmlEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return he.WriteBlocks(w, &zn.Ast) } // WriteBlocks encodes a block slice. func (he *htmlEncoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { v := newVisitor(he, w) ast.Walk(v, bs) v.writeEndnotes() length, err := v.b.Flush() return length, err } // WriteInlines writes an inline slice to the writer func (he *htmlEncoder) WriteInlines(w io.Writer, is *ast.InlineSlice) (int, error) { v := newVisitor(he, w) if env := he.env; env != nil { v.inInteractive = env.Interactive } ast.Walk(v, is) length, err := v.b.Flush() return length, err } |
Added encoder/htmlenc/inline.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 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 htmlenc encodes the abstract syntax tree into HTML5. package htmlenc import ( "fmt" "strconv" "zettelstore.de/c/api" "zettelstore.de/c/html" "zettelstore.de/c/zjson" "zettelstore.de/z/ast" ) func (v *visitor) visitBreak(bn *ast.BreakNode) { if bn.Hard { if v.env.IsXHTML() { v.b.WriteString("<br />\n") } else { v.b.WriteString("<br>\n") } } else { v.b.WriteByte('\n') } } func (v *visitor) visitLink(ln *ast.LinkNode) { switch ln.Ref.State { case ast.RefStateSelf, ast.RefStateFound, ast.RefStateHosted, ast.RefStateBased: v.writeAHref(ln.Ref, ln.Attrs, &ln.Inlines) case ast.RefStateBroken: attrs := ln.Attrs.Clone() attrs = attrs.Set("class", "broken") attrs = attrs.Set("title", "Zettel not found") // l10n v.writeAHref(ln.Ref, attrs, &ln.Inlines) case ast.RefStateExternal: attrs := ln.Attrs.Clone() attrs = attrs.Set("class", "external") if v.env.HasNewWindow() { attrs = attrs.Set("target", "_blank").Set("rel", "noopener noreferrer") } v.writeAHref(ln.Ref, attrs, &ln.Inlines) if v.env != nil { v.b.WriteString(v.env.MarkerExternal) } default: if v.env.IsInteractive(v.inInteractive) { v.writeSpan(&ln.Inlines, ln.Attrs) return } v.b.WriteString("<a href=\"") v.writeQuotedEscaped(ln.Ref.Value) v.b.WriteByte('"') v.visitAttributes(ln.Attrs) v.b.WriteByte('>') v.writeLinkInlines(&ln.Inlines, ln.Ref) v.inInteractive = false v.b.WriteString("</a>") } } func (v *visitor) writeAHref(ref *ast.Reference, attrs zjson.Attributes, is *ast.InlineSlice) { if v.env.IsInteractive(v.inInteractive) { v.writeSpan(is, attrs) return } v.b.WriteString("<a href=\"") v.writeReference(ref) v.b.WriteByte('"') v.visitAttributes(attrs) v.b.WriteByte('>') v.writeLinkInlines(is, ref) v.b.WriteString("</a>") } func (v *visitor) writeLinkInlines(is *ast.InlineSlice, ref *ast.Reference) { saveInteractive := v.inInteractive v.inInteractive = true if len(*is) == 0 { v.writeHTMLEscaped(ref.Value) } else { ast.Walk(v, is) } v.inInteractive = saveInteractive } func (v *visitor) visitEmbedRef(en *ast.EmbedRefNode) { v.b.WriteString("<img src=\"") v.writeReference(en.Ref) v.b.WriteString("\" alt=\"") ast.Walk(v, &en.Inlines) // TODO: wrong, must use textenc v.b.WriteByte('"') v.visitAttributes(en.Attrs) if v.env.IsXHTML() { v.b.WriteString(" />") } else { v.b.WriteByte('>') } } func (v *visitor) visitEmbedBLOB(en *ast.EmbedBLOBNode) { if en.Syntax == api.ValueSyntaxSVG { v.b.Write(en.Blob) return } v.b.WriteString("<img src=\"data:image/") v.b.WriteStrings(en.Syntax, ";base64,") v.b.WriteBase64(en.Blob) v.b.WriteString("\" alt=\"") ast.Walk(v, &en.Inlines) v.b.WriteByte('"') v.visitAttributes(en.Attrs) if v.env.IsXHTML() { v.b.WriteString(" />") } else { v.b.WriteByte('>') } } func (v *visitor) visitCite(cn *ast.CiteNode) { v.b.WriteString(cn.Key) if len(cn.Inlines) > 0 { v.b.WriteString(", ") ast.Walk(v, &cn.Inlines) } } func (v *visitor) visitFootnote(fn *ast.FootnoteNode) { if v.env.IsInteractive(v.inInteractive) { return } n := strconv.Itoa(v.env.AddFootnote(fn)) v.b.WriteStrings("<sup id=\"fnref:", n, "\"><a href=\"#fn:", n, "\" class=\"footnote-ref\" role=\"doc-noteref\">", n, "</a></sup>") // TODO: what to do with Attrs? } func (v *visitor) visitMark(mn *ast.MarkNode) { if v.env.IsInteractive(v.inInteractive) { return } if fragment := mn.Fragment; fragment != "" { v.b.WriteStrings(`<a id="`, fragment, `">`) if len(mn.Inlines) > 0 { ast.Walk(v, &mn.Inlines) } v.b.WriteString("</a>") } } func (v *visitor) visitFormat(fn *ast.FormatNode) { var code string switch fn.Kind { case ast.FormatEmph: code = "em" case ast.FormatStrong: code = "strong" case ast.FormatInsert: code = "ins" case ast.FormatDelete: code = "del" case ast.FormatSuper: code = "sup" case ast.FormatSub: code = "sub" case ast.FormatQuote: code = "q" case ast.FormatSpan: v.writeSpan(&fn.Inlines, processSpanAttributes(fn.Attrs)) return default: panic(fmt.Sprintf("Unknown format kind %v", fn.Kind)) } v.b.WriteStrings("<", code) v.visitAttributes(fn.Attrs) v.b.WriteByte('>') ast.Walk(v, &fn.Inlines) v.b.WriteStrings("</", code, ">") } func (v *visitor) writeSpan(is *ast.InlineSlice, attrs zjson.Attributes) { v.b.WriteString("<span") v.visitAttributes(attrs) v.b.WriteByte('>') ast.Walk(v, is) v.b.WriteString("</span>") } func (v *visitor) visitLiteral(ln *ast.LiteralNode) { switch ln.Kind { case ast.LiteralProg: v.writeLiteral("<code", "</code>", ln.Attrs, ln.Content) case ast.LiteralInput: v.writeLiteral("<kbd", "</kbd>", ln.Attrs, ln.Content) case ast.LiteralOutput: v.writeLiteral("<samp", "</samp>", ln.Attrs, ln.Content) case ast.LiteralZettel, ast.LiteralComment: if v.inlinePos > 0 { v.b.WriteByte(' ') } v.b.WriteString("<!-- ") v.writeHTMLEscaped(string(ln.Content)) // writeCommentEscaped v.b.WriteString(" -->") case ast.LiteralHTML: if html.IsSave(string(ln.Content)) { v.b.Write(ln.Content) } default: panic(fmt.Sprintf("Unknown literal kind %v", ln.Kind)) } } func (v *visitor) writeLiteral(codeS, codeE string, attrs zjson.Attributes, content []byte) { oldVisible := v.visibleSpace if attrs != nil { v.visibleSpace = attrs.HasDefault() } v.b.WriteString(codeS) v.visitAttributes(attrs) v.b.WriteByte('>') v.writeHTMLEscaped(string(content)) v.b.WriteString(codeE) v.visibleSpace = oldVisible } |
Added encoder/htmlenc/visitor.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 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 htmlenc import ( "bytes" "io" "strconv" "strings" "zettelstore.de/c/api" "zettelstore.de/c/html" "zettelstore.de/c/zjson" "zettelstore.de/z/ast" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" "zettelstore.de/z/strfun" ) // visitor writes the abstract syntax tree to an io.Writer. type visitor struct { env *encoder.Environment b encoder.EncWriter visibleSpace bool // Show space character in plain text inVerse bool // In verse block inInteractive bool // Rendered interactive HTML code textEnc encoder.Encoder inlinePos int // Element position in inline list node } func newVisitor(he *htmlEncoder, w io.Writer) *visitor { return &visitor{ env: he.env, b: encoder.NewEncWriter(w), textEnc: encoder.Create(api.EncoderText, nil), } } func (v *visitor) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.BlockSlice: for i, bn := range *n { if i > 0 { v.b.WriteByte('\n') } ast.Walk(v, bn) } case *ast.InlineSlice: for i, in := range *n { v.inlinePos = i ast.Walk(v, in) } v.inlinePos = 0 case *ast.ParaNode: v.b.WriteString("<p>") ast.Walk(v, &n.Inlines) v.writeEndPara() case *ast.VerbatimNode: v.visitVerbatim(n) case *ast.RegionNode: v.visitRegion(n) case *ast.HeadingNode: v.visitHeading(n) case *ast.HRuleNode: v.b.WriteString("<hr") v.visitAttributes(n.Attrs) if v.env.IsXHTML() { v.b.WriteString(" />") } else { v.b.WriteBytes('>') } case *ast.NestedListNode: v.visitNestedList(n) case *ast.DescriptionListNode: v.visitDescriptionList(n) case *ast.TableNode: v.visitTable(n) case *ast.TranscludeNode: return nil // Nothing to write. Or: an iFrame? case *ast.BLOBNode: v.visitBLOB(n) case *ast.TextNode: v.writeHTMLEscaped(n.Text) case *ast.TagNode: v.b.WriteString("<span class=\"zettel-tag\">#") v.writeHTMLEscaped(n.Tag) v.b.WriteString("</span>") case *ast.SpaceNode: if v.inVerse || v.env.IsXHTML() { v.b.WriteString(n.Lexeme) } else { v.b.WriteByte(' ') } case *ast.BreakNode: v.visitBreak(n) case *ast.LinkNode: v.visitLink(n) case *ast.EmbedRefNode: v.visitEmbedRef(n) case *ast.EmbedBLOBNode: v.visitEmbedBLOB(n) case *ast.CiteNode: v.visitCite(n) case *ast.FootnoteNode: v.visitFootnote(n) case *ast.MarkNode: v.visitMark(n) case *ast.FormatNode: v.visitFormat(n) case *ast.LiteralNode: v.visitLiteral(n) default: return v } return nil } var mapMetaKey = map[string]string{ api.KeyCopyright: "copyright", api.KeyLicense: "license", } func (v *visitor) acceptMeta(m *meta.Meta, evalMeta encoder.EvalMetaFunc) { ignore := v.setupIgnoreSet() ignore.Set(api.KeyTitle) if tags, ok := m.Get(api.KeyAllTags); ok { v.writeTags(tags) ignore.Set(api.KeyAllTags) ignore.Set(api.KeyTags) } else if tags, ok = m.Get(api.KeyTags); ok { v.writeTags(tags) ignore.Set(api.KeyTags) } for _, p := range m.ComputedPairs() { key := p.Key if ignore.Has(key) { continue } value := p.Value if m.Type(key) == meta.TypeZettelmarkup { if v := v.evalValue(value, evalMeta); v != "" { value = v } } if mKey, ok := mapMetaKey[key]; ok { v.writeMeta("", mKey, value) } else { v.writeMeta("zs-", key, value) } } } func (v *visitor) evalValue(value string, evalMeta encoder.EvalMetaFunc) string { var buf bytes.Buffer is := evalMeta(value) _, err := v.textEnc.WriteInlines(&buf, &is) if err == nil { return buf.String() } return "" } func (v *visitor) setupIgnoreSet() strfun.Set { if v.env == nil || v.env.IgnoreMeta == nil { return make(strfun.Set) } result := make(strfun.Set, len(v.env.IgnoreMeta)) for k := range v.env.IgnoreMeta { result.Set(k) } return result } func (v *visitor) writeTags(tags string) { v.b.WriteString("\n<meta name=\"keywords\" content=\"") for i, val := range meta.ListFromValue(tags) { if i > 0 { v.b.WriteString(", ") } v.writeQuotedEscaped(strings.TrimPrefix(val, "#")) } v.b.WriteString("\">") } func (v *visitor) writeMeta(prefix, key, value string) { v.b.WriteStrings("\n<meta name=\"", prefix, key, "\" content=\"") v.writeQuotedEscaped(value) v.b.WriteString("\">") } func (v *visitor) writeEndnotes() { fn, fnNum := v.env.PopFootnote() if fn == nil { return } v.b.WriteString("\n<ol class=\"endnotes\">\n") for fn != nil { n := strconv.Itoa(fnNum) v.b.WriteStrings("<li value=\"", n, "\" id=\"fn:", n, "\" role=\"doc-endnote\">") ast.Walk(v, &fn.Inlines) v.b.WriteStrings( " <a href=\"#fnref:", n, "\" class=\"footnote-backref\" role=\"doc-backlink\">↩︎</a></li>\n") fn, fnNum = v.env.PopFootnote() } v.b.WriteString("</ol>\n") } // visitAttributes write HTML attributes func (v *visitor) visitAttributes(a zjson.Attributes) { if a.IsEmpty() { return } a = a.Clone().RemoveDefault() for _, k := range a.Keys() { if k == "" || k == "-" { continue } v.b.WriteStrings(" ", k) vl := a[k] if len(vl) > 0 { v.b.WriteString("=\"") v.writeQuotedEscaped(vl) v.b.WriteByte('"') } } } func (v *visitor) writeHTMLEscaped(s string) { if v.visibleSpace { html.EscapeVisible(&v.b, s) } else { html.Escape(&v.b, s) } } func (v *visitor) writeQuotedEscaped(s string) { html.AttributeEscape(&v.b, s) } func (v *visitor) writeReference(ref *ast.Reference) { if ref.URL == nil { v.writeHTMLEscaped(ref.Value) return } v.b.WriteString(ref.URL.String()) } |
Added encoder/nativeenc/nativeenc.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 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 nativeenc encodes the abstract syntax tree into native format. package nativeenc import ( "fmt" "io" "strconv" "zettelstore.de/c/api" "zettelstore.de/c/zjson" "zettelstore.de/z/ast" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" ) func init() { encoder.Register(api.EncoderNative, encoder.Info{ Create: func(env *encoder.Environment) encoder.Encoder { return &nativeEncoder{env: env} }, }) } type nativeEncoder struct { env *encoder.Environment } // WriteZettel encodes the zettel to the writer. func (ne *nativeEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) { v := newVisitor(w, ne) v.acceptMeta(zn.InhMeta, evalMeta) v.b.WriteByte('\n') ast.Walk(v, &zn.Ast) length, err := v.b.Flush() return length, err } // WriteMeta encodes meta data in native format. func (ne *nativeEncoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) { v := newVisitor(w, ne) v.acceptMeta(m, evalMeta) length, err := v.b.Flush() return length, err } func (ne *nativeEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return ne.WriteBlocks(w, &zn.Ast) } // WriteBlocks writes a block slice to the writer func (ne *nativeEncoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { v := newVisitor(w, ne) ast.Walk(v, bs) length, err := v.b.Flush() return length, err } // WriteInlines writes an inline slice to the writer func (ne *nativeEncoder) WriteInlines(w io.Writer, is *ast.InlineSlice) (int, error) { v := newVisitor(w, ne) ast.Walk(v, is) length, err := v.b.Flush() return length, err } // visitor writes the abstract syntax tree to an io.Writer. type visitor struct { b encoder.EncWriter level int env *encoder.Environment } func newVisitor(w io.Writer, enc *nativeEncoder) *visitor { return &visitor{b: encoder.NewEncWriter(w), env: enc.env} } func (v *visitor) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.BlockSlice: v.visitBlockSlice(n) case *ast.InlineSlice: v.walkInlineSlice(n) case *ast.ParaNode: v.b.WriteString("[Para ") ast.Walk(v, &n.Inlines) v.b.WriteByte(']') case *ast.VerbatimNode: v.visitVerbatim(n) case *ast.RegionNode: v.visitRegion(n) case *ast.HeadingNode: v.visitHeading(n) case *ast.HRuleNode: v.b.WriteString("[Hrule") v.visitAttributes(n.Attrs) v.b.WriteByte(']') case *ast.NestedListNode: v.visitNestedList(n) case *ast.DescriptionListNode: v.visitDescriptionList(n) case *ast.TableNode: v.visitTable(n) case *ast.TranscludeNode: v.b.WriteString("[Transclude ") v.b.WriteString(mapRefState[n.Ref.State]) v.b.WriteString(" \"") v.writeEscaped(n.Ref.String()) v.b.WriteString("\"]") case *ast.BLOBNode: v.visitBLOB(n) case *ast.TextNode: v.b.WriteString("Text \"") v.writeEscaped(n.Text) v.b.WriteByte('"') case *ast.TagNode: v.b.WriteString("Tag \"") v.writeEscaped(n.Tag) v.b.WriteByte('"') case *ast.SpaceNode: v.b.WriteString("Space") if l := n.Count(); l > 1 { v.b.WriteByte(' ') v.b.WriteString(strconv.Itoa(l)) } case *ast.BreakNode: if n.Hard { v.b.WriteString("Break") } else { v.b.WriteString("Space") } case *ast.LinkNode: v.visitLink(n) case *ast.EmbedRefNode: v.visitEmbedRef(n) case *ast.EmbedBLOBNode: v.visitEmbedBLOB(n) case *ast.CiteNode: v.b.WriteString("Cite") v.visitAttributes(n.Attrs) v.b.WriteString(" \"") v.writeEscaped(n.Key) v.b.WriteByte('"') if len(n.Inlines) > 0 { v.b.WriteString(" [") ast.Walk(v, &n.Inlines) v.b.WriteByte(']') } case *ast.FootnoteNode: v.b.WriteString("Footnote") v.visitAttributes(n.Attrs) v.b.WriteString(" [") ast.Walk(v, &n.Inlines) v.b.WriteByte(']') case *ast.MarkNode: v.visitMark(n) case *ast.FormatNode: v.b.Write(mapFormatKind[n.Kind]) v.visitAttributes(n.Attrs) v.b.WriteString(" [") ast.Walk(v, &n.Inlines) v.b.WriteByte(']') case *ast.LiteralNode: kind, ok := mapLiteralKind[n.Kind] if !ok { panic(fmt.Sprintf("Unknown literal kind %v", n.Kind)) } v.b.Write(kind) v.visitAttributes(n.Attrs) v.b.WriteString(" \"") v.writeEscaped(string(n.Content)) v.b.WriteByte('"') default: return v } return nil } var ( rawBackslash = []byte{'\\', '\\'} rawDoubleQuote = []byte{'\\', '"'} rawNewline = []byte{'\\', 'n'} ) func (v *visitor) acceptMeta(m *meta.Meta, evalMeta encoder.EvalMetaFunc) { v.writeZettelmarkup("Title", m.GetDefault(api.KeyTitle, ""), evalMeta) v.writeMetaString(m, api.KeyRole, "Role") v.writeMetaList(m, api.KeyTags, "Tags") v.writeMetaString(m, api.KeySyntax, "Syntax") pairs := m.ComputedPairsRest() if len(pairs) == 0 { return } v.b.WriteString("\n[Header") v.level++ for i, p := range pairs { v.writeComma(i) v.writeNewLine() key, value := p.Key, p.Value if meta.Type(key) == meta.TypeZettelmarkup { v.writeZettelmarkup(key, value, evalMeta) } else { v.b.WriteByte('[') v.b.WriteStrings(key, " \"") v.writeEscaped(value) v.b.WriteString("\"]") } } v.level-- v.b.WriteByte(']') } func (v *visitor) writeZettelmarkup(key, value string, evalMeta encoder.EvalMetaFunc) { v.b.WriteByte('[') v.b.WriteString(key) v.b.WriteByte(' ') is := evalMeta(value) ast.Walk(v, &is) v.b.WriteByte(']') } 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, 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) } v.b.WriteByte(']') } } var mapVerbatimKind = map[ast.VerbatimKind][]byte{ ast.VerbatimZettel: []byte("[ZettelBlock"), ast.VerbatimProg: []byte("[CodeBlock"), ast.VerbatimComment: []byte("[CommentBlock"), ast.VerbatimHTML: []byte("[HTMLBlock"), } func (v *visitor) visitVerbatim(vn *ast.VerbatimNode) { kind, ok := mapVerbatimKind[vn.Kind] if !ok { panic(fmt.Sprintf("Unknown verbatim kind %v", vn.Kind)) } v.b.Write(kind) v.visitAttributes(vn.Attrs) v.b.WriteString(" \"") v.writeEscaped(string(vn.Content)) v.b.WriteString("\"]") } var mapRegionKind = map[ast.RegionKind][]byte{ ast.RegionSpan: []byte("[SpanBlock"), ast.RegionQuote: []byte("[QuoteBlock"), ast.RegionVerse: []byte("[VerseBlock"), } func (v *visitor) visitRegion(rn *ast.RegionNode) { kind, ok := mapRegionKind[rn.Kind] if !ok { panic(fmt.Sprintf("Unknown region kind %v", rn.Kind)) } v.b.Write(kind) v.visitAttributes(rn.Attrs) v.level++ v.writeNewLine() v.b.WriteByte('[') v.level++ ast.Walk(v, &rn.Blocks) v.level-- v.b.WriteByte(']') if len(rn.Inlines) > 0 { v.b.WriteByte(',') v.writeNewLine() v.b.WriteString("[Cite ") ast.Walk(v, &rn.Inlines) v.b.WriteByte(']') } v.level-- v.b.WriteByte(']') } func (v *visitor) visitHeading(hn *ast.HeadingNode) { v.b.WriteStrings("[Heading ", strconv.Itoa(hn.Level)) if fragment := hn.Fragment; fragment != "" { v.b.WriteStrings(" #", fragment) } v.visitAttributes(hn.Attrs) v.b.WriteByte(' ') ast.Walk(v, &hn.Inlines) v.b.WriteByte(']') } var mapNestedListKind = map[ast.NestedListKind][]byte{ ast.NestedListOrdered: []byte("[OrderedList"), ast.NestedListUnordered: []byte("[BulletList"), ast.NestedListQuote: []byte("[QuoteList"), } func (v *visitor) visitNestedList(ln *ast.NestedListNode) { v.b.Write(mapNestedListKind[ln.Kind]) v.level++ for i, item := range ln.Items { v.writeComma(i) v.writeNewLine() v.level++ v.b.WriteByte('[') for i, in := range item { if i > 0 { v.b.WriteByte(',') v.writeNewLine() } ast.Walk(v, in) } v.b.WriteByte(']') v.level-- } v.level-- v.b.WriteByte(']') } func (v *visitor) visitDescriptionList(dn *ast.DescriptionListNode) { v.b.WriteString("[DescriptionList") v.level++ for i, descr := range dn.Descriptions { v.writeComma(i) v.writeNewLine() v.b.WriteString("[Term [") ast.Walk(v, &descr.Term) v.b.WriteByte(']') if len(descr.Descriptions) > 0 { v.level++ for _, b := range descr.Descriptions { v.b.WriteByte(',') v.writeNewLine() v.b.WriteString("[Description") v.level++ v.writeNewLine() for i, dn := range b { if i > 0 { v.b.WriteByte(',') v.writeNewLine() } ast.Walk(v, dn) } v.b.WriteByte(']') v.level-- } v.level-- } v.b.WriteByte(']') } v.level-- v.b.WriteByte(']') } func (v *visitor) visitTable(tn *ast.TableNode) { v.b.WriteString("[Table") v.level++ if len(tn.Header) > 0 { v.writeNewLine() v.b.WriteString("[Header ") for i, cell := range tn.Header { v.writeComma(i) v.writeCell(cell) } v.b.WriteString("],") } for i, row := range tn.Rows { v.writeComma(i) v.writeNewLine() v.b.WriteString("[Row ") for j, cell := range row { v.writeComma(j) v.writeCell(cell) } v.b.WriteByte(']') } v.level-- v.b.WriteByte(']') } var alignString = map[ast.Alignment]string{ ast.AlignDefault: " Default", ast.AlignLeft: " Left", ast.AlignCenter: " Center", ast.AlignRight: " Right", } func (v *visitor) writeCell(cell *ast.TableCell) { v.b.WriteStrings("[Cell", alignString[cell.Align]) if len(cell.Inlines) > 0 { v.b.WriteByte(' ') ast.Walk(v, &cell.Inlines) } v.b.WriteByte(']') } func (v *visitor) visitBLOB(bn *ast.BLOBNode) { v.b.WriteString("[BLOB \"") v.writeEscaped(bn.Title) v.b.WriteString("\" \"") v.writeEscaped(bn.Syntax) v.b.WriteString("\" \"") if bn.Syntax == api.ValueSyntaxSVG { v.writeEscaped(string(bn.Blob)) } else { v.b.WriteBase64(bn.Blob) } v.b.WriteString("\"]") } var mapRefState = map[ast.RefState]string{ ast.RefStateInvalid: "INVALID", ast.RefStateZettel: "ZETTEL", ast.RefStateSelf: "SELF", ast.RefStateFound: "ZETTEL", ast.RefStateBroken: "BROKEN", ast.RefStateHosted: "LOCAL", ast.RefStateBased: "BASED", ast.RefStateExternal: "EXTERNAL", } func (v *visitor) visitLink(ln *ast.LinkNode) { v.b.WriteString("Link") v.visitAttributes(ln.Attrs) v.b.WriteByte(' ') v.b.WriteString(mapRefState[ln.Ref.State]) v.b.WriteString(" \"") v.writeEscaped(ln.Ref.String()) v.b.WriteByte('"') if len(ln.Inlines) > 0 { v.b.WriteString(" [") ast.Walk(v, &ln.Inlines) v.b.WriteByte(']') } } func (v *visitor) visitEmbedRef(en *ast.EmbedRefNode) { v.b.WriteString("Embed") v.visitAttributes(en.Attrs) v.b.WriteByte(' ') v.b.WriteString(mapRefState[en.Ref.State]) v.b.WriteString(" \"") v.writeEscaped(en.Ref.String()) v.b.WriteByte('"') if len(en.Inlines) > 0 { v.b.WriteString(" [") ast.Walk(v, &en.Inlines) v.b.WriteByte(']') } } func (v *visitor) visitEmbedBLOB(en *ast.EmbedBLOBNode) { v.b.WriteString("EmbedBLOB") v.visitAttributes(en.Attrs) v.b.WriteStrings(" {\"", en.Syntax, "\" \"") if en.Syntax == api.ValueSyntaxSVG { v.writeEscaped(string(en.Blob)) } else { v.b.WriteString("\" \"") v.b.WriteBase64(en.Blob) } v.b.WriteString("\"}") if len(en.Inlines) > 0 { v.b.WriteString(" [") ast.Walk(v, &en.Inlines) v.b.WriteByte(']') } } func (v *visitor) visitMark(mn *ast.MarkNode) { v.b.WriteString("Mark") if text := mn.Mark; text != "" { v.b.WriteString(" \"") v.writeEscaped(text) v.b.WriteByte('"') } if fragment := mn.Fragment; fragment != "" { v.b.WriteString(" #") v.writeEscaped(fragment) } if len(mn.Inlines) > 0 { v.b.WriteString(" [") ast.Walk(v, &mn.Inlines) v.b.WriteByte(']') } } var mapFormatKind = map[ast.FormatKind][]byte{ ast.FormatEmph: []byte("Emph"), ast.FormatStrong: []byte("Strong"), ast.FormatInsert: []byte("Insert"), ast.FormatDelete: []byte("Delete"), ast.FormatSuper: []byte("Super"), ast.FormatSub: []byte("Sub"), ast.FormatQuote: []byte("Quote"), ast.FormatSpan: []byte("Span"), } var mapLiteralKind = map[ast.LiteralKind][]byte{ ast.LiteralZettel: []byte("Zettel"), ast.LiteralProg: []byte("Code"), ast.LiteralInput: []byte("Input"), ast.LiteralOutput: []byte("Output"), ast.LiteralComment: []byte("Comment"), ast.LiteralHTML: []byte("HTML"), } func (v *visitor) visitBlockSlice(bs *ast.BlockSlice) { for i, bn := range *bs { if i > 0 { v.b.WriteByte(',') v.writeNewLine() } ast.Walk(v, bn) } } func (v *visitor) walkInlineSlice(is *ast.InlineSlice) { for i, in := range *is { v.writeComma(i) ast.Walk(v, in) } } // visitAttributes write native attributes func (v *visitor) visitAttributes(a zjson.Attributes) { if a.IsEmpty() { return } v.b.WriteString(" (\"") if val, ok := a[""]; ok { v.writeEscaped(val) } v.b.WriteString("\",[") for i, k := range a.Keys() { if k == "" { continue } v.writeComma(i) v.b.WriteString(k) val := a[k] if len(val) > 0 { v.b.WriteString("=\"") v.writeEscaped(val) v.b.WriteByte('"') } } v.b.WriteString("])") } func (v *visitor) writeNewLine() { v.b.WriteByte('\n') for i := 0; i < v.level; i++ { v.b.WriteByte(' ') } } func (v *visitor) writeEscaped(s string) { last := 0 for i, ch := range s { var b []byte switch ch { case '\n': b = rawNewline case '"': b = rawDoubleQuote case '\\': b = rawBackslash default: continue } v.b.WriteString(s[last:i]) v.b.Write(b) last = i + 1 } v.b.WriteString(s[last:]) } func (v *visitor) writeComma(pos int) { if pos > 0 { v.b.WriteByte(',') } } |
Deleted encoder/sexprenc/sexprenc.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted encoder/sexprenc/transform.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to encoder/textenc/textenc.go.
︙ | ︙ | |||
17 18 19 20 21 22 23 | "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" ) func init() { | | > | | < < | < < | | | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" ) func init() { encoder.Register(api.EncoderText, encoder.Info{ Create: func(*encoder.Environment) encoder.Encoder { return &textEncoder{} }, }) } type textEncoder struct{} // WriteZettel writes metadata and content. func (te *textEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) { v := newVisitor(w) te.WriteMeta(&v.b, zn.InhMeta, evalMeta) v.visitBlockSlice(&zn.Ast) length, err := v.b.Flush() return length, err } // WriteMeta encodes metadata as text. func (te *textEncoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) { buf := encoder.NewEncWriter(w) for _, pair := range m.ComputedPairs() { switch meta.Type(pair.Key) { case meta.TypeTagSet: writeTagSet(&buf, meta.ListFromValue(pair.Value)) case meta.TypeZettelmarkup: is := evalMeta(pair.Value) |
︙ | ︙ | |||
65 66 67 68 69 70 71 | buf.WriteByte(' ') } buf.WriteString(meta.CleanTag(tag)) } } | | | | < > | > > > | 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | buf.WriteByte(' ') } buf.WriteString(meta.CleanTag(tag)) } } func (te *textEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return te.WriteBlocks(w, &zn.Ast) } // WriteBlocks writes the content of a block slice to the writer. func (*textEncoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { v := newVisitor(w) v.visitBlockSlice(bs) length, err := v.b.Flush() return length, err } // WriteInlines writes an inline slice to the writer func (*textEncoder) WriteInlines(w io.Writer, is *ast.InlineSlice) (int, error) { v := newVisitor(w) ast.Walk(v, is) length, err := v.b.Flush() return length, err } // visitor writes the abstract syntax tree to an io.Writer. type visitor struct { b encoder.EncWriter inlinePos int } func newVisitor(w io.Writer) *visitor { return &visitor{b: encoder.NewEncWriter(w)} } func (v *visitor) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.BlockSlice: v.visitBlockSlice(n) case *ast.InlineSlice: for i, in := range *n { v.inlinePos = i ast.Walk(v, in) } v.inlinePos = 0 return nil case *ast.VerbatimNode: v.visitVerbatim(n) return nil case *ast.RegionNode: v.visitBlockSlice(&n.Blocks) if len(n.Inlines) > 0 { |
︙ | ︙ | |||
219 220 221 222 223 224 225 | func (v *visitor) visitBlockSlice(bs *ast.BlockSlice) { for i, bn := range *bs { v.writePosChar(i, '\n') ast.Walk(v, bn) } } | < < < < < < < < | 219 220 221 222 223 224 225 226 227 228 229 230 | func (v *visitor) visitBlockSlice(bs *ast.BlockSlice) { for i, bn := range *bs { v.writePosChar(i, '\n') ast.Walk(v, bn) } } func (v *visitor) writePosChar(pos int, ch byte) { if pos > 0 { v.b.WriteByte(ch) } } |
Changes to encoder/zjsonenc/zjsonenc.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 | import ( "fmt" "io" "strconv" "zettelstore.de/c/api" | < | > | | < < | > | < | | | | | | | | | > > | > | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | import ( "fmt" "io" "strconv" "zettelstore.de/c/api" "zettelstore.de/c/zjson" "zettelstore.de/z/ast" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" "zettelstore.de/z/strfun" ) func init() { encoder.Register(api.EncoderZJSON, encoder.Info{ Create: func(env *encoder.Environment) encoder.Encoder { return &jsonDetailEncoder{env: env} }, }) } type jsonDetailEncoder struct { env *encoder.Environment } // WriteZettel writes the encoded zettel to the writer. func (je *jsonDetailEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) { v := newDetailVisitor(w, je) v.b.WriteString(`{"meta":`) v.writeMeta(zn.InhMeta, evalMeta) v.b.WriteString(`,"content":`) ast.Walk(v, &zn.Ast) v.b.WriteByte('}') length, err := v.b.Flush() return length, err } // WriteMeta encodes meta data as JSON. func (je *jsonDetailEncoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) { v := newDetailVisitor(w, je) v.writeMeta(m, evalMeta) length, err := v.b.Flush() return length, err } func (je *jsonDetailEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return je.WriteBlocks(w, &zn.Ast) } // WriteBlocks writes a block slice to the writer func (je *jsonDetailEncoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { v := newDetailVisitor(w, je) ast.Walk(v, bs) length, err := v.b.Flush() return length, err } // WriteInlines writes an inline slice to the writer func (je *jsonDetailEncoder) WriteInlines(w io.Writer, is *ast.InlineSlice) (int, error) { v := newDetailVisitor(w, je) ast.Walk(v, is) length, err := v.b.Flush() return length, err } // visitor writes the abstract syntax tree to an io.Writer. type visitor struct { b encoder.EncWriter env *encoder.Environment inVerse bool // Visiting a verse block: save spaces in ZJSON object } func newDetailVisitor(w io.Writer, je *jsonDetailEncoder) *visitor { return &visitor{b: encoder.NewEncWriter(w), env: je.env} } func (v *visitor) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.BlockSlice: v.visitBlockSlice(n) return nil case *ast.InlineSlice: |
︙ | ︙ | |||
190 191 192 193 194 195 196 | v.b.WriteByte('}') return nil } var mapVerbatimKind = map[ast.VerbatimKind]string{ ast.VerbatimZettel: zjson.TypeVerbatimZettel, ast.VerbatimProg: zjson.TypeVerbatimCode, | < < | 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | v.b.WriteByte('}') return nil } var mapVerbatimKind = map[ast.VerbatimKind]string{ ast.VerbatimZettel: zjson.TypeVerbatimZettel, ast.VerbatimProg: zjson.TypeVerbatimCode, ast.VerbatimComment: zjson.TypeVerbatimComment, ast.VerbatimHTML: zjson.TypeVerbatimHTML, } func (v *visitor) visitVerbatim(vn *ast.VerbatimNode) { kind, ok := mapVerbatimKind[vn.Kind] if !ok { |
︙ | ︙ | |||
436 437 438 439 440 441 442 | var mapLiteralKind = map[ast.LiteralKind]string{ ast.LiteralZettel: zjson.TypeLiteralZettel, ast.LiteralProg: zjson.TypeLiteralCode, ast.LiteralInput: zjson.TypeLiteralInput, ast.LiteralOutput: zjson.TypeLiteralOutput, ast.LiteralComment: zjson.TypeLiteralComment, ast.LiteralHTML: zjson.TypeLiteralHTML, | < | 435 436 437 438 439 440 441 442 443 444 445 446 447 448 | var mapLiteralKind = map[ast.LiteralKind]string{ ast.LiteralZettel: zjson.TypeLiteralZettel, ast.LiteralProg: zjson.TypeLiteralCode, ast.LiteralInput: zjson.TypeLiteralInput, ast.LiteralOutput: zjson.TypeLiteralOutput, ast.LiteralComment: zjson.TypeLiteralComment, ast.LiteralHTML: zjson.TypeLiteralHTML, } func (v *visitor) visitBlockSlice(bs *ast.BlockSlice) { v.b.WriteByte('[') for i, bn := range *bs { v.writeComma(i) ast.Walk(v, bn) |
︙ | ︙ | |||
458 459 460 461 462 463 464 | v.writeComma(i) ast.Walk(v, in) } v.b.WriteByte(']') } // visitAttributes write JSON attributes | | | 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 | v.writeComma(i) ast.Walk(v, in) } v.b.WriteByte(']') } // visitAttributes write JSON attributes func (v *visitor) visitAttributes(a zjson.Attributes) { if a.IsEmpty() { return } v.writeContentStart(zjson.NameAttribute) for i, k := range a.Keys() { if i > 0 { |
︙ | ︙ |
Changes to encoder/zmkenc/zmkenc.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 | package zmkenc import ( "fmt" "io" "zettelstore.de/c/api" | | | > > < < | | | | | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | package zmkenc import ( "fmt" "io" "zettelstore.de/c/api" "zettelstore.de/c/zjson" "zettelstore.de/z/ast" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" "zettelstore.de/z/strfun" ) func init() { encoder.Register(api.EncoderZmk, encoder.Info{ Create: func(*encoder.Environment) encoder.Encoder { return &zmkEncoder{} }, }) } type zmkEncoder struct{} // WriteZettel writes the encoded zettel to the writer. func (ze *zmkEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) { v := newVisitor(w, ze) v.acceptMeta(zn.InhMeta, evalMeta) if zn.InhMeta.YamlSep { v.b.WriteString("---\n") } else { v.b.WriteByte('\n') } ast.Walk(v, &zn.Ast) length, err := v.b.Flush() return length, err } // WriteMeta encodes meta data as zmk. func (ze *zmkEncoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) { v := newVisitor(w, ze) v.acceptMeta(m, evalMeta) length, err := v.b.Flush() return length, err } func (v *visitor) acceptMeta(m *meta.Meta, evalMeta encoder.EvalMetaFunc) { for _, p := range m.ComputedPairs() { |
︙ | ︙ | |||
68 69 70 71 72 73 74 | } func (ze *zmkEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return ze.WriteBlocks(w, &zn.Ast) } // WriteBlocks writes the content of a block slice to the writer. | | | | | > | | > > > | 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | } func (ze *zmkEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return ze.WriteBlocks(w, &zn.Ast) } // WriteBlocks writes the content of a block slice to the writer. func (ze *zmkEncoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { v := newVisitor(w, ze) ast.Walk(v, bs) length, err := v.b.Flush() return length, err } // WriteInlines writes an inline slice to the writer func (ze *zmkEncoder) WriteInlines(w io.Writer, is *ast.InlineSlice) (int, error) { v := newVisitor(w, ze) ast.Walk(v, is) length, err := v.b.Flush() return length, err } // visitor writes the abstract syntax tree to an io.Writer. type visitor struct { b encoder.EncWriter prefix []byte enc *zmkEncoder inVerse bool inlinePos int } func newVisitor(w io.Writer, enc *zmkEncoder) *visitor { return &visitor{ b: encoder.NewEncWriter(w), enc: enc, } } func (v *visitor) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.BlockSlice: v.visitBlockSlice(n) case *ast.InlineSlice: |
︙ | ︙ | |||
178 179 180 181 182 183 184 | } var mapVerbatimKind = map[ast.VerbatimKind]string{ ast.VerbatimZettel: "@@@", ast.VerbatimComment: "%%%", ast.VerbatimHTML: "@@@", // Attribute is set to {="html"} ast.VerbatimProg: "```", | < < | 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | } var mapVerbatimKind = map[ast.VerbatimKind]string{ ast.VerbatimZettel: "@@@", ast.VerbatimComment: "%%%", ast.VerbatimHTML: "@@@", // Attribute is set to {="html"} ast.VerbatimProg: "```", } func (v *visitor) visitVerbatim(vn *ast.VerbatimNode) { kind, ok := mapVerbatimKind[vn.Kind] if !ok { panic(fmt.Sprintf("Unknown verbatim kind %d", vn.Kind)) } |
︙ | ︙ | |||
456 457 458 459 460 461 462 | func (v *visitor) visitLiteral(ln *ast.LiteralNode) { switch ln.Kind { case ast.LiteralZettel: v.writeLiteral('@', ln.Attrs, ln.Content) case ast.LiteralProg: v.writeLiteral('`', ln.Attrs, ln.Content) | < < < | < < | | | | 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 | func (v *visitor) visitLiteral(ln *ast.LiteralNode) { switch ln.Kind { case ast.LiteralZettel: v.writeLiteral('@', ln.Attrs, ln.Content) case ast.LiteralProg: v.writeLiteral('`', ln.Attrs, ln.Content) case ast.LiteralInput: v.writeLiteral('\'', ln.Attrs, ln.Content) case ast.LiteralOutput: v.writeLiteral('=', ln.Attrs, ln.Content) case ast.LiteralComment: if v.inlinePos > 0 { v.b.WriteByte(' ') } v.b.WriteString("%% ") v.b.Write(ln.Content) case ast.LiteralHTML: v.writeLiteral('x', syntaxToHTML(ln.Attrs), ln.Content) default: panic(fmt.Sprintf("Unknown literal kind %v", ln.Kind)) } } func (v *visitor) writeLiteral(code byte, attrs zjson.Attributes, content []byte) { v.b.WriteBytes(code, code) v.writeEscaped(string(content), code) v.b.WriteBytes(code, code) v.visitAttributes(attrs) } // visitAttributes write HTML attributes func (v *visitor) visitAttributes(a zjson.Attributes) { if a.IsEmpty() { return } v.b.WriteByte('{') for i, k := range a.Keys() { if i > 0 { v.b.WriteByte(' ') |
︙ | ︙ | |||
520 521 522 523 524 525 526 | v.b.WriteBytes('\\', b) last = i + 1 } } v.b.WriteString(s[last:]) } | | | 517 518 519 520 521 522 523 524 525 526 | v.b.WriteBytes('\\', b) last = i + 1 } } v.b.WriteString(s[last:]) } func syntaxToHTML(a zjson.Attributes) zjson.Attributes { return a.Clone().Set("", api.ValueSyntaxHTML).Remove(api.KeySyntax) } |
Changes to evaluator/evaluator.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 | "context" "errors" "fmt" "strconv" "strings" "zettelstore.de/c/api" | | < > > > > > > > > > > | | | | | > > > > > | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | "context" "errors" "fmt" "strconv" "strings" "zettelstore.de/c/api" "zettelstore.de/c/zjson" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" "zettelstore.de/z/parser" "zettelstore.de/z/parser/cleaner" ) // Environment contains values to control the evaluation. type Environment struct { GetTagRef func(string) *ast.Reference GetHostedRef func(string) *ast.Reference GetFoundRef func(zid id.Zid, fragment string) *ast.Reference GetImageMaterial func(zettel domain.Zettel, syntax string) ast.InlineEmbedNode } // Port contains all methods to retrieve zettel (or part of it) to evaluate a zettel. type Port interface { GetMeta(context.Context, id.Zid) (*meta.Meta, error) GetZettel(context.Context, id.Zid) (domain.Zettel, error) } var emptyEnv Environment // EvaluateZettel evaluates the given zettel in the given context, with the // given ports, and the given environment. func EvaluateZettel(ctx context.Context, port Port, env *Environment, rtConfig config.Config, zn *ast.ZettelNode) { if zn.Syntax == api.ValueSyntaxNone { // AST is empty, evaluate to a description list of metadata. zn.Ast = evaluateMetadata(zn.Meta) return } evaluateNode(ctx, port, env, rtConfig, &zn.Ast) cleaner.CleanBlockSlice(&zn.Ast) } // EvaluateInline evaluates the given inline list in the given context, with // the given ports, and the given environment. func EvaluateInline(ctx context.Context, port Port, env *Environment, rtConfig config.Config, is *ast.InlineSlice) { evaluateNode(ctx, port, env, rtConfig, is) cleaner.CleanInlineSlice(is) } func evaluateNode(ctx context.Context, port Port, env *Environment, rtConfig config.Config, n ast.Node) { if env == nil { env = &emptyEnv } e := evaluator{ ctx: ctx, port: port, env: env, rtConfig: rtConfig, transcludeMax: rtConfig.GetMaxTransclusions(), transcludeCount: 0, costMap: map[id.Zid]transcludeCost{}, embedMap: map[string]ast.InlineSlice{}, marker: &ast.ZettelNode{}, } ast.Walk(&e, n) } type evaluator struct { ctx context.Context port Port env *Environment rtConfig config.Config transcludeMax int transcludeCount int costMap map[id.Zid]transcludeCost marker *ast.ZettelNode embedMap map[string]ast.InlineSlice } |
︙ | ︙ | |||
136 137 138 139 140 141 142 | if i+1 < len(bns) { newIns = append(newIns, bns[i+1:]...) } return newIns } func (e *evaluator) evalVerbatimNode(vn *ast.VerbatimNode) ast.BlockNode { | < | < < < < < < | | < < | | | 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | if i+1 < len(bns) { newIns = append(newIns, bns[i+1:]...) } return newIns } func (e *evaluator) evalVerbatimNode(vn *ast.VerbatimNode) ast.BlockNode { if vn.Kind != ast.VerbatimZettel { return vn } m := meta.New(id.Invalid) m.Set(api.KeySyntax, getSyntax(vn.Attrs, api.ValueSyntaxDraw)) zettel := domain.Zettel{ Meta: m, Content: domain.NewContent(vn.Content), } e.transcludeCount++ zn := e.evaluateEmbeddedZettel(zettel) return &zn.Ast } func getSyntax(a zjson.Attributes, defSyntax string) string { if a != nil { if val, ok := a.Get(api.KeySyntax); ok { return val } if val, ok := a.Get(""); ok { return val } |
︙ | ︙ | |||
240 241 242 243 244 245 246 247 248 249 250 251 252 253 | func makeBlockNode(in ast.InlineNode) ast.BlockNode { return ast.CreateParaNode(in) } func (e *evaluator) visitInlineSlice(is *ast.InlineSlice) { for i := 0; i < len(*is); i++ { in := (*is)[i] ast.Walk(e, in) switch n := in.(type) { case *ast.LinkNode: (*is)[i] = e.evalLinkNode(n) case *ast.EmbedRefNode: i += embedNode(is, i, e.evalEmbedRefNode(n)) case *ast.LiteralNode: i += embedNode(is, i, e.evalLiteralNode(n)) } | > > | 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 | func makeBlockNode(in ast.InlineNode) ast.BlockNode { return ast.CreateParaNode(in) } func (e *evaluator) visitInlineSlice(is *ast.InlineSlice) { for i := 0; i < len(*is); i++ { in := (*is)[i] ast.Walk(e, in) switch n := in.(type) { case *ast.TagNode: (*is)[i] = e.visitTag(n) case *ast.LinkNode: (*is)[i] = e.evalLinkNode(n) case *ast.EmbedRefNode: i += embedNode(is, i, e.evalEmbedRefNode(n)) case *ast.LiteralNode: i += embedNode(is, i, e.evalLiteralNode(n)) } |
︙ | ︙ | |||
276 277 278 279 280 281 282 283 | newIns = append(newIns, replaceIns...) } if i+1 < len(ins) { newIns = append(newIns, ins[i+1:]...) } return newIns } | | | > > > | | > > > > > > > > > > > > > > | > > | > | 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 | newIns = append(newIns, replaceIns...) } if i+1 < len(ins) { newIns = append(newIns, ins[i+1:]...) } return newIns } func (e *evaluator) visitTag(tn *ast.TagNode) ast.InlineNode { if gtr := e.env.GetTagRef; gtr != nil { fullTag := "#" + tn.Tag return &ast.LinkNode{ Ref: e.env.GetTagRef(fullTag), Inlines: ast.CreateInlineSliceFromWords(fullTag), } } return tn } func (e *evaluator) evalLinkNode(ln *ast.LinkNode) ast.InlineNode { ref := ln.Ref if ref == nil { return ln } if ref.State == ast.RefStateBased { if ghr := e.env.GetHostedRef; ghr != nil { ln.Ref = ghr(ref.Value[1:]) } return ln } if ref.State != ast.RefStateZettel { return ln } zid := mustParseZid(ref) _, err := e.port.GetMeta(box.NoEnrichContext(e.ctx), zid) if errors.Is(err, &box.ErrNotAllowed{}) { return &ast.FormatNode{ Kind: ast.FormatSpan, Attrs: ln.Attrs, Inlines: getLinkInline(ln), } } else if err != nil { ln.Ref.State = ast.RefStateBroken return ln } if gfr := e.env.GetFoundRef; gfr != nil { ln.Inlines = getLinkInline(ln) ln.Ref = gfr(zid, ref.URL.EscapedFragment()) } return ln } func getLinkInline(ln *ast.LinkNode) ast.InlineSlice { if ln.Inlines != nil { return ln.Inlines } |
︙ | ︙ | |||
323 324 325 326 327 328 329 | } switch ref.State { case ast.RefStateZettel: // Only zettel references will be evaluated. case ast.RefStateInvalid, ast.RefStateBroken: e.transcludeCount++ | | | < | | 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 | } switch ref.State { case ast.RefStateZettel: // Only zettel references will be evaluated. case ast.RefStateInvalid, ast.RefStateBroken: e.transcludeCount++ return e.createInlineErrorImage(en) case ast.RefStateSelf: e.transcludeCount++ return createInlineErrorText(ref, "Self", "embed", "reference:") case ast.RefStateFound, ast.RefStateHosted, ast.RefStateBased, ast.RefStateExternal: return en default: panic(fmt.Sprintf("Unknown state %v for reference %v", ref.State, ref)) } zid := mustParseZid(ref) zettel, err := e.port.GetZettel(box.NoEnrichContext(e.ctx), zid) if err != nil { e.transcludeCount++ return e.createInlineErrorImage(en) } if syntax := e.getSyntax(zettel.Meta); parser.IsImageFormat(syntax) { return e.embedImage(en, zettel) } else if !parser.IsTextParser(syntax) { // Not embeddable. e.transcludeCount++ return createInlineErrorText(ref, "Not", "embeddable (syntax="+syntax+"):") } cost, ok := e.costMap[zid] |
︙ | ︙ | |||
373 374 375 376 377 378 379 | // Search for text to be embedded. result = findInlineSlice(&zn.Ast, ref.URL.Fragment) e.embedMap[ref.Value] = result } if len(result) == 0 { return &ast.LiteralNode{ Kind: ast.LiteralComment, | < | 399 400 401 402 403 404 405 406 407 408 409 410 411 412 | // Search for text to be embedded. result = findInlineSlice(&zn.Ast, ref.URL.Fragment) e.embedMap[ref.Value] = result } if len(result) == 0 { return &ast.LiteralNode{ Kind: ast.LiteralComment, Content: append([]byte("Nothing to transclude: "), ref.String()...), } } if ec := cost.ec; ec > 0 { e.transcludeCount += cost.ec } |
︙ | ︙ | |||
397 398 399 400 401 402 403 | } func (e *evaluator) evalLiteralNode(ln *ast.LiteralNode) ast.InlineNode { if ln.Kind != ast.LiteralZettel { return ln } e.transcludeCount++ | | < > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 | } func (e *evaluator) evalLiteralNode(ln *ast.LiteralNode) ast.InlineNode { if ln.Kind != ast.LiteralZettel { return ln } e.transcludeCount++ result := e.evaluateEmbeddedInline(ln.Content, getSyntax(ln.Attrs, api.ValueSyntaxDraw)) if len(result) == 0 { return &ast.LiteralNode{ Kind: ast.LiteralComment, Content: []byte("Nothing to transclude"), } } return &result } func (e *evaluator) getSyntax(m *meta.Meta) string { if cfg := e.rtConfig; cfg != nil { return config.GetSyntax(m, cfg) } return m.GetDefault(api.KeySyntax, "") } func (e *evaluator) getTitle(m *meta.Meta) string { if cfg := e.rtConfig; cfg != nil { return config.GetTitle(m, cfg) } return m.GetDefault(api.KeyTitle, "") } func (e *evaluator) createInlineErrorImage(en *ast.EmbedRefNode) ast.InlineEmbedNode { errorZid := id.EmojiZid if gim := e.env.GetImageMaterial; gim != nil { zettel, err := e.port.GetZettel(box.NoEnrichContext(e.ctx), errorZid) if err != nil { panic(err) } inlines := en.Inlines if len(inlines) == 0 { if title := e.getTitle(zettel.Meta); title != "" { inlines = parser.ParseMetadata(title) } } syntax := e.getSyntax(zettel.Meta) return enrichImageNode(gim(zettel, syntax), inlines, en.Attrs, syntax) } en.Ref = ast.ParseReference(errorZid.String()) if len(en.Inlines) == 0 { en.Inlines = parser.ParseMetadata("Error placeholder") } return en } func (e *evaluator) embedImage(en *ast.EmbedRefNode, zettel domain.Zettel) ast.InlineEmbedNode { syntax := e.getSyntax(zettel.Meta) if gim := e.env.GetImageMaterial; gim != nil { return enrichImageNode(gim(zettel, syntax), en.Inlines, en.Attrs, syntax) } en.Syntax = syntax return en } func enrichImageNode(result ast.InlineEmbedNode, in ast.InlineSlice, a zjson.Attributes, syntax string) ast.InlineEmbedNode { switch er := result.(type) { case *ast.EmbedRefNode: er.Inlines = in er.Attrs = a er.Syntax = syntax case *ast.EmbedBLOBNode: er.Inlines = in er.Attrs = a } return result } func createInlineErrorText(ref *ast.Reference, msgWords ...string) ast.InlineNode { text := strings.Join(msgWords, " ") if ref != nil { text += ": " + ref.String() + "." } ln := &ast.LiteralNode{ |
︙ | ︙ | |||
494 495 496 497 498 499 500 | func (fs *fragmentSearcher) Visit(node ast.Node) ast.Visitor { if len(fs.result) > 0 { return nil } switch n := node.(type) { case *ast.BlockSlice: | > > > > > | > > > > > | > > > > > > > > > < < < < < < < < < < < < < < < < < < < < < < < < < < < | 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 | func (fs *fragmentSearcher) Visit(node ast.Node) ast.Visitor { if len(fs.result) > 0 { return nil } switch n := node.(type) { case *ast.BlockSlice: for i, bn := range *n { if hn, ok := bn.(*ast.HeadingNode); ok && hn.Fragment == fs.fragment { fs.result = (*n)[i+1:].FirstParagraphInlines() return nil } ast.Walk(fs, bn) } case *ast.InlineSlice: for i, in := range *n { if mn, ok := in.(*ast.MarkNode); ok && mn.Fragment == fs.fragment { ris := skipSpaceNodes((*n)[i+1:]) if len(mn.Inlines) > 0 { fs.result = append(ast.InlineSlice{}, mn.Inlines...) fs.result = append(fs.result, &ast.SpaceNode{Lexeme: " "}) fs.result = append(fs.result, ris...) } else { fs.result = ris } return nil } ast.Walk(fs, in) } default: return fs } return nil } func skipSpaceNodes(ins ast.InlineSlice) ast.InlineSlice { for i, in := range ins { switch in.(type) { case *ast.SpaceNode: case *ast.BreakNode: default: return ins[i:] } } return nil } |
Changes to go.mod.
1 2 | module zettelstore.de/z | | | | < | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | module zettelstore.de/z go 1.17 require ( github.com/fsnotify/fsnotify v1.5.1 github.com/pascaldekloe/jwt v1.10.0 github.com/yuin/goldmark v1.4.8 golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 golang.org/x/text v0.3.7 zettelstore.de/c v0.0.0-20220308145137-122c412c3a99 ) require golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect |
Changes to go.sum.
|
| | | | | < < | | | | > > > | | | | > | > > | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= 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.4.8 h1:zHPiabbIRssZOI0MAzJDHsyvG4MXCGqVaMOwR+HeoQQ= github.com/yuin/goldmark v1.4.8/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= zettelstore.de/c v0.0.0-20220308145137-122c412c3a99 h1:0WknFoNBwtwD1pqUq4XPGtvkqyE0nN8tJJAPTCjHt/8= zettelstore.de/c v0.0.0-20220308145137-122c412c3a99/go.mod h1:Hx/qzHCaQ8zzXEzBglBj/2aGkQpBQG81/4XztCIGJ84= |
Changes to input/input.go.
1 | //----------------------------------------------------------------------------- | | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- // Package input provides an abstraction for data to be read. |
︙ | ︙ | |||
187 188 189 190 191 192 193 | } inp.Next() } } func (inp *Input) scanEntityNamed(pos int) (string, bool) { for { switch inp.Ch { | | < < < < < < < < < | < < < | < < < | < < < | 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | } inp.Next() } } func (inp *Input) scanEntityNamed(pos int) (string, bool) { for { switch inp.Ch { case EOS, '\n', '\r': return "", false case ';': inp.Next() es := string(inp.Src[pos:inp.Pos]) ues := html.UnescapeString(es) if es == ues { return "", false } return ues, true } inp.Next() } } |
Changes to input/input_test.go.
1 | //----------------------------------------------------------------------------- | | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- // Package input_test provides some unit-tests for reading data. |
︙ | ︙ | |||
63 64 65 66 67 68 69 | continue } if tc.exp != got { t.Errorf("ID=%d, text=%q: expected %q, but got %q", id, tc.text, tc.exp, got) } } } | < < < < < < < < < < < < < | 63 64 65 66 67 68 69 | continue } if tc.exp != got { t.Errorf("ID=%d, text=%q: expected %q, but got %q", id, tc.text, tc.exp, got) } } } |
Changes to input/runes.go.
1 | //----------------------------------------------------------------------------- | | | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- // Package input provides an abstraction for data to be read. package input // IsSpace returns true if rune is a whitespace. func IsSpace(ch rune) bool { switch ch { case ' ', '\t': return true } return false } |
Changes to kernel/impl/cfg.go.
︙ | ︙ | |||
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | // Predefined Metadata keys for runtime configuration // See: https://zettelstore.de/manual/h/00001004020000 const ( keyDefaultCopyright = "default-copyright" keyDefaultLang = "default-lang" keyDefaultLicense = "default-license" keyDefaultSyntax = "default-syntax" keyDefaultTitle = "default-title" keyDefaultVisibility = "default-visibility" keyExpertMode = "expert-mode" keyFooterHTML = "footer-html" keyHomeZettel = "home-zettel" keyMarkerExternal = "marker-external" keyMaxTransclusions = "max-transclusions" keySiteName = "site-name" keyYAMLHeader = "yaml-header" keyZettelFileSyntax = "zettel-file-syntax" ) func (cs *configService) Initialize(logger *logger.Logger) { cs.logger = logger cs.descr = descriptionMap{ keyDefaultCopyright: {"Default copyright", parseString, true}, keyDefaultLang: {"Default language", parseString, true}, keyDefaultLicense: {"Default license", parseString, true}, keyDefaultSyntax: {"Default syntax", parseString, true}, keyDefaultTitle: {"Default title", parseString, true}, keyDefaultVisibility: { "Default zettel visibility", func(val string) interface{} { vis := meta.GetVisibility(val) if vis == meta.VisibilityUnknown { return nil } return vis }, true, }, keyExpertMode: {"Expert mode", parseBool, true}, keyFooterHTML: {"Footer HTML", parseString, true}, keyHomeZettel: {"Home zettel", parseZid, true}, keyMarkerExternal: {"Marker external URL", parseString, true}, | > > | > | | 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | // Predefined Metadata keys for runtime configuration // See: https://zettelstore.de/manual/h/00001004020000 const ( keyDefaultCopyright = "default-copyright" keyDefaultLang = "default-lang" keyDefaultLicense = "default-license" keyDefaultRole = "default-role" keyDefaultSyntax = "default-syntax" keyDefaultTitle = "default-title" keyDefaultVisibility = "default-visibility" keyExpertMode = "expert-mode" keyFooterHTML = "footer-html" keyHomeZettel = "home-zettel" keyMarkerExternal = "marker-external" keyMaxTransclusions = "max-transclusions" keySiteName = "site-name" keyYAMLHeader = "yaml-header" keyZettelFileSyntax = "zettel-file-syntax" ) func (cs *configService) Initialize(logger *logger.Logger) { cs.logger = logger cs.descr = descriptionMap{ keyDefaultCopyright: {"Default copyright", parseString, true}, keyDefaultLang: {"Default language", parseString, true}, keyDefaultLicense: {"Default license", parseString, true}, keyDefaultRole: {"Default role", parseString, true}, keyDefaultSyntax: {"Default syntax", parseString, true}, keyDefaultTitle: {"Default title", parseString, true}, keyDefaultVisibility: { "Default zettel visibility", func(val string) interface{} { vis := meta.GetVisibility(val) if vis == meta.VisibilityUnknown { return nil } return vis }, true, }, keyExpertMode: {"Expert mode", parseBool, true}, keyFooterHTML: {"Footer HTML", parseString, true}, keyHomeZettel: {"Home zettel", parseZid, true}, keyMarkerExternal: {"Marker external URL", parseString, true}, keyMaxTransclusions: {"Maximum transclusions", parseInt, true}, keySiteName: {"Site name", parseString, true}, keyYAMLHeader: {"YAML header", parseBool, true}, keyZettelFileSyntax: { "Zettel file syntax", func(val string) interface{} { return strings.Fields(val) }, true, }, kernel.ConfigSimpleMode: {"Simple mode", cs.noFrozen(parseBool), true}, } cs.next = interfaceMap{ keyDefaultCopyright: "", keyDefaultLang: api.ValueLangEN, keyDefaultLicense: "", keyDefaultRole: api.ValueRoleZettel, keyDefaultSyntax: api.ValueSyntaxZmk, keyDefaultTitle: "Untitled", keyDefaultVisibility: meta.VisibilityLogin, keyExpertMode: false, keyFooterHTML: "", keyHomeZettel: id.DefaultHomeZid, keyMarkerExternal: "➚", keyMaxTransclusions: 1024, keySiteName: "Zettelstore", keyYAMLHeader: false, keyZettelFileSyntax: nil, kernel.ConfigSimpleMode: false, } } func (cs *configService) GetLogger() *logger.Logger { return cs.logger } |
︙ | ︙ | |||
182 183 184 185 186 187 188 189 190 191 192 193 194 195 | } } var defaultKeys = map[string]string{ api.KeyCopyright: keyDefaultCopyright, api.KeyLang: keyDefaultLang, api.KeyLicense: keyDefaultLicense, api.KeySyntax: keyDefaultSyntax, api.KeyTitle: keyDefaultTitle, api.KeyVisibility: keyDefaultVisibility, } // AddDefaultValues enriches the given meta data with its default values. func (cfg *myConfig) AddDefaultValues(m *meta.Meta) *meta.Meta { | > | 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | } } var defaultKeys = map[string]string{ api.KeyCopyright: keyDefaultCopyright, api.KeyLang: keyDefaultLang, api.KeyLicense: keyDefaultLicense, api.KeyRole: keyDefaultRole, api.KeySyntax: keyDefaultSyntax, api.KeyTitle: keyDefaultTitle, api.KeyVisibility: keyDefaultVisibility, } // AddDefaultValues enriches the given meta data with its default values. func (cfg *myConfig) AddDefaultValues(m *meta.Meta) *meta.Meta { |
︙ | ︙ | |||
224 225 226 227 228 229 230 231 232 233 234 235 236 237 | cfg.mx.RUnlock() return val } // GetDefaultTitle returns the current value of the "default-title" key. func (cfg *myConfig) GetDefaultTitle() string { return cfg.getString(keyDefaultTitle) } // GetDefaultSyntax returns the current value of the "default-syntax" key. func (cfg *myConfig) GetDefaultSyntax() string { return cfg.getString(keyDefaultSyntax) } // GetDefaultLang returns the current value of the "default-lang" key. func (cfg *myConfig) GetDefaultLang() string { return cfg.getString(keyDefaultLang) } // GetSiteName returns the current value of the "site-name" key. | > > > | 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 | cfg.mx.RUnlock() return val } // GetDefaultTitle returns the current value of the "default-title" key. func (cfg *myConfig) GetDefaultTitle() string { return cfg.getString(keyDefaultTitle) } // GetDefaultRole returns the current value of the "default-role" key. func (cfg *myConfig) GetDefaultRole() string { return cfg.getString(keyDefaultRole) } // GetDefaultSyntax returns the current value of the "default-syntax" key. func (cfg *myConfig) GetDefaultSyntax() string { return cfg.getString(keyDefaultSyntax) } // GetDefaultLang returns the current value of the "default-lang" key. func (cfg *myConfig) GetDefaultLang() string { return cfg.getString(keyDefaultLang) } // GetSiteName returns the current value of the "site-name" key. |
︙ | ︙ |
Changes to kernel/impl/cmd.go.
1 | //----------------------------------------------------------------------------- | | | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | //----------------------------------------------------------------------------- // Copyright (c) 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 impl import ( "fmt" "io" "os" "runtime/metrics" "sort" "strconv" "strings" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/strfun" ) type cmdSession struct { w io.Writer |
︙ | ︙ | |||
195 196 197 198 199 200 201 | }, "start": {"start service", cmdStart}, "stat": {"show service statistics", cmdStat}, "stop": {"stop service", cmdStop}, } func cmdHelp(sess *cmdSession, _ string, _ []string) bool { | | > > > > > > > | 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | }, "start": {"start service", cmdStart}, "stat": {"show service statistics", cmdStat}, "stop": {"stop service", cmdStop}, } func cmdHelp(sess *cmdSession, _ string, _ []string) bool { cmds := make([]string, 0, len(commands)) for key := range commands { if key == "" { continue } cmds = append(cmds, key) } sort.Strings(cmds) table := [][]string{{"Command", "Description"}} for _, cmd := range cmds { table = append(table, []string{cmd, commands[cmd].Text}) } sess.printTable(table) return true } |
︙ | ︙ | |||
554 555 556 557 558 559 560 | table = append(table, []string{env[:pos], env[pos+1:]}) } } sess.printTable(table) return true } | | > > > > > > > | 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 | table = append(table, []string{env[:pos], env[pos+1:]}) } } sess.printTable(table) return true } func sortedServiceNames(sess *cmdSession) []string { names := make([]string, 0, len(sess.kern.srvNames)) for name := range sess.kern.srvNames { names = append(names, name) } sort.Strings(names) return names } func getService(sess *cmdSession, name string) (serviceData, bool) { srvD, found := sess.kern.srvNames[name] if !found { sess.println("Unknown service", name) } return srvD, found } |
Changes to kernel/impl/config.go.
1 | //----------------------------------------------------------------------------- | | | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | //----------------------------------------------------------------------------- // 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 impl import ( "fmt" "sort" "strconv" "strings" "sync" "zettelstore.de/z/domain/id" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" ) type parseFunc func(string) interface{} type configDescription struct { |
︙ | ︙ | |||
51 52 53 54 55 56 57 | cur interfaceMap next interfaceMap } func (cfg *srvConfig) ConfigDescriptions() []serviceConfigDescription { cfg.mxConfig.RLock() defer cfg.mxConfig.RUnlock() | > | > > > | 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | cur interfaceMap next interfaceMap } func (cfg *srvConfig) ConfigDescriptions() []serviceConfigDescription { cfg.mxConfig.RLock() defer cfg.mxConfig.RUnlock() keys := make([]string, 0, len(cfg.descr)) for k := range cfg.descr { keys = append(keys, k) } sort.Strings(keys) result := make([]serviceConfigDescription, 0, len(keys)) for _, k := range keys { text := cfg.descr[k].text if strings.HasSuffix(k, "-") { text = text + " (list)" } result = append(result, serviceConfigDescription{Key: k, Descr: text}) |
︙ | ︙ | |||
220 221 222 223 224 225 226 | switch val[0] { case '0', 'f', 'F', 'n', 'N': return false } return true } | | | > | | | 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 | switch val[0] { case '0', 'f', 'F', 'n', 'N': return false } return true } func parseInt(val string) interface{} { i, err := strconv.Atoi(val) if err == nil { return i } return 0 } func parseZid(val string) interface{} { if zid, err := id.Parse(val); err == nil { return zid } return id.Invalid } |
Changes to kernel/impl/core.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 18 19 20 | package impl import ( "fmt" "net" "os" "runtime" "sync" "time" | > < | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | package impl import ( "fmt" "net" "os" "runtime" "sort" "sync" "time" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/strfun" ) type coreService struct { srvConfig |
︙ | ︙ | |||
95 96 97 98 99 100 101 | func (cs *coreService) Stop(*myKernel) { cs.started = false } func (cs *coreService) GetStatistics() []kernel.KeyValue { cs.mxRecover.RLock() defer cs.mxRecover.RUnlock() | > | > > > | 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | func (cs *coreService) Stop(*myKernel) { cs.started = false } func (cs *coreService) GetStatistics() []kernel.KeyValue { cs.mxRecover.RLock() defer cs.mxRecover.RUnlock() names := make([]string, 0, len(cs.mapRecover)) for n := range cs.mapRecover { names = append(names, n) } sort.Strings(names) result := make([]kernel.KeyValue, 0, 3*len(names)) for _, n := range names { ri := cs.mapRecover[n] result = append( result, kernel.KeyValue{ Key: fmt.Sprintf("Recover %q / Count", n), |
︙ | ︙ |
Changes to kernel/impl/web.go.
1 | //----------------------------------------------------------------------------- | | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // 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 impl |
︙ | ︙ | |||
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | type webService struct { srvConfig mxService sync.RWMutex srvw server.Server setupServer kernel.SetupWebServerFunc } func (ws *webService) Initialize(logger *logger.Logger) { ws.logger = logger ws.descr = descriptionMap{ kernel.WebListenAddress: { "Listen address", func(val string) interface{} { host, port, err := net.SplitHostPort(val) if err != nil { return nil } if _, err = net.LookupPort("tcp", port); err != nil { return nil } return net.JoinHostPort(host, port) }, true}, | > > > > > > > > > > < | 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | type webService struct { srvConfig mxService sync.RWMutex srvw server.Server setupServer kernel.SetupWebServerFunc } // Constants for web service keys. const ( WebSecureCookie = "secure" WebListenAddress = "listen" WebPersistentCookie = "persistent" WebTokenLifetimeAPI = "api-lifetime" WebTokenLifetimeHTML = "html-lifetime" WebURLPrefix = "prefix" ) func (ws *webService) Initialize(logger *logger.Logger) { ws.logger = logger ws.descr = descriptionMap{ kernel.WebListenAddress: { "Listen address", func(val string) interface{} { host, port, err := net.SplitHostPort(val) if err != nil { return nil } if _, err = net.LookupPort("tcp", port); err != nil { return nil } return net.JoinHostPort(host, port) }, true}, kernel.WebPersistentCookie: {"Persistent cookie", parseBool, true}, kernel.WebSecureCookie: {"Secure cookie", parseBool, true}, kernel.WebTokenLifetimeAPI: { "Token lifetime API", makeDurationParser(10*time.Minute, 0, 1*time.Hour), true, }, |
︙ | ︙ | |||
69 70 71 72 73 74 75 | return nil }, true, }, } ws.next = interfaceMap{ kernel.WebListenAddress: "127.0.0.1:23123", | < | 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | return nil }, true, }, } ws.next = interfaceMap{ kernel.WebListenAddress: "127.0.0.1:23123", kernel.WebPersistentCookie: false, kernel.WebSecureCookie: true, kernel.WebTokenLifetimeAPI: 1 * time.Hour, kernel.WebTokenLifetimeHTML: 10 * time.Minute, kernel.WebURLPrefix: "/", } } |
︙ | ︙ | |||
101 102 103 104 105 106 107 | func (ws *webService) GetLogger() *logger.Logger { return ws.logger } func (ws *webService) Start(kern *myKernel) error { listenAddr := ws.GetNextConfig(kernel.WebListenAddress).(string) urlPrefix := ws.GetNextConfig(kernel.WebURLPrefix).(string) persistentCookie := ws.GetNextConfig(kernel.WebPersistentCookie).(bool) secureCookie := ws.GetNextConfig(kernel.WebSecureCookie).(bool) | < < < | < | | 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | func (ws *webService) GetLogger() *logger.Logger { return ws.logger } func (ws *webService) Start(kern *myKernel) error { listenAddr := ws.GetNextConfig(kernel.WebListenAddress).(string) urlPrefix := ws.GetNextConfig(kernel.WebURLPrefix).(string) persistentCookie := ws.GetNextConfig(kernel.WebPersistentCookie).(bool) secureCookie := ws.GetNextConfig(kernel.WebSecureCookie).(bool) srvw := impl.New(ws.logger, listenAddr, urlPrefix, persistentCookie, secureCookie, kern.auth.manager) err := kern.web.setupServer(srvw, kern.box.manager, kern.auth.manager, kern.cfg.rtConfig) if err != nil { ws.logger.Fatal().Err(err).Msg("Unable to create") return err } if kern.core.GetNextConfig(kernel.CoreDebug).(bool) { srvw.SetDebug() |
︙ | ︙ |
Changes to kernel/kernel.go.
︙ | ︙ | |||
163 164 165 166 167 168 169 | BoxDirTypeSimple = "simple" ) // Constants for web service keys. const ( WebListenAddress = "listen" WebPersistentCookie = "persistent" | < | 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | BoxDirTypeSimple = "simple" ) // Constants for web service keys. const ( WebListenAddress = "listen" WebPersistentCookie = "persistent" WebSecureCookie = "secure" WebTokenLifetimeAPI = "api-lifetime" WebTokenLifetimeHTML = "html-lifetime" WebURLPrefix = "prefix" ) // KeyDescrValue is a triple of config data. |
︙ | ︙ |
Changes to parser/cleaner/cleaner.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 18 | // Package cleaner provides funxtions to clean up the parsed AST. package cleaner import ( "bytes" "strconv" "zettelstore.de/z/ast" | > | | | | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | // Package cleaner provides funxtions to clean up the parsed AST. package cleaner import ( "bytes" "strconv" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/strfun" ) // CleanBlockSlice cleans the given block list. func CleanBlockSlice(bs *ast.BlockSlice) { cleanNode(bs) } // CleanInlineSlice cleans the given inline list. func CleanInlineSlice(is *ast.InlineSlice) { cleanNode(is) } func cleanNode(n ast.Node) { cv := cleanVisitor{ textEnc: encoder.Create(api.EncoderText, nil), hasMark: false, doMark: false, } ast.Walk(&cv, n) if cv.hasMark { cv.doMark = true ast.Walk(&cv, n) } } type cleanVisitor struct { textEnc encoder.Encoder ids map[string]ast.Node hasMark bool doMark bool } func (cv *cleanVisitor) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { |
︙ | ︙ |
Changes to parser/draw/canvas.go.
︙ | ︙ | |||
26 27 28 29 30 31 32 | "sort" "unicode/utf8" ) // newCanvas returns a new Canvas, initialized from the provided data. If tabWidth is set to a non-negative // value, that value will be used to convert tabs to spaces within the grid. Creation of the Canvas // can fail if the diagram contains invalid UTF-8 sequences. | | > > > > > > > > | | 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | "sort" "unicode/utf8" ) // newCanvas returns a new Canvas, initialized from the provided data. If tabWidth is set to a non-negative // value, that value will be used to convert tabs to spaces within the grid. Creation of the Canvas // can fail if the diagram contains invalid UTF-8 sequences. func newCanvas(data []byte, tabWidth int) (*canvas, error) { c := &canvas{} lines := bytes.Split(data, []byte("\n")) c.siz.Y = len(lines) // Diagrams will often not be padded to a uniform width. To overcome this, we scan over // each line and figure out which is the longest. This becomes the width of the canvas. for i, line := range lines { if ok := utf8.Valid(line); !ok { return nil, fmt.Errorf("invalid UTF-8 encoding on line %d", i) } l, err := expandTabs(line, tabWidth) if err != nil { return nil, err } lines[i] = l if i1 := utf8.RuneCount(lines[i]); i1 > c.siz.X { c.siz.X = i1 } } c.grid = make([]char, c.siz.X*c.siz.Y) c.visited = make([]bool, c.siz.X*c.siz.Y) for y, line := range lines { |
︙ | ︙ | |||
62 63 64 65 66 67 68 69 70 71 72 73 74 75 | c.grid[y*c.siz.X+x] = ' ' } } c.findObjects() return c, nil } // canvas is the parsed source data. type canvas struct { // (0,0) is top left. grid []char visited []bool objs objects | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | c.grid[y*c.siz.X+x] = ' ' } } c.findObjects() return c, nil } // The expandTabs function pads tab characters to the specified width of spaces for the provided // line of input. We cannot simply pad based on byte-offset since our input is UTF-8 encoded. // Fortunately, we can assume that this function is called that the line contains only valid // UTF-8 sequences. We first decode the line rune-wise, and use individual runes to figure out // where we are within the line. When we encounter a tab character, we expand based on our rune // index. func expandTabs(line []byte, tabWidth int) ([]byte, error) { // Initial sizing of our output slice assumes no UTF-8 bytes or tabs, since this is often // the common case. out := make([]byte, 0, len(line)) // pos tracks our position in the input byte slice, while index tracks our position in the // resulting output slice. pos := 0 index := 0 for _, c := range line { if c == '\t' { // Loop over the remaining space count for this particular tabstop until // the next, replacing each position with a space. for s := tabWidth - (pos % tabWidth); s > 0; s-- { out = append(out, ' ') index++ } pos++ } else { // We need to know the byte length of the rune at this position so that we // can account for our tab expansion properly. So we first decode the rune // at this position to get its length in bytes, plop that rune back into our // output slice, and account accordingly. r, l := utf8.DecodeRune(line[pos:]) if r == utf8.RuneError { return nil, fmt.Errorf("invalid rune at byte offset %d; rune offset %d", pos, index) } enc := make([]byte, l) utf8.EncodeRune(enc, r) out = append(out, enc...) pos += l index++ } } return out, nil } // canvas is the parsed source data. type canvas struct { // (0,0) is top left. grid []char visited []bool objs objects |
︙ | ︙ | |||
85 86 87 88 89 90 91 | func (c *canvas) objects() objects { return c.objs } // size returns the visual dimensions of the Canvas. func (c *canvas) size() image.Point { return c.siz } // findObjects finds all objects (lines, polygons, and text) within the underlying grid. func (c *canvas) findObjects() { | | < < | < | | < | | < < < < | | | | | | | | | | | | | | | | < < | | < | < | | < | | | | | | | | | > > > | 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | func (c *canvas) objects() objects { return c.objs } // size returns the visual dimensions of the Canvas. func (c *canvas) size() image.Point { return c.siz } // findObjects finds all objects (lines, polygons, and text) within the underlying grid. func (c *canvas) findObjects() { p := point{} // Find any new paths by starting with a point that wasn't yet visited, beginning at the top // left of the grid. for y := 0; y < c.siz.Y; y++ { p.y = y for x := 0; x < c.siz.X; x++ { p.x = x if c.isVisited(p) { continue } if ch := c.at(p); ch.isPathStart() { // Found the start of a one or multiple connected paths. Traverse all // connecting points. This will generate multiple objects if multiple // paths (either open or closed) are found. c.visit(p) objs := c.scanPath([]point{p}) for _, obj := range objs { // For all points in all objects found, mark the points as visited. for _, p := range obj.Points() { c.visit(p) } } c.objs = append(c.objs, objs...) } } } // A second pass through the grid attempts to identify any text within the grid. for y := 0; y < c.siz.Y; y++ { p.y = y for x := 0; x < c.siz.X; x++ { p.x = x if c.isVisited(p) { continue } if ch := c.at(p); ch.isTextStart() { obj := c.scanText(p) // scanText will return nil if the text at this area is simply // setting options on a container object. if obj == nil { continue } for _, p := range obj.Points() { c.visit(p) } c.objs = append(c.objs, obj) } } } sort.Sort(c.objs) } // scanPath tries to complete a total path (for lines or polygons) starting with some partial path. // It recurses when it finds multiple unvisited outgoing paths. func (c *canvas) scanPath(points []point) objects { cur := points[len(points)-1] next := c.next(cur) |
︙ | ︙ | |||
210 211 212 213 214 215 216 | // Our caller must have called c.visit prior to calling this function. if !c.isVisited(pos) { panic(fmt.Errorf("internal error; revisiting %s", pos)) } var out []point | | | < < < | | | | | < < < < < < < < > > > > > > > > > > | 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 | // Our caller must have called c.visit prior to calling this function. if !c.isVisited(pos) { panic(fmt.Errorf("internal error; revisiting %s", pos)) } var out []point ch := c.at(pos) if ch.canHorizontal() { nextHorizontal := func(p point) { if !c.isVisited(p) && c.at(p).canHorizontal() { out = append(out, p) } } if c.canLeft(pos) { n := pos n.x-- nextHorizontal(n) } if c.canRight(pos) { n := pos n.x++ nextHorizontal(n) } } if ch.canVertical() { nextVertical := func(p point) { if !c.isVisited(p) && c.at(p).canVertical() { out = append(out, p) } } if c.canUp(pos) { n := pos n.y-- nextVertical(n) } if c.canDown(pos) { n := pos n.y++ nextVertical(n) } } if c.canDiagonal(pos) { nextDiagonal := func(from, to point) { if !c.isVisited(to) && c.at(to).canDiagonalFrom(c.at(from)) { out = append(out, to) } } if c.canUp(pos) { if c.canLeft(pos) { n := pos n.x-- n.y-- nextDiagonal(pos, n) } |
︙ | ︙ |
Changes to parser/draw/canvas_test.go.
︙ | ︙ | |||
297 298 299 300 301 302 303 | false, }, // 9 Indented box { []string{ "", | | | | | | | 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | false, }, // 9 Indented box { []string{ "", "\t+-+", "\t| |", "\t+-+", }, []string{"Path{[(9,1) (10,1) (11,1) (11,2) (11,3) (10,3) (9,3) (9,2)]}"}, []string{""}, [][]point{{{x: 9, y: 1}, {x: 11, y: 1}, {x: 11, y: 3}, {x: 9, y: 3}}}, false, }, // 10 Diagonal lines with arrows { []string{ "^ ^", |
︙ | ︙ | |||
442 443 444 445 446 447 448 | {x: 11, y: 2, hint: 0}, {x: 12, y: 2, hint: 0}, {x: 13, y: 2, hint: 0}, }, }, true, }, | | < < < < < < < < < < < < < < < < < < < < < < < < < | | 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 | {x: 11, y: 2, hint: 0}, {x: 12, y: 2, hint: 0}, {x: 13, y: 2, hint: 0}, }, }, true, }, } for i, line := range data { c, err := newCanvas([]byte(strings.Join(line.input, "\n")), 9) if err != nil { t.Fatalf("Test %d: error creating canvas: %s", i, err) } objs := c.objects() if line.strings != nil { if got := getStrings(objs); !reflect.DeepEqual(line.strings, got) { t.Errorf("%d: expected %q, but got %q", i, line.strings, got) |
︙ | ︙ | |||
556 557 558 559 560 561 562 | {{x: 0, y: 0}, {x: 5, y: 0}, {x: 5, y: 2}, {x: 0, y: 2}}, {{x: 0, y: 0}, {x: 5, y: 0}, {x: 5, y: 2}, {x: 2, y: 2}, {x: 2, y: 1}}, {{x: 0, y: 0}, {x: 5, y: 0}, {x: 5, y: 2}, {x: 3, y: 2}, {x: 3, y: 1}}, }, }, } for i, line := range data { | | | 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 | {{x: 0, y: 0}, {x: 5, y: 0}, {x: 5, y: 2}, {x: 0, y: 2}}, {{x: 0, y: 0}, {x: 5, y: 0}, {x: 5, y: 2}, {x: 2, y: 2}, {x: 2, y: 1}}, {{x: 0, y: 0}, {x: 5, y: 0}, {x: 5, y: 2}, {x: 3, y: 2}, {x: 3, y: 1}}, }, }, } for i, line := range data { c, err := newCanvas([]byte(strings.Join(line.input, "\n")), 9) if err != nil { t.Fatalf("Test %d: error creating canvas: %s", i, err) } objs := c.objects() if line.strings != nil { if got := getStrings(objs); !reflect.DeepEqual(line.strings, got) { t.Errorf("%d: expected %q, but got %q", i, line.strings, got) |
︙ | ︙ | |||
659 660 661 662 663 664 665 | chunk := []byte(strings.Join(data, "\n")) input := make([]byte, 0, len(chunk)*b.N) for i := 0; i < b.N; i++ { input = append(input, chunk...) } expected := 30 * b.N b.ResetTimer() | | | 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 | chunk := []byte(strings.Join(data, "\n")) input := make([]byte, 0, len(chunk)*b.N) for i := 0; i < b.N; i++ { input = append(input, chunk...) } expected := 30 * b.N b.ResetTimer() c, err := newCanvas(input, 8) if err != nil { b.Fatalf("Error creating canvas: %s", err) } objs := c.objects() if len(objs) != expected { b.Fatalf("%d != %d", len(objs), expected) |
︙ | ︙ |
Changes to parser/draw/draw.go.
1 2 3 4 5 6 7 8 9 10 | //----------------------------------------------------------------------------- // Copyright (c) 2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- | | < < < | | | | | > > > > > > > > > > > > | | | < | | < < < | | < | | | | | | | | | | > > > | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | //----------------------------------------------------------------------------- // Copyright (c) 2022 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 draw provides a parser to create SVG from ASCII drawing package draw import ( "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" "zettelstore.de/z/parser" ) func init() { parser.Register(&parser.Info{ Name: api.ValueSyntaxDraw, AltNames: []string{}, IsTextParser: true, IsImageFormat: false, ParseBlocks: parseBlocks, ParseInlines: parseInlines, }) } const ( defaultTabSize = 8 defaultFont = "" defaultScaleX = 10 defaultScaleY = 20 ) func parseBlocks(inp *input.Input, m *meta.Meta, _ string) ast.BlockSlice { font := m.GetDefault("font", defaultFont) scaleX := m.GetNumber("x-scale", defaultScaleX) scaleY := m.GetNumber("y-scale", defaultScaleY) canvas, err := newCanvas(inp.Src[inp.Pos:], defaultTabSize) if err != nil { return ast.BlockSlice{ast.CreateParaNode(canvasErrMsg(err)...)} } if scaleX < 1 || 1000000 < scaleX { scaleX = defaultScaleX } if scaleY < 1 || 1000000 < scaleY { scaleY = defaultScaleY } svg := canvasToSVG(canvas, font, int(scaleX), int(scaleY)) if len(svg) == 0 { return ast.BlockSlice{ast.CreateParaNode(noSVGErrMsg()...)} } return ast.BlockSlice{&ast.BLOBNode{ Title: "", Syntax: api.ValueSyntaxSVG, Blob: svg, }} } func parseInlines(inp *input.Input, _ string) ast.InlineSlice { canvas, err := newCanvas(inp.Src[inp.Pos:], defaultTabSize) if err != nil { return canvasErrMsg(err) } svg := canvasToSVG(canvas, defaultFont, defaultScaleX, defaultScaleY) if len(svg) == 0 { return noSVGErrMsg() } return ast.InlineSlice{&ast.EmbedBLOBNode{ Blob: svg, Syntax: api.ValueSyntaxSVG, }} } func canvasErrMsg(err error) ast.InlineSlice { return ast.CreateInlineSliceFromWords("Error:", err.Error()) } func noSVGErrMsg() ast.InlineSlice { return ast.CreateInlineSliceFromWords("NO", "IMAGE") } |
Changes to parser/draw/object.go.
︙ | ︙ | |||
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | func (o *object) String() string { if o.isJustText() { return fmt.Sprintf("Text{%s %q}", o.points[0], string(o.text)) } return fmt.Sprintf("Path{%v}", o.points) } // seal finalizes the object, setting its text, its corners, and its various rendering hints. func (o *object) seal(c *canvas) { if c.at(o.points[0]).isArrow() { o.points[0].hint = startMarker c.hasStartMarker = true } if c.at(o.points[len(o.points)-1]).isArrow() { o.points[len(o.points)-1].hint = endMarker c.hasEndMarker = true } o.corners, o.isClosed = pointsToCorners(o.points) o.text = make([]rune, len(o.points)) for i, p := range o.points { | > > > > > > > > > > > > > > > > > > > < | | > | | | 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | func (o *object) String() string { if o.isJustText() { return fmt.Sprintf("Text{%s %q}", o.points[0], string(o.text)) } return fmt.Sprintf("Path{%v}", o.points) } // HasPoint determines whether the supplied point lives inside the object. Since we support complex // convex and concave polygons, we need to do a full point-in-polygon test. The algorithm implemented // comes from the more efficient, less-clever version at http://alienryderflex.com/polygon/. func (o *object) HasPoint(p point) bool { hasPoint := false ncorners := len(o.corners) j := ncorners - 1 for i := 0; i < ncorners; i++ { if (o.corners[i].y < p.y && o.corners[j].y >= p.y || o.corners[j].y < p.y && o.corners[i].y >= p.y) && (o.corners[i].x <= p.x || o.corners[j].x <= p.x) { if o.corners[i].x+(p.y-o.corners[i].y)/(o.corners[j].y-o.corners[i].y)*(o.corners[j].x-o.corners[i].x) < p.x { hasPoint = !hasPoint } } j = i } return hasPoint } // seal finalizes the object, setting its text, its corners, and its various rendering hints. func (o *object) seal(c *canvas) { if c.at(o.points[0]).isArrow() { o.points[0].hint = startMarker c.hasStartMarker = true } if c.at(o.points[len(o.points)-1]).isArrow() { o.points[len(o.points)-1].hint = endMarker c.hasEndMarker = true } o.corners, o.isClosed = pointsToCorners(o.points) o.text = make([]rune, len(o.points)) for i, p := range o.points { if !o.isJustText() { if c.at(p).isTick() { o.points[i].hint = tick } else if c.at(p).isDot() { o.points[i].hint = dot } if c.at(p).isDashed() { o.isDashed = true } for _, corner := range o.corners { if corner.x == p.x && corner.y == p.y && c.at(p).isRoundedCorner() { o.points[i].hint = roundedCorner } } } o.text[i] = rune(c.at(p)) } } // objects implements a sortable collection of Object interfaces. type objects []*object func (o objects) Len() int { return len(o) } |
︙ | ︙ | |||
157 158 159 160 161 162 163 | dir = dirNW } else if isDiagonalNE(points[0], points[1]) { dir = dirNE } else { panic(fmt.Errorf("discontiguous points: %+v", points)) } | < < < < < < < > > > > > > | 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | dir = dirNW } else if isDiagonalNE(points[0], points[1]) { dir = dirNE } else { panic(fmt.Errorf("discontiguous points: %+v", points)) } // Starting from the third point, check to see if the directionality between points P and // P-1 has changed. for i := 2; i < l; i++ { cornerFunc := func(idx, newDir int) { if dir != newDir { out = append(out, points[idx-1]) dir = newDir } } if isHorizontal(points[i-1], points[i]) { cornerFunc(i, dirH) } else if isVertical(points[i-1], points[i]) { cornerFunc(i, dirV) } else if isDiagonalSE(points[i-1], points[i]) { cornerFunc(i, dirSE) } else if isDiagonalSW(points[i-1], points[i]) { |
︙ | ︙ |
Changes to parser/draw/svg_test.go.
︙ | ︙ | |||
55 56 57 58 59 60 61 | []string{ " foo", }, 265, }, } for i, line := range data { | | | 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | []string{ " foo", }, 265, }, } for i, line := range data { canvas, err := newCanvas([]byte(strings.Join(line.input, "\n")), 9) if err != nil { t.Fatalf("Error creating canvas: %s", err) } actual := string(canvasToSVG(canvas, "", 9, 16)) // TODO(dhobsd): Use golden file? Worth postponing once output is actually // nice. if line.length != len(actual) { t.Errorf("%d: expected length %d, but got %d\n%q", i, line.length, len(actual), actual) } } } |
Changes to parser/markdown/markdown.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 | "bytes" "fmt" gm "github.com/yuin/goldmark" gmAst "github.com/yuin/goldmark/ast" gmText "github.com/yuin/goldmark/text" | | > | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | "bytes" "fmt" gm "github.com/yuin/goldmark" gmAst "github.com/yuin/goldmark/ast" gmText "github.com/yuin/goldmark/text" "zettelstore.de/c/api" "zettelstore.de/c/zjson" "zettelstore.de/z/ast" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" "zettelstore.de/z/input" "zettelstore.de/z/parser" ) func init() { parser.Register(&parser.Info{ Name: "markdown", |
︙ | ︙ | |||
48 49 50 51 52 53 54 | return bs.FirstParagraphInlines() } func parseMarkdown(inp *input.Input) *mdP { source := []byte(inp.Src[inp.Pos:]) parser := gm.DefaultParser() node := parser.Parse(gmText.NewReader(source)) | | | | 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | return bs.FirstParagraphInlines() } func parseMarkdown(inp *input.Input) *mdP { source := []byte(inp.Src[inp.Pos:]) parser := gm.DefaultParser() node := parser.Parse(gmText.NewReader(source)) textEnc := encoder.Create(api.EncoderText, nil) return &mdP{source: source, docNode: node, textEnc: textEnc} } type mdP struct { source []byte docNode gmAst.Node textEnc encoder.Encoder } func (p *mdP) acceptBlockChildren(docNode gmAst.Node) ast.BlockSlice { if docNode.Type() != gmAst.TypeDocument { panic(fmt.Sprintf("Expected document, but got node type %v", docNode.Type())) } result := make(ast.BlockSlice, 0, docNode.ChildCount()) |
︙ | ︙ | |||
128 129 130 131 132 133 134 | Kind: ast.VerbatimProg, Attrs: nil, //TODO Content: p.acceptRawText(node), } } func (p *mdP) acceptFencedCodeBlock(node *gmAst.FencedCodeBlock) *ast.VerbatimNode { | | | | | 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | Kind: ast.VerbatimProg, Attrs: nil, //TODO Content: p.acceptRawText(node), } } func (p *mdP) acceptFencedCodeBlock(node *gmAst.FencedCodeBlock) *ast.VerbatimNode { var attrs zjson.Attributes if language := node.Language(p.source); len(language) > 0 { attrs = attrs.Set("class", "language-"+cleanText(language, true)) } return &ast.VerbatimNode{ Kind: ast.VerbatimProg, Attrs: attrs, Content: p.acceptRawText(node), } } func (p *mdP) acceptRawText(node gmAst.Node) []byte { lines := node.Lines() result := make([]byte, 0, 512) |
︙ | ︙ | |||
171 172 173 174 175 176 177 | p.acceptItemSlice(node), }, } } func (p *mdP) acceptList(node *gmAst.List) ast.ItemNode { kind := ast.NestedListUnordered | | | | | 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | p.acceptItemSlice(node), }, } } func (p *mdP) acceptList(node *gmAst.List) ast.ItemNode { kind := ast.NestedListUnordered var attrs zjson.Attributes if node.IsOrdered() { kind = ast.NestedListOrdered if node.Start != 1 { attrs = attrs.Set("start", fmt.Sprintf("%d", node.Start)) } } items := make([]ast.ItemSlice, 0, node.ChildCount()) for child := node.FirstChild(); child != nil; child = child.NextSibling() { item, ok := child.(*gmAst.ListItem) if !ok { panic(fmt.Sprintf("Expected list item node, but got %v", child.Kind())) } items = append(items, p.acceptItemSlice(item)) } return &ast.NestedListNode{ Kind: kind, Items: items, Attrs: attrs, } } func (p *mdP) acceptItemSlice(node gmAst.Node) ast.ItemSlice { result := make(ast.ItemSlice, 0, node.ChildCount()) for elem := node.FirstChild(); elem != nil; elem = elem.NextSibling() { if item := p.acceptBlock(elem); item != nil { |
︙ | ︙ | |||
400 401 402 403 404 405 406 | Inlines: p.acceptInlineChildren(node), }, } } func (p *mdP) acceptLink(node *gmAst.Link) ast.InlineSlice { ref := ast.ParseReference(cleanText(node.Destination, true)) | | | | | | | | 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 | Inlines: p.acceptInlineChildren(node), }, } } func (p *mdP) acceptLink(node *gmAst.Link) ast.InlineSlice { ref := ast.ParseReference(cleanText(node.Destination, true)) var attrs zjson.Attributes if title := node.Title; len(title) > 0 { attrs = attrs.Set("title", cleanText(title, true)) } return ast.InlineSlice{ &ast.LinkNode{ Ref: ref, Inlines: p.acceptInlineChildren(node), Attrs: attrs, }, } } func (p *mdP) acceptImage(node *gmAst.Image) ast.InlineSlice { ref := ast.ParseReference(cleanText(node.Destination, true)) var attrs zjson.Attributes if title := node.Title; len(title) > 0 { attrs = attrs.Set("title", cleanText(title, true)) } return ast.InlineSlice{ &ast.EmbedRefNode{ Ref: ref, Inlines: p.flattenInlineSlice(node), Attrs: attrs, }, } } func (p *mdP) flattenInlineSlice(node gmAst.Node) ast.InlineSlice { is := p.acceptInlineChildren(node) var buf bytes.Buffer |
︙ | ︙ |
Changes to parser/parser_test.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | "testing" "zettelstore.de/c/api" "zettelstore.de/z/parser" "zettelstore.de/z/strfun" _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. _ "zettelstore.de/z/parser/markdown" // Allow to use markdown parser. _ "zettelstore.de/z/parser/none" // Allow to use none parser. _ "zettelstore.de/z/parser/plain" // Allow to use plain parser. _ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser. ) func TestParserType(t *testing.T) { syntaxSet := strfun.NewSet(parser.GetSyntaxes()...) testCases := []struct { syntax string text bool image bool }{ {api.ValueSyntaxHTML, false, false}, {"css", false, false}, {api.ValueSyntaxGif, false, true}, {"jpeg", false, true}, {"jpg", false, true}, {"markdown", true, false}, {"md", true, false}, {"mustache", false, false}, {api.ValueSyntaxNone, false, false}, | > > | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | "testing" "zettelstore.de/c/api" "zettelstore.de/z/parser" "zettelstore.de/z/strfun" _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. _ "zettelstore.de/z/parser/draw" // Allow to use draw parser. _ "zettelstore.de/z/parser/markdown" // Allow to use markdown parser. _ "zettelstore.de/z/parser/none" // Allow to use none parser. _ "zettelstore.de/z/parser/plain" // Allow to use plain parser. _ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser. ) func TestParserType(t *testing.T) { syntaxSet := strfun.NewSet(parser.GetSyntaxes()...) testCases := []struct { syntax string text bool image bool }{ {api.ValueSyntaxHTML, false, false}, {"css", false, false}, {api.ValueSyntaxDraw, true, false}, {api.ValueSyntaxGif, false, true}, {"jpeg", false, true}, {"jpg", false, true}, {"markdown", true, false}, {"md", true, false}, {"mustache", false, false}, {api.ValueSyntaxNone, false, false}, |
︙ | ︙ |
Changes to parser/plain/plain.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 | // Package plain provides a parser for plain text data. package plain import ( "strings" "zettelstore.de/c/api" | | | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // Package plain provides a parser for plain text data. package plain import ( "strings" "zettelstore.de/c/api" "zettelstore.de/c/zjson" "zettelstore.de/z/ast" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" "zettelstore.de/z/parser" ) func init() { |
︙ | ︙ | |||
71 72 73 74 75 76 77 | func parseBlocksHTML(inp *input.Input, _ *meta.Meta, syntax string) ast.BlockSlice { return doParseBlocks(inp, syntax, ast.VerbatimHTML) } func doParseBlocks(inp *input.Input, syntax string, kind ast.VerbatimKind) ast.BlockSlice { return ast.BlockSlice{ &ast.VerbatimNode{ Kind: kind, | | | > > > > > > > > > > > > > > > > | | 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | func parseBlocksHTML(inp *input.Input, _ *meta.Meta, syntax string) ast.BlockSlice { return doParseBlocks(inp, syntax, ast.VerbatimHTML) } func doParseBlocks(inp *input.Input, syntax string, kind ast.VerbatimKind) ast.BlockSlice { return ast.BlockSlice{ &ast.VerbatimNode{ Kind: kind, Attrs: zjson.Attributes{"": syntax}, Content: readContent(inp), }, } } func readContent(inp *input.Input) []byte { result := make([]byte, 0, len(inp.Src)-inp.Pos+1) for { inp.EatEOL() posL := inp.Pos if inp.Ch == input.EOS { return result } inp.SkipToEOL() if len(result) > 0 { result = append(result, '\n') } result = append(result, inp.Src[posL:inp.Pos]...) } } func parseInlines(inp *input.Input, syntax string) ast.InlineSlice { return doParseInlines(inp, syntax, ast.LiteralProg) } func parseInlinesHTML(inp *input.Input, syntax string) ast.InlineSlice { return doParseInlines(inp, syntax, ast.LiteralHTML) } func doParseInlines(inp *input.Input, syntax string, kind ast.LiteralKind) ast.InlineSlice { inp.SkipToEOL() return ast.InlineSlice{&ast.LiteralNode{ Kind: kind, Attrs: zjson.Attributes{"": syntax}, Content: append([]byte(nil), inp.Src[0:inp.Pos]...), }} } func parseSVGBlocks(inp *input.Input, _ *meta.Meta, syntax string) ast.BlockSlice { is := parseSVGInlines(inp, syntax) if len(is) == 0 { |
︙ | ︙ |
Changes to parser/zettelmark/block.go.
︙ | ︙ | |||
53 54 55 56 57 58 59 | return nil, false case '\n', '\r': inp.EatEOL() cp.cleanupListsAfterEOL() return nil, false case ':': bn, success = cp.parseColon() | | | 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | return nil, false case '\n', '\r': inp.EatEOL() cp.cleanupListsAfterEOL() return nil, false case ':': bn, success = cp.parseColon() case '@', '`', runeModGrave, '%': cp.clearStacked() bn, success = cp.parseVerbatim() case '"', '<': cp.clearStacked() bn, success = cp.parseRegion() case '=': cp.clearStacked() |
︙ | ︙ | |||
137 138 139 140 141 142 143 | return pn } pn.Inlines = append(pn.Inlines, in) if _, ok := in.(*ast.BreakNode); ok { ch := cp.inp.Ch switch ch { // Must contain all cases from above switch in parseBlock. | | | 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | return pn } pn.Inlines = append(pn.Inlines, in) if _, ok := in.(*ast.BreakNode); ok { ch := cp.inp.Ch switch ch { // Must contain all cases from above switch in parseBlock. case input.EOS, '\n', '\r', '@', '`', runeModGrave, '%', '"', '<', '=', '-', '*', '#', '>', ';', ':', ' ', '|', '{': return pn } } } } // countDelim read from input until a non-delimiter is found and returns number of delimiter chars. |
︙ | ︙ | |||
175 176 177 178 179 180 181 | switch fch { case '@': kind = ast.VerbatimZettel case '`', runeModGrave: kind = ast.VerbatimProg case '%': kind = ast.VerbatimComment | < < < < | 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | switch fch { case '@': kind = ast.VerbatimZettel case '`', runeModGrave: kind = ast.VerbatimProg case '%': kind = ast.VerbatimComment default: panic(fmt.Sprintf("%q is not a verbatim char", fch)) } rn = &ast.VerbatimNode{Kind: kind, Attrs: attrs, Content: make([]byte, 0, 512)} for { inp.EatEOL() posL := inp.Pos |
︙ | ︙ |
Changes to parser/zettelmark/inline.go.
︙ | ︙ | |||
70 71 72 73 74 75 76 | return cp.parseTag() case '%': in, success = cp.parseComment() case '_', '*', '>', '~', '^', ',', '"', ':': in, success = cp.parseFormat() case '@', '\'', '`', '=', runeModGrave: in, success = cp.parseLiteral() | < < | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | return cp.parseTag() case '%': in, success = cp.parseComment() case '_', '*', '>', '~', '^', ',', '"', ':': in, success = cp.parseFormat() case '@', '\'', '`', '=', runeModGrave: in, success = cp.parseLiteral() case '\\': return cp.parseBackslash() case '-': in, success = cp.parseNdash() case '&': in, success = cp.parseEntity() } |
︙ | ︙ | |||
98 99 100 101 102 103 104 | return cp.parseTextBackslash() } for { inp.Next() switch inp.Ch { // The following case must contain all runes that occur in parseInline! // Plus the closing brackets ] and } and ) and the middle | | | | 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | return cp.parseTextBackslash() } for { inp.Next() switch inp.Ch { // The following case must contain all runes that occur in parseInline! // Plus the closing brackets ] and } and ) and the middle | case input.EOS, '\n', '\r', ' ', '\t', '[', ']', '{', '}', '(', ')', '|', '#', '%', '_', '*', '>', '~', '^', ',', '"', ':', '\'', '@', '`', runeModGrave, '=', '\\', '-', '&': return &ast.TextNode{Text: string(inp.Src[pos:inp.Pos])} } } } func (cp *zmkP) parseTextBackslash() *ast.TextNode { cp.inp.Next() |
︙ | ︙ | |||
394 395 396 397 398 399 400 | inp.Next() if inp.Ch != '%' { return nil, false } for inp.Ch == '%' { inp.Next() } | < < | 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 | inp.Next() if inp.Ch != '%' { return nil, false } for inp.Ch == '%' { inp.Next() } cp.skipSpace() pos := inp.Pos for { if input.IsEOLEOS(inp.Ch) { return &ast.LiteralNode{ Kind: ast.LiteralComment, Content: append([]byte(nil), inp.Src[pos:inp.Pos]...), }, true } inp.Next() } } |
︙ | ︙ | |||
460 461 462 463 464 465 466 | var mapRuneLiteral = map[rune]ast.LiteralKind{ '@': ast.LiteralZettel, '`': ast.LiteralProg, runeModGrave: ast.LiteralProg, '\'': ast.LiteralInput, '=': ast.LiteralOutput, | < | 456 457 458 459 460 461 462 463 464 465 466 467 468 469 | var mapRuneLiteral = map[rune]ast.LiteralKind{ '@': ast.LiteralZettel, '`': ast.LiteralProg, runeModGrave: ast.LiteralProg, '\'': ast.LiteralInput, '=': ast.LiteralOutput, } func (cp *zmkP) parseLiteral() (res ast.InlineNode, success bool) { inp := cp.inp fch := inp.Ch kind, ok := mapRuneLiteral[fch] if !ok { |
︙ | ︙ | |||
498 499 500 501 502 503 504 | } else { tn := cp.parseText() buf.WriteString(tn.Text) } } } | < < < < < < < < < < < < < < < < < < < < < < < < < < < | 493 494 495 496 497 498 499 500 501 502 503 504 505 506 | } else { tn := cp.parseText() buf.WriteString(tn.Text) } } } func (cp *zmkP) parseNdash() (res *ast.TextNode, success bool) { inp := cp.inp if inp.Peek() != inp.Ch { return nil, false } inp.Next() inp.Next() |
︙ | ︙ |
Changes to parser/zettelmark/post-processor.go.
︙ | ︙ | |||
79 80 81 82 83 84 85 | pp.inVerse = oldVerse } func (pp *postProcessor) visitNestedList(ln *ast.NestedListNode) { for i, item := range ln.Items { ln.Items[i] = pp.processItemSlice(item) } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | pp.inVerse = oldVerse } func (pp *postProcessor) visitNestedList(ln *ast.NestedListNode) { for i, item := range ln.Items { ln.Items[i] = pp.processItemSlice(item) } } func (pp *postProcessor) visitDescriptionList(dn *ast.DescriptionListNode) { for i, def := range dn.Descriptions { if len(def.Term) > 0 { ast.Walk(pp, &dn.Descriptions[i].Term) } |
︙ | ︙ |
Changes to parser/zettelmark/zettelmark.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 | // Package zettelmark provides a parser for zettelmarkup. package zettelmark import ( "unicode" "zettelstore.de/c/api" | | | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // Package zettelmark provides a parser for zettelmarkup. package zettelmark import ( "unicode" "zettelstore.de/c/api" "zettelstore.de/c/zjson" "zettelstore.de/z/ast" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" "zettelstore.de/z/parser" ) func init() { |
︙ | ︙ | |||
158 159 160 161 162 163 164 | // parseAttributes reads optional attributes. // If sameLine is True, it is called from block nodes. In this case, a single // name is allowed. It will parse as {name}. Attributes are not allowed to be // continued on next line. // If sameLine is False, it is called from inline nodes. In this case, the next // rune must be '{'. A continuation on next lines is allowed. | | | | | | | | | | | | | 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | // parseAttributes reads optional attributes. // If sameLine is True, it is called from block nodes. In this case, a single // name is allowed. It will parse as {name}. Attributes are not allowed to be // continued on next line. // If sameLine is False, it is called from inline nodes. In this case, the next // rune must be '{'. A continuation on next lines is allowed. func (cp *zmkP) parseAttributes(sameLine bool) zjson.Attributes { inp := cp.inp if sameLine { pos := inp.Pos for isNameRune(inp.Ch) { inp.Next() } if pos < inp.Pos { return zjson.Attributes{"": string(inp.Src[pos:inp.Pos])} } // No immediate name: skip spaces cp.skipSpace() } pos := inp.Pos attrs, success := cp.doParseAttributes(sameLine) if sameLine || success { return attrs } inp.SetPos(pos) return nil } func (cp *zmkP) doParseAttributes(sameLine bool) (res zjson.Attributes, success bool) { inp := cp.inp if inp.Ch != '{' { return nil, false } inp.Next() attrs := zjson.Attributes{} if !cp.parseAttributeValues(sameLine, attrs) { return nil, false } inp.Next() return attrs, true } func (cp *zmkP) parseAttributeValues(sameLine bool, attrs zjson.Attributes) bool { inp := cp.inp for { cp.skipSpaceLine(sameLine) switch inp.Ch { case input.EOS: return false case '}': return true case '.': inp.Next() posC := inp.Pos for isNameRune(inp.Ch) { inp.Next() } if posC == inp.Pos { return false } updateAttrs(attrs, "class", string(inp.Src[posC:inp.Pos])) case '=': delete(attrs, "") if !cp.parseAttributeValue("", attrs, sameLine) { return false } default: if !cp.parseNormalAttribute(attrs, sameLine) { return false } } switch inp.Ch { case '}': return true |
︙ | ︙ |
Changes to parser/zettelmark/zettelmark_test.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 17 18 19 20 | // Package zettelmark_test provides some tests for the zettelmarkup parser. package zettelmark_test import ( "bytes" "fmt" "strings" "testing" "zettelstore.de/c/api" | > | > > > > | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | // Package zettelmark_test provides some tests for the zettelmarkup parser. package zettelmark_test import ( "bytes" "fmt" "sort" "strings" "testing" "zettelstore.de/c/api" "zettelstore.de/c/zjson" "zettelstore.de/z/ast" "zettelstore.de/z/input" "zettelstore.de/z/parser" // Ensure that the text encoder is available. // Needed by parser/cleanup.go _ "zettelstore.de/z/encoder/textenc" ) type TestCase struct{ source, want string } type TestCases []TestCase func replace(s string, tcs TestCases) TestCases { var testCases TestCases |
︙ | ︙ | |||
365 366 367 368 369 370 371 | {"''````''", "(PARA {' ````})"}, {"''``a``''", "(PARA {' ``a``})"}, {"''``''``", "(PARA {' ``} ``)"}, {"''\\'''", "(PARA {' '})"}, }) } | < < < < < < < < < < < < < < < < < < < < < | 370 371 372 373 374 375 376 377 378 379 380 381 382 383 | {"''````''", "(PARA {' ````})"}, {"''``a``''", "(PARA {' ``a``})"}, {"''``''``", "(PARA {' ``} ``)"}, {"''\\'''", "(PARA {' '})"}, }) } func TestMixFormatCode(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"__abc__\n**def**", "(PARA {_ abc} SB {* def})"}, {"''abc''\n==def==", "(PARA {' abc} SB {= def})"}, {"__abc__\n==def==", "(PARA {_ abc} SB {= def})"}, {"__abc__\n``def``", "(PARA {_ abc} SB {` def})"}, |
︙ | ︙ | |||
430 431 432 433 434 435 436 | } func TestVerbatimZettel(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"@@@\n@@@", "(ZETTEL)"}, {"@@@\nabc\n@@@", "(ZETTEL\nabc)"}, | | < < < < < < < < < < < < < < < < < < < < < < < < | 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 | } func TestVerbatimZettel(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"@@@\n@@@", "(ZETTEL)"}, {"@@@\nabc\n@@@", "(ZETTEL\nabc)"}, {"@@@@draw\nabc\n@@@@", "(ZETTEL\nabc)[ATTR =draw]"}, }) } func TestVerbatimCode(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"```\n```", "(PROG)"}, {"```\nabc\n```", "(PROG\nabc)"}, {"```\nabc\n````", "(PROG\nabc)"}, {"````\nabc\n````", "(PROG\nabc)"}, {"````\nabc\n```\n````", "(PROG\nabc\n```)"}, {"````go\nabc\n````", "(PROG\nabc)[ATTR =go]"}, }) } func TestVerbatimComment(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"%%%\n%%%", "(COMMENT)"}, {"%%%\nabc\n%%%", "(COMMENT\nabc)"}, {"%%%%go\nabc\n%%%%", "(COMMENT\nabc)[ATTR =go]"}, }) |
︙ | ︙ | |||
614 615 616 617 618 619 620 | {"* abc\n# def", "(UL {(PARA abc)})(OL {(PARA def)})"}, // Quotation lists may have empty items {">", "(QL {})"}, }) } | < < < < < < < < < | 574 575 576 577 578 579 580 581 582 583 584 585 586 587 | {"* abc\n# def", "(UL {(PARA abc)})(OL {(PARA def)})"}, // Quotation lists may have empty items {">", "(QL {})"}, }) } func TestEnumAfterPara(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"abc\n* def", "(PARA abc)(UL {(PARA def)})"}, {"abc\n*def", "(PARA abc SB *def)"}, }) } |
︙ | ︙ | |||
959 960 961 962 963 964 965 | } return nil } var mapVerbatimKind = map[ast.VerbatimKind]string{ ast.VerbatimZettel: "(ZETTEL", ast.VerbatimProg: "(PROG", | < < | 910 911 912 913 914 915 916 917 918 919 920 921 922 923 | } return nil } var mapVerbatimKind = map[ast.VerbatimKind]string{ ast.VerbatimZettel: "(ZETTEL", ast.VerbatimProg: "(PROG", ast.VerbatimComment: "(COMMENT", } var mapRegionKind = map[ast.RegionKind]string{ ast.RegionSpan: "(SPAN", ast.RegionQuote: "(QUOTE", ast.RegionVerse: "(VERSE", |
︙ | ︙ | |||
1000 1001 1002 1003 1004 1005 1006 | var mapLiteralKind = map[ast.LiteralKind]rune{ ast.LiteralZettel: '@', ast.LiteralProg: '`', ast.LiteralInput: '\'', ast.LiteralOutput: '=', ast.LiteralComment: '%', | < | > > > > > > | | 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 | var mapLiteralKind = map[ast.LiteralKind]rune{ ast.LiteralZettel: '@', ast.LiteralProg: '`', ast.LiteralInput: '\'', ast.LiteralOutput: '=', ast.LiteralComment: '%', } func (tv *TestVisitor) visitInlineSlice(is *ast.InlineSlice) { for _, in := range *is { tv.buf.WriteByte(' ') ast.Walk(tv, in) } } func (tv *TestVisitor) visitAttributes(a zjson.Attributes) { if a.IsEmpty() { return } tv.buf.WriteString("[ATTR") keys := make([]string, 0, len(a)) for k := range a { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { tv.buf.WriteByte(' ') tv.buf.WriteString(k) v := a[k] if len(v) > 0 { tv.buf.WriteByte('=') if strings.ContainsRune(v, ' ') { tv.buf.WriteByte('"') |
︙ | ︙ |
Changes to search/print.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 16 17 18 19 | //----------------------------------------------------------------------------- // Package search provides a zettel search. package search import ( "io" "strconv" "strings" "zettelstore.de/c/api" | > < | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //----------------------------------------------------------------------------- // Package search provides a zettel search. package search import ( "io" "sort" "strconv" "strings" "zettelstore.de/c/api" ) func (s *Search) String() string { var sb strings.Builder s.Print(&sb) return sb.String() } |
︙ | ︙ | |||
36 37 38 39 40 41 42 | } space := false if len(s.search) > 0 { io.WriteString(w, "ANY") printSelectExprValues(w, s.search) space = true } | > > > > > | | | 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | } space := false if len(s.search) > 0 { io.WriteString(w, "ANY") printSelectExprValues(w, s.search) space = true } names := make([]string, 0, len(s.tags)) for name := range s.tags { names = append(names, name) } sort.Strings(names) for _, name := range names { if space { io.WriteString(w, " AND ") } io.WriteString(w, name) printSelectExprValues(w, s.tags[name]) space = true } if s.negate { io.WriteString(w, ")") space = true } |
︙ | ︙ |
Changes to search/search.go.
︙ | ︙ | |||
52 53 54 55 56 57 58 | // Search specifies a mechanism for selecting zettel. type Search struct { mx sync.RWMutex // Protects other attributes // Fields to be used for selecting preMatch MetaMatchFunc // Match that must be true | | | | | | | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | // Search specifies a mechanism for selecting zettel. type Search struct { mx sync.RWMutex // Protects other attributes // Fields to be used for selecting preMatch MetaMatchFunc // Match that must be true tags expTagValues // Expected values for a tag search []expValue // Search string negate bool // Negate the result of the whole selecting process // Fields to be used for sorting order string // Name of meta key. None given: use "id" descending bool // Sort by order, but descending offset int // <= 0: no offset limit int // <= 0: no limit } type expTagValues map[string][]expValue // Clone the search value. func (s *Search) Clone() *Search { if s == nil { return nil } c := new(Search) c.preMatch = s.preMatch c.tags = make(expTagValues, len(s.tags)) for k, v := range s.tags { c.tags[k] = v } c.search = append([]expValue{}, s.search...) c.negate = s.negate c.order = s.order c.descending = s.descending c.offset = s.offset c.limit = s.limit |
︙ | ︙ | |||
138 139 140 141 142 143 144 | if s == nil { s = new(Search) } s.mx.Lock() defer s.mx.Unlock() if key == "" { s.addSearch(val) | | | | | 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | if s == nil { s = new(Search) } s.mx.Lock() defer s.mx.Unlock() if key == "" { s.addSearch(val) } else if s.tags == nil { s.tags = expTagValues{key: {val}} } else { s.tags[key] = append(s.tags[key], val) } return s } func (s *Search) addSearch(val expValue) { if val.negate { val.op = val.op.negate() |
︙ | ︙ | |||
296 297 298 299 300 301 302 | // is calculated via metadata enrichments. func (s *Search) EnrichNeeded() bool { if s == nil { return false } s.mx.RLock() defer s.mx.RUnlock() | | | 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 | // is calculated via metadata enrichments. func (s *Search) EnrichNeeded() bool { if s == nil { return false } s.mx.RLock() defer s.mx.RUnlock() for key := range s.tags { if meta.IsComputed(key) { return true } } return meta.IsComputed(s.order) } |
︙ | ︙ |
Changes to search/select.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 | "strings" "zettelstore.de/z/domain/meta" ) type matchValueFunc func(value string) bool | | | | | | | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | "strings" "zettelstore.de/z/domain/meta" ) type matchValueFunc func(value string) bool func matchValueNever(string) bool { return false } func matchValueAlways(string) bool { return true } type matchSpec struct { key string match matchValueFunc } // compileMeta calculates a selection func based on the given select criteria. func (s *Search) compileMeta() MetaMatchFunc { posSpecs, negSpecs, nomatch := s.createSelectSpecs() if len(posSpecs) > 0 || len(negSpecs) > 0 || len(nomatch) > 0 { return makeSearchMetaMatchFunc(posSpecs, negSpecs, nomatch) } return nil } func (s *Search) createSelectSpecs() (posSpecs, negSpecs []matchSpec, nomatch []string) { posSpecs = make([]matchSpec, 0, len(s.tags)) negSpecs = make([]matchSpec, 0, len(s.tags)) for key, values := range s.tags { if !meta.KeyIsValid(key) { continue } if always, never := countEmptyValues(values); always+never > 0 { if never == 0 { posSpecs = append(posSpecs, matchSpec{key, matchValueAlways}) continue } if always == 0 { negSpecs = append(negSpecs, matchSpec{key, nil}) continue } // value must match always AND never, at the same time. This results in a no-match. |
︙ | ︙ | |||
389 390 391 392 393 394 395 | } } return true } func matchMetaNegSpecs(m *meta.Meta, negSpecs []matchSpec) bool { for _, s := range negSpecs { if s.match == nil { | | | 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 | } } return true } func matchMetaNegSpecs(m *meta.Meta, negSpecs []matchSpec) bool { for _, s := range negSpecs { if s.match == nil { if _, ok := m.Get(s.key); ok { return false } } else if value, ok := m.Get(s.key); !ok || !s.match(value) { return false } } return true } |
Changes to strfun/escape.go.
︙ | ︙ | |||
61 62 63 64 65 66 67 | jsCr = []byte{'\\', 'r'} jsUnicode = []byte{'\\', 'u', '0', '0', '0', '0'} jsHex = []byte("0123456789ABCDEF") ) // JSONEscape returns the given string as a byte slice, where every non-printable // rune is made printable. | | < | 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | jsCr = []byte{'\\', 'r'} jsUnicode = []byte{'\\', 'u', '0', '0', '0', '0'} jsHex = []byte("0123456789ABCDEF") ) // JSONEscape returns the given string as a byte slice, where every non-printable // rune is made printable. func JSONEscape(w io.Writer, s string) { last := 0 for i, ch := range s { var b []byte switch ch { case '\t': b = jsTab case '\r': |
︙ | ︙ | |||
88 89 90 91 92 93 94 | b[3] = '0' b[4] = jsHex[ch>>4] b[5] = jsHex[ch&0xF] } else { continue } } | | < < < | < < < < | < < | < < | 87 88 89 90 91 92 93 94 95 96 97 98 99 | b[3] = '0' b[4] = jsHex[ch>>4] b[5] = jsHex[ch&0xF] } else { continue } } io.WriteString(w, s[last:i]) w.Write(b) last = i + 1 } io.WriteString(w, s[last:]) } |
Changes to tests/client/client_test.go.
︙ | ︙ | |||
45 46 47 48 49 50 51 | } } } func TestListZettel(t *testing.T) { const ( | | | | | | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | } } } func TestListZettel(t *testing.T) { const ( ownerZettel = 48 configRoleZettel = 30 writerZettel = ownerZettel - 24 readerZettel = ownerZettel - 24 creatorZettel = 7 publicZettel = 4 ) testdata := []struct { user string exp int |
︙ | ︙ | |||
163 164 165 166 167 168 169 | func TestGetParsedEvaluatedZettel(t *testing.T) { t.Parallel() c := getClient() c.SetAuth("owner", "owner") encodings := []api.EncodingEnum{ api.EncoderZJSON, api.EncoderHTML, | | | | 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | func TestGetParsedEvaluatedZettel(t *testing.T) { t.Parallel() c := getClient() c.SetAuth("owner", "owner") encodings := []api.EncodingEnum{ api.EncoderZJSON, api.EncoderHTML, api.EncoderNative, api.EncoderText, } for _, enc := range encodings { content, err := c.GetParsedZettel(context.Background(), api.ZidDefaultHome, enc) if err != nil { t.Error(err) continue } if len(content) == 0 { t.Errorf("Empty content for parsed encoding %v", enc) } content, err = c.GetEvaluatedZettel(context.Background(), api.ZidDefaultHome, enc, true) if err != nil { t.Error(err) continue } if len(content) == 0 { t.Errorf("Empty content for evaluated encoding %v", enc) } |
︙ | ︙ |
Changes to tests/client/embed_test.go.
1 | //----------------------------------------------------------------------------- | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // Copyright (c) 2021 Detlef Stern // // This file is part of zettelstore-client. // // Zettelstore client 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 client_test import ( "context" "strings" |
︙ | ︙ | |||
38 39 40 41 42 43 44 | content, err := c.GetZettel(context.Background(), abcZid, api.PartContent) if err != nil { t.Error(err) return } baseContent := string(content) for zid, siz := range contentMap { | | | 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | content, err := c.GetZettel(context.Background(), abcZid, api.PartContent) if err != nil { t.Error(err) return } baseContent := string(content) for zid, siz := range contentMap { content, err = c.GetEvaluatedZettel(context.Background(), zid, api.EncoderHTML, true) if err != nil { t.Error(err) continue } sContent := string(content) prefix := "<p>" if !strings.HasPrefix(sContent, prefix) { |
︙ | ︙ | |||
60 61 62 63 64 65 66 | } got := sContent[len(prefix) : len(content)-len(suffix)] if expect := strings.Repeat(baseContent, siz); expect != got { t.Errorf("Unexpected content for zettel %q\nExpect: %q\nGot: %q", zid, expect, got) } } | | | | > | | 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | } got := sContent[len(prefix) : len(content)-len(suffix)] if expect := strings.Repeat(baseContent, siz); expect != got { t.Errorf("Unexpected content for zettel %q\nExpect: %q\nGot: %q", zid, expect, got) } } content, err = c.GetEvaluatedZettel(context.Background(), abc10000Zid, api.EncoderHTML, true) if err != nil { t.Error(err) return } checkContentContains(t, abc10000Zid, string(content), "Too many transclusions") } func TestZettelTransclusionNoPrivilegeEscalation(t *testing.T) { t.Parallel() c := getClient() c.SetAuth("reader", "reader") zettelData, err := c.GetZettelJSON(context.Background(), api.ZidEmoji) if err != nil { t.Error(err) return } expectedEnc := "base64" if got := zettelData.Encoding; expectedEnc != got { t.Errorf("Zettel %q: encoding %q expected, but got %q", abcZid, expectedEnc, got) } content, err := c.GetEvaluatedZettel(context.Background(), abc10Zid, api.EncoderHTML, true) if err != nil { t.Error(err) return } expectedContent := "<img src=\"data:image/gif;" + expectedEnc + "," + zettelData.Content checkContentContains(t, abc10Zid, string(content), expectedContent) } func stringHead(s string) string { const maxLen = 40 if len(s) <= maxLen { return s } |
︙ | ︙ | |||
123 124 125 126 127 128 129 | ) recursiveZettel := map[api.ZettelID]api.ZettelID{ selfRecursiveZid: selfRecursiveZid, indirectRecursive1Zid: indirectRecursive2Zid, indirectRecursive2Zid: indirectRecursive1Zid, } for zid, errZid := range recursiveZettel { | | | | | | | 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | ) recursiveZettel := map[api.ZettelID]api.ZettelID{ selfRecursiveZid: selfRecursiveZid, indirectRecursive1Zid: indirectRecursive2Zid, indirectRecursive2Zid: indirectRecursive1Zid, } for zid, errZid := range recursiveZettel { content, err := c.GetEvaluatedZettel(context.Background(), zid, api.EncoderHTML, true) if err != nil { t.Error(err) continue } sContent := string(content) checkContentContains(t, zid, sContent, "Recursive transclusion") checkContentContains(t, zid, sContent, string(errZid)) } } func TestNothingToTransclude(t *testing.T) { t.Parallel() c := getClient() c.SetAuth("owner", "owner") const ( transZid = api.ZettelID("20211020184342") emptyZid = api.ZettelID("20211020184300") ) content, err := c.GetEvaluatedZettel(context.Background(), transZid, api.EncoderHTML, true) if err != nil { t.Error(err) return } sContent := string(content) checkContentContains(t, transZid, sContent, "<!-- Nothing to transclude") checkContentContains(t, transZid, sContent, string(emptyZid)) } func TestSelfEmbedRef(t *testing.T) { t.Parallel() c := getClient() c.SetAuth("owner", "owner") const selfEmbedZid = api.ZettelID("20211020185400") content, err := c.GetEvaluatedZettel(context.Background(), selfEmbedZid, api.EncoderHTML, true) if err != nil { t.Error(err) return } checkContentContains(t, selfEmbedZid, string(content), "Self embed reference") } func checkContentContains(t *testing.T, zid api.ZettelID, content, expected string) { if !strings.Contains(content, expected) { t.Helper() t.Errorf("Zettel %q should contain %q, but does not: %q", zid, expected, content) } } |
Changes to tests/markdown_test.go.
︙ | ︙ | |||
18 19 20 21 22 23 24 | "os" "testing" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" _ "zettelstore.de/z/encoder/htmlenc" | | | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | "os" "testing" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" _ "zettelstore.de/z/encoder/htmlenc" _ "zettelstore.de/z/encoder/nativeenc" _ "zettelstore.de/z/encoder/textenc" _ "zettelstore.de/z/encoder/zjsonenc" _ "zettelstore.de/z/encoder/zmkenc" "zettelstore.de/z/input" "zettelstore.de/z/parser" _ "zettelstore.de/z/parser/markdown" _ "zettelstore.de/z/parser/zettelmark" |
︙ | ︙ | |||
41 42 43 44 45 46 47 | Section string `json:"section"` } func TestEncoderAvailability(t *testing.T) { t.Parallel() encoderMissing := false for _, enc := range encodings { | | | 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | Section string `json:"section"` } func TestEncoderAvailability(t *testing.T) { t.Parallel() encoderMissing := false for _, enc := range encodings { enc := encoder.Create(enc, nil) if enc == nil { t.Errorf("No encoder for %q found", enc) encoderMissing = true } } if encoderMissing { panic("At least one encoder is missing. See test log") |
︙ | ︙ | |||
75 76 77 78 79 80 81 | } func testAllEncodings(t *testing.T, tc markdownTestCase, ast *ast.BlockSlice) { var buf bytes.Buffer testID := tc.Example*100 + 1 for _, enc := range encodings { t.Run(fmt.Sprintf("Encode %v %v", enc, testID), func(st *testing.T) { | | | | 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | } func testAllEncodings(t *testing.T, tc markdownTestCase, ast *ast.BlockSlice) { var buf bytes.Buffer testID := tc.Example*100 + 1 for _, enc := range encodings { t.Run(fmt.Sprintf("Encode %v %v", enc, testID), func(st *testing.T) { encoder.Create(enc, nil).WriteBlocks(&buf, ast) buf.Reset() }) } } func testZmkEncoding(t *testing.T, tc markdownTestCase, ast *ast.BlockSlice) { zmkEncoder := encoder.Create(api.EncoderZmk, nil) var buf bytes.Buffer testID := tc.Example*100 + 1 t.Run(fmt.Sprintf("Encode zmk %14d", testID), func(st *testing.T) { buf.Reset() zmkEncoder.WriteBlocks(&buf, ast) // gotFirst := buf.String() |
︙ | ︙ |
Changes to tests/regression_test.go.
1 | //----------------------------------------------------------------------------- | | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- // Package tests provides some higher-level tests. |
︙ | ︙ | |||
32 33 34 35 36 37 38 | "zettelstore.de/z/parser" _ "zettelstore.de/z/box/dirbox" ) var encodings = []api.EncodingEnum{ api.EncoderHTML, | | | | | 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | "zettelstore.de/z/parser" _ "zettelstore.de/z/box/dirbox" ) var encodings = []api.EncodingEnum{ api.EncoderHTML, api.EncoderZJSON, api.EncoderNative, api.EncoderText, } func getFileBoxes(wd, kind string) (root string, boxes []box.ManagedBox) { root = filepath.Clean(filepath.Join(wd, "..", "testdata", kind)) entries, err := os.ReadDir(root) if err != nil { panic(err) |
︙ | ︙ | |||
117 118 119 120 121 122 123 | } return u.Path[len(root):] } func checkMetaFile(t *testing.T, resultName string, zn *ast.ZettelNode, enc api.EncodingEnum) { t.Helper() | | | 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | } return u.Path[len(root):] } func checkMetaFile(t *testing.T, resultName string, zn *ast.ZettelNode, enc api.EncodingEnum) { t.Helper() if enc := encoder.Create(enc, nil); enc != nil { var buf bytes.Buffer enc.WriteMeta(&buf, zn.Meta, parser.ParseMetadata) checkFileContent(t, resultName, buf.String()) return } panic(fmt.Sprintf("Unknown writer encoding %q", enc)) } |
︙ | ︙ | |||
156 157 158 159 160 161 162 163 164 165 166 167 168 169 | ss.Stop(context.Background()) } type myConfig struct{} func (*myConfig) AddDefaultValues(m *meta.Meta) *meta.Meta { return m } func (*myConfig) GetDefaultTitle() string { return "" } func (*myConfig) GetDefaultSyntax() string { return api.ValueSyntaxZmk } func (*myConfig) GetDefaultLang() string { return "" } func (*myConfig) GetDefaultVisibility() meta.Visibility { return meta.VisibilityPublic } func (*myConfig) GetFooterHTML() string { return "" } func (*myConfig) GetHomeZettel() id.Zid { return id.Invalid } func (*myConfig) GetListPageSize() int { return 0 } func (*myConfig) GetMarkerExternal() string { return "" } | > | 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | ss.Stop(context.Background()) } type myConfig struct{} func (*myConfig) AddDefaultValues(m *meta.Meta) *meta.Meta { return m } func (*myConfig) GetDefaultTitle() string { return "" } func (*myConfig) GetDefaultRole() string { return api.ValueRoleZettel } func (*myConfig) GetDefaultSyntax() string { return api.ValueSyntaxZmk } func (*myConfig) GetDefaultLang() string { return "" } func (*myConfig) GetDefaultVisibility() meta.Visibility { return meta.VisibilityPublic } func (*myConfig) GetFooterHTML() string { return "" } func (*myConfig) GetHomeZettel() id.Zid { return id.Invalid } func (*myConfig) GetListPageSize() int { return 0 } func (*myConfig) GetMarkerExternal() string { return "" } |
︙ | ︙ |
Changes to tools/build.go.
︙ | ︙ | |||
19 20 21 22 23 24 25 26 27 28 29 30 31 32 | "fmt" "io" "io/fs" "net" "os" "os/exec" "path/filepath" "strings" "time" "zettelstore.de/z/strfun" ) var directProxy = []string{"GOPROXY=direct"} | > | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | "fmt" "io" "io/fs" "net" "os" "os/exec" "path/filepath" "regexp" "strings" "time" "zettelstore.de/z/strfun" ) var directProxy = []string{"GOPROXY=direct"} |
︙ | ︙ | |||
68 69 70 71 72 73 74 | return "", err } return strings.TrimFunc(string(content), func(r rune) bool { return r <= ' ' }), nil } | < < < < < < < | | > > > > > > > > > > > | | | > > > > > | | | > > > | | > > > > > > > | 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | return "", err } return strings.TrimFunc(string(content), func(r rune) bool { return r <= ' ' }), nil } var fossilCheckout = regexp.MustCompile(`^checkout:\s+([0-9a-f]+)\s`) var dirtyPrefixes = []string{ "DELETED ", "ADDED ", "UPDATED ", "CONFLICT ", "EDITED ", "RENAMED ", "EXTRA "} const dirtySuffix = "-dirty" func readFossilVersion() (string, error) { s, err := executeCommand(nil, "fossil", "status", "--differ") if err != nil { return "", err } var hash, suffix string for _, line := range strfun.SplitLines(s) { if hash == "" { if m := fossilCheckout.FindStringSubmatch(line); len(m) > 0 { hash = m[1][:10] if suffix != "" { return hash + suffix, nil } continue } } if suffix == "" { for _, prefix := range dirtyPrefixes { if strings.HasPrefix(line, prefix) { suffix = dirtySuffix if hash != "" { return hash + suffix, nil } break } } } } return hash + suffix, nil } func getVersionData() (string, string) { base, err := readVersionFile() if err != nil { base = "dev" } if fossil, err2 := readFossilVersion(); err2 == nil { return base, fossil } return base, "" } func calcVersion(base, vcs string) string { return base + "+" + vcs } func getVersion() string { base, vcs := getVersionData() return calcVersion(base, vcs) } func findExec(cmd string) string { if path, err := executeCommand(nil, "which", cmd); err == nil && path != "" { return strings.TrimSpace(path) } return "" |
︙ | ︙ | |||
187 188 189 190 191 192 193 | } func checkUnparam(forRelease bool) error { path, err := findExecStrict("unparam", forRelease) if path == "" { return err } | | | | | | < < > > | | | | | < < < > > > | 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | } func checkUnparam(forRelease bool) error { path, err := findExecStrict("unparam", forRelease) if path == "" { return err } out, err := executeCommand(nil, path, "./...") if err != nil { fmt.Fprintln(os.Stderr, "Some unparam problems found") if len(out) > 0 { fmt.Fprintln(os.Stderr, out) } } if forRelease { if out2, err2 := executeCommand(nil, path, "-exported", "-tests", "./..."); err2 != nil { fmt.Fprintln(os.Stderr, "Some optional unparam problems found") if len(out2) > 0 { fmt.Fprintln(os.Stderr, out2) } } } return err } func findExecStrict(cmd string, forRelease bool) (string, error) { path := findExec(cmd) if path != "" || !forRelease { return path, nil |
︙ | ︙ | |||
324 325 326 327 328 329 330 | if len(out) > 0 { fmt.Println(out) } return nil } func cmdManual() error { | | | 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 | if len(out) > 0 { fmt.Println(out) } return nil } func cmdManual() error { base, _ := getReleaseVersionData() return createManualZip(".", base) } func createManualZip(path, base string) error { manualPath := filepath.Join("docs", "manual") entries, err := os.ReadDir(manualPath) if err != nil { |
︙ | ︙ | |||
375 376 377 378 379 380 381 | return err } defer manualFile.Close() _, err = io.Copy(w, manualFile) return err } | | < < < | | > > > | | | | 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 | return err } defer manualFile.Close() _, err = io.Copy(w, manualFile) return err } func getReleaseVersionData() (string, string) { base, fossil := getVersionData() if strings.HasSuffix(base, "dev") { base = base[:len(base)-3] + "preview-" + time.Now().Format("20060102") } if strings.HasSuffix(fossil, dirtySuffix) { fmt.Fprintf(os.Stderr, "Warning: releasing a dirty version %v\n", fossil) } return base, fossil } func cmdRelease() error { if err := cmdCheck(true); err != nil { return err } base, fossil := getReleaseVersionData() releases := []struct { arch string os string env []string name string }{ {"amd64", "linux", nil, "zettelstore"}, {"arm", "linux", []string{"GOARM=6"}, "zettelstore"}, {"amd64", "darwin", nil, "zettelstore"}, {"arm64", "darwin", nil, "zettelstore"}, {"amd64", "windows", nil, "zettelstore.exe"}, } for _, rel := range releases { env := append([]string{}, rel.env...) env = append(env, "GOARCH="+rel.arch, "GOOS="+rel.os) env = append(env, directProxy...) zsName := filepath.Join("releases", rel.name) if err := doBuild(env, calcVersion(base, fossil), zsName); err != nil { return err } zipName := fmt.Sprintf("zettelstore-%v-%v-%v.zip", base, rel.os, rel.arch) if err := createReleaseZip(zsName, zipName, rel.name); err != nil { return err } if err := os.Remove(zsName); err != nil { |
︙ | ︙ |
Changes to usecase/create_zettel.go.
︙ | ︙ | |||
60 61 62 63 64 65 66 | // PrepareFolge the zettel for further modification. func (uc *CreateZettel) PrepareFolge(origZettel domain.Zettel) domain.Zettel { origMeta := origZettel.Meta m := meta.New(id.Invalid) if title, ok := origMeta.Get(api.KeyTitle); ok { m.Set(api.KeyTitle, prependTitle(title, "Folge", "Folge of ")) } | | | | | | | | | 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | // PrepareFolge the zettel for further modification. func (uc *CreateZettel) PrepareFolge(origZettel domain.Zettel) domain.Zettel { origMeta := origZettel.Meta m := meta.New(id.Invalid) if title, ok := origMeta.Get(api.KeyTitle); ok { m.Set(api.KeyTitle, prependTitle(title, "Folge", "Folge of ")) } m.Set(api.KeyRole, config.GetRole(origMeta, uc.rtConfig)) m.Set(api.KeyTags, origMeta.GetDefault(api.KeyTags, "")) m.Set(api.KeySyntax, uc.rtConfig.GetDefaultSyntax()) m.Set(api.KeyPrecursor, origMeta.Zid.String()) return domain.Zettel{Meta: m, Content: domain.NewContent(nil)} } // PrepareNew the zettel for further modification. func (*CreateZettel) PrepareNew(origZettel domain.Zettel) domain.Zettel { m := meta.New(id.Invalid) om := origZettel.Meta m.Set(api.KeyTitle, om.GetDefault(api.KeyTitle, "")) m.Set(api.KeyRole, om.GetDefault(api.KeyRole, "")) m.Set(api.KeyTags, om.GetDefault(api.KeyTags, "")) m.Set(api.KeySyntax, om.GetDefault(api.KeySyntax, "")) const prefixLen = len(meta.NewPrefix) for _, pair := range om.PairsRest() { if key := pair.Key; len(key) > prefixLen && key[0:prefixLen] == meta.NewPrefix { m.Set(key[prefixLen:], pair.Value) } } |
︙ | ︙ | |||
110 111 112 113 114 115 116 | // Run executes the use case. func (uc *CreateZettel) Run(ctx context.Context, zettel domain.Zettel) (id.Zid, error) { m := zettel.Meta if m.Zid.IsValid() { return m.Zid, nil // TODO: new error: already exists } if title, ok := m.Get(api.KeyTitle); !ok || title == "" { | | > > > | < | 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | // Run executes the use case. func (uc *CreateZettel) Run(ctx context.Context, zettel domain.Zettel) (id.Zid, error) { m := zettel.Meta if m.Zid.IsValid() { return m.Zid, nil // TODO: new error: already exists } if title, ok := m.Get(api.KeyTitle); !ok || title == "" { m.Set(api.KeyTitle, uc.rtConfig.GetDefaultTitle()) } if role, ok := m.Get(api.KeyRole); !ok || role == "" { m.Set(api.KeyRole, uc.rtConfig.GetDefaultRole()) } if syntax, ok := m.Get(api.KeySyntax); !ok || syntax == "" { m.Set(api.KeySyntax, uc.rtConfig.GetDefaultSyntax()) } m.Delete(api.KeyModified) m.YamlSep = uc.rtConfig.GetYAMLHeader() zettel.Content.TrimSpace() zid, err := uc.port.CreateZettel(ctx, zettel) uc.log.Info().User(ctx).Zid(zid).Err(err).Msg("Create zettel") return zid, err } |
Changes to usecase/evaluate.go.
︙ | ︙ | |||
35 36 37 38 39 40 41 | rtConfig: rtConfig, getZettel: getZettel, getMeta: getMeta, } } // Run executes the use case. | | | | | | 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | rtConfig: rtConfig, getZettel: getZettel, getMeta: getMeta, } } // Run executes the use case. func (uc *Evaluate) Run(ctx context.Context, zid id.Zid, syntax string, env *evaluator.Environment) (*ast.ZettelNode, error) { zettel, err := uc.getZettel.Run(ctx, zid) if err != nil { return nil, err } zn, err := parser.ParseZettel(zettel, syntax, uc.rtConfig), nil if err != nil { return nil, err } evaluator.EvaluateZettel(ctx, uc, env, uc.rtConfig, zn) return zn, nil } // RunMetadata executes the use case for a metadata value. func (uc *Evaluate) RunMetadata(ctx context.Context, value string, env *evaluator.Environment) ast.InlineSlice { is := parser.ParseMetadata(value) evaluator.EvaluateInline(ctx, uc, env, uc.rtConfig, &is) return is } // GetMeta retrieves the metadata of a given zettel identifier. func (uc *Evaluate) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { return uc.getMeta.Run(ctx, zid) } // GetZettel retrieves the full zettel of a given zettel identifier. func (uc *Evaluate) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) { return uc.getZettel.Run(ctx, zid) } |
Added usecase/list_meta.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package usecase import ( "context" "zettelstore.de/z/domain/meta" "zettelstore.de/z/search" ) // ListMetaPort is the interface used by this use case. type ListMetaPort interface { // SelectMeta returns all zettel meta data that match the selection criteria. SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) } // ListMeta is the data for this use case. type ListMeta struct { port ListMetaPort } // NewListMeta creates a new use case. func NewListMeta(port ListMetaPort) ListMeta { return ListMeta{port: port} } // Run executes the use case. func (uc ListMeta) Run(ctx context.Context, s *search.Search) ([]*meta.Meta, error) { return uc.port.SelectMeta(ctx, s) } |
Added usecase/list_role.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 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 usecase import ( "context" "sort" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/domain/meta" "zettelstore.de/z/search" "zettelstore.de/z/strfun" ) // ListRolePort is the interface used by this use case. type ListRolePort interface { // SelectMeta returns all zettel meta data that match the selection criteria. SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) } // ListRole is the data for this use case. type ListRole struct { port ListRolePort } // NewListRole creates a new use case. func NewListRole(port ListRolePort) ListRole { return ListRole{port: port} } // Run executes the use case. func (uc ListRole) Run(ctx context.Context) ([]string, error) { metas, err := uc.port.SelectMeta(box.NoEnrichContext(ctx), nil) if err != nil { return nil, err } roles := make(strfun.Set, 256) for _, m := range metas { if role, ok := m.Get(api.KeyRole); ok && role != "" { roles.Set(role) } } result := make([]string, 0, len(roles)) for role := range roles { result = append(result, role) } sort.Strings(result) return result, nil } |
Added usecase/list_tags.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package usecase import ( "context" "zettelstore.de/c/api" "zettelstore.de/z/domain/meta" "zettelstore.de/z/search" ) // ListTagsPort is the interface used by this use case. type ListTagsPort interface { // SelectMeta returns all zettel meta data that match the selection criteria. SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) } // ListTags is the data for this use case. type ListTags struct { port ListTagsPort } // NewListTags creates a new use case. func NewListTags(port ListTagsPort) ListTags { return ListTags{port: port} } // TagData associates tags with a list of all zettel meta that use this tag type TagData map[string][]*meta.Meta // Run executes the use case. func (uc ListTags) Run(ctx context.Context, minCount int) (TagData, error) { metas, err := uc.port.SelectMeta(ctx, nil) if err != nil { return nil, err } result := make(TagData) for _, m := range metas { if tl, ok := m.GetList(api.KeyAllTags); ok && len(tl) > 0 { for _, t := range tl { result[t] = append(result[t], m) } } } if minCount > 1 { for t, ms := range result { if len(ms) < minCount { delete(result, t) } } } return result, nil } |
Deleted usecase/lists.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to usecase/order.go.
1 | //----------------------------------------------------------------------------- | | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // 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 usecase |
︙ | ︙ | |||
35 36 37 38 39 40 41 | return ZettelOrder{port: port, evaluate: evaluate} } // Run executes the use case. func (uc ZettelOrder) Run(ctx context.Context, zid id.Zid, syntax string) ( start *meta.Meta, result []*meta.Meta, err error, ) { | | | 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | return ZettelOrder{port: port, evaluate: evaluate} } // Run executes the use case. func (uc ZettelOrder) Run(ctx context.Context, zid id.Zid, syntax string) ( start *meta.Meta, result []*meta.Meta, err error, ) { zn, err := uc.evaluate.Run(ctx, zid, syntax, nil) if err != nil { return nil, nil, err } for _, ref := range collect.Order(zn) { if collectedZid, err2 := id.Parse(ref.URL.Path); err2 == nil { if m, err3 := uc.port.GetMeta(ctx, collectedZid); err3 == nil { result = append(result, m) } } } return zn.Meta, result, nil } |
Changes to usecase/unlinked_refs.go.
︙ | ︙ | |||
17 18 19 20 21 22 23 | "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/config" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" | | | | | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/config" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" "zettelstore.de/z/evaluator" "zettelstore.de/z/parser" "zettelstore.de/z/search" ) // UnlinkedReferencesPort is the interface used by this use case. type UnlinkedReferencesPort interface { GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) SelectMeta(ctx context.Context, s *search.Search) ([]*meta.Meta, error) } // UnlinkedReferences is the data for this use case. type UnlinkedReferences struct { port UnlinkedReferencesPort rtConfig config.Config encText encoder.Encoder } // NewUnlinkedReferences creates a new use case. func NewUnlinkedReferences(port UnlinkedReferencesPort, rtConfig config.Config) UnlinkedReferences { return UnlinkedReferences{ port: port, rtConfig: rtConfig, encText: encoder.Create(api.EncoderText, nil), } } // Run executes the usecase with already evaluated title value. func (uc *UnlinkedReferences) Run(ctx context.Context, title string, s *search.Search) ([]*meta.Meta, error) { words := makeWords(title) if len(words) == 0 { |
︙ | ︙ | |||
93 94 95 96 97 98 99 | v.text = v.joinWords(words) for _, pair := range zettel.Meta.Pairs() { if meta.Type(pair.Key) != meta.TypeZettelmarkup { continue } is := parser.ParseMetadata(pair.Value) | | | | 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | v.text = v.joinWords(words) for _, pair := range zettel.Meta.Pairs() { if meta.Type(pair.Key) != meta.TypeZettelmarkup { continue } is := parser.ParseMetadata(pair.Value) evaluator.EvaluateInline(ctx, uc.port, nil, uc.rtConfig, &is) ast.Walk(&v, &is) if v.found { result = append(result, cand) continue candLoop } } syntax := zettel.Meta.GetDefault(api.KeySyntax, "") if !parser.IsTextParser(syntax) { continue } zn, err := parser.ParseZettel(zettel, syntax, nil), nil if err != nil { continue } evaluator.EvaluateZettel(ctx, uc.port, nil, uc.rtConfig, zn) ast.Walk(&v, &zn.Ast) if v.found { result = append(result, cand) } } return result } |
︙ | ︙ |
Changes to web/adapter/api/content_type.go.
︙ | ︙ | |||
17 18 19 20 21 22 23 | ctHTML = "text/html; charset=utf-8" ctJSON = "application/json" ctPlainText = "text/plain; charset=utf-8" ctSVG = "image/svg+xml" ) var mapEncoding2CT = map[api.EncodingEnum]string{ | | | < | > | > | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | ctHTML = "text/html; charset=utf-8" ctJSON = "application/json" ctPlainText = "text/plain; charset=utf-8" ctSVG = "image/svg+xml" ) var mapEncoding2CT = map[api.EncodingEnum]string{ api.EncoderHTML: ctHTML, api.EncoderNative: ctPlainText, api.EncoderZJSON: ctJSON, api.EncoderText: ctPlainText, api.EncoderZmk: ctPlainText, } func encoding2ContentType(enc api.EncodingEnum) string { if ct, ok := mapEncoding2CT[enc]; ok { return ct } return "application/octet-stream" } var mapSyntax2CT = map[string]string{ "css": "text/css; charset=utf-8", api.ValueSyntaxDraw: ctSVG, api.ValueSyntaxGif: "image/gif", api.ValueSyntaxHTML: "text/html; charset=utf-8", "jpeg": "image/jpeg", "jpg": "image/jpeg", "js": "text/javascript; charset=utf-8", "pdf": "application/pdf", "png": "image/png", |
︙ | ︙ |
Changes to web/adapter/api/get_eval_zettel.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package api import ( "net/http" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/domain/id" "zettelstore.de/z/encoder" "zettelstore.de/z/usecase" ) // MakeGetEvalZettelHandler creates a new HTTP handler to return a evaluated zettel. func (a *API) MakeGetEvalZettelHandler(evaluate usecase.Evaluate) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { zid, err := id.Parse(r.URL.Path[1:]) if err != nil { http.NotFound(w, r) return } ctx := r.Context() q := r.URL.Query() | > > > | > > > > > > > > > | | | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | package api import ( "net/http" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/encoder" "zettelstore.de/z/evaluator" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeGetEvalZettelHandler creates a new HTTP handler to return a evaluated zettel. func (a *API) MakeGetEvalZettelHandler(evaluate usecase.Evaluate) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { zid, err := id.Parse(r.URL.Path[1:]) if err != nil { http.NotFound(w, r) return } ctx := r.Context() q := r.URL.Query() enc, encStr := adapter.GetEncoding(r, q, encoder.GetDefaultEncoding()) part := getPart(q, partContent) var env evaluator.Environment if q.Has(api.QueryKeyEmbed) { env.GetImageMaterial = func(zettel domain.Zettel, syntax string) ast.InlineEmbedNode { return &ast.EmbedBLOBNode{ Blob: zettel.Content.AsBytes(), Syntax: syntax, } } } zn, err := evaluate.Run(ctx, zid, q.Get(api.KeySyntax), &env) if err != nil { a.reportUsecaseError(w, err) return } evalMeta := func(value string) ast.InlineSlice { return evaluate.RunMetadata(ctx, value, &env) } a.writeEncodedZettelPart(w, zn, evalMeta, enc, encStr, part) } } |
Changes to web/adapter/api/get_parsed_zettel.go.
︙ | ︙ | |||
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | import ( "bytes" "fmt" "net/http" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/domain/id" "zettelstore.de/z/encoder" "zettelstore.de/z/parser" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeGetParsedZettelHandler creates a new HTTP handler to return a parsed zettel. func (a *API) MakeGetParsedZettelHandler(parseZettel usecase.ParseZettel) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { zid, err := id.Parse(r.URL.Path[1:]) if err != nil { http.NotFound(w, r) return } q := r.URL.Query() | > > | > > > > > > > | | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | import ( "bytes" "fmt" "net/http" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/encoder" "zettelstore.de/z/parser" "zettelstore.de/z/strfun" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeGetParsedZettelHandler creates a new HTTP handler to return a parsed zettel. func (a *API) MakeGetParsedZettelHandler(parseZettel usecase.ParseZettel) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { zid, err := id.Parse(r.URL.Path[1:]) if err != nil { http.NotFound(w, r) return } q := r.URL.Query() enc, encStr := adapter.GetEncoding(r, q, encoder.GetDefaultEncoding()) part := getPart(q, partContent) zn, err := parseZettel.Run(r.Context(), zid, q.Get(api.KeySyntax)) if err != nil { a.reportUsecaseError(w, err) return } a.writeEncodedZettelPart(w, zn, parser.ParseMetadata, enc, encStr, part) } } func (a *API) writeEncodedZettelPart( w http.ResponseWriter, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc, enc api.EncodingEnum, encStr string, part partType, ) { env := encoder.Environment{ Lang: config.GetLang(zn.InhMeta, a.rtConfig), Xhtml: false, MarkerExternal: "", NewWindow: false, IgnoreMeta: strfun.NewSet(api.KeyLang), } encdr := encoder.Create(enc, &env) if encdr == nil { adapter.BadRequest(w, fmt.Sprintf("Zettel %q not available in encoding %q", zn.Meta.Zid.String(), encStr)) return } var err error var buf bytes.Buffer switch part { |
︙ | ︙ |
Changes to web/adapter/api/get_role_list.go.
1 | //----------------------------------------------------------------------------- | | | | | < < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- // Package api provides api handlers for web requests. package api import ( "bytes" "net/http" "zettelstore.de/c/api" "zettelstore.de/z/usecase" ) // MakeListRoleHandler creates a new HTTP handler for the use case "list roles". func (a *API) MakeListRoleHandler(listRole usecase.ListRole) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { roleList, err := listRole.Run(r.Context()) if err != nil { a.reportUsecaseError(w, err) return } var buf bytes.Buffer err = encodeJSONData(&buf, api.RoleListJSON{Roles: roleList}) if err != nil { a.log.Fatal().Err(err).Msg("Unable to store role list in buffer") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } err = writeBuffer(w, &buf, ctJSON) a.log.IfErr(err).Msg("Write Roles") } } |
Changes to web/adapter/api/get_unlinked_refs.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 | import ( "bytes" "net/http" "strings" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" | | | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import ( "bytes" "net/http" "strings" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/encoder" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeListUnlinkedMetaHandler creates a new HTTP handler for the use case "list unlinked references". func (a *API) MakeListUnlinkedMetaHandler( getMeta usecase.GetMeta, |
︙ | ︙ | |||
41 42 43 44 45 46 47 | return } q := r.URL.Query() phrase := q.Get(api.QueryKeyPhrase) if phrase == "" { zmkTitle := zm.GetDefault(api.KeyTitle, "") | | | | 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | return } q := r.URL.Query() phrase := q.Get(api.QueryKeyPhrase) if phrase == "" { zmkTitle := zm.GetDefault(api.KeyTitle, "") isTitle := evaluate.RunMetadata(ctx, zmkTitle, nil) encdr := encoder.Create(api.EncoderText, nil) var b strings.Builder _, err = encdr.WriteInlines(&b, &isTitle) if err == nil { phrase = b.String() } } |
︙ | ︙ |
Changes to web/adapter/api/request.go.
1 | //----------------------------------------------------------------------------- | | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- // Package api provides api handlers for web requests. |
︙ | ︙ | |||
19 20 21 22 23 24 25 | "zettelstore.de/c/api" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" ) | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | "zettelstore.de/c/api" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" ) type partType int const ( _ partType = iota partMeta partContent partZettel |
︙ | ︙ |
Changes to web/adapter/request.go.
︙ | ︙ | |||
45 46 47 48 49 50 51 52 53 54 55 56 57 58 | if s != "" { if val, err := strconv.Atoi(s); err == nil { return val, true } } return 0, false } // GetSearch retrieves the specified search and sorting options from a query. func GetSearch(q url.Values) (s *search.Search) { for key, values := range q { switch key { case api.QueryKeySort, api.QueryKeyOrder: s = extractOrderFromQuery(values, s) | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | if s != "" { if val, err := strconv.Atoi(s); err == nil { return val, true } } return 0, false } // GetEncoding returns the data encoding selected by the caller. func GetEncoding(r *http.Request, q url.Values, defEncoding api.EncodingEnum) (api.EncodingEnum, string) { encoding := q.Get(api.QueryKeyEncoding) if len(encoding) > 0 { return api.Encoder(encoding), encoding } if enc, ok := getOneEncoding(r, api.HeaderAccept); ok { return api.Encoder(enc), enc } if enc, ok := getOneEncoding(r, api.HeaderContentType); ok { return api.Encoder(enc), enc } return defEncoding, defEncoding.String() } func getOneEncoding(r *http.Request, key string) (string, bool) { if values, ok := r.Header[key]; ok { for _, value := range values { if enc, ok2 := contentType2encoding(value); ok2 { return enc, true } } } return "", false } var mapCT2encoding = map[string]string{ "application/json": "json", "text/html": api.EncodingHTML, } func contentType2encoding(contentType string) (string, bool) { // TODO: only check before first ';' enc, ok := mapCT2encoding[contentType] return enc, ok } // GetSearch retrieves the specified search and sorting options from a query. func GetSearch(q url.Values) (s *search.Search) { for key, values := range q { switch key { case api.QueryKeySort, api.QueryKeyOrder: s = extractOrderFromQuery(values, s) |
︙ | ︙ |
Changes to web/adapter/response.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import ( "errors" "fmt" "net/http" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/usecase" ) // PrepareHeader sets the HTTP header to defined values. func PrepareHeader(w http.ResponseWriter, contentType string) http.Header { h := w.Header() if contentType != "" { h.Set(api.HeaderContentType, contentType) | > > > | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import ( "errors" "fmt" "net/http" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/usecase" "zettelstore.de/z/web/server" ) // PrepareHeader sets the HTTP header to defined values. func PrepareHeader(w http.ResponseWriter, contentType string) http.Header { h := w.Header() if contentType != "" { h.Set(api.HeaderContentType, contentType) |
︙ | ︙ | |||
64 65 66 67 68 69 70 | return http.StatusConflict, "Zettelstore operations conflicted" } if errors.Is(err, box.ErrCapacity) { return http.StatusInsufficientStorage, "Zettelstore reached one of its storage limits" } return http.StatusInternalServerError, err.Error() } | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | return http.StatusConflict, "Zettelstore operations conflicted" } if errors.Is(err, box.ErrCapacity) { return http.StatusInsufficientStorage, "Zettelstore reached one of its storage limits" } return http.StatusInternalServerError, err.Error() } // CreateTagReference builds a reference to list all tags. func CreateTagReference(b server.Builder, key byte, enc, s string) *ast.Reference { u := b.NewURLBuilder(key).AppendQuery(api.QueryKeyEncoding, enc).AppendQuery(api.KeyAllTags, s) ref := ast.ParseReference(u.String()) ref.State = ast.RefStateHosted return ref } // CreateHostedReference builds a reference with state "hosted". func CreateHostedReference(b server.Builder, s string) *ast.Reference { urlPrefix := b.GetURLPrefix() ref := ast.ParseReference(urlPrefix + s) ref.State = ast.RefStateHosted return ref } // CreateFoundReference builds a reference for a found zettel. func CreateFoundReference(b server.Builder, key byte, part, enc string, zid id.Zid, fragment string) *ast.Reference { ub := b.NewURLBuilder(key).SetZid(api.ZettelID(zid.String())) if part != "" { ub.AppendQuery(api.QueryKeyPart, part) } if enc != "" { ub.AppendQuery(api.QueryKeyEncoding, enc) } if fragment != "" { ub.SetFragment(fragment) } ref := ast.ParseReference(ub.String()) ref.State = ast.RefStateFound return ref } |
Changes to web/adapter/webui/create_zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package webui import ( | | > | > > > > > < | | | > > > > > > < < < < < | | < < < < < | < | > < > < > | < < | < < < | | | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package webui import ( "fmt" "net/http" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/encoder" "zettelstore.de/z/parser" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeGetCreateZettelHandler creates a new HTTP handler to display the // HTML edit view for the various zettel creation methods. func (wui *WebUI) MakeGetCreateZettelHandler(getZettel usecase.GetZettel, createZettel *usecase.CreateZettel) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() q := r.URL.Query() op := getCreateAction(q.Get(queryKeyAction)) if enc, encText := adapter.GetEncoding(r, q, api.EncoderHTML); enc != api.EncoderHTML { wui.reportError(ctx, w, adapter.NewErrBadRequest( fmt.Sprintf("%v zettel not possible in encoding %q", mapActionOp[op], encText))) return } zid, err := id.Parse(r.URL.Path[1:]) if err != nil { wui.reportError(ctx, w, box.ErrNotFound) return } origZettel, err := getZettel.Run(box.NoEnrichContext(ctx), zid) if err != nil { wui.reportError(ctx, w, box.ErrNotFound) return } switch op { case actionCopy: wui.renderZettelForm(w, r, createZettel.PrepareCopy(origZettel), "Copy Zettel", "Copy Zettel") case actionFolge: wui.renderZettelForm(w, r, createZettel.PrepareFolge(origZettel), "Folge Zettel", "Folgezettel") case actionNew: m := origZettel.Meta title := parser.ParseMetadata(config.GetTitle(m, wui.rtConfig)) textTitle, err2 := encodeInlines(&title, api.EncoderText, nil) if err2 != nil { wui.reportError(ctx, w, err2) return } env := encoder.Environment{Lang: config.GetLang(m, wui.rtConfig)} htmlTitle, err2 := encodeInlines(&title, api.EncoderHTML, &env) if err2 != nil { wui.reportError(ctx, w, err2) return } wui.renderZettelForm(w, r, createZettel.PrepareNew(origZettel), textTitle, htmlTitle) } } } var mapActionOp = map[createAction]string{ actionCopy: "Copy", actionFolge: "Folge", actionNew: "New", } func (wui *WebUI) renderZettelForm( w http.ResponseWriter, r *http.Request, zettel domain.Zettel, title, heading string, ) { ctx := r.Context() user := wui.getUser(ctx) m := zettel.Meta var base baseData wui.makeBaseData(ctx, config.GetLang(m, wui.rtConfig), title, user, &base) wui.renderTemplate(ctx, w, id.FormTemplateZid, &base, formZettelData{ Heading: heading, MetaTitle: m.GetDefault(api.KeyTitle, ""), MetaTags: m.GetDefault(api.KeyTags, ""), MetaRole: config.GetRole(m, wui.rtConfig), MetaSyntax: config.GetSyntax(m, wui.rtConfig), MetaPairsRest: m.PairsRest(), IsTextContent: !zettel.Content.IsBinary(), Content: zettel.Content.AsString(), }) } // MakePostCreateZettelHandler creates a new HTTP handler to store content of // an existing zettel. func (wui *WebUI) MakePostCreateZettelHandler(createZettel *usecase.CreateZettel) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() zettel, hasContent, err := parseZettelForm(r, id.Invalid) if err != nil { wui.reportError(ctx, w, adapter.NewErrBadRequest("Unable to read form data")) return } if !hasContent { wui.reportError(ctx, w, adapter.NewErrBadRequest("Content is missing")) return } newZid, err := createZettel.Run(ctx, zettel) if err != nil { wui.reportError(ctx, w, err) return } wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(api.ZettelID(newZid.String()))) } } |
Changes to web/adapter/webui/delete_zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package webui import ( "net/http" "zettelstore.de/c/api" | > > < > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package webui import ( "fmt" "net/http" "sort" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/strfun" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeGetDeleteZettelHandler creates a new HTTP handler to display the // HTML delete view of a zettel. func (wui *WebUI) MakeGetDeleteZettelHandler( getMeta usecase.GetMeta, getAllMeta usecase.GetAllMeta, evaluate *usecase.Evaluate, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if enc, encText := adapter.GetEncoding(r, r.URL.Query(), api.EncoderHTML); enc != api.EncoderHTML { wui.reportError(ctx, w, adapter.NewErrBadRequest( fmt.Sprintf("Delete zettel not possible in encoding %q", encText))) return } zid, err := id.Parse(r.URL.Path[1:]) if err != nil { wui.reportError(ctx, w, box.ErrNotFound) return } ms, err := getAllMeta.Run(ctx, zid) |
︙ | ︙ | |||
53 54 55 56 57 58 59 | getTextTitle := wui.makeGetTextTitle(ctx, getMeta, evaluate) incomingLinks = wui.encodeIncoming(m, getTextTitle) } uselessFiles := retrieveUselessFiles(m) user := wui.getUser(ctx) var base baseData | | | 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | getTextTitle := wui.makeGetTextTitle(ctx, getMeta, evaluate) incomingLinks = wui.encodeIncoming(m, getTextTitle) } uselessFiles := retrieveUselessFiles(m) user := wui.getUser(ctx) var base baseData wui.makeBaseData(ctx, config.GetLang(m, wui.rtConfig), "Delete Zettel "+m.Zid.String(), user, &base) wui.renderTemplate(ctx, w, id.DeleteTemplateZid, &base, struct { Zid string MetaPairs []meta.Pair HasShadows bool ShadowedBox string HasIncoming bool Incoming []simpleLink |
︙ | ︙ | |||
119 120 121 122 123 124 125 | if val, ok := m.Get(inverseKey); ok { zidMap.Set(val) } case meta.TypeIDSet: addListValues(zidMap, m, inverseKey) } } | > > > > > | | 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | if val, ok := m.Get(inverseKey); ok { zidMap.Set(val) } case meta.TypeIDSet: addListValues(zidMap, m, inverseKey) } } values := make([]string, 0, len(zidMap)) for val := range zidMap { values = append(values, val) } sort.Strings(values) return wui.encodeZidLinks(values, getTextTitle) } func addListValues(zidMap strfun.Set, m *meta.Meta, key string) { if values, ok := m.GetList(key); ok { for _, val := range values { zidMap.Set(val) } } } |
Changes to web/adapter/webui/edit_zettel.go.
1 2 3 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // | | > > | > > > > > | > > > | > > > > > > > > > > | < < < < | | | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package webui import ( "fmt" "net/http" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeEditGetZettelHandler creates a new HTTP handler to display the // HTML edit view of a zettel. func (wui *WebUI) MakeEditGetZettelHandler(getZettel usecase.GetZettel) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() zid, err := id.Parse(r.URL.Path[1:]) if err != nil { wui.reportError(ctx, w, box.ErrNotFound) return } zettel, err := getZettel.Run(box.NoEnrichContext(ctx), zid) if err != nil { wui.reportError(ctx, w, err) return } if enc, encText := adapter.GetEncoding(r, r.URL.Query(), api.EncoderHTML); enc != api.EncoderHTML { wui.reportError(ctx, w, adapter.NewErrBadRequest( fmt.Sprintf("Edit zettel %q not possible in encoding %q", zid, encText))) return } user := wui.getUser(ctx) m := zettel.Meta var base baseData wui.makeBaseData(ctx, config.GetLang(m, wui.rtConfig), "Edit Zettel", user, &base) wui.renderTemplate(ctx, w, id.FormTemplateZid, &base, formZettelData{ Heading: base.Title, MetaTitle: m.GetDefault(api.KeyTitle, ""), MetaRole: m.GetDefault(api.KeyRole, ""), MetaTags: m.GetDefault(api.KeyTags, ""), MetaSyntax: m.GetDefault(api.KeySyntax, ""), MetaPairsRest: m.PairsRest(), IsTextContent: !zettel.Content.IsBinary(), Content: zettel.Content.AsString(), }) } } // MakeEditSetZettelHandler creates a new HTTP handler to store content of // an existing zettel. func (wui *WebUI) MakeEditSetZettelHandler(updateZettel *usecase.UpdateZettel) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() zid, err := id.Parse(r.URL.Path[1:]) if err != nil { wui.reportError(ctx, w, box.ErrNotFound) return } zettel, hasContent, err := parseZettelForm(r, zid) if err != nil { wui.reportError(ctx, w, adapter.NewErrBadRequest("Unable to read zettel form")) return } if err = updateZettel.Run(r.Context(), zettel, hasContent); err != nil { wui.reportError(ctx, w, err) return } wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(api.ZettelID(zid.String()))) } } |
Changes to web/adapter/webui/forms.go.
︙ | ︙ | |||
23 24 25 26 27 28 29 | "zettelstore.de/z/input" ) type formZettelData struct { Heading string MetaTitle string MetaRole string | < < | | < | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | "zettelstore.de/z/input" ) type formZettelData struct { Heading string MetaTitle string MetaRole string MetaTags string MetaSyntax string MetaPairsRest []meta.Pair IsTextContent bool Content string } var ( bsCRLF = []byte{'\r', '\n'} bsLF = []byte{'\n'} ) func parseZettelForm(r *http.Request, zid id.Zid) (domain.Zettel, bool, error) { err := r.ParseForm() if err != nil { return domain.Zettel{}, false, err } var m *meta.Meta if postMeta, ok := trimmedFormValue(r, "meta"); ok { m = meta.NewFromInput(zid, input.NewInput(removeEmptyLines([]byte(postMeta)))) m.Sanitize() } else { m = meta.New(zid) |
︙ | ︙ | |||
66 67 68 69 70 71 72 | if postRole, ok := trimmedFormValue(r, "role"); ok { m.Set(api.KeyRole, meta.RemoveNonGraphic(postRole)) } if postSyntax, ok := trimmedFormValue(r, "syntax"); ok { m.Set(api.KeySyntax, meta.RemoveNonGraphic(postSyntax)) } if values, ok := r.PostForm["content"]; ok && len(values) > 0 { | | | | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | if postRole, ok := trimmedFormValue(r, "role"); ok { m.Set(api.KeyRole, meta.RemoveNonGraphic(postRole)) } if postSyntax, ok := trimmedFormValue(r, "syntax"); ok { m.Set(api.KeySyntax, meta.RemoveNonGraphic(postSyntax)) } if values, ok := r.PostForm["content"]; ok && len(values) > 0 { return domain.Zettel{ Meta: m, Content: domain.NewContent(bytes.ReplaceAll([]byte(values[0]), bsCRLF, bsLF)), }, true, nil } return domain.Zettel{ Meta: m, Content: domain.NewContent(nil), }, false, nil } func trimmedFormValue(r *http.Request, key string) (string, bool) { if values, ok := r.PostForm[key]; ok && len(values) > 0 { |
︙ | ︙ |
Changes to web/adapter/webui/get_info.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | //----------------------------------------------------------------------------- package webui import ( "bytes" "context" "net/http" "sort" "strings" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/collect" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/encoder" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) type metaDataInfo struct { Key string Value string | > > > | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | //----------------------------------------------------------------------------- package webui import ( "bytes" "context" "fmt" "net/http" "sort" "strings" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/collect" "zettelstore.de/z/config" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/encoder" "zettelstore.de/z/evaluator" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) type metaDataInfo struct { Key string Value string |
︙ | ︙ | |||
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | getMeta usecase.GetMeta, getAllMeta usecase.GetAllMeta, unlinkedRefs usecase.UnlinkedReferences, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() q := r.URL.Query() zid, err := id.Parse(r.URL.Path[1:]) if err != nil { wui.reportError(ctx, w, box.ErrNotFound) return } zn, err := parseZettel.Run(ctx, zid, q.Get(api.KeySyntax)) if err != nil { wui.reportError(ctx, w, err) return } | > > > > > > > > > > > > > > > > > > > > | | | | | | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | getMeta usecase.GetMeta, getAllMeta usecase.GetAllMeta, unlinkedRefs usecase.UnlinkedReferences, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() q := r.URL.Query() if enc, encText := adapter.GetEncoding(r, q, api.EncoderHTML); enc != api.EncoderHTML { wui.reportError(ctx, w, adapter.NewErrBadRequest( fmt.Sprintf("Zettel info not available in encoding %q", encText))) return } zid, err := id.Parse(r.URL.Path[1:]) if err != nil { wui.reportError(ctx, w, box.ErrNotFound) return } zn, err := parseZettel.Run(ctx, zid, q.Get(api.KeySyntax)) if err != nil { wui.reportError(ctx, w, err) return } envEval := evaluator.Environment{ GetTagRef: func(s string) *ast.Reference { return adapter.CreateTagReference(wui, 'h', api.EncodingHTML, s) }, GetHostedRef: func(s string) *ast.Reference { return adapter.CreateHostedReference(wui, s) }, GetFoundRef: func(zid id.Zid, fragment string) *ast.Reference { return adapter.CreateFoundReference(wui, 'h', "", "", zid, fragment) }, GetImageMaterial: func(zettel domain.Zettel, _ string) ast.InlineEmbedNode { return wui.createImageMaterial(zettel.Meta.Zid) }, } lang := config.GetLang(zn.InhMeta, wui.rtConfig) envHTML := encoder.Environment{Lang: lang} pairs := zn.Meta.ComputedPairs() metaData := make([]metaDataInfo, len(pairs)) getTextTitle := wui.makeGetTextTitle(ctx, getMeta, evaluate) for i, p := range pairs { var buf bytes.Buffer wui.writeHTMLMetaValue( &buf, p.Key, p.Value, getTextTitle, func(val string) ast.InlineSlice { return evaluate.RunMetadata(ctx, val, &envEval) }, &envHTML) metaData[i] = metaDataInfo{p.Key, buf.String()} } summary := collect.References(zn) locLinks, extLinks := splitLocExtLinks(append(summary.Links, summary.Embeds...)) textTitle := wui.encodeTitleAsText(ctx, zn.InhMeta, evaluate) phrase := q.Get(api.QueryKeyPhrase) if phrase == "" { phrase = textTitle } phrase = strings.TrimSpace(phrase) unlinkedMeta, err := unlinkedRefs.Run( ctx, phrase, adapter.AddUnlinkedRefsToSearch(nil, zn.InhMeta)) if err != nil { wui.reportError(ctx, w, err) return } unLinks := wui.buildHTMLMetaList(ctx, unlinkedMeta, evaluate) shadowLinks := getShadowLinks(ctx, zid, getAllMeta) endnotes, err := encodeBlocks(&ast.BlockSlice{}, api.EncoderHTML, &envHTML) if err != nil { endnotes = "" } user := wui.getUser(ctx) canCreate := wui.canCreate(ctx, user) apiZid := api.ZettelID(zid.String()) var base baseData wui.makeBaseData(ctx, lang, textTitle, user, &base) wui.renderTemplate(ctx, w, id.InfoTemplateZid, &base, struct { Zid string WebURL string ContextURL string CanWrite bool EditURL string CanFolge bool |
︙ | ︙ |
Changes to web/adapter/webui/get_zettel.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // under this license. //----------------------------------------------------------------------------- package webui import ( "bytes" "net/http" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" | > > | > > > > > > > > > > > > > > > > > | < | | < > | > > | > > > > | | | > < < < | < | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | // under this license. //----------------------------------------------------------------------------- package webui import ( "bytes" "errors" "net/http" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" "zettelstore.de/z/evaluator" "zettelstore.de/z/strfun" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeGetHTMLZettelHandler creates a new HTTP handler for the use case "get zettel". func (wui *WebUI) MakeGetHTMLZettelHandler(evaluate *usecase.Evaluate, getMeta usecase.GetMeta) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() zid, err := id.Parse(r.URL.Path[1:]) if err != nil { wui.reportError(ctx, w, box.ErrNotFound) return } q := r.URL.Query() env := evaluator.Environment{ GetTagRef: func(s string) *ast.Reference { return adapter.CreateTagReference(wui, 'h', api.EncodingHTML, s) }, GetHostedRef: func(s string) *ast.Reference { return adapter.CreateHostedReference(wui, s) }, GetFoundRef: func(zid id.Zid, fragment string) *ast.Reference { return adapter.CreateFoundReference(wui, 'h', "", "", zid, fragment) }, GetImageMaterial: func(zettel domain.Zettel, _ string) ast.InlineEmbedNode { return wui.createImageMaterial(zettel.Meta.Zid) }, } zn, err := evaluate.Run(ctx, zid, q.Get(api.KeySyntax), &env) if err != nil { wui.reportError(ctx, w, err) return } evalMeta := func(value string) ast.InlineSlice { return evaluate.RunMetadata(ctx, value, &env) } lang := config.GetLang(zn.InhMeta, wui.rtConfig) envHTML := encoder.Environment{ Lang: lang, Xhtml: false, MarkerExternal: wui.rtConfig.GetMarkerExternal(), NewWindow: true, IgnoreMeta: strfun.NewSet(api.KeyTitle, api.KeyLang), } metaHeader, err := encodeMeta(zn.InhMeta, evalMeta, api.EncoderHTML, &envHTML) if err != nil { wui.reportError(ctx, w, err) return } textTitle := wui.encodeTitleAsText(ctx, zn.InhMeta, evaluate) htmlTitle := wui.encodeTitleAsHTML(ctx, zn.InhMeta, evaluate, &env, &envHTML) htmlContent, err := encodeBlocks(&zn.Ast, api.EncoderHTML, &envHTML) if err != nil { wui.reportError(ctx, w, err) return } user := wui.getUser(ctx) roleText := zn.Meta.GetDefault(api.KeyRole, "*") tags := wui.buildTagInfos(zn.Meta) canCreate := wui.canCreate(ctx, user) getTextTitle := wui.makeGetTextTitle(ctx, getMeta, evaluate) extURL, hasExtURL := zn.Meta.Get(api.KeyURL) folgeLinks := wui.encodeZettelLinks(zn.InhMeta, api.KeyFolge, getTextTitle) backLinks := wui.encodeZettelLinks(zn.InhMeta, api.KeyBack, getTextTitle) apiZid := api.ZettelID(zid.String()) var base baseData wui.makeBaseData(ctx, lang, textTitle, user, &base) base.MetaHeader = metaHeader wui.renderTemplate(ctx, w, id.ZettelTemplateZid, &base, struct { HTMLTitle string CanWrite bool EditURL string Zid string InfoURL string RoleText string RoleURL string HasTags bool |
︙ | ︙ | |||
96 97 98 99 100 101 102 | Content string HasFolgeLinks bool FolgeLinks []simpleLink HasBackLinks bool BackLinks []simpleLink }{ HTMLTitle: htmlTitle, | < | > > | > > | > > > | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | Content string HasFolgeLinks bool FolgeLinks []simpleLink HasBackLinks bool BackLinks []simpleLink }{ HTMLTitle: htmlTitle, CanWrite: wui.canWrite(ctx, user, zn.Meta, zn.Content), EditURL: wui.NewURLBuilder('e').SetZid(apiZid).String(), Zid: zid.String(), InfoURL: wui.NewURLBuilder('i').SetZid(apiZid).String(), RoleText: roleText, RoleURL: wui.NewURLBuilder('h').AppendQuery("role", roleText).String(), HasTags: len(tags) > 0, Tags: tags, CanCopy: canCreate && !zn.Content.IsBinary(), CopyURL: wui.NewURLBuilder('c').SetZid(apiZid).AppendQuery(queryKeyAction, valueActionCopy).String(), CanFolge: canCreate, FolgeURL: wui.NewURLBuilder('c').SetZid(apiZid).AppendQuery(queryKeyAction, valueActionFolge).String(), PrecursorRefs: wui.encodeIdentifierSet(zn.InhMeta, api.KeyPrecursor, getTextTitle), ExtURL: extURL, HasExtURL: hasExtURL, ExtNewWindow: htmlAttrNewWindow(envHTML.NewWindow && hasExtURL), Content: htmlContent, HasFolgeLinks: len(folgeLinks) > 0, FolgeLinks: folgeLinks, HasBackLinks: len(backLinks) > 0, BackLinks: backLinks, }) } } // errNoSuchEncoding signals an unsupported encoding encoding var errNoSuchEncoding = errors.New("no such encoding") // encodeInlines returns a string representation of the inline slice. func encodeInlines(is *ast.InlineSlice, enc api.EncodingEnum, env *encoder.Environment) (string, error) { if is == nil { return "", nil } encdr := encoder.Create(enc, env) if encdr == nil { return "", errNoSuchEncoding } var buf bytes.Buffer _, err := encdr.WriteInlines(&buf, is) if err != nil { return "", err } return buf.String(), nil } func encodeBlocks(bs *ast.BlockSlice, enc api.EncodingEnum, env *encoder.Environment) (string, error) { encdr := encoder.Create(enc, env) if encdr == nil { return "", errNoSuchEncoding } var buf bytes.Buffer _, err := encdr.WriteBlocks(&buf, bs) if err != nil { return "", err } return buf.String(), nil } func encodeMeta( m *meta.Meta, evalMeta encoder.EvalMetaFunc, enc api.EncodingEnum, env *encoder.Environment, ) (string, error) { encdr := encoder.Create(enc, env) if encdr == nil { return "", errNoSuchEncoding } var buf bytes.Buffer _, err := encdr.WriteMeta(&buf, m, evalMeta) if err != nil { return "", err } return buf.String(), nil } func (wui *WebUI) buildTagInfos(m *meta.Meta) []simpleLink { |
︙ | ︙ |
Deleted web/adapter/webui/htmlgen.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to web/adapter/webui/htmlmeta.go.
︙ | ︙ | |||
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | "zettelstore.de/c/api" "zettelstore.de/c/html" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/usecase" ) var space = []byte{' '} type evalMetadataFunc = func(string) ast.InlineSlice func (wui *WebUI) writeHTMLMetaValue( w io.Writer, key, value string, getTextTitle getTextTitleFunc, evalMetadata evalMetadataFunc, | > > | | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | "zettelstore.de/c/api" "zettelstore.de/c/html" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" "zettelstore.de/z/evaluator" "zettelstore.de/z/usecase" ) var space = []byte{' '} type evalMetadataFunc = func(string) ast.InlineSlice func (wui *WebUI) writeHTMLMetaValue( w io.Writer, key, value string, getTextTitle getTextTitleFunc, evalMetadata evalMetadataFunc, envEnc *encoder.Environment, ) { switch kt := meta.Type(key); kt { case meta.TypeCredential: writeCredential(w, value) case meta.TypeEmpty: writeEmpty(w, value) case meta.TypeID: |
︙ | ︙ | |||
61 62 63 64 65 66 67 | case meta.TypeURL: writeURL(w, value) case meta.TypeWord: wui.writeWord(w, key, value) case meta.TypeWordSet: wui.writeWordSet(w, key, meta.ListFromValue(value)) case meta.TypeZettelmarkup: | | | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | case meta.TypeURL: writeURL(w, value) case meta.TypeWord: wui.writeWord(w, key, value) case meta.TypeWordSet: wui.writeWordSet(w, key, meta.ListFromValue(value)) case meta.TypeZettelmarkup: io.WriteString(w, encodeZmkMetadata(value, evalMetadata, api.EncoderHTML, envEnc)) default: html.Escape(w, value) fmt.Fprintf(w, " <b>(Unhandled type: %v, key: %v)</b>", kt, key) } } func writeCredential(w io.Writer, val string) { html.Escape(w, val) } |
︙ | ︙ | |||
171 172 173 174 175 176 177 | } return wui.encodeTitleAsText(ctx, m, evaluate), 1 } } func (wui *WebUI) encodeTitleAsHTML( ctx context.Context, m *meta.Meta, | | > > > > > > > > > > | > > | < < < < < | < < < | < > > | > > > > > | | 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | } return wui.encodeTitleAsText(ctx, m, evaluate), 1 } } func (wui *WebUI) encodeTitleAsHTML( ctx context.Context, m *meta.Meta, evaluate *usecase.Evaluate, envEval *evaluator.Environment, envHTML *encoder.Environment, ) string { plainTitle := config.GetTitle(m, wui.rtConfig) return encodeZmkMetadata( plainTitle, func(val string) ast.InlineSlice { return evaluate.RunMetadata(ctx, plainTitle, envEval) }, api.EncoderHTML, envHTML) } func (wui *WebUI) encodeTitleAsText( ctx context.Context, m *meta.Meta, evaluate *usecase.Evaluate, ) string { plainTitle := config.GetTitle(m, wui.rtConfig) return encodeZmkMetadata( plainTitle, func(val string) ast.InlineSlice { return evaluate.RunMetadata(ctx, plainTitle, nil) }, api.EncoderText, nil) } func encodeZmkMetadata( value string, evalMetadata evalMetadataFunc, enc api.EncodingEnum, envHTML *encoder.Environment, ) string { is := evalMetadata(value) if len(is) == 0 { return "" } result, err := encodeInlines(&is, enc, envHTML) if err != nil { return err.Error() } return result } |
Changes to web/adapter/webui/lists.go.
︙ | ︙ | |||
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | "sort" "strconv" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/search" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeListHTMLMetaHandler creates a HTTP handler for rendering the list of // zettel as HTML. func (wui *WebUI) MakeListHTMLMetaHandler( listMeta usecase.ListMeta, | > | | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | "sort" "strconv" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" "zettelstore.de/z/search" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeListHTMLMetaHandler creates a HTTP handler for rendering the list of // zettel as HTML. func (wui *WebUI) MakeListHTMLMetaHandler( listMeta usecase.ListMeta, listRole usecase.ListRole, listTags usecase.ListTags, evaluate *usecase.Evaluate, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() switch query.Get("_l") { case "r": |
︙ | ︙ | |||
64 65 66 67 68 69 70 | if err != nil { wui.reportError(ctx, w, err) return } user := wui.getUser(ctx) metas := wui.buildHTMLMetaList(ctx, metaList, evaluate) var base baseData | | | | < < | | | 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | if err != nil { wui.reportError(ctx, w, err) return } user := wui.getUser(ctx) metas := wui.buildHTMLMetaList(ctx, metaList, evaluate) var base baseData wui.makeBaseData(ctx, wui.rtConfig.GetDefaultLang(), wui.rtConfig.GetSiteName(), user, &base) wui.renderTemplate(ctx, w, id.ListTemplateZid, &base, struct { Title string Metas []simpleLink }{ Title: title, Metas: metas, }) } type roleInfo struct { Text string URL string } func (wui *WebUI) renderRolesList(w http.ResponseWriter, r *http.Request, listRole usecase.ListRole) { ctx := r.Context() roleList, err := listRole.Run(ctx) if err != nil { wui.reportError(ctx, w, err) return } roleInfos := make([]roleInfo, len(roleList)) for i, role := range roleList { roleInfos[i] = roleInfo{role, wui.NewURLBuilder('h').AppendQuery("role", role).String()} } user := wui.getUser(ctx) var base baseData wui.makeBaseData(ctx, wui.rtConfig.GetDefaultLang(), wui.rtConfig.GetSiteName(), user, &base) wui.renderTemplate(ctx, w, id.RolesTemplateZid, &base, struct { Roles []roleInfo }{ Roles: roleInfos, }) } |
︙ | ︙ | |||
160 161 162 163 164 165 166 | for i := 0; i < len(tagsList); i++ { count := tagsList[i].iCount tagsList[i].Count = strconv.Itoa(count) tagsList[i].Size = strconv.Itoa(countMap[count]) } var base baseData | | | 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | for i := 0; i < len(tagsList); i++ { count := tagsList[i].iCount tagsList[i].Count = strconv.Itoa(count) tagsList[i].Size = strconv.Itoa(countMap[count]) } var base baseData wui.makeBaseData(ctx, wui.rtConfig.GetDefaultLang(), wui.rtConfig.GetSiteName(), user, &base) minCounts := make([]countInfo, 0, len(countList)) for _, c := range countList { sCount := strconv.Itoa(c) minCounts = append(minCounts, countInfo{sCount, base.ListTagsURL + "&min=" + sCount}) } wui.renderTemplate(ctx, w, id.TagsTemplateZid, &base, struct { |
︙ | ︙ | |||
215 216 217 218 219 220 221 | } depthURL.AppendQuery(api.QueryKeyDepth, depth) depthLinks[i].Text = depth depthLinks[i].URL = depthURL.String() } var base baseData user := wui.getUser(ctx) | | | 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | } depthURL.AppendQuery(api.QueryKeyDepth, depth) depthLinks[i].Text = depth depthLinks[i].URL = depthURL.String() } var base baseData user := wui.getUser(ctx) wui.makeBaseData(ctx, wui.rtConfig.GetDefaultLang(), wui.rtConfig.GetSiteName(), user, &base) wui.renderTemplate(ctx, w, id.ContextTemplateZid, &base, struct { Title string InfoURL string Depths []simpleLink Start simpleLink Metas []simpleLink }{ |
︙ | ︙ | |||
250 251 252 253 254 255 256 | } var buf bytes.Buffer s.Print(&buf) return buf.String() } // buildHTMLMetaList builds a zettel list based on a meta list for HTML rendering. | > | > > < > > > > > > > | | 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | } var buf bytes.Buffer s.Print(&buf) return buf.String() } // buildHTMLMetaList builds a zettel list based on a meta list for HTML rendering. func (wui *WebUI) buildHTMLMetaList( ctx context.Context, metaList []*meta.Meta, evaluate *usecase.Evaluate, ) []simpleLink { defaultLang := wui.rtConfig.GetDefaultLang() metas := make([]simpleLink, 0, len(metaList)) for _, m := range metaList { var lang string if val, ok := m.Get(api.KeyLang); ok { lang = val } else { lang = defaultLang } env := encoder.Environment{Lang: lang, Interactive: true} metas = append(metas, simpleLink{ Text: wui.encodeTitleAsHTML(ctx, m, evaluate, nil, &env), URL: wui.NewURLBuilder('h').SetZid(api.ZettelID(m.Zid.String())).String(), }) } return metas } |
Changes to web/adapter/webui/login.go.
1 | //----------------------------------------------------------------------------- | | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package webui |
︙ | ︙ | |||
32 33 34 35 36 37 38 | } wui.renderLoginForm(wui.clearToken(r.Context(), w), w, false) } } func (wui *WebUI) renderLoginForm(ctx context.Context, w http.ResponseWriter, retry bool) { var base baseData | | | 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | } wui.renderLoginForm(wui.clearToken(r.Context(), w), w, false) } } func (wui *WebUI) renderLoginForm(ctx context.Context, w http.ResponseWriter, retry bool) { var base baseData wui.makeBaseData(ctx, wui.rtConfig.GetDefaultLang(), "Login", nil, &base) wui.renderTemplate(ctx, w, id.LoginTemplateZid, &base, struct { Title string Retry bool }{ Title: base.Title, Retry: retry, }) |
︙ | ︙ |
Changes to web/adapter/webui/rename_zettel.go.
1 2 3 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // | | | 1 2 3 4 5 6 7 8 9 10 11 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package webui |
︙ | ︙ | |||
36 37 38 39 40 41 42 43 44 45 46 47 48 49 | } m, err := getMeta.Run(ctx, zid) if err != nil { wui.reportError(ctx, w, err) return } getTextTitle := wui.makeGetTextTitle(ctx, getMeta, evaluate) incomingLinks := wui.encodeIncoming(m, getTextTitle) uselessFiles := retrieveUselessFiles(m) user := wui.getUser(ctx) var base baseData | > > > > > > | | 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | } m, err := getMeta.Run(ctx, zid) if err != nil { wui.reportError(ctx, w, err) return } if enc, encText := adapter.GetEncoding(r, r.URL.Query(), api.EncoderHTML); enc != api.EncoderHTML { wui.reportError(ctx, w, adapter.NewErrBadRequest( fmt.Sprintf("Rename zettel %q not possible in encoding %q", zid.String(), encText))) return } getTextTitle := wui.makeGetTextTitle(ctx, getMeta, evaluate) incomingLinks := wui.encodeIncoming(m, getTextTitle) uselessFiles := retrieveUselessFiles(m) user := wui.getUser(ctx) var base baseData wui.makeBaseData(ctx, config.GetLang(m, wui.rtConfig), "Rename Zettel "+zid.String(), user, &base) wui.renderTemplate(ctx, w, id.RenameTemplateZid, &base, struct { Zid string MetaPairs []meta.Pair HasIncoming bool Incoming []simpleLink HasUselessFiles bool UselessFiles []string |
︙ | ︙ |
Changes to web/adapter/webui/response.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package webui import ( "net/http" "zettelstore.de/c/api" ) func (wui *WebUI) redirectFound(w http.ResponseWriter, r *http.Request, ub *api.URLBuilder) { us := ub.String() wui.log.Debug().Str("uri", us).Msg("redirect") http.Redirect(w, r, us, http.StatusFound) } | > > > > > > > > > | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package webui import ( "net/http" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/domain/id" ) func (wui *WebUI) redirectFound(w http.ResponseWriter, r *http.Request, ub *api.URLBuilder) { us := ub.String() wui.log.Debug().Str("uri", us).Msg("redirect") http.Redirect(w, r, us, http.StatusFound) } func (wui *WebUI) createImageMaterial(zid id.Zid) ast.InlineEmbedNode { ub := wui.NewURLBuilder('z').SetZid(api.ZettelID(zid.String())) ref := ast.ParseReference(ub.String()) ref.State = ast.RefStateFound return &ast.EmbedRefNode{Ref: ref} } |
Changes to web/adapter/webui/webui.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 17 | // Package webui provides web-UI handlers for web requests. package webui import ( "bytes" "context" "net/http" | > < | < < < < | < | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | // Package webui provides web-UI handlers for web requests. package webui import ( "bytes" "context" "io" "net/http" "sync" "time" "zettelstore.de/c/api" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/collect" "zettelstore.de/z/config" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/parser" "zettelstore.de/z/template" "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/server" ) // WebUI holds all data for delivering the web ui. type WebUI struct { log *logger.Logger debug bool ab server.AuthBuilder authz auth.AuthzManager rtConfig config.Config token auth.TokenManager box webuiBox policy auth.Policy templateCache map[id.Zid]*template.Template mxCache sync.RWMutex tokenLifetime time.Duration cssBaseURL string cssUserURL string homeURL string listZettelURL string listRolesURL string |
︙ | ︙ | |||
88 89 90 91 92 93 94 | ab: ab, rtConfig: rtConfig, authz: authz, token: token, box: mgr, policy: pol, | < < | 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | ab: ab, rtConfig: rtConfig, authz: authz, token: token, box: mgr, policy: pol, tokenLifetime: kernel.Main.GetConfig(kernel.WebService, kernel.WebTokenLifetimeHTML).(time.Duration), cssBaseURL: ab.NewURLBuilder('z').SetZid(api.ZidBaseCSS).String(), cssUserURL: ab.NewURLBuilder('z').SetZid(api.ZidUserCSS).String(), homeURL: ab.NewURLBuilder('/').String(), listZettelURL: ab.NewURLBuilder('h').String(), listRolesURL: ab.NewURLBuilder('h').AppendQuery("_l", "r").String(), listTagsURL: ab.NewURLBuilder('h').AppendQuery("_l", "t").String(), |
︙ | ︙ | |||
116 117 118 119 120 121 122 | wui.mxCache.Lock() if ci.Reason == box.OnReload || ci.Zid == id.BaseTemplateZid { wui.templateCache = make(map[id.Zid]*template.Template, len(wui.templateCache)) } else { delete(wui.templateCache, ci.Zid) } wui.mxCache.Unlock() | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | wui.mxCache.Lock() if ci.Reason == box.OnReload || ci.Zid == id.BaseTemplateZid { wui.templateCache = make(map[id.Zid]*template.Template, len(wui.templateCache)) } else { delete(wui.templateCache, ci.Zid) } wui.mxCache.Unlock() } func (wui *WebUI) cacheSetTemplate(zid id.Zid, t *template.Template) { wui.mxCache.Lock() wui.templateCache[zid] = t wui.mxCache.Unlock() } func (wui *WebUI) cacheGetTemplate(zid id.Zid) (*template.Template, bool) { wui.mxCache.RLock() t, ok := wui.templateCache[zid] wui.mxCache.RUnlock() return t, ok } func (wui *WebUI) canCreate(ctx context.Context, user *meta.Meta) bool { m := meta.New(id.Invalid) return wui.policy.CanCreate(user, m) && wui.box.CanCreateZettel(ctx) } func (wui *WebUI) canWrite( ctx context.Context, user, meta *meta.Meta, content domain.Content) bool { |
︙ | ︙ | |||
232 233 234 235 236 237 238 | } type baseData struct { Lang string MetaHeader string CSSBaseURL string CSSUserURL string | < < | < < < < < < < < < | 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 | } type baseData struct { Lang string MetaHeader string CSSBaseURL string CSSUserURL string Title string HomeURL string WithUser bool WithAuth bool UserIsValid bool UserZettelURL string UserIdent string LoginURL string LogoutURL string ListZettelURL string ListRolesURL string ListTagsURL string CanRefresh bool RefreshURL string HasNewZettelLinks bool NewZettelLinks []simpleLink SearchURL string QueryKeySearch string Content string FooterHTML string } func (wui *WebUI) makeBaseData(ctx context.Context, lang, title string, user *meta.Meta, data *baseData) { var userZettelURL string var userIdent string userIsValid := user != nil if userIsValid { userZettelURL = wui.NewURLBuilder('h').SetZid(api.ZettelID(user.Zid.String())).String() userIdent = user.GetDefault(api.KeyUserID, "") } newZettelLinks := wui.fetchNewTemplates(ctx, user) data.Lang = lang data.CSSBaseURL = wui.cssBaseURL data.CSSUserURL = wui.cssUserURL data.Title = title data.HomeURL = wui.homeURL data.WithAuth = wui.withAuth data.WithUser = data.WithAuth data.UserIsValid = userIsValid data.UserZettelURL = userZettelURL data.UserIdent = userIdent data.LoginURL = wui.loginURL data.LogoutURL = wui.logoutURL data.ListZettelURL = wui.listZettelURL data.ListRolesURL = wui.listRolesURL data.ListTagsURL = wui.listTagsURL data.CanRefresh = wui.canRefresh(user) data.RefreshURL = wui.refreshURL data.HasNewZettelLinks = len(newZettelLinks) > 0 data.NewZettelLinks = newZettelLinks data.SearchURL = wui.searchURL data.QueryKeySearch = api.QueryKeySearch data.FooterHTML = wui.rtConfig.GetFooterHTML() } // htmlAttrNewWindow returns HTML attribute string for opening a link in a new window. // If hasURL is false an empty string is returned. func htmlAttrNewWindow(hasURL bool) string { if hasURL { return " target=\"_blank\" ref=\"noopener noreferrer\"" |
︙ | ︙ | |||
333 334 335 336 337 338 339 | continue } if !wui.policy.CanRead(user, m) { continue } title := config.GetTitle(m, wui.rtConfig) astTitle := parser.ParseMetadata(title) | > | | | 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 | continue } if !wui.policy.CanRead(user, m) { continue } title := config.GetTitle(m, wui.rtConfig) astTitle := parser.ParseMetadata(title) env := encoder.Environment{Lang: config.GetLang(m, wui.rtConfig)} menuTitle, err2 := encodeInlines(&astTitle, api.EncoderHTML, &env) if err2 != nil { menuTitle, err2 = encodeInlines(&astTitle, api.EncoderText, nil) if err2 != nil { menuTitle = title } } result = append(result, simpleLink{ Text: menuTitle, URL: wui.NewURLBuilder('c').SetZid(api.ZettelID(m.Zid.String())). |
︙ | ︙ | |||
365 366 367 368 369 370 371 | func (wui *WebUI) reportError(ctx context.Context, w http.ResponseWriter, err error) { code, text := adapter.CodeMessageFromError(err) if code == http.StatusInternalServerError { wui.log.Error().Msg(err.Error()) } user := wui.getUser(ctx) var base baseData | | | 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 | func (wui *WebUI) reportError(ctx context.Context, w http.ResponseWriter, err error) { code, text := adapter.CodeMessageFromError(err) if code == http.StatusInternalServerError { wui.log.Error().Msg(err.Error()) } user := wui.getUser(ctx) var base baseData wui.makeBaseData(ctx, api.ValueLangEN, "Error", user, &base) wui.renderTemplateStatus(ctx, w, code, id.ErrorTemplateZid, &base, struct { ErrorTitle string ErrorText string }{ ErrorTitle: http.StatusText(code), ErrorText: text, }) |
︙ | ︙ | |||
404 405 406 407 408 409 410 | wui.setToken(w, tok) } } var content bytes.Buffer err = t.Render(&content, data) if err == nil { wui.prepareAndWriteHeader(w, code) | > > | | > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 | wui.setToken(w, tok) } } var content bytes.Buffer err = t.Render(&content, data) if err == nil { wui.prepareAndWriteHeader(w, code) err = writeHTMLStart(w, base.Lang) if err == nil { base.Content = content.String() err = bt.Render(w, base) if err == nil { err = wui.writeHTMLEnd(w) } } } if err != nil { wui.log.IfErr(err).Msg("Unable to write HTML via template") } } func writeHTMLStart(w http.ResponseWriter, lang string) error { _, err := io.WriteString(w, "<!DOCTYPE html>\n<html") if err != nil { return err } if lang != "" { _, err = io.WriteString(w, " lang=\"") if err == nil { _, err = io.WriteString(w, lang) } if err == nil { _, err = io.WriteString(w, "\">\n<head>\n") } } else { _, err = io.WriteString(w, ">\n<head>\n") } return err } func (wui *WebUI) writeHTMLEnd(w http.ResponseWriter) error { if wui.debug { _, err := io.WriteString(w, "<div><b>WARNING: Debug mode is enabled. DO NOT USE IN PRODUCTION!</b></div>\n") if err != nil { return err } } _, err := io.WriteString(w, "</body>\n</html>") return err } func (wui *WebUI) getUser(ctx context.Context) *meta.Meta { return wui.ab.GetUser(ctx) } // GetURLPrefix returns the configured URL prefix of the web server. func (wui *WebUI) GetURLPrefix() string { return wui.ab.GetURLPrefix() } // NewURLBuilder creates a new URL builder object with the given key. |
︙ | ︙ |
Changes to web/server/impl/impl.go.
︙ | ︙ | |||
28 29 30 31 32 33 34 | server httpServer router httpRouter persistentCookie bool secureCookie bool } // New creates a new web server. | | | | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | server httpServer router httpRouter persistentCookie bool secureCookie bool } // New creates a new web server. func New(log *logger.Logger, listenAddr, urlPrefix string, persistentCookie, secureCookie bool, auth auth.TokenManager) server.Server { srv := myServer{ log: log, persistentCookie: persistentCookie, secureCookie: secureCookie, } srv.router.initializeRouter(log, urlPrefix, auth) srv.server.initializeHTTPServer(listenAddr, &srv.router) return &srv } func (srv *myServer) Handle(pattern string, handler http.Handler) { srv.router.Handle(pattern, handler) } |
︙ | ︙ |
Changes to web/server/impl/router.go.
︙ | ︙ | |||
46 47 48 49 50 51 52 | minKey byte maxKey byte reURL *regexp.Regexp listTable routingTable zettelTable routingTable ur server.UserRetriever mux *http.ServeMux | < | < | 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | minKey byte maxKey byte reURL *regexp.Regexp listTable routingTable zettelTable routingTable ur server.UserRetriever mux *http.ServeMux } // initializeRouter creates a new, empty router with the given root handler. func (rt *httpRouter) initializeRouter(log *logger.Logger, urlPrefix string, auth auth.TokenManager) { rt.log = log rt.urlPrefix = urlPrefix rt.auth = auth rt.minKey = 255 rt.maxKey = 0 rt.reURL = regexp.MustCompile("^$") rt.mux = http.NewServeMux() } func (rt *httpRouter) addRoute(key byte, method server.Method, handler http.Handler, table *routingTable) { // Set minKey and maxKey; re-calculate regexp. if key < rt.minKey || rt.maxKey < key { if key < rt.minKey { rt.minKey = key |
︙ | ︙ | |||
128 129 130 131 132 133 134 | if withDebug { rt.log.Debug().Int("sc", int64(w.(*traceResponseWriter).statusCode)).Msg("/ServeHTTP/prefix") } return } r.URL.Path = r.URL.Path[prefixLen-1:] } | < | 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | if withDebug { rt.log.Debug().Int("sc", int64(w.(*traceResponseWriter).statusCode)).Msg("/ServeHTTP/prefix") } return } r.URL.Path = r.URL.Path[prefixLen-1:] } match := rt.reURL.FindStringSubmatch(r.URL.Path) if len(match) != 3 { rt.mux.ServeHTTP(w, rt.addUserContext(r)) if withDebug { rt.log.Debug().Int("sc", int64(w.(*traceResponseWriter).statusCode)).Msg("match other") } return |
︙ | ︙ | |||
167 168 169 170 171 172 173 | if withDebug { rt.log.Debug().Int("sc", int64(w.(*traceResponseWriter).statusCode)).Msg("no match") } } func (rt *httpRouter) addUserContext(r *http.Request) *http.Request { if rt.ur == nil { | < < < < < | 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | if withDebug { rt.log.Debug().Int("sc", int64(w.(*traceResponseWriter).statusCode)).Msg("no match") } } func (rt *httpRouter) addUserContext(r *http.Request) *http.Request { if rt.ur == nil { return r } k := auth.KindJSON t := getHeaderToken(r) if len(t) == 0 { k = auth.KindHTML t = getSessionToken(r) } if len(t) == 0 { return r } tokenData, err := rt.auth.CheckToken(t, k) if err != nil { return r } ctx := r.Context() user, err := rt.ur.GetUser(ctx, tokenData.Zid, tokenData.Ident) if err != nil { return r } return r.WithContext(updateContext(ctx, user, &tokenData)) } func getSessionToken(r *http.Request) []byte { cookie, err := r.Cookie(sessionName) |
︙ | ︙ |
Changes to www/changes.wiki.
1 2 3 4 | <title>Change Log</title> <a name="0_5"></a> <h2>Changes for Version 0.5 (pending)</h2> | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 1 2 3 4 5 6 7 8 9 10 11 | <title>Change Log</title> <a name="0_5"></a> <h2>Changes for Version 0.5 (pending)</h2> <a name="0_4"></a> <h2>Changes for Version 0.4 (2022-03-08)</h2> * Encoding “djson” renamed to “zjson” (<em>zettel json</em>). (breaking: api; minor: webui) * Remove inline quotation syntax <tt><<...<<</tt>. Now, |
︙ | ︙ |