Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From v0.7.0 To v0.8.0
2022-10-24
| ||
15:42 | Increase version to 0.9.0-dev to begin next development cycle ... (check-in: f38439155f user: stern tags: trunk) | |
2022-10-20
| ||
17:44 | Version 0.8.0 ... (check-in: 2790cb4cf1 user: stern tags: trunk, release, v0.8.0) | |
12:39 | Add manual to help when HTML content is not shown ... (check-in: f8a4ebc639 user: stern tags: trunk) | |
2022-09-18
| ||
12:52 | Increase version to 0.7.1-dev to begin release update development cycle ... (check-in: f005505248 user: stern tags: release-0.7) | |
2022-09-17
| ||
15:10 | Increase version to 0.8.0-dev to begin next development cycle ... (check-in: 97bb0f0d93 user: t73fde tags: trunk) | |
12:28 | Version 0.7.0 ... (check-in: f2b8d470c7 user: stern tags: trunk, release, v0.7.0) | |
11:54 | Update to newest go/x/crypto, go/x/sys ... (check-in: 61528d5462 user: stern tags: trunk) | |
Changes to VERSION.
|
| | | 1 | 0.8.0 |
Changes to ast/inline.go.
︙ | ︙ | |||
51 52 53 54 55 56 57 | } func (*TextNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. func (*TextNode) WalkChildren(Visitor) { /* No children*/ } | < < < < < < < < < < < < | 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | } func (*TextNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. func (*TextNode) WalkChildren(Visitor) { /* No children*/ } // -------------------------------------------------------------------------- // SpaceNode tracks inter-word space characters. type SpaceNode struct { Lexeme string } |
︙ | ︙ |
Changes to box/constbox/base.css.
︙ | ︙ | |||
180 181 182 183 184 185 186 | div.zs-indication p:first-child { margin-top: 0 } span.zs-indication { border: 1px solid black; border-radius: .25rem; padding: .1rem .2rem; font-size: 95%; } | < | 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | div.zs-indication p:first-child { margin-top: 0 } span.zs-indication { border: 1px solid black; border-radius: .25rem; padding: .1rem .2rem; font-size: 95%; } .zs-info { background-color: lightblue; padding: .5rem 1rem; } .zs-warning { background-color: lightyellow; padding: .5rem 1rem; |
︙ | ︙ |
Changes to box/constbox/base.mustache.
︙ | ︙ | |||
39 40 41 42 43 44 45 | <a href="{{{ListRolesURL}}}">List Roles</a> <a href="{{{ListTagsURL}}}">List Tags</a> {{#CanRefresh}} <a href="{{{RefreshURL}}}">Refresh</a> {{/CanRefresh}} </nav> </div> | | | | | | 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 | <a href="{{{ListRolesURL}}}">List Roles</a> <a href="{{{ListTagsURL}}}">List Tags</a> {{#CanRefresh}} <a href="{{{RefreshURL}}}">Refresh</a> {{/CanRefresh}} </nav> </div> {{#NewZettelLinks.Has}} <div class="zs-dropdown"> <button>New</button> <nav class="zs-dropdown-content"> {{#NewZettelLinks.Links}} <a href="{{{URL}}}">{{Text}}</a> {{/NewZettelLinks.Links}} </nav> </div> {{/NewZettelLinks.Has}} <form action="{{{SearchURL}}}"> <input type="text" placeholder="Search.." name="{{QueryKeyQuery}}"> </form> </nav> <main class="content"> {{{Content}}} </main> {{#FooterHTML}}<footer>{{{FooterHTML}}}</footer>{{/FooterHTML}} {{#DebugMode}}<div><b>WARNING: Debug mode is enabled. DO NOT USE IN PRODUCTION!</b></div>{{/DebugMode}} </body> </html> |
Changes to box/constbox/constbox.go.
︙ | ︙ | |||
182 183 184 185 186 187 188 | api.KeyTitle: "Zettelstore Dependencies", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: api.ValueSyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityLogin, api.KeyCreated: "20210504135842", | | | 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | api.KeyTitle: "Zettelstore Dependencies", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: api.ValueSyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityLogin, api.KeyCreated: "20210504135842", api.KeyModified: "20221013105100", }, domain.NewContent(contentDependencies)}, id.BaseTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Base HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, |
︙ | ︙ |
Changes to box/constbox/delete.mustache.
1 2 3 4 5 6 7 8 9 10 11 | <article> <header> <h1>Delete Zettel {{Zid}}</h1> </header> <p>Do you really want to delete this zettel?</p> {{#HasShadows}} <div class="zs-info"> <h2>Infomation</h2> <p>If you delete this zettel, the previoulsy shadowed zettel from overlayed box {{ShadowedBox}} becomes available.</p> </div> {{/HasShadows}} | | | | | | 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 | <article> <header> <h1>Delete Zettel {{Zid}}</h1> </header> <p>Do you really want to delete this zettel?</p> {{#HasShadows}} <div class="zs-info"> <h2>Infomation</h2> <p>If you delete this zettel, the previoulsy shadowed zettel from overlayed box {{ShadowedBox}} becomes available.</p> </div> {{/HasShadows}} {{#Incoming.Has}} <div class="zs-warning"> <h2>Warning!</h2> <p>If you delete this zettel, incoming references from the following zettel will become invalid.</p> <ul> {{#Incoming.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/Incoming.Links}} </ul> </div> {{/Incoming.Has}} {{#HasUselessFiles}} <div class="zs-warning"> <h2>Warning!</h2> <p>Deleting this zettel will also delete the following files, so that they will not be interpreted as content for this zettel.</p> <ul> {{#UselessFiles}} <li>{{{.}}}</li> |
︙ | ︙ |
Changes to box/constbox/dependencies.zettel.
︙ | ︙ | |||
70 71 72 73 74 75 76 | ; URL : [[https://fsnotify.org/]] ; License : BSD 3-Clause "New" or "Revised" License ; Source : [[https://github.com/fsnotify/fsnotify]] ``` | | | | | < | | | | | < | | | | | | | < | | | | | | | 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 | ; URL : [[https://fsnotify.org/]] ; License : BSD 3-Clause "New" or "Revised" License ; Source : [[https://github.com/fsnotify/fsnotify]] ``` Copyright © 2012 The Go Authors. All rights reserved. Copyright © fsnotify Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ``` === gopikchr ; URL & Source : [[https://github.com/gopikchr/gopikchr]] ; License : MIT License |
︙ | ︙ |
Changes to box/constbox/info.mustache.
1 2 3 4 5 6 | <article> <header> <h1>Information for Zettel {{Zid}}</h1> <a href="{{{WebURL}}}">Web</a> · <a href="{{{ContextURL}}}">Context</a> {{#CanWrite}} · <a href="{{{EditURL}}}">Edit</a>{{/CanWrite}} | < > > | | | | | 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 | <article> <header> <h1>Information for Zettel {{Zid}}</h1> <a href="{{{WebURL}}}">Web</a> · <a href="{{{ContextURL}}}">Context</a> {{#CanWrite}} · <a href="{{{EditURL}}}">Edit</a>{{/CanWrite}} {{#CanCopy}} · <a href="{{{CopyURL}}}">Copy</a>{{/CanCopy}} {{#CanVersion}} · <a href="{{{VersionURL}}}">Version</a>{{/CanVersion}} {{#CanFolge}} · <a href="{{{FolgeURL}}}">Folge</a>{{/CanFolge}} {{#CanRename}}· <a href="{{{RenameURL}}}">Rename</a>{{/CanRename}} {{#CanDelete}}· <a href="{{{DeleteURL}}}">Delete</a>{{/CanDelete}} </header> <h2>Interpreted Metadata</h2> <table>{{#MetaData}}<tr><td>{{Key}}</td><td>{{{Value}}}</td></tr>{{/MetaData}}</table> <h2>References</h2> {{#HasLocLinks}} <h3>Local</h3> <ul> {{#LocLinks}} {{#Valid}}<li><a href="{{{Zid}}}">{{Zid}}</a></li>{{/Valid}} {{^Valid}}<li>{{Zid}}</li>{{/Valid}} {{/LocLinks}} </ul> {{/HasLocLinks}} {{#QueryLinks.Has}} <h3>Queries</h3> <ul> {{#QueryLinks.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/QueryLinks.Links}} </ul> {{/QueryLinks.Has}} {{#HasExtLinks}} <h3>External</h3> <ul> {{#ExtLinks}} <li><a href="{{{.}}}"{{{ExtNewWindow}}}>{{.}}</a></li> {{/ExtLinks}} </ul> |
︙ | ︙ |
Changes to box/constbox/rename.mustache.
1 2 3 4 5 | <article> <header> <h1>Rename Zettel {{Zid}}</h1> </header> <p>Do you really want to rename this zettel?</p> | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <article> <header> <h1>Rename Zettel {{Zid}}</h1> </header> <p>Do you really want to rename this zettel?</p> {{#Incoming.Has}} <div class="zs-warning"> <h2>Warning!</h2> <p>If you rename this zettel, incoming references from the following zettel will become invalid.</p> <ul> {{#Incoming.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/Incoming.Links}} </ul> </div> {{/Incoming.Has}} {{#HasUselessFiles}} <div class="zs-warning"> <h2>Warning!</h2> <p>Renaming this zettel will also delete the following files, so that they will not be interpreted as content for a zettel with identifier {{Zid}}.</p> <ul> {{#UselessFiles}} <li>{{{.}}}</li> |
︙ | ︙ |
Changes to box/constbox/zettel.mustache.
1 2 3 4 5 6 7 8 | <article> <header> <h1>{{{HTMLTitle}}}</h1> <div class="zs-meta"> {{#CanWrite}}<a href="{{{EditURL}}}">Edit</a> ·{{/CanWrite}} {{Zid}} · <a href="{{{InfoURL}}}">Info</a> · (<a href="{{{RoleURL}}}">{{RoleText}}</a>) | | > > > | < | | < | | < | | > > > > > > > > | > | > | 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 | <article> <header> <h1>{{{HTMLTitle}}}</h1> <div class="zs-meta"> {{#CanWrite}}<a href="{{{EditURL}}}">Edit</a> ·{{/CanWrite}} {{Zid}} · <a href="{{{InfoURL}}}">Info</a> · (<a href="{{{RoleURL}}}">{{RoleText}}</a>) {{#Tags.Has}}· {{#Tags.Links}} <a href="{{{URL}}}">{{Text}}</a>{{/Tags.Links}}{{/Tags.Has}} {{#CanCopy}}· <a href="{{{CopyURL}}}">Copy</a>{{/CanCopy}} {{#CanVersion}}· <a href="{{{VersionURL}}}">Version</a>{{/CanVersion}} {{#CanFolge}}· <a href="{{{FolgeURL}}}">Folge</a>{{/CanFolge}} {{#PredecessorRefs}}<br>Predecessor: {{{PredecessorRefs}}}{{/PredecessorRefs}} {{#PrecursorRefs}}<br>Precursor: {{{PrecursorRefs}}}{{/PrecursorRefs}} {{#HasExtURL}}<br>URL: <a href="{{{ExtURL}}}"{{{ExtNewWindow}}}>{{ExtURL}}</a>{{/HasExtURL}} {{#Author}}<br>By {{Author}}{{/Author}} </div> </header> {{{Content}}} </article> {{#NeedBottomNav}}<nav>{{/NeedBottomNav}} {{#FolgeLinks.Has}} <details open> <summary>Folgezettel</summary> <ul> {{#FolgeLinks.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/FolgeLinks.Links}} </ul> </details> {{/FolgeLinks.Has}} {{#BackLinks.Has}} <details open> <summary>Incoming</summary> <ul> {{#BackLinks.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/BackLinks.Links}} </ul> </details> {{/BackLinks.Has}} {{#SuccessorLinks.Has}} <details open> <summary>Successors</summary> <ul> {{#SuccessorLinks.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/SuccessorLinks.Links}} </ul> </details> {{/SuccessorLinks.Has}} {{#NeedBottomNav}}</nav>{{/NeedBottomNav}} |
Changes to box/manager/collect.go.
︙ | ︙ | |||
19 20 21 22 23 24 25 | "zettelstore.de/z/strfun" ) type collectData struct { refs id.Set words store.WordSet urls store.WordSet | < < < < < | 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/z/strfun" ) type collectData struct { refs id.Set words store.WordSet urls store.WordSet } func (data *collectData) initialize() { data.refs = id.NewSet() data.words = store.NewWordSet() data.urls = store.NewWordSet() } func collectZettelIndexData(zn *ast.ZettelNode, data *collectData) { ast.Walk(data, &zn.Ast) } func collectInlineIndexData(is *ast.InlineSlice, data *collectData) { ast.Walk(data, is) } func (data *collectData) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.VerbatimNode: data.addText(string(n.Content)) case *ast.TranscludeNode: data.addRef(n.Ref) case *ast.TextNode: data.addText(n.Text) case *ast.LinkNode: data.addRef(n.Ref) case *ast.EmbedRefNode: data.addRef(n.Ref) case *ast.CiteNode: data.addText(n.Key) case *ast.LiteralNode: |
︙ | ︙ |
Changes to box/manager/indexer.go.
︙ | ︙ | |||
204 205 206 207 208 209 210 | zi.AddBackRef(ref) } else { zi.AddDeadRef(ref) } } zi.SetWords(cData.words) zi.SetUrls(cData.urls) | < | 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | zi.AddBackRef(ref) } else { zi.AddDeadRef(ref) } } zi.SetWords(cData.words) zi.SetUrls(cData.urls) } func (mgr *Manager) idxUpdateValue(ctx context.Context, inverseKey, value string, zi *store.ZettelIndex) { zid, err := id.Parse(value) if err != nil { return } |
︙ | ︙ |
Changes to box/manager/memstore/memstore.go.
︙ | ︙ | |||
34 35 36 37 38 39 40 | type zettelIndex struct { dead id.Slice forward id.Slice backward id.Slice meta map[string]metaRefs words []string urls []string | < | 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | type zettelIndex struct { dead id.Slice forward id.Slice backward id.Slice meta map[string]metaRefs words []string urls []string } func (zi *zettelIndex) isEmpty() bool { if len(zi.forward) > 0 || len(zi.backward) > 0 || len(zi.dead) > 0 || len(zi.words) > 0 { return false } return len(zi.meta) == 0 |
︙ | ︙ | |||
108 109 110 111 112 113 114 | updated = true } } if len(back) > 0 { m.Set(api.KeyBack, back.String()) updated = true } | < < < < < < < < < < < < | 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | updated = true } } if len(back) > 0 { m.Set(api.KeyBack, back.String()) updated = true } return updated } // SearchEqual returns all zettel that contains the given exact word. // The word must be normalized through Unicode NKFD, trimmed and not empty. func (ms *memStore) SearchEqual(word string) id.Set { ms.mx.RLock() |
︙ | ︙ | |||
158 159 160 161 162 163 164 | ms.mx.RLock() defer ms.mx.RUnlock() result := ms.selectWithPred(prefix, strings.HasPrefix) l := len(prefix) if l > 14 { return result } | | > > > > | | | > | 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 | ms.mx.RLock() defer ms.mx.RUnlock() result := ms.selectWithPred(prefix, strings.HasPrefix) l := len(prefix) if l > 14 { return result } maxZid, err := id.Parse(prefix + "99999999999999"[:14-l]) if err != nil { return result } var minZid id.Zid if l < 14 && prefix == "0000000000000"[:l] { minZid = id.Zid(1) } else { minZid, err = id.Parse(prefix + "00000000000000"[:14-l]) if err != nil { return result } } for zid, zi := range ms.idx { if minZid <= zid && zid <= maxZid { addBackwardZids(result, zid, zi) } } return result |
︙ | ︙ | |||
287 288 289 290 291 292 293 | } ms.updateDeadReferences(zidx, zi) ms.updateForwardBackwardReferences(zidx, zi) ms.updateMetadataReferences(zidx, zi) zi.words = updateWordSet(zidx.Zid, ms.words, zi.words, zidx.GetWords()) zi.urls = updateWordSet(zidx.Zid, ms.urls, zi.urls, zidx.GetUrls()) | < | 279 280 281 282 283 284 285 286 287 288 289 290 291 292 | } ms.updateDeadReferences(zidx, zi) ms.updateForwardBackwardReferences(zidx, zi) ms.updateMetadataReferences(zidx, zi) zi.words = updateWordSet(zidx.Zid, ms.words, zi.words, zidx.GetWords()) zi.urls = updateWordSet(zidx.Zid, ms.urls, zi.urls, zidx.GetUrls()) // Check if zi must be inserted into ms.idx if !ziExist && !zi.isEmpty() { ms.idx[zidx.Zid] = zi } return toCheck |
︙ | ︙ | |||
381 382 383 384 385 386 387 | continue } srefs[word] = refs2 } return next.Words() } | < < < < < < < < < | 372 373 374 375 376 377 378 379 380 381 382 383 384 385 | continue } srefs[word] = refs2 } return next.Words() } func (ms *memStore) getEntry(zid id.Zid) *zettelIndex { // Must only be called if ms.mx is write-locked! if zi, ok := ms.idx[zid]; ok { return zi } zi := &zettelIndex{} ms.idx[zid] = zi |
︙ | ︙ |
Changes to box/manager/store/zettel.go.
︙ | ︙ | |||
16 17 18 19 20 21 22 | type ZettelIndex struct { Zid id.Zid // zid of the indexed zettel backrefs id.Set // set of back references metarefs map[string]id.Set // references to inverse keys deadrefs id.Set // set of dead references words WordSet urls WordSet | < | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | type ZettelIndex struct { Zid id.Zid // zid of the indexed zettel backrefs id.Set // set of back references metarefs map[string]id.Set // references to inverse keys deadrefs id.Set // set of dead references words WordSet urls WordSet } // NewZettelIndex creates a new zettel index. func NewZettelIndex(zid id.Zid) *ZettelIndex { return &ZettelIndex{ Zid: zid, backrefs: id.NewSet(), |
︙ | ︙ | |||
56 57 58 59 60 61 62 | // SetWords sets the words to the given value. func (zi *ZettelIndex) SetWords(words WordSet) { zi.words = words } // SetUrls sets the words to the given value. func (zi *ZettelIndex) SetUrls(urls WordSet) { zi.urls = urls } | < < < | 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | // SetWords sets the words to the given value. func (zi *ZettelIndex) SetWords(words WordSet) { zi.words = words } // SetUrls sets the words to the given value. func (zi *ZettelIndex) SetUrls(urls WordSet) { zi.urls = urls } // GetDeadRefs returns all dead references as a sorted list. func (zi *ZettelIndex) GetDeadRefs() id.Slice { return zi.deadrefs.Sorted() } // GetBackRefs returns all back references as a sorted list. func (zi *ZettelIndex) GetBackRefs() id.Slice { |
︙ | ︙ | |||
86 87 88 89 90 91 92 | } // GetWords returns a reference to the set of words. It must not be modified. func (zi *ZettelIndex) GetWords() WordSet { return zi.words } // GetUrls returns a reference to the set of URLs. It must not be modified. func (zi *ZettelIndex) GetUrls() WordSet { return zi.urls } | < < < | 82 83 84 85 86 87 88 | } // GetWords returns a reference to the set of words. It must not be modified. func (zi *ZettelIndex) GetWords() WordSet { return zi.words } // GetUrls returns a reference to the set of URLs. It must not be modified. func (zi *ZettelIndex) GetUrls() WordSet { return zi.urls } |
Changes to box/notify/fsdir.go.
︙ | ︙ | |||
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 | for fsdn.readAndProcessEvent() { } } func (fsdn *fsdirNotifier) readAndProcessEvent() bool { select { case <-fsdn.done: return false default: } select { case <-fsdn.done: return false case <-fsdn.refresh: listDirElements(fsdn.log, fsdn.fetcher, fsdn.events, fsdn.done) case err, ok := <-fsdn.base.Errors: if !ok { return false } select { case fsdn.events <- Event{Op: Error, Err: err}: case <-fsdn.done: return false } case ev, ok := <-fsdn.base.Events: if !ok { return false } if !fsdn.processEvent(&ev) { return false } } return true } func (fsdn *fsdirNotifier) processEvent(ev *fsnotify.Event) bool { if strings.HasPrefix(ev.Name, fsdn.path) { if len(ev.Name) == len(fsdn.path) { return fsdn.processDirEvent(ev) } return fsdn.processFileEvent(ev) } return true } func (fsdn *fsdirNotifier) processDirEvent(ev *fsnotify.Event) bool { | > > > > > > > < | < > > > > | > > | | < < < | < > > > > | > > > > | > > > | > > | | | > > > > > > > > > | | | > | < < < | 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 | for fsdn.readAndProcessEvent() { } } func (fsdn *fsdirNotifier) readAndProcessEvent() bool { select { case <-fsdn.done: fsdn.log.Trace().Int("i", 1).Msg("done with read and process events") return false default: } select { case <-fsdn.done: fsdn.log.Trace().Int("i", 2).Msg("done with read and process events") return false case <-fsdn.refresh: fsdn.log.Trace().Msg("refresh") listDirElements(fsdn.log, fsdn.fetcher, fsdn.events, fsdn.done) case err, ok := <-fsdn.base.Errors: fsdn.log.Trace().Err(err).Bool("ok", ok).Msg("got errors") if !ok { return false } select { case fsdn.events <- Event{Op: Error, Err: err}: case <-fsdn.done: fsdn.log.Trace().Int("i", 3).Msg("done with read and process events") return false } case ev, ok := <-fsdn.base.Events: fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Bool("ok", ok).Msg("file event") if !ok { return false } if !fsdn.processEvent(&ev) { return false } } return true } func (fsdn *fsdirNotifier) processEvent(ev *fsnotify.Event) bool { if strings.HasPrefix(ev.Name, fsdn.path) { if len(ev.Name) == len(fsdn.path) { return fsdn.processDirEvent(ev) } return fsdn.processFileEvent(ev) } fsdn.log.Trace().Str("path", fsdn.path).Str("name", ev.Name).Str("op", ev.Op.String()).Msg("event does not match") return true } func (fsdn *fsdirNotifier) processDirEvent(ev *fsnotify.Event) bool { if ev.Has(fsnotify.Remove) || ev.Has(fsnotify.Rename) { fsdn.log.Debug().Str("name", fsdn.path).Msg("Directory removed") fsdn.base.Remove(fsdn.path) select { case fsdn.events <- Event{Op: Destroy}: case <-fsdn.done: fsdn.log.Trace().Int("i", 1).Msg("done dir event processing") return false } return true } if ev.Has(fsnotify.Create) { err := fsdn.base.Add(fsdn.path) if err != nil { fsdn.log.IfErr(err).Str("name", fsdn.path).Msg("Unable to add directory") select { case fsdn.events <- Event{Op: Error, Err: err}: case <-fsdn.done: fsdn.log.Trace().Int("i", 2).Msg("done dir event processing") return false } } fsdn.log.Debug().Str("name", fsdn.path).Msg("Directory added") return listDirElements(fsdn.log, fsdn.fetcher, fsdn.events, fsdn.done) } fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("Directory processed") return true } func (fsdn *fsdirNotifier) processFileEvent(ev *fsnotify.Event) bool { if ev.Has(fsnotify.Create) || ev.Has(fsnotify.Write) { if fi, err := os.Lstat(ev.Name); err != nil || !fi.Mode().IsRegular() { regular := err == nil && fi.Mode().IsRegular() fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Err(err).Bool("regular", regular).Msg("error with file") return true } fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File updated") return fsdn.sendEvent(Update, filepath.Base(ev.Name)) } if ev.Has(fsnotify.Rename) { fi, err := os.Lstat(ev.Name) if err != nil { fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File deleted") return fsdn.sendEvent(Delete, filepath.Base(ev.Name)) } if fi.Mode().IsRegular() { fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File updated") return fsdn.sendEvent(Update, filepath.Base(ev.Name)) } fsdn.log.Trace().Str("name", ev.Name).Msg("File not regular") return true } if ev.Has(fsnotify.Remove) { fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File deleted") return fsdn.sendEvent(Delete, filepath.Base(ev.Name)) } fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File processed") return true } func (fsdn *fsdirNotifier) sendEvent(op EventOp, filename string) bool { select { case fsdn.events <- Event{Op: op, Name: filename}: case <-fsdn.done: fsdn.log.Trace().Msg("done file event processing") return false } return true } func (fsdn *fsdirNotifier) Close() { close(fsdn.done) } |
Changes to cmd/cmd_run.go.
︙ | ︙ | |||
69 70 71 72 73 74 75 | ucGetAllMeta := usecase.NewGetAllMeta(protectedBoxManager) ucGetZettel := usecase.NewGetZettel(protectedBoxManager) ucParseZettel := usecase.NewParseZettel(rtConfig, ucGetZettel) ucListMeta := usecase.NewListMeta(protectedBoxManager) ucEvaluate := usecase.NewEvaluate(rtConfig, ucGetZettel, ucGetMeta, ucListMeta) ucListSyntax := usecase.NewListSyntax(protectedBoxManager) ucListRoles := usecase.NewListRoles(protectedBoxManager) | < | 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | ucGetAllMeta := usecase.NewGetAllMeta(protectedBoxManager) ucGetZettel := usecase.NewGetZettel(protectedBoxManager) ucParseZettel := usecase.NewParseZettel(rtConfig, ucGetZettel) ucListMeta := usecase.NewListMeta(protectedBoxManager) ucEvaluate := usecase.NewEvaluate(rtConfig, ucGetZettel, ucGetMeta, ucListMeta) ucListSyntax := usecase.NewListSyntax(protectedBoxManager) ucListRoles := usecase.NewListRoles(protectedBoxManager) ucZettelContext := usecase.NewZettelContext(protectedBoxManager, rtConfig) ucDelete := usecase.NewDeleteZettel(logUc, protectedBoxManager) ucUpdate := usecase.NewUpdateZettel(logUc, protectedBoxManager) ucRename := usecase.NewRenameZettel(logUc, protectedBoxManager) ucUnlinkedRefs := usecase.NewUnlinkedReferences(protectedBoxManager, rtConfig) ucRefresh := usecase.NewRefresh(logUc, protectedBoxManager) ucVersion := usecase.NewVersion(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) |
︙ | ︙ | |||
112 113 114 115 116 117 118 | ucParseZettel, &ucEvaluate, ucGetMeta, ucGetAllMeta, ucUnlinkedRefs)) webSrv.AddZettelRoute('k', server.MethodGet, wui.MakeZettelContextHandler( ucZettelContext, &ucEvaluate)) // API webSrv.AddListRoute('a', server.MethodPost, a.MakePostLoginHandler(&ucAuthenticate)) webSrv.AddListRoute('a', server.MethodPut, a.MakeRenewAuthHandler()) | | < | 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | ucParseZettel, &ucEvaluate, ucGetMeta, ucGetAllMeta, ucUnlinkedRefs)) webSrv.AddZettelRoute('k', server.MethodGet, wui.MakeZettelContextHandler( ucZettelContext, &ucEvaluate)) // API webSrv.AddListRoute('a', server.MethodPost, a.MakePostLoginHandler(&ucAuthenticate)) webSrv.AddListRoute('a', server.MethodPut, a.MakeRenewAuthHandler()) webSrv.AddListRoute('j', server.MethodGet, a.MakeQueryHandler(ucListMeta)) webSrv.AddZettelRoute('j', server.MethodGet, a.MakeGetZettelHandler(ucGetZettel)) webSrv.AddZettelRoute('m', server.MethodGet, a.MakeGetMetaHandler(ucGetMeta)) webSrv.AddZettelRoute('o', server.MethodGet, a.MakeGetOrderHandler( usecase.NewZettelOrder(protectedBoxManager, ucEvaluate))) webSrv.AddZettelRoute('p', server.MethodGet, a.MakeGetParsedZettelHandler(ucParseZettel)) webSrv.AddListRoute('q', server.MethodGet, a.MakeQueryHandler(ucListMeta)) webSrv.AddZettelRoute('u', server.MethodGet, a.MakeListUnlinkedMetaHandler( ucGetMeta, ucUnlinkedRefs, &ucEvaluate)) |
︙ | ︙ |
Changes to cmd/main.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 | // under this license. //----------------------------------------------------------------------------- package cmd import ( "crypto/sha256" | < | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // under this license. //----------------------------------------------------------------------------- package cmd import ( "crypto/sha256" "flag" "fmt" "net" "net/url" "os" "runtime/debug" "strconv" |
︙ | ︙ | |||
119 120 121 122 123 124 125 | } func getConfig(fs *flag.FlagSet) *meta.Meta { cfg := fetchStartupConfiguration(fs) fs.Visit(func(flg *flag.Flag) { switch flg.Name { case "p": | < | < < | < | 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | } func getConfig(fs *flag.FlagSet) *meta.Meta { cfg := fetchStartupConfiguration(fs) fs.Visit(func(flg *flag.Flag) { switch flg.Name { case "p": cfg.Set(keyListenAddr, net.JoinHostPort("127.0.0.1", flg.Value.String())) case "a": cfg.Set(keyAdminPort, flg.Value.String()) case "d": val := flg.Value.String() if strings.HasPrefix(val, "/") { val = "dir://" + val } else { val = "dir:" + val } |
︙ | ︙ | |||
148 149 150 151 152 153 154 | case "v": cfg.Set(keyVerbose, flg.Value.String()) } }) return cfg } | < < < < < < < < < > | | | | | | | | | | | | | | | | | | | | | | | < < < < | | > | | | | > | | | | 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 | case "v": cfg.Set(keyVerbose, flg.Value.String()) } }) return cfg } func deleteConfiguredBoxes(cfg *meta.Meta) { for _, p := range cfg.PairsRest() { if key := p.Key; strings.HasPrefix(key, kernel.BoxURIs) { cfg.Delete(key) } } } const ( keyAdminPort = "admin-port" keyAssetDir = "asset-dir" keyBaseURL = "base-url" keyDebug = "debug-mode" keyDefaultDirBoxType = "default-dir-box-type" keyInsecureCookie = "insecure-cookie" keyInsecureHTML = "insecure-html" keyListenAddr = "listen-addr" keyLogLevel = "log-level" keyMaxRequestSize = "max-request-size" keyOwner = "owner" keyPersistentCookie = "persistent-cookie" keyBoxOneURI = kernel.BoxURIs + "1" keyReadOnly = "read-only-mode" keyTokenLifetimeHTML = "token-lifetime-html" keyTokenLifetimeAPI = "token-lifetime-api" keyURLPrefix = "url-prefix" keyVerbose = "verbose-mode" ) func setServiceConfig(cfg *meta.Meta) bool { debugMode := cfg.GetBool(keyDebug) if debugMode && kernel.Main.GetKernelLogger().Level() > logger.DebugLevel { kernel.Main.SetGlobalLogLevel(logger.DebugLevel) } if strLevel, found := cfg.Get(keyLogLevel); found { if level := logger.ParseLevel(strLevel); level.IsValid() { kernel.Main.SetGlobalLogLevel(level) } } err := setConfigValue(nil, kernel.CoreService, kernel.CoreDebug, debugMode) err = setConfigValue(err, kernel.CoreService, kernel.CoreVerbose, cfg.GetBool(keyVerbose)) if val, found := cfg.Get(keyAdminPort); found { err = setConfigValue(err, kernel.CoreService, kernel.CorePort, val) } err = setConfigValue(err, kernel.AuthService, kernel.AuthOwner, cfg.GetDefault(keyOwner, "")) err = setConfigValue(err, kernel.AuthService, kernel.AuthReadonly, cfg.GetBool(keyReadOnly)) err = setConfigValue( err, kernel.BoxService, kernel.BoxDefaultDirType, cfg.GetDefault(keyDefaultDirBoxType, kernel.BoxDirTypeNotify)) err = setConfigValue(err, kernel.BoxService, kernel.BoxURIs+"1", "dir:./zettel") for i := 1; ; i++ { key := kernel.BoxURIs + strconv.Itoa(i) val, found := cfg.Get(key) if !found { break } err = setConfigValue(err, kernel.BoxService, key, val) } err = setConfigValue(err, kernel.ConfigService, kernel.ConfigInsecureHTML, cfg.GetDefault(keyInsecureHTML, kernel.ConfigSecureHTML)) err = setConfigValue(err, kernel.WebService, kernel.WebListenAddress, cfg.GetDefault(keyListenAddr, "127.0.0.1:23123")) if val, found := cfg.Get(keyBaseURL); found { err = setConfigValue(err, kernel.WebService, kernel.WebBaseURL, val) } if val, found := cfg.Get(keyURLPrefix); found { err = setConfigValue(err, kernel.WebService, kernel.WebURLPrefix, val) } err = setConfigValue(err, kernel.WebService, kernel.WebSecureCookie, !cfg.GetBool(keyInsecureCookie)) err = setConfigValue(err, kernel.WebService, kernel.WebPersistentCookie, cfg.GetBool(keyPersistentCookie)) if val, found := cfg.Get(keyMaxRequestSize); found { err = setConfigValue(err, kernel.WebService, kernel.WebMaxRequestSize, val) } err = setConfigValue( err, kernel.WebService, kernel.WebTokenLifetimeAPI, cfg.GetDefault(keyTokenLifetimeAPI, "")) err = setConfigValue( err, kernel.WebService, kernel.WebTokenLifetimeHTML, cfg.GetDefault(keyTokenLifetimeHTML, "")) if val, found := cfg.Get(keyAssetDir); found { err = setConfigValue(err, kernel.WebService, kernel.WebAssetDir, val) } return err == nil } func setConfigValue(err error, subsys kernel.Service, key string, val any) error { if err == nil { err = kernel.Main.SetConfig(subsys, key, fmt.Sprint(val)) if err != nil { kernel.Main.GetKernelLogger().Fatal().Str("key", key).Str("value", fmt.Sprint(val)).Err(err).Msg("Unable to set configuration") } } return err } func executeCommand(name string, args ...string) int { command, ok := Get(name) if !ok { fmt.Fprintf(os.Stderr, "Unknown command %q\n", name) return 1 } fs := command.GetFlags() if err := fs.Parse(args); err != nil { fmt.Fprintf(os.Stderr, "%s: unable to parse flags: %v %v\n", name, args, err) return 1 } cfg := getConfig(fs) if !setServiceConfig(cfg) { fs.Usage() return 2 } kern := kernel.Main var createManager kernel.CreateBoxManagerFunc if command.Boxes { createManager = func(boxURIs []*url.URL, authManager auth.Manager, rtConfig config.Config) (box.Manager, error) { |
︙ | ︙ |
Changes to config/config.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // Package config provides functions to retrieve runtime configuration data. package config import ( "context" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) // Key values that are supported by Config.Get const ( KeyFooterHTML = "footer-html" | > | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // Package config provides functions to retrieve runtime configuration data. package config import ( "context" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) // Key values that are supported by Config.Get const ( KeyFooterHTML = "footer-html" |
︙ | ︙ | |||
38 39 40 41 42 43 44 | // GetSiteName returns the current value of the "site-name" key. GetSiteName() string // GetHomeZettel returns the value of the "home-zettel" key. GetHomeZettel() id.Zid | > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | // GetSiteName returns the current value of the "site-name" key. GetSiteName() string // GetHomeZettel returns the value of the "home-zettel" key. GetHomeZettel() id.Zid // GetHTMLInsecurity returns the current GetHTMLInsecurity() HTMLInsecurity // GetMaxTransclusions returns the maximum number of indirect transclusions. GetMaxTransclusions() int // GetYAMLHeader returns the current value of the "yaml-header" key. GetYAMLHeader() bool // GetZettelFileSyntax returns the current value of the "zettel-file-syntax" key. GetZettelFileSyntax() []string } // AuthConfig are relevant configuration values for authentication. type AuthConfig interface { // GetSimpleMode returns true if system tuns in simple-mode. GetSimpleMode() bool // GetExpertMode returns the current value of the "expert-mode" key. GetExpertMode() bool // GetVisibility returns the visibility value of the metadata. GetVisibility(m *meta.Meta) meta.Visibility } // HTMLInsecurity states what kind of insecure HTML is allowed. // The lowest value is the most secure one (disallowing any HTML) type HTMLInsecurity uint8 // Constant values for HTMLInsecurity: const ( NoHTML HTMLInsecurity = iota SyntaxHTML MarkdownHTML ZettelmarkupHTML ) func (hi HTMLInsecurity) String() string { switch hi { case SyntaxHTML: return "html" case MarkdownHTML: return "markdown" case ZettelmarkupHTML: return "zettelmarkup" } return "secure" } // AllowHTML returns true, if the given HTML insecurity level matches the given syntax value. func (hi HTMLInsecurity) AllowHTML(syntax string) bool { switch hi { case SyntaxHTML: return syntax == api.ValueSyntaxHTML case MarkdownHTML: return syntax == api.ValueSyntaxHTML || syntax == "markdown" || syntax == "md" case ZettelmarkupHTML: return syntax == api.ValueSyntaxZmk || syntax == api.ValueSyntaxHTML || syntax == "markdown" || syntax == "md" } return false } |
Added docs/manual/00000000025001.
> > > > > > > | 1 2 3 4 5 6 7 | id: 00000000025001 title: Zettelstore User CSS role: configuration syntax: css created: 20210622110143 modified: 20220926183101 visibility: public |
Added docs/manual/00000000025001.css.
> > | 1 2 | /* User-defined CSS */ .example { border-style: dotted !important } |
Changes to docs/manual/00001002000000.zettel.
1 2 3 4 5 | id: 00001002000000 title: Design goals for the Zettelstore role: manual tags: #design #goal #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 37 | id: 00001002000000 title: Design goals for the Zettelstore role: manual tags: #design #goal #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20221018105415 Zettelstore supports the following design goals: ; Longevity of stored notes / zettel : Every zettel you create should be readable without the help of any tool, even without Zettelstore. : It should be not hard to write other software that works with your zettel. ; Single user : All zettel belong to you, only to you. Zettelstore provides its services only to one person: you. If the computer running Zettelstore is securely configured, there should be no risk that others are able to read or update your zettel. : If you want, you can customize Zettelstore in a way that some specific or all persons are able to read some of your zettel. ; Ease of installation : If you want to use the Zettelstore software, all you need is to copy the executable to an appropriate file directory and start working. : Upgrading the software is done just by replacing the executable with a newer one. ; Ease of operation : There is only one executable for Zettelstore and one directory, where your zettel are stored. : If you decide to use multiple directories, you are free to configure Zettelstore appropriately. ; Multiple modes of operation : You can use Zettelstore as a standalone software on your device, but you are not restricted to it. : You can install the software on a central server, or you can install it on all your devices with no restrictions how to synchronize your zettel. ; Multiple user interfaces : Zettelstore provides a default [[web-based user interface|00001014000000]]. Anybody can provide alternative user interfaces, e.g. for special purposes. ; Simple service : The purpose of Zettelstore is to safely store your zettel and to provide some initial relations between them. : External software can be written to deeply analyze your zettel and the structures they form. ; Security by default : Without any customization, Zettelstore provides its services in a safe and secure manner and does not expose you (or other users) to security risks. : If you know what use are doing, Zettelstore allows you to relax some security-related preferences. However, even in this case, the more secure way is chosen. |
Changes to docs/manual/00001004010000.zettel.
1 2 3 4 5 6 | id: 00001004010000 title: Zettelstore startup configuration role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001004010000 title: Zettelstore startup configuration role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20221018184208 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. |
︙ | ︙ | |||
64 65 66 67 68 69 70 71 72 73 74 75 76 77 | Default: ""notify"" ; [!insecure-cookie|''insecure-cookie''] : Must be set to [[true|00001006030500]], if authentication is enabled and Zettelstore is not accessible not via HTTPS (but via HTTP). Otherwise web browser are free to ignore the authentication cookie. Default: ""false"" ; [!listen-addr|''listen-addr''] : Configures the network address, where the Zettelstore service is listening for requests. Syntax is: ''[NETWORKIP]:PORT'', where ''NETWORKIP'' is the IP-address of the networking interface (or something like ""0.0.0.0"" if you want to listen on all network interfaces, and ''PORT'' is the TCP port. Default value: ""127.0.0.1:23123"" ; [!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]]. | > > > > > > > > > > | 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | Default: ""notify"" ; [!insecure-cookie|''insecure-cookie''] : Must be set to [[true|00001006030500]], if authentication is enabled and Zettelstore is not accessible not via HTTPS (but via HTTP). Otherwise web browser are free to ignore the authentication cookie. Default: ""false"" ; [!insecure-html|''insecure-html''] : Allows to use HTML, e.g. within supported markup languages, even if this might introduce security-related problems. However, HTML containing the ``<script>`` or the ``<iframe>`` tag is always ignored. But due to ""clever"" ways of combining HTML, CSS, JavaScript, there might be some negative security consequences. Please be aware of this! Allowed values: ""html"" (allow zettel with [[syntax ""html""|00001008000000#html]]), ""markdown"" (""html"", plus allow inline HTML for Markdown markup only), ""zettelmarkup"" (""markdown"", plus allow inline HTML for Zettelmarkup). Any other value is interpreted as ""secure"". Default: ""secure"". ; [!listen-addr|''listen-addr''] : Configures the network address, where the Zettelstore service is listening for requests. Syntax is: ''[NETWORKIP]:PORT'', where ''NETWORKIP'' is the IP-address of the networking interface (or something like ""0.0.0.0"" if you want to listen on all network interfaces, and ''PORT'' is the TCP port. Default value: ""127.0.0.1:23123"" ; [!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]]. |
︙ | ︙ |
Changes to docs/manual/00001006020000.zettel.
1 2 3 4 5 6 | id: 00001006020000 title: Supported Metadata Keys role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 | | < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | id: 00001006020000 title: Supported Metadata Keys role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 modified: 20221004134841 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]]. ; [!author|''author''] : A string value describing the author of a zettel. If given, it will be shown in the [[web user interface|00001014000000]] for the zettel. ; [!back|''back''] : Is a property that contains the identifier of all zettel that reference the zettel of this metadata, that are not referenced by this zettel. Basically, it is the value of [[''backward''|#backward]], but without any zettel identifier that is contained in [[''forward''|#forward]]. ; [!backward|''backward''] : Is a property that contains the identifier of all zettel that reference the zettel of this metadata. References within invertible values are not included here, e.g. [[''precursor''|#precursor]]. ; [!box-number|''box-number''] : Is a computed value and contains the number of the box where the zettel was found. For all but the [[predefined zettel|00001005090000]], this number is equal to the number __X__ specified in startup configuration key [[''box-uri-__X__''|00001004010000#box-uri-x]]. ; [!copyright|''copyright''] : Defines a copyright string that will be encoded. If not given, the value ''default-copyright'' from the [[configuration zettel|00001004020000#default-copyright]] will be used. ; [!created|''created''] : Date and time when a zettel was created through Zettelstore. If you create a zettel with an editor software outside Zettelstore, you should set it manually to an appropriate value. |
︙ | ︙ | |||
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 | If you edit a zettel with an editor software outside Zettelstore, you should set it manually to an appropriate value. This is a computed value. There is no need to set it via Zettelstore. ; [!precursor|''precursor''] : References zettel for which this zettel is a ""Folgezettel"" / follow-up zettel. Basically the inverse of key [[''folge''|#folge]]. ; [!published|''published''] : This property contains the timestamp of the mast modification / creation of the zettel. If [[''modified''|#modified]] is set with a valid timestamp, it contains the its value. Otherwise, if [[''created''|#created]] is set with a valid timestamp, it contains the its value. Otherwise, if the zettel identifier contains a valid timestamp, the identifier is used. In all other cases, this property is not set. It can be used for [[sorting|00001007700000]] zettel based on their publication date. It is a computed value. There is no need to set it via Zettelstore. ; [!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, it is ignored. ; [!syntax|''syntax''] : Specifies the syntax that should be used for interpreting the zettel. The zettel about [[other markup languages|00001008000000]] defines supported values. If it is not given, it defaults to ''plain''. ; [!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). | > > > > > > > > > > | 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 | If you edit a zettel with an editor software outside Zettelstore, you should set it manually to an appropriate value. This is a computed value. There is no need to set it via Zettelstore. ; [!precursor|''precursor''] : References zettel for which this zettel is a ""Folgezettel"" / follow-up zettel. Basically the inverse of key [[''folge''|#folge]]. ; [!predecessor|''predecessor''] : References the zettel that contains a previous version of the content. In contrast to [[''precursor''|#precurso]] / [[''folge''|#folge]], this is a reference because of technical reasons, not because of content-related reasons. Basically the inverse of key [[''successors''|#successors]]. ; [!published|''published''] : This property contains the timestamp of the mast modification / creation of the zettel. If [[''modified''|#modified]] is set with a valid timestamp, it contains the its value. Otherwise, if [[''created''|#created]] is set with a valid timestamp, it contains the its value. Otherwise, if the zettel identifier contains a valid timestamp, the identifier is used. In all other cases, this property is not set. It can be used for [[sorting|00001007700000]] zettel based on their publication date. It is a computed value. There is no need to set it via Zettelstore. ; [!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, it is ignored. ; [!successors|''successors''] : Is a property that contains identifier of all zettel that reference this zettel through the [[''predecessor''|#predecessor]] value. Therefore, it references all zettel that contain a new version of the content and/or metadata. In contrast to [[''folge''|#folge]], these are references because of technical reasons, not because of content-related reasons. In most cases, zettel referencing the current zettel should be updated to reference a successor zettel. The [[query reference|00001007040310]] [[query:backward? successors?]] lists all such zettel. ; [!syntax|''syntax''] : Specifies the syntax that should be used for interpreting the zettel. The zettel about [[other markup languages|00001008000000]] defines supported values. If it is not given, it defaults to ''plain''. ; [!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/00001007000000.zettel.
1 2 3 4 5 6 | id: 00001007000000 title: Zettelmarkup role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 | | | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | id: 00001007000000 title: Zettelmarkup role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20221018104725 Zettelmarkup is a rich plain-text based markup language for writing zettel content. Besides the zettel content, Zettelmarkup is also used for specifying the title of a zettel, regardless of the syntax of a zettel. Zettelmarkup supports the longevity of stored notes by providing a syntax that any person can easily read, as well as a computer. Zettelmarkup can be much easier parsed / consumed by a software compared to other markup languages. Writing a parser for [[Markdown|https://daringfireball.net/projects/markdown/syntax]] is quite challenging. [[CommonMark|00001008010500]] is an attempt to make it simpler by providing a comprehensive specification, combined with an extra chapter to give hints for the implementation. Zettelmarkup follows some simple principles that anybody who knows to ho write software should be able understand to create an implementation. Zettelmarkup is a markup language on its own. This is in contrast to Markdown, which is basically a super-set of HTML: every HTML document is a valid Markdown document.[^To be precise: the content of the ``<body>`` of each HTML document is a valid Markdown document.] While HTML is a markup language that will probably last for a long time, it cannot be easily translated to other formats, such as PDF, JSON, or LaTeX. Additionally, it is allowed to embed other languages into HTML, such as CSS or even JavaScript. This could create problems with longevity as well as security problems. Zettelmarkup is a rich markup language, but it focuses on relatively short zettel content. It allows embedding other content, simple tables, quotations, description lists, and images. It provides a broad range of inline formatting, including __emphasized__, **strong**, ~~deleted~~{-} and >>inserted>> text. Footnotes[^like this] are supported, links to other zettel and to external material, as well as citation keys. Zettelmarkup allows to include content from other zettel and to embed the result of a search query. Zettelmarkup might be seen as a proprietary markup language. But if you want to use [[Markdown/CommonMark|00001008010000]] and you need support for footnotes or tables, you'll end up with proprietary extensions. However, the Zettelstore supports CommonMark as a zettel syntax, so you can mix both Zettelmarkup zettel and CommonMark zettel in one store to get the best of both worlds. * [[General principles|00001007010000]] * [[Basic definitions|00001007020000]] * [[Block-structured elements|00001007030000]] * [[Inline-structured element|00001007040000]] * [[Attributes|00001007050000]] * [[Query expressions|00001007700000]] * [[Summary of formatting characters|00001007800000]] * [[Tutorial|00001007900000]] |
Changes to docs/manual/00001007031100.zettel.
1 2 3 4 5 6 | id: 00001007031100 title: Zettelmarkup: Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20220131151022 | | | | | > | | 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 | id: 00001007031100 title: Zettelmarkup: Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20220131151022 modified: 20220922155031 A transclusion allows to include the content of other zettel into the current zettel. The transclusion specification begins with three consecutive left curly bracket characters (""''{''"", U+007B) at the first position of a line and ends with three consecutive right curly bracket characters (""''}''"", U+007D). The curly brackets delimit either a [[zettel identifier|00001006050000]] or a searched zettel list. You can add some [[attributes|00001007050000]], although a transclusion does not support the default attribute. Any other characters in this line will be ignored. This leads to three variants of transclusions: # Transclusion of the content of another zettel into the current zettel. This is done if you specify a zettel identifier, and is called __zettel transclusion__. # Transclusion of the list of zettel references that satisfy a [[query expression|00001007700000]]. This is called __query transclusion__. # Transclusion of the content of an image, referenced by a [[hosted or based|00001007040310#link-specifications]] link / URL. The first two variants are described on separate zettel: * [[Zettel transclusion|00001007031110]] * [[Query transclusion|00001007031140]] |
Changes to docs/manual/00001007031110.zettel.
1 2 3 4 5 6 | id: 00001007031110 title: Zettelmarkup: Zettel Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20220809132350 | | | | 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 | id: 00001007031110 title: Zettelmarkup: Zettel Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20220809132350 modified: 20220926183331 A zettel transclusion is specified by the following sequence, starting at the first position in a line: ''{{{zettel-identifier}}}''. When evaluated, the referenced zettel is read. If it contains some transclusions itself, these will be expanded, recursively. When a recursion is detected, expansion does not take place. Instead an error message replaces the transclude specification. An error message is also given, if the zettel cannot be read or if too many transclusions are made. The maximum number of transclusion can be controlled by setting the value [[''max-transclusions''|00001004020000#max-transclusions]] of the runtime configuration zettel. If everything went well, the referenced, expanded zettel will replace the transclusion element. For example, to include the text of the Zettel titled ""Zettel identifier"", just specify its identifier [[''00001006050000''|00001006050000]] in the transclude element: ```zmk {{{00001006050000}}} ``` This will result in: :::example {{{00001006050000}}} ::: Please note: if the referenced zettel is changed, all transclusions will also change. This allows, for example, to create a bigger document just by transcluding smaller zettel. |
︙ | ︙ |
Changes to docs/manual/00001007031140.zettel.
1 2 3 4 5 6 | id: 00001007031140 title: Zettelmarkup: Query Transclusion role: manual tags: #manual #search #zettelmarkup #zettelstore syntax: zmk created: 20220809132350 | | | | | | | > > > | | | | | | 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 | id: 00001007031140 title: Zettelmarkup: Query Transclusion role: manual tags: #manual #search #zettelmarkup #zettelstore syntax: zmk created: 20220809132350 modified: 20221014164027 A query transclusion is specified by the following sequence, starting at the first position in a line: ''{{{query:query-expression}}}''. The line must literally start with the sequence ''{{{query:''. Everything after this prefix is interpreted as a [[query expression|00001007700000]]. When evaluated, the query expression is evaluated, often resulting in a list of [[links|00001007040310]] to zettel, matching the query expression. The result replaces the query transclusion element. For example, to include the list of all zettel with the [[tags|00001006020000#tags]] ""#search"", ordered by title specify the following query transclude element: ```zmk {{{query:tags:#search ORDER title}}} ``` This will result in: :::example {{{query:tags:#search ORDER title}}} ::: For example, this allows to create a dynamic list of zettel inside a zettel, maybe to provide some introductory text followed by a list of child zettel. The query will deliver only those zettel, which the current user is allowed to read. In the above example, the action list is empty. This leads to the described list of zettel. The following actions are supported, parameter and aggregate actions: ; ''N'' (or any word that starts with ""''N''"" (parameter) : The resulting list will be a numbered list. ; ''MINn'' (parameter) : Emit only those values with at least __n__ aggregated values. __n__ must be a positive integer, ''MIN'' must be given in upper-case letters. ; ''MAXn'' (parameter) : Emit only those values with at most __n__ aggregated values. __n__ must be a positive integer, ''MAX'' must be given in upper-case letters. ; ''TITLE'' (parameter) : All words following ''TITLE'' are joined together to form a title. It is used for the ''ATOM'' and ''RSS'' action. ; ''ATOM'' (aggregate) : Transform the zettel list into an [[Atom 1.0|https://www.rfc-editor.org/rfc/rfc4287]]-conformant document / feed. The document is embedded into the referencing zettel. ; ''RSS'' (aggregate) : Transform the zettel list into a [[RSS 2.0|https://www.rssboard.org/rss-specification]]-conformant document / feed. The document is embedded into the referencing zettel. ; Any [[metadata key|00001006020000]] of type [[Word|00001006035500]], [[WordSet|00001006036000]], or [[TagSet|00001006034000]] (aggregates) : Emit an aggregate of the given metadata key. The key can be given in any letter case. ```zmk {{{query:tags:#search | tags}}} ``` This is a tag cloud of all tags that are used together with the tag #search: :::example {{{query:tags:#search | tags}}} ::: |
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 | id: 00001007031200 title: Zettelmarkup: Inline-Zettel Block role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20220201142439 modified: 20221018121251 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 embed some [[Markdown|00001008010500]] content, because you are too lazy to translate Markdown into Zettelmarkup. Another example is to specify HTML code to use it for some kind of web front-end 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. |
︙ | ︙ | |||
31 32 33 34 35 36 37 | will be rendered as: :::example @@@markdown A link to [this](00001007031200) zettel. @@@ ::: | > | | | 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | will be rendered as: :::example @@@markdown A link to [this](00001007031200) zettel. @@@ ::: If you have set [[''insecure-html''|00001004010000#insecure-html]] to the value ""zettelmarkup"", the following markup is not ignored: ```zmk @@@html <h1>H1 Heading</h1> Alea iacta est @@@ ``` will render a section heading of level 1, which is not allowed within Zettelmarkup: :::example @@@html <h1>H1 Heading</h1> Alea iacta est @@@ ::: :::note |
︙ | ︙ |
Changes to docs/manual/00001007040000.zettel.
1 2 3 4 5 6 | id: 00001007040000 title: Zettelmarkup: Inline-Structured Elements role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001007040000 title: Zettelmarkup: Inline-Structured Elements role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20220920143243 Most characters you type is concerned with inline-structured elements. The content of a zettel contains is many cases just ordinary text, lightly formatted. Inline-structured elements allow to format your text and add some helpful links or images. Sometimes, you want to enter characters that have no representation on your keyboard. ; Text formatting |
︙ | ︙ | |||
36 37 38 39 40 41 42 | ==== Backslash The backslash character (""''\\''"", U+005C) gives the next character another meaning. * If a space character follows, it is converted in a non-breaking space (U+00A0). * If a line ending follows the backslash character, the line break is converted from a __soft break__ into a __hard break__. * Every other character is taken as itself, but without the interpretation of a Zettelmarkup element. For example, if you want to enter a ""'']''"" into a [[footnote text|00001007040330]], you should escape it with a backslash. | < < < < < < < < < < < < < < < < < < < < < < < < > > > | 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 | ==== Backslash The backslash character (""''\\''"", U+005C) gives the next character another meaning. * If a space character follows, it is converted in a non-breaking space (U+00A0). * If a line ending follows the backslash character, the line break is converted from a __soft break__ into a __hard break__. * Every other character is taken as itself, but without the interpretation of a Zettelmarkup element. For example, if you want to enter a ""'']''"" into a [[footnote text|00001007040330]], you should escape it with a backslash. ==== Entities & more Sometimes it is not easy to enter special characters. If you know the Unicode code point of that character, or its name according to the [[HTML standard|https://html.spec.whatwg.org/multipage/named-characters.html]], you can enter it by number or by name. Regardless which method you use, an entity always begins with an ampersand character (""''&''"", U+0026) and ends with a semicolon character (""'';''"", U+003B). If you know the HTML name of the character you want to enter, put it between these two character. Example: ``&`` is rendered as ::&::{=example}. If you want to enter its numeric code point, a number sign character must follow the ampersand character, followed by digits to base 10. Example: ``&`` is rendered in HTML as ::&::{=example}. You also can enter its numeric code point as a hex number, if you put the letter ""x"" after the numeric sign character. Example: ``&`` is rendered in HTML as ::&::{=example}. According to the [[HTML Standard|https://html.spec.whatwg.org/multipage/syntax.html#character-references]], some numeric code points are not allowed. These are all code point below the numeric value 32 (decimal) or 0x20 (hex) and all code points for [[noncharacter|https://infra.spec.whatwg.org/#noncharacter]] values. Since some Unicode character are used quite often, a special notation is introduced for them: * Two consecutive hyphen-minus characters result in an __en-dash__ character. It is typically used in numeric ranges. ``pages 4--7`` will be rendered in HTML as: ::pages 4--7::{=example}. Alternative specifications are: ``–``, ``&x8211``, and ``–``. |
Changes to docs/manual/00001007040310.zettel.
1 2 3 4 5 6 | id: 00001007040310 title: Zettelmarkup: Links role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210810155955 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001007040310 title: Zettelmarkup: Links role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210810155955 modified: 20220922154843 There are two kinds of links, regardless of links to (internal) other zettel or to (external) material. Both kinds begin with two consecutive left square bracket characters (""''[''"", U+005B) and ends with two consecutive right square bracket characters (""'']''"", U+005D). The first form provides some text plus the link specification, delimited by a vertical bar character (""''|''"", U+007C): ``[[text|linkspecification]]``. The text is a sequence of [[inline elements|00001007040000]]. However, it should not contain links itself. |
︙ | ︙ | |||
22 23 24 25 26 27 28 | The resulting reference is called ""zettel reference"". If the link specification begins with the string ''query:'', the text following this string will be interpreted as a [[query expression|00001007700000]]. The resulting reference is called ""query reference"". When this type of references is rendered, it will typically reference a list of all zettel that fulfills the query expression. A link specification starting with one slash character (""''/''"", U+002F), or one or two full stop characters (""''.''"", U+002E) followed by a slash character, | | | | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | The resulting reference is called ""zettel reference"". If the link specification begins with the string ''query:'', the text following this string will be interpreted as a [[query expression|00001007700000]]. The resulting reference is called ""query reference"". When this type of references is rendered, it will typically reference a list of all zettel that fulfills the query expression. A link specification starting with one slash character (""''/''"", U+002F), or one or two full stop characters (""''.''"", U+002E) followed by a slash character, will be interpreted as a local reference, called __hosted reference__. Such references will be interpreted relative to the web server hosting the Zettelstore. If a link specification begins with two slash characters (called __based reference__), it will be interpreted relative to the value of [[''url-prefix''|00001004010000#url-prefix]]. To specify some material outside the Zettelstore, just use an normal Uniform Resource Identifier (URI) as defined by [[RFC\ 3986|https://tools.ietf.org/html/rfc3986]]. === Other topics If the link references another zettel, and this zettel is not readable for the current user, because of a missing access rights, then only the associated text is presented. |
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 | id: 00001007040324 title: Zettelmarkup: Inline-mode Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210811154251 modified: 20220926183509 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]]). 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. |
︙ | ︙ | |||
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | ** In case the fragment names a [[mark|00001007040350]], the inline-structured elements after the mark are used. Initial spaces and line breaks are ignored in this case. Example: ``{{00001007040322#spin}}`` is rendered as ::{{00001007040322#spin}}::{=example}. ** Just specifying the fragment identifier will reference something in the current page. This is not allowed, to prevent a possible endless recursion. If no inline-structured elements are found, the transclude specification is replaced by an error message. To avoid an exploding ""transclusion bomb"", a form of a [[billion laughs attack|https://en.wikipedia.org/wiki/Billion_laughs_attack]] (also known as ""XML bomb""), the total number of transclusions / expansions is limited. The limit can be controlled by setting the value [[''max-transclusions''|00001004020000#max-transclusions]] of the runtime configuration zettel. === See also [[Full transclusion|00001007031100]] does not work inside some text, but is used for [[block-structured elements|00001007030000]]. | > > > > | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | ** In case the fragment names a [[mark|00001007040350]], the inline-structured elements after the mark are used. Initial spaces and line breaks are ignored in this case. Example: ``{{00001007040322#spin}}`` is rendered as ::{{00001007040322#spin}}::{=example}. ** Just specifying the fragment identifier will reference something in the current page. This is not allowed, to prevent a possible endless recursion. * If the reference is a [[hosted or based|00001007040310#link-specifications]] link / URL to an image, that image will be rendered. Example: ``{{/z/00000000040001}}`` is rendered as ::{{/z/00000000040001}}::{=example} If no inline-structured elements are found, the transclude specification is replaced by an error message. To avoid an exploding ""transclusion bomb"", a form of a [[billion laughs attack|https://en.wikipedia.org/wiki/Billion_laughs_attack]] (also known as ""XML bomb""), the total number of transclusions / expansions is limited. The limit can be controlled by setting the value [[''max-transclusions''|00001004020000#max-transclusions]] of the runtime configuration zettel. === See also [[Full transclusion|00001007031100]] does not work inside some text, but is used for [[block-structured elements|00001007030000]]. |
Changes to docs/manual/00001007790000.zettel.
1 2 3 4 5 6 | id: 00001007790000 title: Useful query expressions role: manual tags: #example #manual #search #zettelstore syntax: zmk created: 20220810144539 | | < | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | id: 00001007790000 title: Useful query expressions role: manual tags: #example #manual #search #zettelstore syntax: zmk created: 20220810144539 modified: 20220917174956 |= Query Expression |= Meaning | [[query:role:configuration]] | Zettel that contains some configuration data for the Zettelstore | [[query:ORDER REVERSE created LIMIT 40]] | 40 recently created zettel | [[query:ORDER REVERSE published LIMIT 40]] | 40 recently updated zettel | [[query:RANDOM LIMIT 40]] | 40 random zettel | [[query:dead?]] | Zettel with invalid / dead links | [[query:backward!? precursor!?]] | Zettel that are not referenced by other zettel | [[query:tags!?]] | Zettel without tags |
Changes to docs/manual/00001007900000.zettel.
1 2 3 4 5 6 7 8 9 | id: 00001007900000 title: Zettelmarkup: Tutorial role: manual tags: #manual #tutorial #zettelmarkup #zettelstore syntax: zmk modified: 20220811135314 * [[First steps|00001007903000]]: learn something about paragraphs, emphasized text, and lists. * [[Second steps|00001007906000]]: know about links, thematic breaks, and headings. | > | 1 2 3 4 5 6 7 8 9 10 | id: 00001007900000 title: Zettelmarkup: Tutorial role: manual tags: #manual #tutorial #zettelmarkup #zettelstore syntax: zmk created: 20220810182917 modified: 20220811135314 * [[First steps|00001007903000]]: learn something about paragraphs, emphasized text, and lists. * [[Second steps|00001007906000]]: know about links, thematic breaks, and headings. |
Changes to docs/manual/00001007903000.zettel.
1 2 3 4 5 | id: 00001007903000 title: Zettelmarkup: First Steps role: manual tags: #manual #tutorial #zettelmarkup #zettelstore syntax: zmk | > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001007903000 title: Zettelmarkup: First Steps role: manual tags: #manual #tutorial #zettelmarkup #zettelstore syntax: zmk created: 20220810182917 modified: 20220926183359 [[Zettelmarkup|00001007000000]] allows you to leave your text as it is, at least in many situations. Some characters have a special meaning, but you have to enter them is a defined way to see a visible change. Zettelmarkup is designed to be used for zettel, which are relatively short. It allows to produce longer texts, but you should probably use a different tool, if you want to produce an scientific paper, to name an example. === Paragraphs |
︙ | ︙ | |||
47 48 49 50 51 52 53 | * First item * Second item * Third item ``` This is rendered as: | | | | 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 | * First item * Second item * Third item ``` This is rendered as: :::example * First item * Second item * Third item ::: Similar, an numbered list element begins a line with the number sign (sic!) followed by a space character: ```zmk # First item # Second item # Third item ``` This is rendered as: :::example # First item # Second item # Third item ::: --- After trying out these markup elements, you might want to continue with the [[second steps|00001007906000]]. |
Changes to docs/manual/00001007906000.zettel.
1 2 3 4 5 | id: 00001007906000 title: Zettelmarkup: Second Steps role: manual tags: #manual #tutorial #zettelmarkup #zettelstore syntax: zmk | > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001007906000 title: Zettelmarkup: Second Steps role: manual tags: #manual #tutorial #zettelmarkup #zettelstore syntax: zmk created: 20220811115501 modified: 20220926183427 After you have [[learned|00001007903000]] the basic concepts and markup of Zettelmarkup (paragraphs, emphasized text, and lists), this zettel introduces you into the concepts of links, thematic breaks, and headings. === Links A Zettelstore is much more useful, if you connect related zettel. If you read a zettel later, this allows you to know about the context of a zettel. [[Zettelmarkup|00001007000000]] allows you to specify such a connection. |
︙ | ︙ | |||
48 49 50 51 52 53 54 | --- Second paragraph. ``` Both are rendered as: | | | 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | --- Second paragraph. ``` Both are rendered as: :::example First paragraph. --- Second paragraph. ::: Try it! |
︙ | ︙ |
Changes to docs/manual/00001008000000.zettel.
1 2 3 4 5 6 | id: 00001008000000 title: Other Markup Languages role: manual tags: #manual #zettelstore syntax: zmk created: 20210126175300 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001008000000 title: Other Markup Languages role: manual tags: #manual #zettelstore syntax: zmk created: 20210126175300 modified: 20221018115054 [[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 |
︙ | ︙ | |||
25 26 27 28 29 30 31 32 33 34 35 36 37 38 | : A [[Cascading Style Sheet|https://www.w3.org/Style/CSS/]], to be used when rendering a zettel as HTML. ; [!gif|''gif'']; [!jpeg|''jpeg'']; [!jpg|''jpg'']; [!png|''png''] : The formats for pixel graphics. Typically the data is stored in a separate file and the syntax is given in the meta-file, which has the same name as the zettel identifier and has no file extension.[^Before version 0.2, the meta-file 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. The ``< script ...>`` tag is an example. See [[security aspects of Markdown|00001008010000#security-aspects]] for some details. ; [!markdown|''markdown''], [!md|''md''] : For those who desperately need [[Markdown|https://daringfireball.net/projects/markdown/]]. Since the world of Markdown is so diverse, a [[CommonMark|00001008010500]] parser is used. | > > | 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | : A [[Cascading Style Sheet|https://www.w3.org/Style/CSS/]], to be used when rendering a zettel as HTML. ; [!gif|''gif'']; [!jpeg|''jpeg'']; [!jpg|''jpg'']; [!png|''png''] : The formats for pixel graphics. Typically the data is stored in a separate file and the syntax is given in the meta-file, which has the same name as the zettel identifier and has no file extension.[^Before version 0.2, the meta-file 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]]). Since HTML from unknown sources may contain security-related problems, zettel with this syntax are treated as an empty zettel, unless the startup configuration value for [[''insecure-html''|00001004010000#insecure-html]] is set to at least the value ""html"". For security reasons, equivocal elements will not be encoded in the HTML format / web user interface. The ``< script ...>`` tag is an example. See [[security aspects of Markdown|00001008010000#security-aspects]] for some details. ; [!markdown|''markdown''], [!md|''md''] : For those who desperately need [[Markdown|https://daringfireball.net/projects/markdown/]]. Since the world of Markdown is so diverse, a [[CommonMark|00001008010500]] parser is used. |
︙ | ︙ |
Changes to docs/manual/00001008010000.zettel.
1 2 3 4 5 | id: 00001008010000 title: Use Markdown within Zettelstore role: manual tags: #manual #markdown #zettelstore syntax: zmk | > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001008010000 title: Use Markdown within Zettelstore role: manual tags: #manual #markdown #zettelstore syntax: zmk created: 20210126175322 modified: 20221018115601 If you are customized to use Markdown as your markup language, you can configure Zettelstore to support your decision. Zettelstore supports the [[CommonMark|00001008010500]] dialect of Markdown. === Use Markdown as the default markup language of Zettelstore Update the [[New Zettel|00000000090001]] template (and other relevant template zettel) by setting the syntax value to ''md'' or ''markdown''. |
︙ | ︙ | |||
27 28 29 30 31 32 33 | Not every Markdown tool allows both file extensions. BTW, metadata is stored in a file without a file extension, if neither ''yaml-header'' nor ''zettel-file-syntax'' is set. === Security aspects You should be aware that Markdown is a super-set of HTML. | | > > | > | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | Not every Markdown tool allows both file extensions. BTW, metadata is stored in a file without a file extension, if neither ''yaml-header'' nor ''zettel-file-syntax'' is set. === Security aspects You should be aware that Markdown is a super-set of HTML. The body of any HTML document is also a valid Markdown document. If you write your own zettel, this is probably not a problem. However, if you receive zettel from others, you should be careful. An attacker might include malicious HTML code in your zettel. For example, HTML allows to embed JavaScript, a full-sized programming language that drives many web sites. When a zettel is displayed, JavaScript code might be executed, sometimes with harmful results. By default, Zettelstore prohibits any HTML content. If you want to relax this rule, you should take a look at the startup configuration key [[''insecure-html''|00001004010000#insecure-html]]. Even if you have allowed HTML content, Zettelstore mitigates some of the security problems by ignoring suspicious text when it encodes a zettel as HTML. Any HTML text that might contain the ``<script>`` tag or the ``<iframe>`` tag is ignored. This may lead to unexpected results if you depend on these. Other [[encodings|00001012920500]] may still contain the full HTML text. Any external client of Zettelstore, which does not use Zettelstore's [[HTML encoding|00001012920510]], must be programmed to take care of malicious code. |
Changes to docs/manual/00001008010500.zettel.
1 2 3 4 5 | id: 00001008010500 title: CommonMark role: manual tags: #manual #markdown #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: 00001008010500 title: CommonMark role: manual tags: #manual #markdown #zettelstore syntax: zmk created: 20220113183435 modified: 20221018123145 url: https://commonmark.org/ [[CommonMark|https://commonmark.org/]] is a Markdown dialect, an [[attempt|https://xkcd.com/927/]] to unify all the different, divergent dialects of Markdown by providing an unambiguous syntax specification for Markdown, together with a suite of comprehensive tests to validate implementation. Time will show, if this attempt is successful. However, CommonMark is a well specified Markdown dialect, in contrast to most (if not all) other dialects. Other software adopts CommonMark somehow, notably [[GitHub Flavored Markdown|https://github.github.com/gfm/]] (GFM). But they provide proprietary extensions, which makes it harder to change to another CommonMark implementation if needed. Plus, they sometimes build on an older specification of CommonMark. Zettelstore supports the latest CommonMark [[specification version 0.30 (2021-06-19)|https://spec.commonmark.org/0.30/]]. If possible, Zettelstore will adapt to newer versions when they are available. To provide CommonMark support, Zettelstore uses currently the [[Goldmark|https://github.com/yuin/goldmark]] implementation, which passes all validation tests of CommonMark. Internally, CommonMark is translated into some kind of super-set of [[Zettelmarkup|00001007000000]], which additionally allows to use HTML code.[^Effectively, Markdown and CommonMark are itself super-sets of HTML.] This Zettelmarkup super-set is later [[encoded|00001012920500]], often into [[HTML|00001012920510]]. Because Zettelstore HTML encoding philosophy differs a little bit to that of CommonMark, Zettelstore itself will not pass the CommonMark test suite fully. However, no CommonMark language element will fail to be encoded as HTML. In most cases, the differences are not visible for an user, but only by comparing the generated HTML code. Be aware, depending on the value of the startup configuration key [[''insecure-html''|00001004010000#insecure-html]], HTML code found within a CommonMark document or within the mentioned kind of super-set of Zettelmarkup will typically be ignored for security-related reasons. |
Changes to docs/manual/00001010000000.zettel.
1 2 3 4 5 | id: 00001010000000 title: Security role: manual tags: #configuration #manual #security #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 | id: 00001010000000 title: Security role: manual tags: #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 modified: 20221018123622 Your zettel could contain sensitive content. You probably want to ensure that only authorized person can read and/or modify them. Zettelstore ensures this in various ways. === Local first The Zettelstore is designed to run on your local computer. If you do not configure it in other ways, no person from another computer can connect to your Zettelstore. You must explicitly configure it to allow access from other computers. In the case that you own multiple computers, you do not have to access the Zettelstore remotely. You could install Zettelstore on each computer and set-up some software to synchronize your zettel. Since zettel are stored as ordinary files, this task could be done in various ways. === Read-only You can start the Zettelstore in an read-only mode. Nobody, not even you as the owner of the Zettelstore, can change something via its interfaces[^However, as an owner, you have access to the files that store the zettel. If you modify the files, these changes will be reflected via its interfaces.]. |
︙ | ︙ | |||
56 57 58 59 60 61 62 | When Zettelstore is accessed remotely, the messages that are sent between Zettelstore and the client must be encrypted. Otherwise, an eavesdropper could fetch sensible data, such as passwords or precious content that is not for the public. The Zettelstore itself does not encrypt messages. But you can put a server in front of it, which is able to handle encryption. Most generic web server software do allow this. | | | 57 58 59 60 61 62 63 64 65 66 67 68 | When Zettelstore is accessed remotely, the messages that are sent between Zettelstore and the client must be encrypted. Otherwise, an eavesdropper could fetch sensible data, such as passwords or precious content that is not for the public. The Zettelstore itself does not encrypt messages. But you can put a server in front of it, which is able to handle encryption. Most generic web server software do allow this. To enforce encryption, [[authenticated sessions|00001010040700]] are marked as secure by default. If you still want to access the Zettelstore remotely without encryption, you must change the startup configuration. Otherwise, authentication will not work. * [[Use a server for encryption|00001010090100]] |
Changes to docs/manual/00001010070200.zettel.
1 2 3 4 5 6 | id: 00001010070200 title: Visibility rules for zettel role: manual tags: #authorization #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001010070200 title: Visibility rules for zettel role: manual tags: #authorization #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 modified: 20220923104643 For every zettel you can specify under which condition the zettel is visible to others. This is controlled with the metadata key [[''visibility''|00001006020000#visibility]]. The following values are supported: ; [!public|""public""] : The zettel is visible to everybody, even if the user is not authenticated. |
︙ | ︙ | |||
37 38 39 40 41 42 43 | Please note: if [[authentication is not enabled|00001010040100]], every user has the same rights as the owner of a Zettelstore. This is also true, if the Zettelstore runs additionally in [[read-only mode|00001004010000#read-only-mode]]. In this case, the [[runtime configuration zettel|00001004020000]] is shown (its visibility is ""owner""). The [[startup configuration|00001004010000]] is not shown, because the associated computed zettel with identifier ''00000000000096'' is stored with the visibility ""expert"". If you want to show such a zettel, you must set ''expert-mode'' to true. === Examples | | | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | Please note: if [[authentication is not enabled|00001010040100]], every user has the same rights as the owner of a Zettelstore. This is also true, if the Zettelstore runs additionally in [[read-only mode|00001004010000#read-only-mode]]. In this case, the [[runtime configuration zettel|00001004020000]] is shown (its visibility is ""owner""). The [[startup configuration|00001004010000]] is not shown, because the associated computed zettel with identifier ''00000000000096'' is stored with the visibility ""expert"". If you want to show such a zettel, you must set ''expert-mode'' to true. === Examples By using a [[query expression|00001007700000]], you can easily create a zettel list based on the ''visibility'' metadata key: | public | [[query:visibility:public]] | login | [[query:visibility:login]] | creator | [[query:visibility:creator]] | owner | [[query:visibility:owner]] | expert | [[query:visibility:expert]][^Only if [[''expert-mode''|00001004020000#expert-mode]] is enabled, this list will show some zettel.] |
Changes to docs/manual/00001012000000.zettel.
1 2 3 4 5 6 | id: 00001012000000 title: API role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 | | < < < > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | id: 00001012000000 title: API role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20220923105117 The API (short for ""**A**pplication **P**rogramming **I**nterface"") is the primary way to communicate with a running Zettelstore. Most integration with other systems and services is done through the API. The [[web user interface|00001014000000]] is just an alternative, secondary way of interacting with a Zettelstore. === Background The API is HTTP-based and uses plain text and JSON as its main encoding format for exchanging messages between a Zettelstore and its client software. There is an [[overview zettel|00001012920000]] that shows the structure of the endpoints used by the API and gives an indication about its use. === Authentication If [[authentication is enabled|00001010040100]], most API calls must include an [[access token|00001010040700]] that proves the identity of the caller. * [[Authenticate an user|00001012050200]] to obtain an access token * [[Renew an access token|00001012050400]] without costly re-authentication * [[Provide an access token|00001012050600]] when doing an API call === Zettel lists * [[Query the list of all zettel|00001012051400]] * [[List plain text titles of all zettel|00001012051200]] === Working with zettel * [[Create a new zettel|00001012053200]] * [[Retrieve metadata and content of an existing zettel|00001012053300]] * [[Retrieve metadata of an existing zettel|00001012053400]] * [[Retrieve evaluated metadata and content of an existing zettel in various encodings|00001012053500]] * [[Retrieve parsed metadata and content of an existing zettel in various encodings|00001012053600]] |
︙ | ︙ |
Changes to docs/manual/00001012051200.zettel.
1 | id: 00001012051200 | | | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | > | > | > | > > > > > > > > | > > > > > > > > > > > > > | > > | 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 | id: 00001012051200 title: API: List plain text titles of all or some zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20220923105412 To list the plain text titles of all zettel just send a HTTP GET request to the [[endpoint|00001012920000]] ''/z''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. A plain text document is returned, with one line per zettel. Each line contains in the first 14 characters the [[zettel identifier|00001006050000]]. Separated by a space character, the title of the zettel follows: ```sh # curl http://127.0.0.1:23123/z 00001012051200 API: Renew an access token 00001012050600 API: Provide an access token 00001012050400 API: Renew an access token 00001012050200 API: Authenticate a client 00001012000000 API ``` The query parameter ""''q''"" allows you to specify [[query expressions|00001007700000]] for a full-text search of all zettel content and/or restricting the search according to specific metadata, similar to endpoint ''/q'' to [[query zettel|00001012051400]]. You are allowed to specify this query parameter more than once. This parameter loosely resembles the search form of the [[web user interface|00001014000000]]. For example, if you want to retrieve all zettel that contain the string ""API"" in its title, your request will be: ```sh # curl 'http://127.0.0.1:23123/z?q=title%3AAPI' 00001012921000 API: JSON structure of an access token 00001012920500 Formats available by the API 00001012920000 Endpoints used by the API ... ``` An implicit precondition is that the zettel must contain the given metadata key. For a metadata key like [[''title''|00001006020000#title]], which has a default value, this precondition should always be true. But the situation is different for a key like [[''url''|00001006020000#url]]. Both ``curl 'http://localhost:23123/z?q=url%3A'`` and ``curl 'http://localhost:23123/z?q=url%3A!'`` may result in an empty list. Alternatively, you also can use the [[endpoint|00001012920000]] ''/z'' for a simpler result format. The first example translates to: ```sh # curl 'http://127.0.0.1:23123/z?q=title%3AAPI' 00001012921000 API: JSON structure of an access token 00001012920500 Formats available by the API 00001012920000 Endpoints used by the API ... ``` === Other output formats If you want to get the list of metadata of all or some zettel in JSON format, use endpoint ''/q'' to [[query the list of zettel|00001012051400]]. === HTTP Status codes ; ''200'' : Retrieval was successful, the body contains an [[appropriate JSON object|00001012921000]]. ; ''400'' : Request was not valid. There are several reasons for this. Maybe the access bearer token was not valid. |
Changes to docs/manual/00001012051400.zettel.
1 2 3 4 5 6 | id: 00001012051400 title: API: Query the list of all zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20220912111111 | | | | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | id: 00001012051400 title: API: Query the list of all zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20220912111111 modified: 20220923105405 The [[endpoint|00001012920000]] ''/q'' allows to query the list of all zettel[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. A [[query|00001007700000]] is an optional [[search expression|00001007700000#search-expression]], together with an optional [[list of actions|00001007700000#action-list]] (described below). An empty search expression will select all zettel. An empty list of action, or no valid action, returns the list of all selected zettel metadata. Search expression and action list are separated by a vertical bar character (""''|''"", U+007C), and must be given with the query parameter ''q''. For example, to list all roles used in the Zettelstore, send a HTTP GET request to the endpoint ''/q?q=|role''. If successful, the output is a JSON object: ```sh |
︙ | ︙ | |||
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | __n__ must be a positive integer, ''MAX'' must be given in upper-case letters. ; Any [[metadata key|00001006020000]] of type [[Word|00001006035500]], [[WordSet|00001006036000]], or [[TagSet|00001006034000]] (aggregates) : Emit an aggregate of the given metadata key. The key can be given in any letter case. Only the first aggregate action will be executed. === HTTP Status codes ; ''200'' : Query was successful. ; ''204'' : Query was successful, but results in no content. Most likely, you specified no appropriate aggregator. ; ''400'' : Request was not valid. There are several reasons for this. Maybe the access bearer token was not valid, or you forgot to specify a valid query. | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | __n__ must be a positive integer, ''MAX'' must be given in upper-case letters. ; Any [[metadata key|00001006020000]] of type [[Word|00001006035500]], [[WordSet|00001006036000]], or [[TagSet|00001006034000]] (aggregates) : Emit an aggregate of the given metadata key. The key can be given in any letter case. Only the first aggregate action will be executed. If no valid aggregate action is given, the metadata of all selected zettel are returned.[^For this reason, a HTTP GET to the endpoint ''/j'' is an alias for the endpoint ''/q''.] ```sh # curl http://127.0.0.1:23123/q {"query":"","list":[{"id":"00001012051200","meta":{"title":"API: Renew an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"},"rights":62},{"id":"00001012050600","meta":{"title":"API: Provide an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"},"rights":62},{"id":"00001012050400","meta":{"title":"API: Renew an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"},"rights":62},{"id":"00001012050200","meta":{"title":"API: Authenticate a client","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"},"rights":62},{"id":"00001012000000","meta":{"title":"API","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"},"rights":62}]} ``` The JSON object contains a key ''"list"'' where its value is a list of zettel JSON objects. These zettel JSON objects themselves contains the keys ''"id"'' (value is a string containing the [[zettel identifier|00001006050000]]), ''"meta"'' (value as a JSON object), and ''"rights"'' (encodes the [[access rights|00001012921200]] for the given zettel). The value of key ''"meta"'' effectively contains all metadata of the identified zettel, where metadata keys are encoded as JSON object keys and metadata values encoded as JSON strings. Additionally, the JSON object contains the keys ''"query"'' and ''"human"'' with a string value. Both will contain a textual description of the underlying query if you select only some zettel with a [[query expression|00001007700000]]. Without a selection, the values are the empty string. ''"query"'' returns the normalized query expression itself, while ''"human"'' is the normalized query expression to be read by humans. If you reformat the JSON output from the ''GET /q'' call, you'll see its structure better: ```json { "query": "", "human": "", "list": [ { "id": "00001012051200", "meta": { "title": "API: List for all zettel some data", "tags": "#api #manual #zettelstore", "syntax": "zmk", "role": "manual" }, "rights":62 }, { "id": "00001012050600", "meta": { "title": "API: Provide an access token", "tags": "#api #manual #zettelstore", "syntax": "zmk", "role": "manual" }, "rights":62 }, { "id": "00001012050400", "meta": { "title": "API: Renew an access token", "tags": "#api #manual #zettelstore", "syntax": "zmk", "role": "manual" }, "rights":62 }, { "id": "00001012050200", "meta": { "title": "API: Authenticate a client", "tags": "#api #manual #zettelstore", "syntax": "zmk", "role": "manual" }, "rights":62 }, { "id": "00001012000000", "meta": { "title": "API", "tags": "#api #manual #zettelstore", "syntax": "zmk", "role": "manual" }, "rights":62 } ] } ``` In this special case, the metadata of each zettel just contains the four default keys ''title'', ''tags'', ''syntax'', and ''role''. === Note This request (and similar others) will always return a list of metadata, provided the request was syntactically correct. There will never be a HTTP status code 403 (Forbidden), even if [[authentication was enabled|00001010040100]] and you did not provide a valid access token. In this case, the resulting list might be quite short (some zettel will have [[public visibility|00001010070200]]) or the list might be empty. With this call, you cannot differentiate between an empty result list (e.g because your search did not found a zettel with the specified term) and an empty list because of missing authorization (e.g. an invalid access token). === HTTP Status codes ; ''200'' : Query was successful. ; ''204'' : Query was successful, but results in no content. Most likely, you specified no appropriate aggregator. ; ''400'' : Request was not valid. There are several reasons for this. Maybe the access bearer token was not valid, or you forgot to specify a valid query. |
Deleted docs/manual/00001012051840.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted docs/manual/00001012052400.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to docs/manual/00001012053400.zettel.
1 2 3 4 5 6 | id: 00001012053400 title: API: Retrieve metadata of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210726174524 | | | < | 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: 00001012053400 title: API: Retrieve metadata of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210726174524 modified: 20220917175233 The [[endpoint|00001012920000]] to work with metadata of a specific zettel is ''/m/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/j/00001012053400''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. If successful, the output is a JSON object: ```sh # curl http://127.0.0.1:23123/m/00001012053400 {"meta":{"back":"00001012000000 00001012053300","backward":"00001012000000 00001012053300 00001012920000","box-number":"1","forward":"00001010040100 00001012050200 00001012920000 00001012920800","modified":"20211004111240","published":"20211004111240","role":"manual","syntax":"zmk","tags":"#api #manual #zettelstore","title":"API: Retrieve metadata of an existing zettel"},"rights":62} ``` Pretty-printed, this results in: ``` { "meta": { "back": "00001012000000 00001012053300", "backward": "00001012000000 00001012053300 00001012920000", "box-number": "1", "forward": "00001010040100 00001012050200 00001012920000 00001012920800", "modified": "20211004111240", "published": "20211004111240", "role": "manual", |
︙ | ︙ |
Changes to docs/manual/00001012080500.zettel.
1 2 3 4 5 6 | id: 00001012080500 title: API: Refresh internal data role: manual tags: #api #manual #zettelstore syntax: zmk created: 20211230230441 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | id: 00001012080500 title: API: Refresh internal data role: manual tags: #api #manual #zettelstore syntax: zmk created: 20211230230441 modified: 20220923104836 Zettelstore maintains some internal data to allow faster operations. One example is the [[content search|00001012051400]] for a term: Zettelstore does not need to scan all zettel to find all occurrences for the term. Instead, all word are stored internally, with a list of zettel where they occur. Another example is the way to determine which zettel are stored in a [[ZIP file|00001004011200]]. Scanning a ZIP file is a lengthy operation, therefore Zettelstore maintains a directory of zettel for each ZIP file. All these internal data may become stale. This should not happen, but when it comes e.g. to file handling, every operating systems behaves differently in very subtle ways. |
︙ | ︙ |
Changes to docs/manual/00001012920000.zettel.
1 2 3 4 5 6 | id: 00001012920000 title: Endpoints used by the API role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20210126175322 | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | id: 00001012920000 title: Endpoints used by the API role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20210126175322 modified: 20220923101703 All API endpoints conform to the pattern ''[PREFIX]LETTER[/ZETTEL-ID]'', where: ; ''PREFIX'' : is the URL prefix (default: ""/""), configured via the ''url-prefix'' [[startup configuration|00001004010000]], ; ''LETTER'' : is a single letter that specifies the resource type, ; ''ZETTEL-ID'' : is an optional 14 digits string that uniquely [[identify a zettel|00001006050000]]. The following letters are currently in use: |= Letter:| Without zettel identifier | With [[zettel identifier|00001006050000]] | Mnemonic | ''a'' | POST: [[client authentication|00001012050200]] | | **A**uthenticate | | PUT: [[renew access token|00001012050400]] | | ''j'' | GET: [[query zettel list|00001012051400]] (alias of ''/q'') | GET: [[retrieve zettel AS JSON|00001012053300]] | **J**SON | | POST: [[create new zettel|00001012053200]] | PUT: [[update a zettel|00001012054200]] | | | DELETE: [[delete the zettel|00001012054600]] | | | MOVE: [[rename the zettel|00001012054400]] | ''m'' | | GET: [[retrieve metadata|00001012053400]] | **M**etadata | ''o'' | | GET: [[list zettel order|00001012054000]] | **O**rder | ''p'' | | GET: [[retrieve parsed zettel|00001012053600]]| **P**arsed | ''q'' | GET: [[query zettel list|00001012051400]] | | **Q**uery | ''u'' | | GET [[unlinked references|00001012053900]] | **U**nlinked | ''v'' | | GET: [[retrieve evaluated zettel|00001012053500]] | E**v**aluated | ''x'' | GET: [[retrieve administrative data|00001012070500]] | GET: [[list zettel context|00001012053800]] | Conte**x**t | | POST: [[execute command|00001012080100]] |
︙ | ︙ |
Changes to docs/manual/00001018000000.zettel.
1 2 3 4 5 | id: 00001018000000 title: Troubleshooting role: manual tags: #manual #zettelstore syntax: zmk | > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001018000000 title: Troubleshooting role: manual tags: #manual #zettelstore syntax: zmk created: 00010101000000 modified: 20221020132617 This page lists some problems and their solutions that may occur when using your Zettelstore. === Installation * **Problem:** When you double-click on the Zettelstore executable icon, macOS complains that Zettelstore is an application from an unknown developer. Therefore, it will not start Zettelstore. ** **Solution:** Press the ''Ctrl'' key while opening the context menu of the Zettelstore executable with a right-click. |
︙ | ︙ | |||
30 31 32 33 34 35 36 | If you access the zettel via Zettelstore, a fatal error is reported. ** **Explanation:** Sometimes, the operating system does not tell Zettelstore about the removed zettel. This occurs mostly under MacOS. ** **Solution 1:** If you are running Zettelstore in [[""simple-mode""|00001004051100]] or if you have enabled [[''expert-mode''|00001004020000#expert-mode]], you are allowed to refresh the internal data by selecting ""Refresh"" in the Web User Interface (you find it in the menu ""Lists""). ** **Solution 2:** There is an [[API|00001012080500]] call to make Zettelstore aware of this change. ** **Solution 3:** If you have an enabled [[Administrator Console|00001004100000]] you can use the command [[''refresh''|00001004101000#refresh]] to make your changes visible. ** **Solution 4:** You configure the zettel box as [[""simple""|00001004011400]]. | > > > > > > > > > > > > > > > > > > > | 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 | If you access the zettel via Zettelstore, a fatal error is reported. ** **Explanation:** Sometimes, the operating system does not tell Zettelstore about the removed zettel. This occurs mostly under MacOS. ** **Solution 1:** If you are running Zettelstore in [[""simple-mode""|00001004051100]] or if you have enabled [[''expert-mode''|00001004020000#expert-mode]], you are allowed to refresh the internal data by selecting ""Refresh"" in the Web User Interface (you find it in the menu ""Lists""). ** **Solution 2:** There is an [[API|00001012080500]] call to make Zettelstore aware of this change. ** **Solution 3:** If you have an enabled [[Administrator Console|00001004100000]] you can use the command [[''refresh''|00001004101000#refresh]] to make your changes visible. ** **Solution 4:** You configure the zettel box as [[""simple""|00001004011400]]. === HTML content is not shown * **Problem:** You have entered some HTML code as content for your Zettelstore, but this content is not shown on the Web User Interface. You may have entered a Zettel with syntax [[""html""|00001008000000#html]], or you have used an [[inline-zettel block|00001007031200]] with syntax ""html"", or you entered a Zettel with syntax [[""markdown""|00001008000000#markdown]] (or ""md"") and used some HTML code fragments. ** **Explanation:** Working with HTML code from unknown sources may lead so severe security problems. The HTML code may force web browsers to load more content from external server, it may contain malicious JavaScript code, it may reference to CSS artifacts that itself load from external servers and may contains malicious software. Zettelstore tries to do its best to ignore problematic HTML code, but it may fail. Either because of unknown bugs or because of yet unknown changes in the future. Zettelstore sets a restrictive [[Content Security Policy|https://www.w3.org/TR/CSP/]], but this depends on web browsers to implement them correctly and on users to not disable it. Zettelstore will not display any HTML code, which contains a ``<script>>`` or an ``<iframe>`` tag. But attackers may find other ways to deploy their malicious code. Therefore, Zettelstore disallows any HTML content as a default. If you know what you are doing, e.g. because you will never copy HTML code you do not understand, you can relax this default. ** **Solution 1:** If you want zettel with syntax ""html"" not to be ignored, you set the startup configuration key [[''insecure-html''|00001004010000#insecure-html]] to the value ""html"". ** **Solution 2:** If you want zettel with syntax ""html"" not to be ignored, **and** want to allow HTML in Markdown, you set the startup configuration key [[''insecure-html''|00001004010000#insecure-html]] to the value ""markdown"". ** **Solution 3:** If you want zettel with syntax ""html"" not to be ignored, **and** want to allow HTML in Markdown, **and** want to use HTML code within Zettelmarkup, you set the startup configuration key [[''insecure-html''|00001004010000#insecure-html]] to the value ""zettelmarkup"". |
Changes to domain/meta/meta.go.
︙ | ︙ | |||
130 131 132 133 134 135 136 | func init() { registerKey(api.KeyID, TypeID, usageComputed, "") registerKey(api.KeyTitle, TypeZettelmarkup, usageUser, "") registerKey(api.KeyRole, TypeWord, usageUser, "") registerKey(api.KeyTags, TypeTagSet, usageUser, "") registerKey(api.KeySyntax, TypeWord, usageUser, "") | > | > > < < > | 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 | func init() { registerKey(api.KeyID, TypeID, usageComputed, "") registerKey(api.KeyTitle, TypeZettelmarkup, usageUser, "") registerKey(api.KeyRole, TypeWord, usageUser, "") registerKey(api.KeyTags, TypeTagSet, usageUser, "") registerKey(api.KeySyntax, TypeWord, usageUser, "") // Properties that are inverse keys registerKey(api.KeyFolge, TypeIDSet, usageProperty, "") registerKey(api.KeySuccessors, TypeIDSet, usageProperty, "") registerKey(api.KeyAuthor, TypeString, usageUser, "") registerKey(api.KeyBack, TypeIDSet, usageProperty, "") registerKey(api.KeyBackward, TypeIDSet, usageProperty, "") registerKey(api.KeyBoxNumber, TypeNumber, usageProperty, "") registerKey(api.KeyCopyright, TypeString, usageUser, "") registerKey(api.KeyCreated, TypeTimestamp, usageComputed, "") registerKey(api.KeyCredential, TypeCredential, usageUser, "") registerKey(api.KeyDead, TypeIDSet, usageProperty, "") registerKey(api.KeyForward, TypeIDSet, usageProperty, "") registerKey(api.KeyLang, TypeWord, usageUser, "") registerKey(api.KeyLicense, TypeEmpty, usageUser, "") registerKey(api.KeyModified, TypeTimestamp, usageComputed, "") registerKey(api.KeyPrecursor, TypeIDSet, usageUser, api.KeyFolge) registerKey(api.KeyPredecessor, TypeID, usageUser, api.KeySuccessors) registerKey(api.KeyPublished, TypeTimestamp, usageProperty, "") registerKey(api.KeyReadOnly, TypeWord, usageUser, "") registerKey(api.KeySummary, TypeZettelmarkup, usageUser, "") registerKey(api.KeyURL, TypeURL, usageUser, "") registerKey(api.KeyUselessFiles, TypeString, usageProperty, "") registerKey(api.KeyUserID, TypeWord, usageUser, "") registerKey(api.KeyUserRole, TypeWord, usageUser, "") |
︙ | ︙ |
Changes to encoder/encoder_blob_test.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package encoder_test import ( "testing" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" "zettelstore.de/z/parser" _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. ) | > | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package encoder_test import ( "testing" "zettelstore.de/c/api" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" "zettelstore.de/z/parser" _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. ) |
︙ | ︙ | |||
49 50 51 52 53 54 55 | } 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) | | | 50 51 52 53 54 55 56 57 58 59 60 | } 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", config.NoHTML)} checkEncodings(t, testNum, pe, tc.descr, tc.expect, "???") } } |
Changes to encoder/encoder_inline_test.go.
︙ | ︙ | |||
473 474 475 476 477 478 479 | encoderZmk: useZmk, }, }, { descr: "Inline HTML Zettel", zmk: `@@<hr>@@{="html"}`, expect: expectMap{ | | | | > > > > > > > > > > > | 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 | encoderZmk: useZmk, }, }, { descr: "Inline HTML Zettel", zmk: `@@<hr>@@{="html"}`, expect: expectMap{ encoderZJSON: `[]`, encoderHTML: ``, encoderSexpr: `()`, encoderText: ``, encoderZmk: ``, }, }, { descr: "Inline Text Zettel", zmk: `@@<hr>@@{="text"}`, expect: expectMap{ encoderZJSON: `[{"":"Zettel","a":{"":"text"},"s":"<hr>"}]`, encoderHTML: ``, encoderSexpr: `((LITERAL-ZETTEL (("" "text")) "<hr>"))`, encoderText: `<hr>`, encoderZmk: useZmk, }, }, { descr: "", zmk: ``, |
︙ | ︙ |
Changes to encoder/encoder_test.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 22 23 24 25 26 27 28 | "fmt" "testing" "codeberg.org/t73fde/sxpf" "zettelstore.de/c/api" "zettelstore.de/c/sexpr" "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/sexprenc" // Allow to use sexpr encoder. _ "zettelstore.de/z/encoder/textenc" // Allow to use text encoder. | > | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | "fmt" "testing" "codeberg.org/t73fde/sxpf" "zettelstore.de/c/api" "zettelstore.de/c/sexpr" "zettelstore.de/z/ast" "zettelstore.de/z/config" "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/sexprenc" // Allow to use sexpr encoder. _ "zettelstore.de/z/encoder/textenc" // Allow to use text encoder. |
︙ | ︙ | |||
62 63 64 65 66 67 68 | inp := input.NewInput([]byte(tc.zmk)) var pe parserEncoder if tc.inline { is := parser.ParseInlines(inp, api.ValueSyntaxZmk) cleaner.CleanInlineSlice(&is) pe = &peInlines{is: is} } else { | | | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | inp := input.NewInput([]byte(tc.zmk)) var pe parserEncoder if tc.inline { is := parser.ParseInlines(inp, api.ValueSyntaxZmk) cleaner.CleanInlineSlice(&is) pe = &peInlines{is: is} } else { pe = &peBlocks{bs: parser.ParseBlocks(inp, nil, api.ValueSyntaxZmk, config.NoHTML)} } checkEncodings(t, testNum, pe, tc.descr, tc.expect, tc.zmk) checkSexpr(t, testNum, pe, tc.descr) } } func checkEncodings(t *testing.T, testNum int, pe parserEncoder, descr string, expected expectMap, zmkDefault string) { |
︙ | ︙ |
Changes to encoder/sexprenc/transform.go.
︙ | ︙ | |||
79 80 81 82 83 84 85 | return t.getTable(n) case *ast.TranscludeNode: return sxpf.NewPairFromValues(sexpr.SymTransclude, getAttributes(n.Attrs), getReference(n.Ref)) case *ast.BLOBNode: return getBLOB(n) case *ast.TextNode: return sxpf.NewPairFromValues(sexpr.SymText, sxpf.NewString(n.Text)) | < < | 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | return t.getTable(n) case *ast.TranscludeNode: return sxpf.NewPairFromValues(sexpr.SymTransclude, getAttributes(n.Attrs), getReference(n.Ref)) case *ast.BLOBNode: return getBLOB(n) case *ast.TextNode: return sxpf.NewPairFromValues(sexpr.SymText, sxpf.NewString(n.Text)) case *ast.SpaceNode: if t.inVerse { return sxpf.NewPairFromValues(sexpr.SymSpace, sxpf.NewString(n.Lexeme)) } return sxpf.NewPairFromValues(sexpr.SymSpace) case *ast.BreakNode: if n.Hard { |
︙ | ︙ |
Changes to encoder/textenc/textenc.go.
︙ | ︙ | |||
127 128 129 130 131 132 133 | v.visitTable(n) return nil case *ast.TranscludeNode, *ast.BLOBNode: return nil case *ast.TextNode: v.b.WriteString(n.Text) return nil | < < < | 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | v.visitTable(n) return nil case *ast.TranscludeNode, *ast.BLOBNode: return nil case *ast.TextNode: v.b.WriteString(n.Text) return nil case *ast.SpaceNode: v.b.WriteByte(' ') return nil case *ast.BreakNode: if n.Hard { v.b.WriteByte('\n') } else { |
︙ | ︙ |
Changes to encoder/zjsonenc/zjsonenc.go.
︙ | ︙ | |||
120 121 122 123 124 125 126 | writeEscaped(&v.b, n.Ref.String()) case *ast.BLOBNode: v.visitBLOB(n) case *ast.TextNode: v.writeNodeStart(zjson.TypeText) v.writeContentStart(zjson.NameString) writeEscaped(&v.b, n.Text) | < < < < | 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | writeEscaped(&v.b, n.Ref.String()) case *ast.BLOBNode: v.visitBLOB(n) case *ast.TextNode: v.writeNodeStart(zjson.TypeText) v.writeContentStart(zjson.NameString) writeEscaped(&v.b, n.Text) case *ast.SpaceNode: v.writeNodeStart(zjson.TypeSpace) if v.inVerse { v.writeContentStart(zjson.NameString) writeEscaped(&v.b, n.Lexeme) } case *ast.BreakNode: |
︙ | ︙ |
Changes to encoder/zmkenc/zmkenc.go.
︙ | ︙ | |||
127 128 129 130 131 132 133 | case *ast.TranscludeNode: v.b.WriteStrings("{{{", n.Ref.String(), "}}}") v.visitAttributes(n.Attrs) case *ast.BLOBNode: v.visitBLOB(n) case *ast.TextNode: v.visitText(n) | < < | 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | case *ast.TranscludeNode: v.b.WriteStrings("{{{", n.Ref.String(), "}}}") v.visitAttributes(n.Attrs) case *ast.BLOBNode: v.visitBLOB(n) case *ast.TextNode: v.visitText(n) case *ast.SpaceNode: v.b.WriteString(n.Lexeme) case *ast.BreakNode: v.visitBreak(n) case *ast.LinkNode: v.visitLink(n) case *ast.EmbedRefNode: |
︙ | ︙ |
Added encoding/atom/atom.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | //----------------------------------------------------------------------------- // 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 atom provides an Atom encoding. package atom import ( "bytes" "context" "time" "zettelstore.de/c/api" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoding" "zettelstore.de/z/encoding/xml" "zettelstore.de/z/kernel" "zettelstore.de/z/query" "zettelstore.de/z/strfun" ) const ContentType = "application/atom+xml" type Configuration struct { Title string Generator string NewURLBuilderAbs func() *api.URLBuilder } func (c *Configuration) Setup(ctx context.Context, cfg config.Config) { baseURL := kernel.Main.GetConfig(kernel.WebService, kernel.WebBaseURL).(string) c.Title = cfg.GetSiteName() c.Generator = (kernel.Main.GetConfig(kernel.CoreService, kernel.CoreProgname).(string) + " " + kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) c.NewURLBuilderAbs = func() *api.URLBuilder { return api.NewURLBuilder(baseURL, 'h') } } func (c *Configuration) Marshal(q *query.Query, ml []*meta.Meta) []byte { atomUpdated := encoding.LastUpdated(ml, time.RFC3339) feedLink := c.NewURLBuilderAbs().String() var buf bytes.Buffer buf.WriteString(`<feed xmlns="http://www.w3.org/2005/Atom">` + "\n") xml.WriteTag(&buf, " ", "title", c.Title) xml.WriteTag(&buf, " ", "id", feedLink) buf.WriteString(` <link rel="self" href="`) if s := q.String(); s != "" { strfun.XMLEscape(&buf, c.NewURLBuilderAbs().AppendQuery(s).String()) } else { strfun.XMLEscape(&buf, feedLink) } buf.WriteString(`"/>` + "\n") if atomUpdated != "" { xml.WriteTag(&buf, " ", "updated", atomUpdated) } xml.WriteTag(&buf, " ", "generator", c.Generator) buf.WriteString(" <author><name>Unknown</name></author>\n") for _, m := range ml { entryUpdated := "" if val, found := m.Get(api.KeyPublished); found { if published, err := time.ParseInLocation(id.ZidLayout, val, time.Local); err == nil { entryUpdated = published.UTC().Format(time.RFC3339) } } link := c.NewURLBuilderAbs().SetZid(api.ZettelID(m.Zid.String())).String() buf.WriteString(" <entry>\n") xml.WriteTag(&buf, " ", "title", encoding.TitleAsText(m)) xml.WriteTag(&buf, " ", "id", link) buf.WriteString(` <link rel="self" href="`) strfun.XMLEscape(&buf, link) buf.WriteString(`"/>` + "\n") buf.WriteString(` <link rel="alternate" type="text/html" href="`) strfun.XMLEscape(&buf, link) buf.WriteString(`"/>` + "\n") if entryUpdated != "" { xml.WriteTag(&buf, " ", "updated", entryUpdated) } if tags, found := m.GetList(api.KeyTags); found && len(tags) > 0 { for _, tag := range tags { for len(tag) > 0 && tag[0] == '#' { tag = tag[1:] } if tag != "" { buf.WriteString(` <category term="`) strfun.XMLEscape(&buf, tag) buf.WriteString("\"/>\n") } } } buf.WriteString(" </entry>\n") } buf.WriteString("</feed>") return buf.Bytes() } |
Added encoding/encoding.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | //----------------------------------------------------------------------------- // 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 encoding provides helper functions for encodings. package encoding import ( "bytes" "time" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/parser" ) // LastUpdated returns the formated time of the zettel which was updated at the latest time. func LastUpdated(ml []*meta.Meta, timeFormat string) string { maxPublished := time.Date(1, time.January, 1, 0, 0, 0, 0, time.Local) for _, m := range ml { if val, found := m.Get(api.KeyPublished); found { if published, err := time.ParseInLocation(id.ZidLayout, val, time.Local); err == nil { if maxPublished.Before(published) { maxPublished = published } } } } if maxPublished.Year() > 1 { return maxPublished.UTC().Format(timeFormat) } return "" } var textEnc = textenc.Create() // TitleAsText returns the title of a zettel as plain text func TitleAsText(m *meta.Meta) string { var title bytes.Buffer titleIns := parser.ParseMetadata(m.GetTitle()) if _, err := textEnc.WriteInlines(&title, &titleIns); err != nil { return m.GetTitle() } return title.String() } |
Changes to encoding/rss/rss.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 | // Package rss provides a RSS encoding. package rss import ( "bytes" "context" | < | | | > | 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 | // Package rss provides a RSS encoding. package rss import ( "bytes" "context" "time" "zettelstore.de/c/api" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoding" "zettelstore.de/z/encoding/xml" "zettelstore.de/z/kernel" "zettelstore.de/z/query" "zettelstore.de/z/strfun" ) const ContentType = "application/rss+xml" type Configuration struct { Title string Language string |
︙ | ︙ | |||
46 47 48 49 50 51 52 | c.Copyright = defVals.GetDefault(api.KeyCopyright, "") c.Generator = (kernel.Main.GetConfig(kernel.CoreService, kernel.CoreProgname).(string) + " " + kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) c.NewURLBuilderAbs = func() *api.URLBuilder { return api.NewURLBuilder(baseURL, 'h') } } | | | | | | > > | > | > | < | > > > > | > > > > > > | > < < | | | < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | > > | > > > | > > > > | | < < < < | | < < < < < < < < < < < < < | < < < < < | < < < | < | > > > | 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 | c.Copyright = defVals.GetDefault(api.KeyCopyright, "") c.Generator = (kernel.Main.GetConfig(kernel.CoreService, kernel.CoreProgname).(string) + " " + kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) c.NewURLBuilderAbs = func() *api.URLBuilder { return api.NewURLBuilder(baseURL, 'h') } } func (c *Configuration) Marshal(q *query.Query, ml []*meta.Meta) []byte { rssPublished := encoding.LastUpdated(ml, time.RFC1123Z) atomLink := "" if s := q.String(); s != "" { atomLink = c.NewURLBuilderAbs().AppendQuery(s).String() } var buf bytes.Buffer buf.WriteString(`<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">` + "\n<channel>\n") xml.WriteTag(&buf, " ", "title", c.Title) xml.WriteTag(&buf, " ", "link", c.NewURLBuilderAbs().String()) xml.WriteTag(&buf, " ", "description", "") xml.WriteTag(&buf, " ", "language", c.Language) xml.WriteTag(&buf, " ", "copyright", c.Copyright) if rssPublished != "" { xml.WriteTag(&buf, " ", "pubDate", rssPublished) xml.WriteTag(&buf, " ", "lastBuildDate", rssPublished) } xml.WriteTag(&buf, " ", "generator", c.Generator) buf.WriteString(" <docs>https://www.rssboard.org/rss-specification</docs>\n") if atomLink != "" { buf.WriteString(` <atom:link href="`) strfun.XMLEscape(&buf, atomLink) buf.WriteString(`" rel="self" type="application/rss+xml"></atom:link>` + "\n") } for _, m := range ml { itemPublished := "" if val, found := m.Get(api.KeyPublished); found { if published, err := time.ParseInLocation(id.ZidLayout, val, time.Local); err == nil { itemPublished = published.UTC().Format(time.RFC1123Z) } } link := c.NewURLBuilderAbs().SetZid(api.ZettelID(m.Zid.String())).String() buf.WriteString(" <item>\n") xml.WriteTag(&buf, " ", "title", encoding.TitleAsText(m)) xml.WriteTag(&buf, " ", "link", link) xml.WriteTag(&buf, " ", "guid", link) if itemPublished != "" { xml.WriteTag(&buf, " ", "pubDate", itemPublished) } if tags, found := m.GetList(api.KeyTags); found && len(tags) > 0 { for _, tag := range tags { for len(tag) > 0 && tag[0] == '#' { tag = tag[1:] } if tag != "" { xml.WriteTag(&buf, " ", "category", tag) } } } buf.WriteString(" </item>\n") } buf.WriteString("</channel>\n</rss>") return buf.Bytes() } |
Added encoding/xml/xml.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | //----------------------------------------------------------------------------- // 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 xml provides helper for a XML-based encoding. package xml import ( "bytes" "zettelstore.de/z/strfun" ) // Header contains the string that should start all XML documents. const Header = `<?xml version="1.0" encoding="UTF-8"?>` + "\n" // WriteTag writes a simple XML tag with a given prefix and a specific value. func WriteTag(buf *bytes.Buffer, prefix, tag, value string) { buf.WriteString(prefix) buf.WriteByte('<') buf.WriteString(tag) buf.WriteByte('>') strfun.XMLEscape(buf, value) buf.WriteString("</") buf.WriteString(tag) buf.WriteString(">\n") } |
Changes to evaluator/evaluator.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // Package evaluator interprets and evaluates the AST. package evaluator import ( "context" "errors" "fmt" "strconv" "strings" "zettelstore.de/c/api" "zettelstore.de/c/attrs" "zettelstore.de/z/ast" "zettelstore.de/z/box" | > | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // Package evaluator interprets and evaluates the AST. package evaluator import ( "context" "errors" "fmt" "path" "strconv" "strings" "zettelstore.de/c/api" "zettelstore.de/c/attrs" "zettelstore.de/z/ast" "zettelstore.de/z/box" |
︙ | ︙ | |||
51 52 53 54 55 56 57 | EvaluateBlock(ctx, port, rtConfig, &zn.Ast) } // EvaluateBlock evaluates the given block list in the given context, with // the given ports, and the given environment. func EvaluateBlock(ctx context.Context, port Port, rtConfig config.Config, bns *ast.BlockSlice) { evaluateNode(ctx, port, rtConfig, bns) | | | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | EvaluateBlock(ctx, port, rtConfig, &zn.Ast) } // EvaluateBlock evaluates the given block list in the given context, with // the given ports, and the given environment. func EvaluateBlock(ctx context.Context, port Port, rtConfig config.Config, bns *ast.BlockSlice) { evaluateNode(ctx, port, rtConfig, bns) cleaner.CleanBlockSlice(bns, true) } // 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, rtConfig config.Config, is *ast.InlineSlice) { evaluateNode(ctx, port, rtConfig, is) cleaner.CleanInlineSlice(is) |
︙ | ︙ | |||
199 200 201 202 203 204 205 | // Only zettel references will be evaluated. case ast.RefStateInvalid, ast.RefStateBroken: e.transcludeCount++ return makeBlockNode(createInlineErrorText(ref, "Invalid", "or", "broken", "transclusion", "reference")) case ast.RefStateSelf: e.transcludeCount++ return makeBlockNode(createInlineErrorText(ref, "Self", "transclusion", "reference")) | | > > > > > > | 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | // Only zettel references will be evaluated. case ast.RefStateInvalid, ast.RefStateBroken: e.transcludeCount++ return makeBlockNode(createInlineErrorText(ref, "Invalid", "or", "broken", "transclusion", "reference")) case ast.RefStateSelf: e.transcludeCount++ return makeBlockNode(createInlineErrorText(ref, "Self", "transclusion", "reference")) case ast.RefStateFound, ast.RefStateExternal: return tn case ast.RefStateHosted, ast.RefStateBased: if n := createEmbeddedNodeLocal(ref); n != nil { n.Attrs = tn.Attrs return makeBlockNode(n) } return tn case ast.RefStateQuery: e.transcludeCount++ return e.evalQueryTransclusion(tn.Ref.Value) default: return makeBlockNode(createInlineErrorText(ref, "Illegal", "block", "state", strconv.Itoa(int(ref.State)))) } |
︙ | ︙ | |||
374 375 376 377 378 379 380 | // Only zettel references will be evaluated. case ast.RefStateInvalid, ast.RefStateBroken: e.transcludeCount++ return createInlineErrorImage(en) case ast.RefStateSelf: e.transcludeCount++ return createInlineErrorText(ref, "Self", "embed", "reference") | | > > > > > > > | 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 | // Only zettel references will be evaluated. case ast.RefStateInvalid, ast.RefStateBroken: e.transcludeCount++ return createInlineErrorImage(en) case ast.RefStateSelf: e.transcludeCount++ return createInlineErrorText(ref, "Self", "embed", "reference") case ast.RefStateFound, ast.RefStateExternal: return en case ast.RefStateHosted, ast.RefStateBased: if n := createEmbeddedNodeLocal(ref); n != nil { n.Attrs = en.Attrs n.Inlines = en.Inlines return n } return en default: return createInlineErrorText(ref, "Illegal", "inline", "state", strconv.Itoa(int(ref.State))) } zid := mustParseZid(ref) zettel, err := e.port.GetZettel(box.NoEnrichContext(e.ctx), zid) |
︙ | ︙ | |||
483 484 485 486 487 488 489 490 491 492 493 494 495 496 | fn := &ast.FormatNode{ Kind: ast.FormatStrong, Inlines: ast.InlineSlice{ln}, } fn.Attrs = fn.Attrs.AddClass("error") return fn } func (e *evaluator) evaluateEmbeddedInline(content []byte, syntax string) ast.InlineSlice { is := parser.ParseInlines(input.NewInput(content), syntax) ast.Walk(e, &is) return is } | > > > > > > > > > > > > > > > | 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 | fn := &ast.FormatNode{ Kind: ast.FormatStrong, Inlines: ast.InlineSlice{ln}, } fn.Attrs = fn.Attrs.AddClass("error") return fn } func createEmbeddedNodeLocal(ref *ast.Reference) *ast.EmbedRefNode { ext := path.Ext(ref.Value) if ext != "" && ext[0] == '.' { ext = ext[1:] } pinfo := parser.Get(ext) if pinfo == nil || !pinfo.IsImageFormat { return nil } return &ast.EmbedRefNode{ Ref: ref, Syntax: ext, } } func (e *evaluator) evaluateEmbeddedInline(content []byte, syntax string) ast.InlineSlice { is := parser.ParseInlines(input.NewInput(content), syntax) ast.Walk(e, &is) return is } |
︙ | ︙ |
Changes to evaluator/list.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- package evaluator import ( "bytes" "context" | | > | 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 evaluator import ( "bytes" "context" "math" "sort" "strconv" "strings" "zettelstore.de/c/api" "zettelstore.de/c/attrs" "zettelstore.de/z/ast" "zettelstore.de/z/config" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoding/atom" "zettelstore.de/z/encoding/rss" "zettelstore.de/z/parser" "zettelstore.de/z/query" ) // QueryAction transforms a list of metadata according to query actions into a AST nested list. func QueryAction(ctx context.Context, q *query.Query, ml []*meta.Meta, rtConfig config.Config) ast.BlockNode { |
︙ | ︙ | |||
61 62 63 64 65 66 67 | if act == "TITLE" && i+1 < len(actions) { ap.title = strings.Join(actions[i+1:], " ") break } acts = append(acts, act) } for _, act := range acts { | > > > | | 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | if act == "TITLE" && i+1 < len(actions) { ap.title = strings.Join(actions[i+1:], " ") break } acts = append(acts, act) } for _, act := range acts { switch act { case "ATOM": return ap.createBlockNodeAtom(rtConfig) case "RSS": return ap.createBlockNodeRSS(rtConfig) } key := strings.ToLower(act) switch meta.Type(key) { case meta.TypeWord: return ap.createBlockNodeWord(key) case meta.TypeTagSet: |
︙ | ︙ | |||
214 215 216 217 218 219 220 221 222 223 224 225 226 227 | buf.WriteByte(':') bufLen := buf.Len() return ccs, bufLen } const fontSizes = 6 // Must be the number of CSS classes zs-font-size-* in base.css func (*actionPara) calcFontSizes(ccs meta.CountedCategories) map[int]attrs.Attributes { var fsAttrs [fontSizes]attrs.Attributes var a attrs.Attributes for i := 0; i < fontSizes; i++ { fsAttrs[i] = a.AddClass("zs-font-size-" + strconv.Itoa(i)) } | > | 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | buf.WriteByte(':') bufLen := buf.Len() return ccs, bufLen } const fontSizes = 6 // Must be the number of CSS classes zs-font-size-* in base.css const fontSizes64 = float64(fontSizes) func (*actionPara) calcFontSizes(ccs meta.CountedCategories) map[int]attrs.Attributes { var fsAttrs [fontSizes]attrs.Attributes var a attrs.Attributes for i := 0; i < fontSizes; i++ { fsAttrs[i] = a.AddClass("zs-font-size-" + strconv.Itoa(i)) } |
︙ | ︙ | |||
245 246 247 248 249 250 251 | result[count] = fsAttrs[curSize] curSize++ } return result } // Idea: the number of occurences for a specific count is substracted from a budget. | | < > | > | | | > > > > > | | < | > > > > > > > > > > > | 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 | result[count] = fsAttrs[curSize] curSize++ } return result } // Idea: the number of occurences for a specific count is substracted from a budget. total := float64(len(ccs)) curSize := 0 budget := calcBudget(total, 0.0) for _, count := range countList { result[count] = fsAttrs[curSize] cc := float64(countMap[count]) total -= cc budget -= cc if budget < 1 { curSize++ if curSize >= fontSizes { curSize = fontSizes budget = 0.0 } else { budget = calcBudget(total, float64(curSize)) } } } return result } func calcBudget(total, curSize float64) float64 { return math.Round(total / (fontSizes64 - curSize)) } func (ap *actionPara) createBlockNodeRSS(cfg config.Config) ast.BlockNode { var rssConfig rss.Configuration rssConfig.Setup(ap.ctx, cfg) rssConfig.Title = ap.title data := rssConfig.Marshal(ap.q, ap.ml) return &ast.VerbatimNode{ Kind: ast.VerbatimProg, Attrs: attrs.Attributes{"lang": "xml"}, Content: data, } } func (ap *actionPara) createBlockNodeAtom(cfg config.Config) ast.BlockNode { var atomConfig atom.Configuration atomConfig.Setup(ap.ctx, cfg) atomConfig.Title = ap.title data := atomConfig.Marshal(ap.q, ap.ml) return &ast.VerbatimNode{ Kind: ast.VerbatimProg, Attrs: attrs.Attributes{"lang": "xml"}, Content: data, } } |
Changes to go.mod.
1 2 3 4 5 6 | module zettelstore.de/z go 1.19 require ( codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0 | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | module zettelstore.de/z go 1.19 require ( codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0 github.com/fsnotify/fsnotify v1.6.0 github.com/pascaldekloe/jwt v1.12.0 github.com/yuin/goldmark v1.5.2 golang.org/x/crypto v0.0.0-20221012134737-56aed061732a golang.org/x/term v0.0.0-20221017184919-83659145692c golang.org/x/text v0.4.0 zettelstore.de/c v0.7.1-0.20220927073310-998ac1ba24c3 ) require golang.org/x/sys v0.1.0 // indirect |
Changes to go.sum.
1 2 | codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0 h1:viya/OgeF16+i8caBPJmcLQhGpZodPh+/nxtJzSSO1s= codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0/go.mod h1:4fAHEF3VH+ofbZkF6NzqiItTNy2X11tVCnZX99jXouA= | | | | | | | | | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0 h1:viya/OgeF16+i8caBPJmcLQhGpZodPh+/nxtJzSSO1s= codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0/go.mod h1:4fAHEF3VH+ofbZkF6NzqiItTNy2X11tVCnZX99jXouA= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/pascaldekloe/jwt v1.12.0 h1:imQSkPOtAIBAXoKKjL9ZVJuF/rVqJ+ntiLGpLyeqMUQ= github.com/pascaldekloe/jwt v1.12.0/go.mod h1:LiIl7EwaglmH1hWThd/AmydNCnHf/mmfluBlNqHbk8U= github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU= github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg= golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20221017184919-83659145692c h1:dveknrit5futqEmXAvd2I1BbZIDhxRijsyWHM86NlcA= golang.org/x/term v0.0.0-20221017184919-83659145692c/go.mod h1:VTIZ7TEbF0BS9Sv9lPTvGbtW8i4z6GGbJBCM37uMCzY= golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= zettelstore.de/c v0.7.1-0.20220927073310-998ac1ba24c3 h1:67MVyYuOVrzeriJlFkuZYLtFTw2YVqt8Xw16u7kiuOQ= zettelstore.de/c v0.7.1-0.20220927073310-998ac1ba24c3/go.mod h1:+SoneUhKQ81A2Id/bC6FdDYYQAHYfVryh7wHFnnklew= |
Added input/entity.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 | //----------------------------------------------------------------------------- // 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 input import ( "html" "unicode" ) // ScanEntity scans either a named or a numbered entity and returns it as a string. // // For numbered entities (like { or ģ) html.UnescapeString returns // sometimes other values as expected, if the number is not well-formed. This // may happen because of some strange HTML parsing rules. But these do not // apply to Zettelmarkup. Therefore, I parse the number here in the code. func (inp *Input) ScanEntity() (res string, success bool) { if inp.Ch != '&' { return "", false } pos := inp.Pos inp.Next() if inp.Ch == '#' { inp.Next() if inp.Ch == 'x' || inp.Ch == 'X' { return inp.scanEntityBase16() } return inp.scanEntityBase10() } return inp.scanEntityNamed(pos) } func (inp *Input) scanEntityBase16() (string, bool) { inp.Next() if inp.Ch == ';' { return "", false } code := 0 for { switch ch := inp.Ch; ch { case ';': inp.Next() if r := rune(code); isValidEntity(r) { return string(r), true } return "", false case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': code = 16*code + int(ch-'0') case 'a', 'b', 'c', 'd', 'e', 'f': code = 16*code + int(ch-'a'+10) case 'A', 'B', 'C', 'D', 'E', 'F': code = 16*code + int(ch-'A'+10) default: return "", false } if code > unicode.MaxRune { return "", false } inp.Next() } } func (inp *Input) scanEntityBase10() (string, bool) { // Base 10 code if inp.Ch == ';' { return "", false } code := 0 for { switch ch := inp.Ch; ch { case ';': inp.Next() if r := rune(code); isValidEntity(r) { return string(r), true } return "", false case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': code = 10*code + int(ch-'0') default: return "", false } if code > unicode.MaxRune { return "", false } 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 default: inp.Next() } } } // isValidEntity checks if the given code is valid for an entity. // // According to https://html.spec.whatwg.org/multipage/syntax.html#character-references // ""The numeric character reference forms described above are allowed to reference any code point // excluding U+000D CR, noncharacters, and controls other than ASCII whitespace."" func isValidEntity(r rune) bool { // No C0 control and no "code point in the range U+007F DELETE to U+009F APPLICATION PROGRAM COMMAND, inclusive." if r < ' ' || ('\u007f' <= r && r <= '\u009f') { return false } // If below any noncharacter code point, return true // // See: https://infra.spec.whatwg.org/#noncharacter if r < '\ufdd0' { return true } // First range of noncharacter code points: "(...) in the range U+FDD0 to U+FDEF, inclusive" if r <= '\ufdef' { return false } // Other noncharacter code points: switch r { case '\uFFFE', '\uFFFF', '\U0001FFFE', '\U0001FFFF', '\U0002FFFE', '\U0002FFFF', '\U0003FFFE', '\U0003FFFF', '\U0004FFFE', '\U0004FFFF', '\U0005FFFE', '\U0005FFFF', '\U0006FFFE', '\U0006FFFF', '\U0007FFFE', '\U0007FFFF', '\U0008FFFE', '\U0008FFFF', '\U0009FFFE', '\U0009FFFF', '\U000AFFFE', '\U000AFFFF', '\U000BFFFE', '\U000BFFFF', '\U000CFFFE', '\U000CFFFF', '\U000DFFFE', '\U000DFFFF', '\U000EFFFE', '\U000EFFFF', '\U000FFFFE', '\U000FFFFF', '\U0010FFFE', '\U0010FFFF': return false } return true } |
Added input/entity_test.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 | //----------------------------------------------------------------------------- // 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 input_test import ( "testing" "zettelstore.de/z/input" ) func TestScanEntity(t *testing.T) { t.Parallel() var testcases = []struct { text string exp string }{ {"", ""}, {"a", ""}, {"&", "&"}, {"!", "!"}, {"3", "3"}, {""", "\""}, } for id, tc := range testcases { inp := input.NewInput([]byte(tc.text)) got, ok := inp.ScanEntity() if !ok { if tc.exp != "" { t.Errorf("ID=%d, text=%q: expected error, but got %q", id, tc.text, got) } if inp.Pos != 0 { t.Errorf("ID=%d, text=%q: input position advances to %d", id, tc.text, inp.Pos) } continue } if tc.exp != got { t.Errorf("ID=%d, text=%q: expected %q, but got %q", id, tc.text, tc.exp, got) } } } func TestScanIllegalEntity(t *testing.T) { t.Parallel() testcases := []string{"", "a", "& Input →", "	", ""} for i, tc := range testcases { inp := input.NewInput([]byte(tc)) got, ok := inp.ScanEntity() if ok { t.Errorf("%d: scanning %q was unexpected successful, got %q", i, tc, got) continue } } } |
Changes to input/input.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 | // under this license. //----------------------------------------------------------------------------- // Package input provides an abstraction for data to be read. package input import ( | < < | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // under this license. //----------------------------------------------------------------------------- // Package input provides an abstraction for data to be read. package input import ( "unicode/utf8" ) // Input is an abstract input source type Input struct { // Read-only, will never change Src []byte // The source string |
︙ | ︙ | |||
135 136 137 138 139 140 141 | case EOS, '\n', '\r': return } inp.Next() } } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | case EOS, '\n', '\r': return } inp.Next() } } // ScanLineContent reads the reaining input stream and interprets it as lines of text. func (inp *Input) ScanLineContent() []byte { result := make([]byte, 0, len(inp.Src)-inp.Pos+1) for { inp.EatEOL() posL := inp.Pos if inp.Ch == EOS { |
︙ | ︙ |
Changes to input/input_test.go.
︙ | ︙ | |||
34 35 36 37 38 39 40 | } inp.EatEOL() if inp.Ch != 'A' { t.Errorf("First ch != 'A', got %q", inp.Ch) } } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | } inp.EatEOL() if inp.Ch != 'A' { t.Errorf("First ch != 'A', got %q", inp.Ch) } } func TestAccept(t *testing.T) { t.Parallel() testcases := []struct { accept string src string acc bool exp rune |
︙ | ︙ |
Changes to kernel/impl/auth.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 | //----------------------------------------------------------------------------- // 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 impl import ( "errors" "sync" "zettelstore.de/z/auth" "zettelstore.de/z/domain/id" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" ) type authService struct { srvConfig mxService sync.RWMutex manager auth.Manager createManager kernel.CreateAuthManagerFunc } var errAlreadySetOwner = errors.New("changing an existing owner not allowed") var errAlreadyROMode = errors.New("system in readonly mode cannot change this mode") func (as *authService) Initialize(logger *logger.Logger) { as.logger = logger as.descr = descriptionMap{ kernel.AuthOwner: { "Owner's zettel id", func(val string) (any, error) { if owner := as.cur[kernel.AuthOwner]; owner != nil && owner != id.Invalid { return nil, errAlreadySetOwner } if val == "" { return id.Invalid, nil } return parseZid(val) }, false, }, kernel.AuthReadonly: { "Readonly mode", func(val string) (any, error) { if ro := as.cur[kernel.AuthReadonly]; ro == true { return nil, errAlreadyROMode } return parseBool(val) }, true, }, } as.next = interfaceMap{ |
︙ | ︙ |
Changes to kernel/impl/box.go.
︙ | ︙ | |||
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 | // under this license. //----------------------------------------------------------------------------- package impl import ( "context" "fmt" "io" "net/url" "strconv" "sync" "zettelstore.de/z/box" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" ) type boxService struct { srvConfig mxService sync.RWMutex manager box.Manager createManager kernel.CreateBoxManagerFunc } func (ps *boxService) Initialize(logger *logger.Logger) { ps.logger = logger ps.descr = descriptionMap{ kernel.BoxDefaultDirType: { "Default directory box type", | > > > | | | | | | | 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 | // under this license. //----------------------------------------------------------------------------- package impl import ( "context" "errors" "fmt" "io" "net/url" "strconv" "sync" "zettelstore.de/z/box" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" ) type boxService struct { srvConfig mxService sync.RWMutex manager box.Manager createManager kernel.CreateBoxManagerFunc } var errInvalidDirType = errors.New("invalid directory type") func (ps *boxService) Initialize(logger *logger.Logger) { ps.logger = logger ps.descr = descriptionMap{ kernel.BoxDefaultDirType: { "Default directory box type", ps.noFrozen(func(val string) (any, error) { switch val { case kernel.BoxDirTypeNotify, kernel.BoxDirTypeSimple: return val, nil } return nil, errInvalidDirType }), true, }, kernel.BoxURIs: { "Box URI", func(val string) (any, error) { uVal, err := url.Parse(val) if err != nil { return nil, err } if uVal.Scheme == "" { uVal.Scheme = "dir" } return uVal, nil }, true, }, } ps.next = interfaceMap{ kernel.BoxDefaultDirType: kernel.BoxDirTypeNotify, } |
︙ | ︙ |
Changes to kernel/impl/cfg.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // under this license. //----------------------------------------------------------------------------- package impl import ( "context" "fmt" "strconv" "strings" "sync" "zettelstore.de/c/api" "zettelstore.de/z/box" | > | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // under this license. //----------------------------------------------------------------------------- package impl import ( "context" "errors" "fmt" "strconv" "strings" "sync" "zettelstore.de/c/api" "zettelstore.de/z/box" |
︙ | ︙ | |||
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | keyExpertMode = "expert-mode" keyHomeZettel = "home-zettel" 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}, keyDefaultLicense: {"Default license", parseString, true}, keyDefaultVisibility: { "Default zettel visibility", | > > | | | | | | > > > > > > > > > > > > > > > | | | | | | | > | | | | | | | | 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 | keyExpertMode = "expert-mode" keyHomeZettel = "home-zettel" keyMaxTransclusions = "max-transclusions" keySiteName = "site-name" keyYAMLHeader = "yaml-header" keyZettelFileSyntax = "zettel-file-syntax" ) var errUnknownVisibility = errors.New("unknown visibility") func (cs *configService) Initialize(logger *logger.Logger) { cs.logger = logger cs.descr = descriptionMap{ keyDefaultCopyright: {"Default copyright", parseString, true}, keyDefaultLicense: {"Default license", parseString, true}, keyDefaultVisibility: { "Default zettel visibility", func(val string) (any, error) { vis := meta.GetVisibility(val) if vis == meta.VisibilityUnknown { return nil, errUnknownVisibility } return vis, nil }, true, }, keyExpertMode: {"Expert mode", parseBool, true}, config.KeyFooterHTML: {"Footer HTML", parseString, true}, keyHomeZettel: {"Home zettel", parseZid, true}, kernel.ConfigInsecureHTML: { "Insecure HTML", cs.noFrozen(func(val string) (any, error) { switch val { case kernel.ConfigSyntaxHTML: return config.SyntaxHTML, nil case kernel.ConfigMarkdownHTML: return config.MarkdownHTML, nil case kernel.ConfigZmkHTML: return config.ZettelmarkupHTML, nil } return config.NoHTML, nil }), true, }, api.KeyLang: {"Language", parseString, true}, config.KeyMarkerExternal: {"Marker external URL", parseString, true}, keyMaxTransclusions: {"Maximum transclusions", parseInt64, true}, keySiteName: {"Site name", parseString, true}, keyYAMLHeader: {"YAML header", parseBool, true}, keyZettelFileSyntax: { "Zettel file syntax", func(val string) (any, error) { return strings.Fields(val), nil }, true, }, kernel.ConfigSimpleMode: {"Simple mode", cs.noFrozen(parseBool), true}, } cs.next = interfaceMap{ keyDefaultCopyright: "", keyDefaultLicense: "", keyDefaultVisibility: meta.VisibilityLogin, keyExpertMode: false, config.KeyFooterHTML: "", keyHomeZettel: id.DefaultHomeZid, kernel.ConfigInsecureHTML: config.NoHTML, api.KeyLang: api.ValueLangEN, config.KeyMarkerExternal: "➚", keyMaxTransclusions: int64(1024), keySiteName: "Zettelstore", keyYAMLHeader: false, keyZettelFileSyntax: nil, kernel.ConfigSimpleMode: false, } } func (cs *configService) GetLogger() *logger.Logger { return cs.logger } func (cs *configService) Start(*myKernel) error { cs.logger.Info().Msg("Start Service") data := meta.New(id.ConfigurationZid) |
︙ | ︙ | |||
226 227 228 229 230 231 232 233 234 235 236 237 238 239 | func updateMeta(result, m *meta.Meta, key, val string) *meta.Meta { if result == m { result = m.Clone() } result.Set(key, val) return result } // GetSiteName returns the current value of the "site-name" key. func (cs *configService) GetSiteName() string { return cs.GetConfig(keySiteName).(string) } // GetHomeZettel returns the value of the "home-zettel" key. func (cs *configService) GetHomeZettel() id.Zid { homeZid := cs.GetConfig(keyHomeZettel).(id.Zid) | > > > > | 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 | func updateMeta(result, m *meta.Meta, key, val string) *meta.Meta { if result == m { result = m.Clone() } result.Set(key, val) return result } func (cs *configService) GetHTMLInsecurity() config.HTMLInsecurity { return cs.GetConfig(kernel.ConfigInsecureHTML).(config.HTMLInsecurity) } // GetSiteName returns the current value of the "site-name" key. func (cs *configService) GetSiteName() string { return cs.GetConfig(keySiteName).(string) } // GetHomeZettel returns the value of the "home-zettel" key. func (cs *configService) GetHomeZettel() id.Zid { homeZid := cs.GetConfig(keyHomeZettel).(id.Zid) |
︙ | ︙ |
Changes to kernel/impl/cmd.go.
︙ | ︙ | |||
288 289 290 291 292 293 294 | } srvD, found := getService(sess, args[0]) if !found { return true } key := args[1] newValue := strings.Join(args[2:], " ") | | | | 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 | } srvD, found := getService(sess, args[0]) if !found { return true } key := args[1] newValue := strings.Join(args[2:], " ") if err := srvD.srv.SetConfig(key, newValue); err == nil { sess.kern.logger.Mandatory().Str("key", key).Str("value", newValue).Msg("Update system configuration") } else { sess.println("Unable to set key", args[1], "to value", newValue, "because:", err.Error()) } return true } func cmdServices(sess *cmdSession, _ string, _ []string) bool { table := [][]string{{"Service", "Status"}} for _, name := range sortedServiceNames(sess) { |
︙ | ︙ |
Changes to kernel/impl/config.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 | //----------------------------------------------------------------------------- // 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 impl import ( "fmt" "sort" "strconv" "strings" "sync" "zettelstore.de/c/maps" "zettelstore.de/z/domain/id" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" ) | > | | 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 | //----------------------------------------------------------------------------- // 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 impl import ( "errors" "fmt" "sort" "strconv" "strings" "sync" "zettelstore.de/c/maps" "zettelstore.de/z/domain/id" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" ) type parseFunc func(string) (any, error) type configDescription struct { text string parse parseFunc canList bool } type descriptionMap map[string]configDescription type interfaceMap map[string]interface{} |
︙ | ︙ | |||
63 64 65 66 67 68 69 70 | text = text + " (list)" } result = append(result, serviceConfigDescription{Key: k, Descr: text}) } return result } func (cfg *srvConfig) noFrozen(parse parseFunc) parseFunc { | > > | | > > | | | | | | | | | | 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 | text = text + " (list)" } result = append(result, serviceConfigDescription{Key: k, Descr: text}) } return result } var errAlreadyFrozen = errors.New("value not allowed to be set") func (cfg *srvConfig) noFrozen(parse parseFunc) parseFunc { return func(val string) (any, error) { if cfg.frozen { return nil, errAlreadyFrozen } return parse(val) } } var errListKeyNotFound = errors.New("no list key found") func (cfg *srvConfig) SetConfig(key, value string) error { cfg.mxConfig.Lock() defer cfg.mxConfig.Unlock() descr, ok := cfg.descr[key] if !ok { d, baseKey, num := cfg.getListDescription(key) if num < 0 { return errListKeyNotFound } for i := num + 1; ; i++ { k := baseKey + strconv.Itoa(i) if _, ok = cfg.next[k]; !ok { break } delete(cfg.next, k) } if num == 0 { return nil } descr = d } parse := descr.parse if parse == nil { if cfg.frozen { return errAlreadyFrozen } cfg.next[key] = value return nil } iVal, err := parse(value) // TODO if err != nil { return err } cfg.next[key] = iVal return nil } func (cfg *srvConfig) getListDescription(key string) (configDescription, string, int) { for k, d := range cfg.descr { if !strings.HasSuffix(k, "-") { continue } |
︙ | ︙ | |||
205 206 207 208 209 210 211 | func (cfg *srvConfig) SwitchNextToCur() { cfg.mxConfig.Lock() defer cfg.mxConfig.Unlock() cfg.cur = cfg.next.Clone() } | | > > | | | | | | | | | | > | | | | | > | 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 | func (cfg *srvConfig) SwitchNextToCur() { cfg.mxConfig.Lock() defer cfg.mxConfig.Unlock() cfg.cur = cfg.next.Clone() } func parseString(val string) (any, error) { return val, nil } var errNoBoolean = errors.New("no boolean value") func parseBool(val string) (any, error) { if val == "" { return false, errNoBoolean } switch val[0] { case '0', 'f', 'F', 'n', 'N': return false, nil } return true, nil } func parseInt64(val string) (any, error) { if u64, err := strconv.ParseInt(val, 10, 64); err == nil { return u64, nil } else { return nil, err } } func parseZid(val string) (any, error) { if zid, err := id.Parse(val); err == nil { return zid, nil } else { return id.Invalid, err } } |
Changes to kernel/impl/core.go.
︙ | ︙ | |||
46 47 48 49 50 51 52 | kernel.CoreDebug: {"Debug mode", parseBool, false}, kernel.CoreGoArch: {"Go processor architecture", nil, false}, kernel.CoreGoOS: {"Go Operating System", nil, false}, kernel.CoreGoVersion: {"Go Version", nil, false}, kernel.CoreHostname: {"Host name", nil, false}, kernel.CorePort: { "Port of command line server", | | | | | | | | 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 | kernel.CoreDebug: {"Debug mode", parseBool, false}, kernel.CoreGoArch: {"Go processor architecture", nil, false}, kernel.CoreGoOS: {"Go Operating System", nil, false}, kernel.CoreGoVersion: {"Go Version", nil, false}, kernel.CoreHostname: {"Host name", nil, false}, kernel.CorePort: { "Port of command line server", cs.noFrozen(func(val string) (any, error) { port, err := net.LookupPort("tcp", val) if err != nil { return nil, err } return port, nil }), true, }, kernel.CoreProgname: {"Program name", nil, false}, kernel.CoreStarted: {"Start time", nil, false}, kernel.CoreVerbose: {"Verbose output", parseBool, true}, kernel.CoreVersion: { "Version", cs.noFrozen(func(val string) (any, error) { if val == "" { return kernel.CoreDefaultVersion, nil } return val, nil }), false, }, kernel.CoreVTime: {"Version time", nil, false}, } cs.next = interfaceMap{ kernel.CoreDebug: false, |
︙ | ︙ |
Changes to kernel/impl/impl.go.
︙ | ︙ | |||
299 300 301 302 303 304 305 | kern.profile = nil kern.profileFile = nil return err } // --- Service handling -------------------------------------------------- | > > | | | 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 | kern.profile = nil kern.profileFile = nil return err } // --- Service handling -------------------------------------------------- var errUnknownService = errors.New("unknown service") func (kern *myKernel) SetConfig(srvnum kernel.Service, key, value string) error { kern.mx.Lock() defer kern.mx.Unlock() if srvD, ok := kern.srvs[srvnum]; ok { return srvD.srv.SetConfig(key, value) } return errUnknownService } func (kern *myKernel) GetConfig(srvnum kernel.Service, key string) interface{} { kern.mx.RLock() defer kern.mx.RUnlock() if srvD, ok := kern.srvs[srvnum]; ok { return srvD.srv.GetConfig(key) |
︙ | ︙ | |||
444 445 446 447 448 449 450 | // Get service logger. GetLogger() *logger.Logger // ConfigDescriptions returns a sorted list of configuration descriptions. ConfigDescriptions() []serviceConfigDescription // SetConfig stores a configuration value. | | | 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 | // Get service logger. GetLogger() *logger.Logger // ConfigDescriptions returns a sorted list of configuration descriptions. ConfigDescriptions() []serviceConfigDescription // SetConfig stores a configuration value. SetConfig(key, value string) error // GetConfig returns the current configuration value. GetConfig(key string) interface{} // GetNextConfig returns the next configuration value. GetNextConfig(key string) interface{} |
︙ | ︙ |
Changes to kernel/impl/web.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //----------------------------------------------------------------------------- package impl import ( "errors" "net" "net/url" "os" "path/filepath" "strconv" "strings" "sync" "time" | > | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //----------------------------------------------------------------------------- package impl import ( "errors" "net" "net/netip" "net/url" "os" "path/filepath" "strconv" "strings" "sync" "time" |
︙ | ︙ | |||
30 31 32 33 34 35 36 37 38 39 40 41 | 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.WebAssetDir: { "Asset file directory", | > > | | | | > | | | | > | | < > > | | | | | | | | | | | | 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 | type webService struct { srvConfig mxService sync.RWMutex srvw server.Server setupServer kernel.SetupWebServerFunc } var errURLPrefixSyntax = errors.New("must not be empty and must start with '//'") func (ws *webService) Initialize(logger *logger.Logger) { ws.logger = logger ws.descr = descriptionMap{ kernel.WebAssetDir: { "Asset file directory", func(val string) (any, error) { val = filepath.Clean(val) if finfo, err := os.Stat(val); err == nil && finfo.IsDir() { return val, nil } else { return nil, err } }, true, }, kernel.WebBaseURL: { "Base URL", func(val string) (any, error) { if _, err := url.Parse(val); err != nil { return nil, err } return val, nil }, true, }, kernel.WebListenAddress: { "Listen address", func(val string) (any, error) { // If there is no host, prepend 127.0.0.1 as host. host, _, err := net.SplitHostPort(val) if err == nil && host == "" { val = "127.0.0.1" + val } ap, err := netip.ParseAddrPort(val) if err != nil { return "", err } return ap.String(), nil }, true}, kernel.WebMaxRequestSize: {"Max Request Size", parseInt64, 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, }, kernel.WebTokenLifetimeHTML: { "Token lifetime HTML", makeDurationParser(1*time.Hour, 1*time.Minute, 30*24*time.Hour), true, }, kernel.WebURLPrefix: { "URL prefix under which the web server runs", func(val string) (any, error) { if val != "" && val[0] == '/' && val[len(val)-1] == '/' { return val, nil } return nil, errURLPrefixSyntax }, true, }, } ws.next = interfaceMap{ kernel.WebAssetDir: "", kernel.WebBaseURL: "http://127.0.0.1:23123/", kernel.WebListenAddress: "127.0.0.1:23123", kernel.WebMaxRequestSize: int64(16 * 1024 * 1024), kernel.WebPersistentCookie: false, kernel.WebSecureCookie: true, kernel.WebTokenLifetimeAPI: 1 * time.Hour, kernel.WebTokenLifetimeHTML: 10 * time.Minute, kernel.WebURLPrefix: "/", } } func makeDurationParser(defDur, minDur, maxDur time.Duration) parseFunc { return func(val string) (any, error) { if d, err := strconv.ParseUint(val, 10, 64); err == nil { secs := time.Duration(d) * time.Minute if secs < minDur { return minDur, nil } if secs > maxDur { return maxDur, nil } return secs, nil } return defDur, nil } } var errWrongBasePrefix = errors.New(kernel.WebURLPrefix + " does not match " + kernel.WebBaseURL) func (ws *webService) GetLogger() *logger.Logger { return ws.logger } |
︙ | ︙ | |||
140 141 142 143 144 145 146 147 148 149 150 151 152 153 | } if !strings.HasSuffix(baseURL, urlPrefix) { ws.logger.Fatal().Str("base-url", baseURL).Str("url-prefix", urlPrefix).Msg( "url-prefix is not a suffix of base-url") return errWrongBasePrefix } srvw := impl.New(ws.logger, listenAddr, baseURL, urlPrefix, persistentCookie, secureCookie, maxRequestSize, kern.auth.manager) err := kern.web.setupServer(srvw, kern.box.manager, kern.auth.manager, &kern.cfg) if err != nil { ws.logger.Fatal().Err(err).Msg("Unable to create") return err } | > > > > | 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | } if !strings.HasSuffix(baseURL, urlPrefix) { ws.logger.Fatal().Str("base-url", baseURL).Str("url-prefix", urlPrefix).Msg( "url-prefix is not a suffix of base-url") return errWrongBasePrefix } if lap := netip.MustParseAddrPort(listenAddr); !kern.auth.manager.WithAuth() && !lap.Addr().IsLoopback() { ws.logger.Warn().Str("listen", listenAddr).Msg("service may be reached from outside, but authentication is not enabled") } srvw := impl.New(ws.logger, listenAddr, baseURL, urlPrefix, persistentCookie, secureCookie, maxRequestSize, kern.auth.manager) err := kern.web.setupServer(srvw, kern.box.manager, kern.auth.manager, &kern.cfg) if err != nil { ws.logger.Fatal().Err(err).Msg("Unable to create") return err } |
︙ | ︙ | |||
164 165 166 167 168 169 170 | ws.mxService.Unlock() if kern.cfg.GetConfig(kernel.ConfigSimpleMode).(bool) { listenAddr := ws.GetNextConfig(kernel.WebListenAddress).(string) if idx := strings.LastIndexByte(listenAddr, ':'); idx >= 0 { ws.logger.Mandatory().Msg(strings.Repeat("--------------------", 3)) ws.logger.Mandatory().Msg("Open your browser and enter the following URL:") | | > > > | 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | ws.mxService.Unlock() if kern.cfg.GetConfig(kernel.ConfigSimpleMode).(bool) { listenAddr := ws.GetNextConfig(kernel.WebListenAddress).(string) if idx := strings.LastIndexByte(listenAddr, ':'); idx >= 0 { ws.logger.Mandatory().Msg(strings.Repeat("--------------------", 3)) ws.logger.Mandatory().Msg("Open your browser and enter the following URL:") ws.logger.Mandatory().Msg(" http://localhost" + listenAddr[idx:]) ws.logger.Mandatory().Msg("") ws.logger.Mandatory().Msg("If this does not work, try:") ws.logger.Mandatory().Msg(" http://127.0.0.1" + listenAddr[idx:]) } } return nil } func (ws *webService) IsStarted() bool { |
︙ | ︙ |
Changes to kernel/kernel.go.
︙ | ︙ | |||
58 59 60 61 62 63 64 | // StopProfiling stops the current profiling and writes the result to // the file, which was named during StartProfiling(). // It will always be called before the software stops its operations. StopProfiling() error // SetConfig stores a configuration value. | | | 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | // StopProfiling stops the current profiling and writes the result to // the file, which was named during StartProfiling(). // It will always be called before the software stops its operations. StopProfiling() error // SetConfig stores a configuration value. SetConfig(srv Service, key, value string) error // GetConfig returns a configuration value. GetConfig(srv Service, key string) interface{} // GetConfigList returns a sorted list of configuration data. GetConfigList(Service) []KeyDescrValue |
︙ | ︙ | |||
147 148 149 150 151 152 153 | // Defined values for core service. const ( CoreDefaultVersion = "unknown" ) // Constants for config service keys. const ( | | > > > > > > > > > | 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 | // Defined values for core service. const ( CoreDefaultVersion = "unknown" ) // Constants for config service keys. const ( ConfigSimpleMode = "simple-mode" ConfigInsecureHTML = "insecure-html" ) // Constants for authentication service keys. const ( AuthOwner = "owner" AuthReadonly = "readonly" ) // Constants for box service keys. const ( BoxDefaultDirType = "defdirtype" BoxURIs = "box-uri-" ) // Allowed values for BoxDefaultDirType const ( BoxDirTypeNotify = "notify" BoxDirTypeSimple = "simple" ) // Constants for config service keys. const ( ConfigSecureHTML = "secure" ConfigSyntaxHTML = "html" ConfigMarkdownHTML = "markdown" ConfigZmkHTML = "zettelmarkup" ) // Constants for web service keys. const ( WebAssetDir = "asset-dir" WebBaseURL = "base-url" WebListenAddress = "listen" WebPersistentCookie = "persistent" |
︙ | ︙ |
Changes to logger/message.go.
︙ | ︙ | |||
65 66 67 68 69 70 71 | buf = append(buf, '=') m.buf = append(buf, val...) } return m } // Bool adds a boolean value to the full message | | > | 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | buf = append(buf, '=') m.buf = append(buf, val...) } return m } // Bool adds a boolean value to the full message func (m *Message) Bool(text string, val bool) *Message { if val { m.Str(text, "true") } else { m.Str(text, "false") } return m } // Bytes adds a byte slice value to the full message func (m *Message) Bytes(text string, val []byte) *Message { if m.Enabled() { buf := append(m.buf, ',', ' ') buf = append(buf, text...) |
︙ | ︙ |
Changes to parser/cleaner/cleaner.go.
1 2 3 4 5 6 7 8 9 10 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- | | | | | | > | | | | > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | //----------------------------------------------------------------------------- // 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 cleaner provides functions to clean up the parsed AST. package cleaner import ( "bytes" "strconv" "zettelstore.de/z/ast" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/strfun" ) // CleanBlockSlice cleans the given block list. func CleanBlockSlice(bs *ast.BlockSlice, allowHTML bool) { cleanNode(bs, allowHTML) } // CleanInlineSlice cleans the given inline list. func CleanInlineSlice(is *ast.InlineSlice) { cleanNode(is, false) } func cleanNode(n ast.Node, allowHTML bool) { cv := cleanVisitor{ textEnc: textenc.Create(), allowHTML: allowHTML, hasMark: false, doMark: false, } ast.Walk(&cv, n) if cv.hasMark { cv.doMark = true ast.Walk(&cv, n) } } type cleanVisitor struct { textEnc *textenc.Encoder ids map[string]ast.Node allowHTML bool hasMark bool doMark bool } func (cv *cleanVisitor) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.BlockSlice: if !cv.allowHTML { cv.visitBlockSlice(n) return nil } case *ast.InlineSlice: if !cv.allowHTML { cv.visitInlineSlice(n) return nil } case *ast.HeadingNode: cv.visitHeading(n) return nil case *ast.MarkNode: cv.visitMark(n) return nil } return cv } func (cv *cleanVisitor) visitBlockSlice(bs *ast.BlockSlice) { if bs == nil { return } if len(*bs) == 0 { *bs = nil return } for _, bn := range *bs { ast.Walk(cv, bn) } fromPos, toPos := 0, 0 for fromPos < len(*bs) { (*bs)[toPos] = (*bs)[fromPos] fromPos++ switch bn := (*bs)[toPos].(type) { case *ast.VerbatimNode: if bn.Kind != ast.VerbatimHTML { toPos++ } default: toPos++ } } for pos := toPos; pos < len(*bs); pos++ { (*bs)[pos] = nil // Allow excess nodes to be garbage collected. } *bs = (*bs)[:toPos:toPos] } func (cv *cleanVisitor) visitInlineSlice(is *ast.InlineSlice) { if is == nil { return } if len(*is) == 0 { *is = nil return } for _, bn := range *is { ast.Walk(cv, bn) } fromPos, toPos := 0, 0 for fromPos < len(*is) { (*is)[toPos] = (*is)[fromPos] fromPos++ switch in := (*is)[toPos].(type) { case *ast.LiteralNode: if in.Kind != ast.LiteralHTML { toPos++ } default: toPos++ } } for pos := toPos; pos < len(*is); pos++ { (*is)[pos] = nil // Allow excess nodes to be garbage collected. } *is = (*is)[:toPos:toPos] } func (cv *cleanVisitor) visitHeading(hn *ast.HeadingNode) { if cv.doMark || hn == nil || len(hn.Inlines) == 0 { return } if hn.Slug == "" { var buf bytes.Buffer |
︙ | ︙ |
Changes to parser/parser.go.
︙ | ︙ | |||
89 90 91 92 93 94 95 | if !ok { return false } return pi.IsImageFormat } // ParseBlocks parses some input and returns a slice of block nodes. | | > > > | | 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | if !ok { return false } return pi.IsImageFormat } // ParseBlocks parses some input and returns a slice of block nodes. func ParseBlocks(inp *input.Input, m *meta.Meta, syntax string, hi config.HTMLInsecurity) ast.BlockSlice { return parseBlocksAndClean(inp, m, syntax, hi) } func parseBlocksAndClean(inp *input.Input, m *meta.Meta, syntax string, hi config.HTMLInsecurity) ast.BlockSlice { bs := Get(syntax).ParseBlocks(inp, m, syntax) cleaner.CleanBlockSlice(&bs, hi.AllowHTML(syntax)) return bs } // ParseInlines parses some input and returns a slice of inline nodes. func ParseInlines(inp *input.Input, syntax string) ast.InlineSlice { // Do not clean, because we don't know the context where this function will be called. return Get(syntax).ParseInlines(inp, syntax) |
︙ | ︙ | |||
129 130 131 132 133 134 135 136 137 138 139 140 | if syntax == "" { syntax, _ = inhMeta.Get(api.KeySyntax) } parseMeta := inhMeta if syntax == api.ValueSyntaxNone { parseMeta = m } return &ast.ZettelNode{ Meta: m, Content: zettel.Content, Zid: m.Zid, InhMeta: inhMeta, | > > > > > | | 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | if syntax == "" { syntax, _ = inhMeta.Get(api.KeySyntax) } parseMeta := inhMeta if syntax == api.ValueSyntaxNone { parseMeta = m } hi := config.NoHTML if rtConfig != nil { hi = rtConfig.GetHTMLInsecurity() } return &ast.ZettelNode{ Meta: m, Content: zettel.Content, Zid: m.Zid, InhMeta: inhMeta, Ast: parseBlocksAndClean(input.NewInput(zettel.Content.AsBytes()), parseMeta, syntax, hi), Syntax: syntax, } } |
Changes to parser/pikchr/internal/pikchr.go.
︙ | ︙ | |||
128 129 130 131 132 133 134 135 136 137 138 139 140 141 | "math" "os" "regexp" "strconv" "strings" ) // Numeric value type PNum = float64 // Compass points const ( CP_N uint8 = iota + 1 CP_NE | > > > > | 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | "math" "os" "regexp" "strconv" "strings" ) // Limit the number of tokens in a single script to avoid run-away macro expansion attacks. // See forum post https://pikchr.org/home/forumpost/ef8684c6955a411a const PIKCHR_TOKEN_LIMIT = 100000 // Numeric value type PNum = float64 // Compass points const ( CP_N uint8 = iota + 1 CP_NE |
︙ | ︙ | |||
360 361 362 363 364 365 366 367 368 369 370 371 372 373 | } /* Each call to the pikchr() subroutine uses an instance of the following ** object to pass around context to all of its subroutines. */ type Pik struct { nErr int /* Number of errors seen */ sIn PToken /* Input Pikchr-language text */ zOut bytes.Buffer /* Result accumulates here */ nOut uint /* Bytes written to zOut[] so far */ nOutAlloc uint /* Space allocated to zOut[] */ eDir uint8 /* Current direction */ mFlags uint /* Flags passed to pikchr() */ cur *PObj /* Object under construction */ | > | 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 | } /* Each call to the pikchr() subroutine uses an instance of the following ** object to pass around context to all of its subroutines. */ type Pik struct { nErr int /* Number of errors seen */ nToken int // Number of tokens parsed sIn PToken /* Input Pikchr-language text */ zOut bytes.Buffer /* Result accumulates here */ nOut uint /* Bytes written to zOut[] so far */ nOutAlloc uint /* Space allocated to zOut[] */ eDir uint8 /* Current direction */ mFlags uint /* Flags passed to pikchr() */ cur *PObj /* Object under construction */ |
︙ | ︙ | |||
433 434 435 436 437 438 439 | xOffset func(*Pik, *PObj, uint8) PPoint /* Offset from .c to edge point */ xFit func(pik *Pik, pobj *PObj, w PNum, h PNum) /* Size to fit text */ xRender func(*Pik, *PObj) /* Render */ } func yytestcase(condition bool) {} | | | 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 | xOffset func(*Pik, *PObj, uint8) PPoint /* Offset from .c to edge point */ xFit func(pik *Pik, pobj *PObj, w PNum, h PNum) /* Size to fit text */ xRender func(*Pik, *PObj) /* Render */ } func yytestcase(condition bool) {} //line 480 "pikchr.go" /**************** End of %include directives **********************************/ /* These constants specify the various numeric values for terminal symbols. ***************** Begin token definitions *************************************/ const ( T_ID = 1 |
︙ | ︙ | |||
1609 1610 1611 1612 1613 1614 1615 | ** Note: during a reduce, the only symbols destroyed are those ** which appear on the RHS of the rule, but which are *not* used ** inside the C code. */ /********* Begin destructor definitions ***************************************/ case 99: /* statement_list */ { | | | | | | 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 | ** Note: during a reduce, the only symbols destroyed are those ** which appear on the RHS of the rule, but which are *not* used ** inside the C code. */ /********* Begin destructor definitions ***************************************/ case 99: /* statement_list */ { //line 460 "pikchr.y" p.pik_elist_free(&(yypminor.yy186)) //line 1656 "pikchr.go" } break case 100: /* statement */ case 101: /* unnamed_statement */ case 102: /* basetype */ { //line 462 "pikchr.y" p.pik_elem_free((yypminor.yy104)) //line 1665 "pikchr.go" } break /********* End destructor definitions *****************************************/ default: break /* If no destructor action specified: do nothing */ } } |
︙ | ︙ | |||
1832 1833 1834 1835 1836 1837 1838 | } for yypParser.yytos > 0 { yypParser.yy_pop_parser_stack() } /* Here code is inserted which will execute if the parser ** stack every overflows */ /******** Begin %stack_overflow code ******************************************/ | | | | 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 | } for yypParser.yytos > 0 { yypParser.yy_pop_parser_stack() } /* Here code is inserted which will execute if the parser ** stack every overflows */ /******** Begin %stack_overflow code ******************************************/ //line 493 "pikchr.y" p.pik_error(nil, "parser stack overflow") //line 1879 "pikchr.go" /******** End %stack_overflow code ********************************************/ /* Suppress warning about unused %extra_argument var */ yypParser.p = p } /* |
︙ | ︙ | |||
2258 2259 2260 2261 2262 2263 2264 | ** #line <lineno> <grammarfile> ** { ... } // User supplied code ** #line <lineno> <thisfile> ** break; */ /********** Begin reduce actions **********************************************/ case 0: /* document ::= statement_list */ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 | ** #line <lineno> <grammarfile> ** { ... } // User supplied code ** #line <lineno> <thisfile> ** break; */ /********** Begin reduce actions **********************************************/ case 0: /* document ::= statement_list */ //line 497 "pikchr.y" { p.pik_render(yypParser.yystack[yypParser.yytos+0].minor.yy186) } //line 2306 "pikchr.go" break case 1: /* statement_list ::= statement */ //line 500 "pikchr.y" { yylhsminor.yy186 = p.pik_elist_append(nil, yypParser.yystack[yypParser.yytos+0].minor.yy104) } //line 2311 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy186 = yylhsminor.yy186 break case 2: /* statement_list ::= statement_list EOL statement */ //line 502 "pikchr.y" { yylhsminor.yy186 = p.pik_elist_append(yypParser.yystack[yypParser.yytos+-2].minor.yy186, yypParser.yystack[yypParser.yytos+0].minor.yy104) } //line 2317 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy186 = yylhsminor.yy186 break case 3: /* statement ::= */ //line 505 "pikchr.y" { yypParser.yystack[yypParser.yytos+1].minor.yy104 = nil } //line 2323 "pikchr.go" break case 4: /* statement ::= direction */ //line 506 "pikchr.y" { p.pik_set_direction(uint8(yypParser.yystack[yypParser.yytos+0].minor.yy0.eCode)) yylhsminor.yy104 = nil } //line 2328 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy104 = yylhsminor.yy104 break case 5: /* statement ::= lvalue ASSIGN rvalue */ //line 507 "pikchr.y" { p.pik_set_var(&yypParser.yystack[yypParser.yytos+-2].minor.yy0, yypParser.yystack[yypParser.yytos+0].minor.yy153, &yypParser.yystack[yypParser.yytos+-1].minor.yy0) yylhsminor.yy104 = nil } //line 2334 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy104 = yylhsminor.yy104 break case 6: /* statement ::= PLACENAME COLON unnamed_statement */ //line 509 "pikchr.y" { yylhsminor.yy104 = yypParser.yystack[yypParser.yytos+0].minor.yy104 p.pik_elem_setname(yypParser.yystack[yypParser.yytos+0].minor.yy104, &yypParser.yystack[yypParser.yytos+-2].minor.yy0) } //line 2340 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy104 = yylhsminor.yy104 break case 7: /* statement ::= PLACENAME COLON position */ //line 511 "pikchr.y" { yylhsminor.yy104 = p.pik_elem_new(nil, nil, nil) if yylhsminor.yy104 != nil { yylhsminor.yy104.ptAt = yypParser.yystack[yypParser.yytos+0].minor.yy79 p.pik_elem_setname(yylhsminor.yy104, &yypParser.yystack[yypParser.yytos+-2].minor.yy0) } } //line 2347 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy104 = yylhsminor.yy104 break case 8: /* statement ::= unnamed_statement */ //line 513 "pikchr.y" { yylhsminor.yy104 = yypParser.yystack[yypParser.yytos+0].minor.yy104 } //line 2353 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy104 = yylhsminor.yy104 break case 9: /* statement ::= print prlist */ //line 514 "pikchr.y" { p.pik_append("<br>\n") yypParser.yystack[yypParser.yytos+-1].minor.yy104 = nil } //line 2359 "pikchr.go" break case 10: /* statement ::= ASSERT LP expr EQ expr RP */ //line 519 "pikchr.y" { yypParser.yystack[yypParser.yytos+-5].minor.yy104 = p.pik_assert(yypParser.yystack[yypParser.yytos+-3].minor.yy153, &yypParser.yystack[yypParser.yytos+-2].minor.yy0, yypParser.yystack[yypParser.yytos+-1].minor.yy153) } //line 2364 "pikchr.go" break case 11: /* statement ::= ASSERT LP position EQ position RP */ //line 521 "pikchr.y" { yypParser.yystack[yypParser.yytos+-5].minor.yy104 = p.pik_position_assert(&yypParser.yystack[yypParser.yytos+-3].minor.yy79, &yypParser.yystack[yypParser.yytos+-2].minor.yy0, &yypParser.yystack[yypParser.yytos+-1].minor.yy79) } //line 2369 "pikchr.go" break case 12: /* statement ::= DEFINE ID CODEBLOCK */ //line 522 "pikchr.y" { yypParser.yystack[yypParser.yytos+-2].minor.yy104 = nil p.pik_add_macro(&yypParser.yystack[yypParser.yytos+-1].minor.yy0, &yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2374 "pikchr.go" break case 13: /* rvalue ::= PLACENAME */ //line 533 "pikchr.y" { yylhsminor.yy153 = p.pik_lookup_color(&yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2379 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy153 = yylhsminor.yy153 break case 14: /* pritem ::= FILL */ fallthrough case 15: /* pritem ::= COLOR */ yytestcase(yyruleno == 15) fallthrough case 16: /* pritem ::= THICKNESS */ yytestcase(yyruleno == 16) //line 538 "pikchr.y" { p.pik_append_num("", p.pik_value(yypParser.yystack[yypParser.yytos+0].minor.yy0.String(), nil)) } //line 2389 "pikchr.go" break case 17: /* pritem ::= rvalue */ //line 541 "pikchr.y" { p.pik_append_num("", yypParser.yystack[yypParser.yytos+0].minor.yy153) } //line 2394 "pikchr.go" break case 18: /* pritem ::= STRING */ //line 542 "pikchr.y" { p.pik_append_text(string(yypParser.yystack[yypParser.yytos+0].minor.yy0.z[1:yypParser.yystack[yypParser.yytos+0].minor.yy0.n-1]), 0) } //line 2399 "pikchr.go" break case 19: /* prsep ::= COMMA */ //line 543 "pikchr.y" { p.pik_append(" ") } //line 2404 "pikchr.go" break case 20: /* unnamed_statement ::= basetype attribute_list */ //line 546 "pikchr.y" { yylhsminor.yy104 = yypParser.yystack[yypParser.yytos+-1].minor.yy104 p.pik_after_adding_attributes(yylhsminor.yy104) } //line 2409 "pikchr.go" yypParser.yystack[yypParser.yytos+-1].minor.yy104 = yylhsminor.yy104 break case 21: /* basetype ::= CLASSNAME */ //line 548 "pikchr.y" { yylhsminor.yy104 = p.pik_elem_new(&yypParser.yystack[yypParser.yytos+0].minor.yy0, nil, nil) } //line 2415 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy104 = yylhsminor.yy104 break case 22: /* basetype ::= STRING textposition */ //line 550 "pikchr.y" { yypParser.yystack[yypParser.yytos+-1].minor.yy0.eCode = int16(yypParser.yystack[yypParser.yytos+0].minor.yy112) yylhsminor.yy104 = p.pik_elem_new(nil, &yypParser.yystack[yypParser.yytos+-1].minor.yy0, nil) } //line 2421 "pikchr.go" yypParser.yystack[yypParser.yytos+-1].minor.yy104 = yylhsminor.yy104 break case 23: /* basetype ::= LB savelist statement_list RB */ //line 552 "pikchr.y" { p.list = yypParser.yystack[yypParser.yytos+-2].minor.yy186 yypParser.yystack[yypParser.yytos+-3].minor.yy104 = p.pik_elem_new(nil, nil, yypParser.yystack[yypParser.yytos+-1].minor.yy186) if yypParser.yystack[yypParser.yytos+-3].minor.yy104 != nil { yypParser.yystack[yypParser.yytos+-3].minor.yy104.errTok = yypParser.yystack[yypParser.yytos+0].minor.yy0 } } //line 2427 "pikchr.go" break case 24: /* savelist ::= */ //line 557 "pikchr.y" { yypParser.yystack[yypParser.yytos+1].minor.yy186 = p.list p.list = nil } //line 2432 "pikchr.go" break case 25: /* relexpr ::= expr */ //line 564 "pikchr.y" { yylhsminor.yy10.rAbs = yypParser.yystack[yypParser.yytos+0].minor.yy153 yylhsminor.yy10.rRel = 0 } //line 2437 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy10 = yylhsminor.yy10 break case 26: /* relexpr ::= expr PERCENT */ //line 565 "pikchr.y" { yylhsminor.yy10.rAbs = 0 yylhsminor.yy10.rRel = yypParser.yystack[yypParser.yytos+-1].minor.yy153 / 100 } //line 2443 "pikchr.go" yypParser.yystack[yypParser.yytos+-1].minor.yy10 = yylhsminor.yy10 break case 27: /* optrelexpr ::= */ //line 567 "pikchr.y" { yypParser.yystack[yypParser.yytos+1].minor.yy10.rAbs = 0 yypParser.yystack[yypParser.yytos+1].minor.yy10.rRel = 1.0 } //line 2449 "pikchr.go" break case 28: /* attribute_list ::= relexpr alist */ //line 569 "pikchr.y" { p.pik_add_direction(nil, &yypParser.yystack[yypParser.yytos+-1].minor.yy10) } //line 2454 "pikchr.go" break case 29: /* attribute ::= numproperty relexpr */ //line 573 "pikchr.y" { p.pik_set_numprop(&yypParser.yystack[yypParser.yytos+-1].minor.yy0, &yypParser.yystack[yypParser.yytos+0].minor.yy10) } //line 2459 "pikchr.go" break case 30: /* attribute ::= dashproperty expr */ //line 574 "pikchr.y" { p.pik_set_dashed(&yypParser.yystack[yypParser.yytos+-1].minor.yy0, &yypParser.yystack[yypParser.yytos+0].minor.yy153) } //line 2464 "pikchr.go" break case 31: /* attribute ::= dashproperty */ //line 575 "pikchr.y" { p.pik_set_dashed(&yypParser.yystack[yypParser.yytos+0].minor.yy0, nil) } //line 2469 "pikchr.go" break case 32: /* attribute ::= colorproperty rvalue */ //line 576 "pikchr.y" { p.pik_set_clrprop(&yypParser.yystack[yypParser.yytos+-1].minor.yy0, yypParser.yystack[yypParser.yytos+0].minor.yy153) } //line 2474 "pikchr.go" break case 33: /* attribute ::= go direction optrelexpr */ //line 577 "pikchr.y" { p.pik_add_direction(&yypParser.yystack[yypParser.yytos+-1].minor.yy0, &yypParser.yystack[yypParser.yytos+0].minor.yy10) } //line 2479 "pikchr.go" break case 34: /* attribute ::= go direction even position */ //line 578 "pikchr.y" { p.pik_evenwith(&yypParser.yystack[yypParser.yytos+-2].minor.yy0, &yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2484 "pikchr.go" break case 35: /* attribute ::= CLOSE */ //line 579 "pikchr.y" { p.pik_close_path(&yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2489 "pikchr.go" break case 36: /* attribute ::= CHOP */ //line 580 "pikchr.y" { p.cur.bChop = true } //line 2494 "pikchr.go" break case 37: /* attribute ::= FROM position */ //line 581 "pikchr.y" { p.pik_set_from(p.cur, &yypParser.yystack[yypParser.yytos+-1].minor.yy0, &yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2499 "pikchr.go" break case 38: /* attribute ::= TO position */ //line 582 "pikchr.y" { p.pik_add_to(p.cur, &yypParser.yystack[yypParser.yytos+-1].minor.yy0, &yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2504 "pikchr.go" break case 39: /* attribute ::= THEN */ //line 583 "pikchr.y" { p.pik_then(&yypParser.yystack[yypParser.yytos+0].minor.yy0, p.cur) } //line 2509 "pikchr.go" break case 40: /* attribute ::= THEN optrelexpr HEADING expr */ fallthrough case 42: /* attribute ::= GO optrelexpr HEADING expr */ yytestcase(yyruleno == 42) //line 585 "pikchr.y" { p.pik_move_hdg(&yypParser.yystack[yypParser.yytos+-2].minor.yy10, &yypParser.yystack[yypParser.yytos+-1].minor.yy0, yypParser.yystack[yypParser.yytos+0].minor.yy153, nil, &yypParser.yystack[yypParser.yytos+-3].minor.yy0) } //line 2516 "pikchr.go" break case 41: /* attribute ::= THEN optrelexpr EDGEPT */ fallthrough case 43: /* attribute ::= GO optrelexpr EDGEPT */ yytestcase(yyruleno == 43) //line 586 "pikchr.y" { p.pik_move_hdg(&yypParser.yystack[yypParser.yytos+-1].minor.yy10, nil, 0, &yypParser.yystack[yypParser.yytos+0].minor.yy0, &yypParser.yystack[yypParser.yytos+-2].minor.yy0) } //line 2523 "pikchr.go" break case 44: /* attribute ::= AT position */ //line 591 "pikchr.y" { p.pik_set_at(nil, &yypParser.yystack[yypParser.yytos+0].minor.yy79, &yypParser.yystack[yypParser.yytos+-1].minor.yy0) } //line 2528 "pikchr.go" break case 45: /* attribute ::= SAME */ //line 593 "pikchr.y" { p.pik_same(nil, &yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2533 "pikchr.go" break case 46: /* attribute ::= SAME AS object */ //line 594 "pikchr.y" { p.pik_same(yypParser.yystack[yypParser.yytos+0].minor.yy104, &yypParser.yystack[yypParser.yytos+-2].minor.yy0) } //line 2538 "pikchr.go" break case 47: /* attribute ::= STRING textposition */ //line 595 "pikchr.y" { p.pik_add_txt(&yypParser.yystack[yypParser.yytos+-1].minor.yy0, int16(yypParser.yystack[yypParser.yytos+0].minor.yy112)) } //line 2543 "pikchr.go" break case 48: /* attribute ::= FIT */ //line 596 "pikchr.y" { p.pik_size_to_fit(&yypParser.yystack[yypParser.yytos+0].minor.yy0, 3) } //line 2548 "pikchr.go" break case 49: /* attribute ::= BEHIND object */ //line 597 "pikchr.y" { p.pik_behind(yypParser.yystack[yypParser.yytos+0].minor.yy104) } //line 2553 "pikchr.go" break case 50: /* withclause ::= DOT_E edge AT position */ fallthrough case 51: /* withclause ::= edge AT position */ yytestcase(yyruleno == 51) //line 605 "pikchr.y" { p.pik_set_at(&yypParser.yystack[yypParser.yytos+-2].minor.yy0, &yypParser.yystack[yypParser.yytos+0].minor.yy79, &yypParser.yystack[yypParser.yytos+-1].minor.yy0) } //line 2560 "pikchr.go" break case 52: /* numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS */ //line 609 "pikchr.y" { yylhsminor.yy0 = yypParser.yystack[yypParser.yytos+0].minor.yy0 } //line 2565 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy0 = yylhsminor.yy0 break case 53: /* boolproperty ::= CW */ //line 620 "pikchr.y" { p.cur.cw = true } //line 2571 "pikchr.go" break case 54: /* boolproperty ::= CCW */ //line 621 "pikchr.y" { p.cur.cw = false } //line 2576 "pikchr.go" break case 55: /* boolproperty ::= LARROW */ //line 622 "pikchr.y" { p.cur.larrow = true p.cur.rarrow = false } //line 2581 "pikchr.go" break case 56: /* boolproperty ::= RARROW */ //line 623 "pikchr.y" { p.cur.larrow = false p.cur.rarrow = true } //line 2586 "pikchr.go" break case 57: /* boolproperty ::= LRARROW */ //line 624 "pikchr.y" { p.cur.larrow = true p.cur.rarrow = true } //line 2591 "pikchr.go" break case 58: /* boolproperty ::= INVIS */ //line 625 "pikchr.y" { p.cur.sw = 0.0 } //line 2596 "pikchr.go" break case 59: /* boolproperty ::= THICK */ //line 626 "pikchr.y" { p.cur.sw *= 1.5 } //line 2601 "pikchr.go" break case 60: /* boolproperty ::= THIN */ //line 627 "pikchr.y" { p.cur.sw *= 0.67 } //line 2606 "pikchr.go" break case 61: /* boolproperty ::= SOLID */ //line 628 "pikchr.y" { p.cur.sw = p.pik_value("thickness", nil) p.cur.dotted = 0.0 p.cur.dashed = 0.0 } //line 2612 "pikchr.go" break case 62: /* textposition ::= */ //line 631 "pikchr.y" { yypParser.yystack[yypParser.yytos+1].minor.yy112 = 0 } //line 2617 "pikchr.go" break case 63: /* textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL */ //line 634 "pikchr.y" { yylhsminor.yy112 = pik_text_position(yypParser.yystack[yypParser.yytos+-1].minor.yy112, &yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2622 "pikchr.go" yypParser.yystack[yypParser.yytos+-1].minor.yy112 = yylhsminor.yy112 break case 64: /* position ::= expr COMMA expr */ //line 637 "pikchr.y" { yylhsminor.yy79.x = yypParser.yystack[yypParser.yytos+-2].minor.yy153 yylhsminor.yy79.y = yypParser.yystack[yypParser.yytos+0].minor.yy153 } //line 2628 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy79 = yylhsminor.yy79 break case 65: /* position ::= place PLUS expr COMMA expr */ //line 639 "pikchr.y" { yylhsminor.yy79.x = yypParser.yystack[yypParser.yytos+-4].minor.yy79.x + yypParser.yystack[yypParser.yytos+-2].minor.yy153 yylhsminor.yy79.y = yypParser.yystack[yypParser.yytos+-4].minor.yy79.y + yypParser.yystack[yypParser.yytos+0].minor.yy153 } //line 2634 "pikchr.go" yypParser.yystack[yypParser.yytos+-4].minor.yy79 = yylhsminor.yy79 break case 66: /* position ::= place MINUS expr COMMA expr */ //line 640 "pikchr.y" { yylhsminor.yy79.x = yypParser.yystack[yypParser.yytos+-4].minor.yy79.x - yypParser.yystack[yypParser.yytos+-2].minor.yy153 yylhsminor.yy79.y = yypParser.yystack[yypParser.yytos+-4].minor.yy79.y - yypParser.yystack[yypParser.yytos+0].minor.yy153 } //line 2640 "pikchr.go" yypParser.yystack[yypParser.yytos+-4].minor.yy79 = yylhsminor.yy79 break case 67: /* position ::= place PLUS LP expr COMMA expr RP */ //line 642 "pikchr.y" { yylhsminor.yy79.x = yypParser.yystack[yypParser.yytos+-6].minor.yy79.x + yypParser.yystack[yypParser.yytos+-3].minor.yy153 yylhsminor.yy79.y = yypParser.yystack[yypParser.yytos+-6].minor.yy79.y + yypParser.yystack[yypParser.yytos+-1].minor.yy153 } //line 2646 "pikchr.go" yypParser.yystack[yypParser.yytos+-6].minor.yy79 = yylhsminor.yy79 break case 68: /* position ::= place MINUS LP expr COMMA expr RP */ //line 644 "pikchr.y" { yylhsminor.yy79.x = yypParser.yystack[yypParser.yytos+-6].minor.yy79.x - yypParser.yystack[yypParser.yytos+-3].minor.yy153 yylhsminor.yy79.y = yypParser.yystack[yypParser.yytos+-6].minor.yy79.y - yypParser.yystack[yypParser.yytos+-1].minor.yy153 } //line 2652 "pikchr.go" yypParser.yystack[yypParser.yytos+-6].minor.yy79 = yylhsminor.yy79 break case 69: /* position ::= LP position COMMA position RP */ //line 645 "pikchr.y" { yypParser.yystack[yypParser.yytos+-4].minor.yy79.x = yypParser.yystack[yypParser.yytos+-3].minor.yy79.x yypParser.yystack[yypParser.yytos+-4].minor.yy79.y = yypParser.yystack[yypParser.yytos+-1].minor.yy79.y } //line 2658 "pikchr.go" break case 70: /* position ::= LP position RP */ //line 646 "pikchr.y" { yypParser.yystack[yypParser.yytos+-2].minor.yy79 = yypParser.yystack[yypParser.yytos+-1].minor.yy79 } //line 2663 "pikchr.go" break case 71: /* position ::= expr between position AND position */ //line 648 "pikchr.y" { yylhsminor.yy79 = pik_position_between(yypParser.yystack[yypParser.yytos+-4].minor.yy153, yypParser.yystack[yypParser.yytos+-2].minor.yy79, yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2668 "pikchr.go" yypParser.yystack[yypParser.yytos+-4].minor.yy79 = yylhsminor.yy79 break case 72: /* position ::= expr LT position COMMA position GT */ //line 650 "pikchr.y" { yylhsminor.yy79 = pik_position_between(yypParser.yystack[yypParser.yytos+-5].minor.yy153, yypParser.yystack[yypParser.yytos+-3].minor.yy79, yypParser.yystack[yypParser.yytos+-1].minor.yy79) } //line 2674 "pikchr.go" yypParser.yystack[yypParser.yytos+-5].minor.yy79 = yylhsminor.yy79 break case 73: /* position ::= expr ABOVE position */ //line 651 "pikchr.y" { yylhsminor.yy79 = yypParser.yystack[yypParser.yytos+0].minor.yy79 yylhsminor.yy79.y += yypParser.yystack[yypParser.yytos+-2].minor.yy153 } //line 2680 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy79 = yylhsminor.yy79 break case 74: /* position ::= expr BELOW position */ //line 652 "pikchr.y" { yylhsminor.yy79 = yypParser.yystack[yypParser.yytos+0].minor.yy79 yylhsminor.yy79.y -= yypParser.yystack[yypParser.yytos+-2].minor.yy153 } //line 2686 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy79 = yylhsminor.yy79 break case 75: /* position ::= expr LEFT OF position */ //line 653 "pikchr.y" { yylhsminor.yy79 = yypParser.yystack[yypParser.yytos+0].minor.yy79 yylhsminor.yy79.x -= yypParser.yystack[yypParser.yytos+-3].minor.yy153 } //line 2692 "pikchr.go" yypParser.yystack[yypParser.yytos+-3].minor.yy79 = yylhsminor.yy79 break case 76: /* position ::= expr RIGHT OF position */ //line 654 "pikchr.y" { yylhsminor.yy79 = yypParser.yystack[yypParser.yytos+0].minor.yy79 yylhsminor.yy79.x += yypParser.yystack[yypParser.yytos+-3].minor.yy153 } //line 2698 "pikchr.go" yypParser.yystack[yypParser.yytos+-3].minor.yy79 = yylhsminor.yy79 break case 77: /* position ::= expr ON HEADING EDGEPT OF position */ //line 656 "pikchr.y" { yylhsminor.yy79 = pik_position_at_hdg(yypParser.yystack[yypParser.yytos+-5].minor.yy153, &yypParser.yystack[yypParser.yytos+-2].minor.yy0, yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2704 "pikchr.go" yypParser.yystack[yypParser.yytos+-5].minor.yy79 = yylhsminor.yy79 break case 78: /* position ::= expr HEADING EDGEPT OF position */ //line 658 "pikchr.y" { yylhsminor.yy79 = pik_position_at_hdg(yypParser.yystack[yypParser.yytos+-4].minor.yy153, &yypParser.yystack[yypParser.yytos+-2].minor.yy0, yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2710 "pikchr.go" yypParser.yystack[yypParser.yytos+-4].minor.yy79 = yylhsminor.yy79 break case 79: /* position ::= expr EDGEPT OF position */ //line 660 "pikchr.y" { yylhsminor.yy79 = pik_position_at_hdg(yypParser.yystack[yypParser.yytos+-3].minor.yy153, &yypParser.yystack[yypParser.yytos+-2].minor.yy0, yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2716 "pikchr.go" yypParser.yystack[yypParser.yytos+-3].minor.yy79 = yylhsminor.yy79 break case 80: /* position ::= expr ON HEADING expr FROM position */ //line 662 "pikchr.y" { yylhsminor.yy79 = pik_position_at_angle(yypParser.yystack[yypParser.yytos+-5].minor.yy153, yypParser.yystack[yypParser.yytos+-2].minor.yy153, yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2722 "pikchr.go" yypParser.yystack[yypParser.yytos+-5].minor.yy79 = yylhsminor.yy79 break case 81: /* position ::= expr HEADING expr FROM position */ //line 664 "pikchr.y" { yylhsminor.yy79 = pik_position_at_angle(yypParser.yystack[yypParser.yytos+-4].minor.yy153, yypParser.yystack[yypParser.yytos+-2].minor.yy153, yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2728 "pikchr.go" yypParser.yystack[yypParser.yytos+-4].minor.yy79 = yylhsminor.yy79 break case 82: /* place ::= edge OF object */ //line 676 "pikchr.y" { yylhsminor.yy79 = p.pik_place_of_elem(yypParser.yystack[yypParser.yytos+0].minor.yy104, &yypParser.yystack[yypParser.yytos+-2].minor.yy0) } //line 2734 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy79 = yylhsminor.yy79 break case 83: /* place2 ::= object */ //line 677 "pikchr.y" { yylhsminor.yy79 = p.pik_place_of_elem(yypParser.yystack[yypParser.yytos+0].minor.yy104, nil) } //line 2740 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy79 = yylhsminor.yy79 break case 84: /* place2 ::= object DOT_E edge */ //line 678 "pikchr.y" { yylhsminor.yy79 = p.pik_place_of_elem(yypParser.yystack[yypParser.yytos+-2].minor.yy104, &yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2746 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy79 = yylhsminor.yy79 break case 85: /* place2 ::= NTH VERTEX OF object */ //line 679 "pikchr.y" { yylhsminor.yy79 = p.pik_nth_vertex(&yypParser.yystack[yypParser.yytos+-3].minor.yy0, &yypParser.yystack[yypParser.yytos+-2].minor.yy0, yypParser.yystack[yypParser.yytos+0].minor.yy104) } //line 2752 "pikchr.go" yypParser.yystack[yypParser.yytos+-3].minor.yy79 = yylhsminor.yy79 break case 86: /* object ::= nth */ //line 691 "pikchr.y" { yylhsminor.yy104 = p.pik_find_nth(nil, &yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2758 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy104 = yylhsminor.yy104 break case 87: /* object ::= nth OF|IN object */ //line 692 "pikchr.y" { yylhsminor.yy104 = p.pik_find_nth(yypParser.yystack[yypParser.yytos+0].minor.yy104, &yypParser.yystack[yypParser.yytos+-2].minor.yy0) } //line 2764 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy104 = yylhsminor.yy104 break case 88: /* objectname ::= THIS */ //line 694 "pikchr.y" { yypParser.yystack[yypParser.yytos+0].minor.yy104 = p.cur } //line 2770 "pikchr.go" break case 89: /* objectname ::= PLACENAME */ //line 695 "pikchr.y" { yylhsminor.yy104 = p.pik_find_byname(nil, &yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2775 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy104 = yylhsminor.yy104 break case 90: /* objectname ::= objectname DOT_U PLACENAME */ //line 697 "pikchr.y" { yylhsminor.yy104 = p.pik_find_byname(yypParser.yystack[yypParser.yytos+-2].minor.yy104, &yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2781 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy104 = yylhsminor.yy104 break case 91: /* nth ::= NTH CLASSNAME */ //line 699 "pikchr.y" { yylhsminor.yy0 = yypParser.yystack[yypParser.yytos+0].minor.yy0 yylhsminor.yy0.eCode = p.pik_nth_value(&yypParser.yystack[yypParser.yytos+-1].minor.yy0) } //line 2787 "pikchr.go" yypParser.yystack[yypParser.yytos+-1].minor.yy0 = yylhsminor.yy0 break case 92: /* nth ::= NTH LAST CLASSNAME */ //line 700 "pikchr.y" { yylhsminor.yy0 = yypParser.yystack[yypParser.yytos+0].minor.yy0 yylhsminor.yy0.eCode = -p.pik_nth_value(&yypParser.yystack[yypParser.yytos+-2].minor.yy0) } //line 2793 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy0 = yylhsminor.yy0 break case 93: /* nth ::= LAST CLASSNAME */ //line 701 "pikchr.y" { yypParser.yystack[yypParser.yytos+-1].minor.yy0 = yypParser.yystack[yypParser.yytos+0].minor.yy0 yypParser.yystack[yypParser.yytos+-1].minor.yy0.eCode = -1 } //line 2799 "pikchr.go" break case 94: /* nth ::= LAST */ //line 702 "pikchr.y" { yylhsminor.yy0 = yypParser.yystack[yypParser.yytos+0].minor.yy0 yylhsminor.yy0.eCode = -1 } //line 2804 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy0 = yylhsminor.yy0 break case 95: /* nth ::= NTH LB RB */ //line 703 "pikchr.y" { yylhsminor.yy0 = yypParser.yystack[yypParser.yytos+-1].minor.yy0 yylhsminor.yy0.eCode = p.pik_nth_value(&yypParser.yystack[yypParser.yytos+-2].minor.yy0) } //line 2810 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy0 = yylhsminor.yy0 break case 96: /* nth ::= NTH LAST LB RB */ //line 704 "pikchr.y" { yylhsminor.yy0 = yypParser.yystack[yypParser.yytos+-1].minor.yy0 yylhsminor.yy0.eCode = -p.pik_nth_value(&yypParser.yystack[yypParser.yytos+-3].minor.yy0) } //line 2816 "pikchr.go" yypParser.yystack[yypParser.yytos+-3].minor.yy0 = yylhsminor.yy0 break case 97: /* nth ::= LAST LB RB */ //line 705 "pikchr.y" { yypParser.yystack[yypParser.yytos+-2].minor.yy0 = yypParser.yystack[yypParser.yytos+-1].minor.yy0 yypParser.yystack[yypParser.yytos+-2].minor.yy0.eCode = -1 } //line 2822 "pikchr.go" break case 98: /* expr ::= expr PLUS expr */ //line 707 "pikchr.y" { yylhsminor.yy153 = yypParser.yystack[yypParser.yytos+-2].minor.yy153 + yypParser.yystack[yypParser.yytos+0].minor.yy153 } //line 2827 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy153 = yylhsminor.yy153 break case 99: /* expr ::= expr MINUS expr */ //line 708 "pikchr.y" { yylhsminor.yy153 = yypParser.yystack[yypParser.yytos+-2].minor.yy153 - yypParser.yystack[yypParser.yytos+0].minor.yy153 } //line 2833 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy153 = yylhsminor.yy153 break case 100: /* expr ::= expr STAR expr */ //line 709 "pikchr.y" { yylhsminor.yy153 = yypParser.yystack[yypParser.yytos+-2].minor.yy153 * yypParser.yystack[yypParser.yytos+0].minor.yy153 } //line 2839 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy153 = yylhsminor.yy153 break case 101: /* expr ::= expr SLASH expr */ //line 710 "pikchr.y" { if yypParser.yystack[yypParser.yytos+0].minor.yy153 == 0.0 { p.pik_error(&yypParser.yystack[yypParser.yytos+-1].minor.yy0, "division by zero") yylhsminor.yy153 = 0.0 } else { yylhsminor.yy153 = yypParser.yystack[yypParser.yytos+-2].minor.yy153 / yypParser.yystack[yypParser.yytos+0].minor.yy153 } } //line 2847 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy153 = yylhsminor.yy153 break case 102: /* expr ::= MINUS expr */ //line 713 "pikchr.y" { yypParser.yystack[yypParser.yytos+-1].minor.yy153 = -yypParser.yystack[yypParser.yytos+0].minor.yy153 } //line 2853 "pikchr.go" break case 103: /* expr ::= PLUS expr */ //line 714 "pikchr.y" { yypParser.yystack[yypParser.yytos+-1].minor.yy153 = yypParser.yystack[yypParser.yytos+0].minor.yy153 } //line 2858 "pikchr.go" break case 104: /* expr ::= LP expr RP */ //line 715 "pikchr.y" { yypParser.yystack[yypParser.yytos+-2].minor.yy153 = yypParser.yystack[yypParser.yytos+-1].minor.yy153 } //line 2863 "pikchr.go" break case 105: /* expr ::= LP FILL|COLOR|THICKNESS RP */ //line 716 "pikchr.y" { yypParser.yystack[yypParser.yytos+-2].minor.yy153 = p.pik_get_var(&yypParser.yystack[yypParser.yytos+-1].minor.yy0) } //line 2868 "pikchr.go" break case 106: /* expr ::= NUMBER */ //line 717 "pikchr.y" { yylhsminor.yy153 = pik_atof(&yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2873 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy153 = yylhsminor.yy153 break case 107: /* expr ::= ID */ //line 718 "pikchr.y" { yylhsminor.yy153 = p.pik_get_var(&yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2879 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy153 = yylhsminor.yy153 break case 108: /* expr ::= FUNC1 LP expr RP */ //line 719 "pikchr.y" { yylhsminor.yy153 = p.pik_func(&yypParser.yystack[yypParser.yytos+-3].minor.yy0, yypParser.yystack[yypParser.yytos+-1].minor.yy153, 0.0) } //line 2885 "pikchr.go" yypParser.yystack[yypParser.yytos+-3].minor.yy153 = yylhsminor.yy153 break case 109: /* expr ::= FUNC2 LP expr COMMA expr RP */ //line 720 "pikchr.y" { yylhsminor.yy153 = p.pik_func(&yypParser.yystack[yypParser.yytos+-5].minor.yy0, yypParser.yystack[yypParser.yytos+-3].minor.yy153, yypParser.yystack[yypParser.yytos+-1].minor.yy153) } //line 2891 "pikchr.go" yypParser.yystack[yypParser.yytos+-5].minor.yy153 = yylhsminor.yy153 break case 110: /* expr ::= DIST LP position COMMA position RP */ //line 721 "pikchr.y" { yypParser.yystack[yypParser.yytos+-5].minor.yy153 = pik_dist(&yypParser.yystack[yypParser.yytos+-3].minor.yy79, &yypParser.yystack[yypParser.yytos+-1].minor.yy79) } //line 2897 "pikchr.go" break case 111: /* expr ::= place2 DOT_XY X */ //line 722 "pikchr.y" { yylhsminor.yy153 = yypParser.yystack[yypParser.yytos+-2].minor.yy79.x } //line 2902 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy153 = yylhsminor.yy153 break case 112: /* expr ::= place2 DOT_XY Y */ //line 723 "pikchr.y" { yylhsminor.yy153 = yypParser.yystack[yypParser.yytos+-2].minor.yy79.y } //line 2908 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy153 = yylhsminor.yy153 break case 113: /* expr ::= object DOT_L numproperty */ fallthrough case 114: /* expr ::= object DOT_L dashproperty */ yytestcase(yyruleno == 114) fallthrough case 115: /* expr ::= object DOT_L colorproperty */ yytestcase(yyruleno == 115) //line 724 "pikchr.y" { yylhsminor.yy153 = pik_property_of(yypParser.yystack[yypParser.yytos+-2].minor.yy104, &yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2918 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy153 = yylhsminor.yy153 break default: /* (116) lvalue ::= ID */ yytestcase(yyruleno == 116) /* (117) lvalue ::= FILL */ yytestcase(yyruleno == 117) /* (118) lvalue ::= COLOR */ yytestcase(yyruleno == 118) /* (119) lvalue ::= THICKNESS */ yytestcase(yyruleno == 119) |
︙ | ︙ | |||
3247 3248 3249 3250 3251 3252 3253 | p := yypParser.p _ = p TOKEN := yyminor _ = TOKEN /************ Begin %syntax_error code ****************************************/ | | | | 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 | p := yypParser.p _ = p TOKEN := yyminor _ = TOKEN /************ Begin %syntax_error code ****************************************/ //line 486 "pikchr.y" if TOKEN.z != nil && TOKEN.z[0] != 0 { p.pik_error(&TOKEN, "syntax error") } else { p.pik_error(nil, "syntax error") } //line 3031 "pikchr.go" /************ End %syntax_error code ******************************************/ /* Suppress warning about unused %extra_argument variable */ yypParser.p = p } |
︙ | ︙ | |||
3541 3542 3543 3544 3545 3546 3547 | // to check invariants. func assert(condition bool, message string) { if !condition { panic(message) } } | | | 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 | // to check invariants. func assert(condition bool, message string) { if !condition { panic(message) } } //line 729 "pikchr.y" /* Chart of the 148 official CSS color names with their ** corresponding RGB values thru Color Module Level 4: ** https://developer.mozilla.org/en-US/docs/Web/CSS/color_value ** ** Two new names "None" and "Off" are added with a value ** of -1. |
︙ | ︙ | |||
4008 4009 4010 4011 4012 4013 4014 | pObj.h = pObj.w pObj.rad = 0.5 * pObj.w } func circleNumProp(p *Pik, pObj *PObj, pId *PToken) { /* For a circle, the width must equal the height and both must ** be twice the radius. Enforce those constraints. */ switch pId.eType { | | | 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 | pObj.h = pObj.w pObj.rad = 0.5 * pObj.w } func circleNumProp(p *Pik, pObj *PObj, pId *PToken) { /* For a circle, the width must equal the height and both must ** be twice the radius. Enforce those constraints. */ switch pId.eType { case T_DIAMETER, T_RADIUS: pObj.w = 2.0 * pObj.rad pObj.h = 2.0 * pObj.rad case T_WIDTH: pObj.h = pObj.w pObj.rad = 0.5 * pObj.w case T_HEIGHT: pObj.w = pObj.h |
︙ | ︙ | |||
4824 4825 4826 4827 4828 4829 4830 | /* ** Append raw text to zOut */ func (p *Pik) pik_append(zText string) { p.zOut.WriteString(zText) } | | | > > | | | | | | > > > > > > > | > | > > | | < > | > > > | 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 | /* ** Append raw text to zOut */ func (p *Pik) pik_append(zText string) { p.zOut.WriteString(zText) } var re_entity = regexp.MustCompile(`&(#([0-9]{2,});)|([a-zA-Z][a-zA-Z0-9]+;)`) /* ** Append text to zOut with HTML characters escaped. ** ** * The space character is changed into non-breaking space (U+00a0) ** if mFlags has the 0x01 bit set. This is needed when outputting ** text to preserve leading and trailing whitespace. Turns out we ** cannot use as that is an HTML-ism and is not valid in XML. ** ** * The "&" character is changed into "&" if mFlags has the ** 0x02 bit set. This is needed when generating error message text. ** ** * Except for the above, only "<" and ">" are escaped. */ func (p *Pik) pik_append_text(zText string, mFlags int) { bQSpace := mFlags&1 > 0 bQAmp := mFlags&2 > 0 last := 0 var html string for i := 0; i < len(zText); i++ { switch zText[i] { case '<': html = "<" case '>': html = ">" case '&': if !bQAmp { continue } match := re_entity.FindStringSubmatch(zText[i:]) if len(match) > 0 { continue } html = "&" case ' ': if !bQSpace { continue } html = "\u00a0" default: continue } p.pik_append(zText[last:i]) p.pik_append(html) last = i + 1 } p.pik_append(zText[last:]) } /* ** Append error message text. This is either a raw append, or an append ** with HTML escapes, depending on whether the PIKCHR_PLAINTEXT_ERRORS flag ** is set. */ |
︙ | ︙ | |||
6174 6175 6176 6177 6178 6179 6180 | } p.pik_reset_samepath() n := 0 for n < 1 { n = p.pik_next_rpath(pErr) } if pHeading != nil { | | < < < | 6194 6195 6196 6197 6198 6199 6200 6201 6202 6203 6204 6205 6206 6207 6208 | } p.pik_reset_samepath() n := 0 for n < 1 { n = p.pik_next_rpath(pErr) } if pHeading != nil { rHdg = math.Mod(rHdg, 360.0) } else if pEdgept.eEdge == CP_C { p.pik_error(pEdgept, "syntax error") return } else { rHdg = pik_hdg_angle[pEdgept.eEdge] } if rHdg <= 45.0 { |
︙ | ︙ | |||
7105 7106 7107 7108 7109 7110 7111 | /* Compute one of the built-in functions */ func (p *Pik) pik_func(pFunc *PToken, x PNum, y PNum) PNum { var v PNum switch pFunc.eCode { case FN_ABS: | > | | | | 7122 7123 7124 7125 7126 7127 7128 7129 7130 7131 7132 7133 7134 7135 7136 7137 7138 7139 | /* Compute one of the built-in functions */ func (p *Pik) pik_func(pFunc *PToken, x PNum, y PNum) PNum { var v PNum switch pFunc.eCode { case FN_ABS: if x < 0 { v = -x } else { v = x } case FN_COS: v = math.Cos(x) case FN_INT: v = math.Round(x) case FN_SIN: v = math.Sin(x) |
︙ | ︙ | |||
8324 8325 8326 8327 8328 8329 8330 8331 8332 8333 8334 8335 8336 8337 | n = 0 } fmt.Printf("******** Token %s (%d): \"%s\" **************\n", yyTokenName[token.eType], token.eType, string(token.z[:n])) } // #endif token.n = sz pParser.pik_parser(token.eType, token) } } /* ** Parse the PIKCHR script contained in zText[]. Return a rendering. Or ** if an error is encountered, return the error text. The error message | > > > > > | 8342 8343 8344 8345 8346 8347 8348 8349 8350 8351 8352 8353 8354 8355 8356 8357 8358 8359 8360 | n = 0 } fmt.Printf("******** Token %s (%d): \"%s\" **************\n", yyTokenName[token.eType], token.eType, string(token.z[:n])) } // #endif token.n = sz p.nToken++ if p.nToken > PIKCHR_TOKEN_LIMIT { p.pik_error(&token, "script is too complex") break } pParser.pik_parser(token.eType, token) } } /* ** Parse the PIKCHR script contained in zText[]. Return a rendering. Or ** if an error is encountered, return the error text. The error message |
︙ | ︙ | |||
8458 8459 8460 8461 8462 8463 8464 | if b[i] != bb { return false } } return true } | | | 8481 8482 8483 8484 8485 8486 8487 8488 | if b[i] != bb { return false } } return true } //line 8253 "pikchr.go" |
Changes to parser/pikchr/internal/pikchr.y.
︙ | ︙ | |||
127 128 129 130 131 132 133 134 135 136 137 138 139 140 | "math" "os" "regexp" "strconv" "strings" ) // Numeric value type PNum = float64 // Compass points const ( CP_N uint8 = iota + 1 CP_NE | > > > > | 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | "math" "os" "regexp" "strconv" "strings" ) // Limit the number of tokens in a single script to avoid run-away macro expansion attacks. // See forum post https://pikchr.org/home/forumpost/ef8684c6955a411a const PIKCHR_TOKEN_LIMIT = 100000 // Numeric value type PNum = float64 // Compass points const ( CP_N uint8 = iota + 1 CP_NE |
︙ | ︙ | |||
359 360 361 362 363 364 365 366 367 368 369 370 371 372 | } /* Each call to the pikchr() subroutine uses an instance of the following ** object to pass around context to all of its subroutines. */ type Pik struct { nErr int /* Number of errors seen */ sIn PToken /* Input Pikchr-language text */ zOut bytes.Buffer /* Result accumulates here */ nOut uint /* Bytes written to zOut[] so far */ nOutAlloc uint /* Space allocated to zOut[] */ eDir uint8 /* Current direction */ mFlags uint /* Flags passed to pikchr() */ cur *PObj /* Object under construction */ | > | 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 | } /* Each call to the pikchr() subroutine uses an instance of the following ** object to pass around context to all of its subroutines. */ type Pik struct { nErr int /* Number of errors seen */ nToken int // Number of tokens parsed sIn PToken /* Input Pikchr-language text */ zOut bytes.Buffer /* Result accumulates here */ nOut uint /* Bytes written to zOut[] so far */ nOutAlloc uint /* Space allocated to zOut[] */ eDir uint8 /* Current direction */ mFlags uint /* Flags passed to pikchr() */ cur *PObj /* Object under construction */ |
︙ | ︙ | |||
1184 1185 1186 1187 1188 1189 1190 | pObj.h = pObj.w pObj.rad = 0.5 * pObj.w } func circleNumProp(p *Pik, pObj *PObj, pId *PToken) { /* For a circle, the width must equal the height and both must ** be twice the radius. Enforce those constraints. */ switch pId.eType { | | | 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 | pObj.h = pObj.w pObj.rad = 0.5 * pObj.w } func circleNumProp(p *Pik, pObj *PObj, pId *PToken) { /* For a circle, the width must equal the height and both must ** be twice the radius. Enforce those constraints. */ switch pId.eType { case T_DIAMETER, T_RADIUS: pObj.w = 2.0 * pObj.rad pObj.h = 2.0 * pObj.rad case T_WIDTH: pObj.h = pObj.w pObj.rad = 0.5 * pObj.w case T_HEIGHT: pObj.w = pObj.h |
︙ | ︙ | |||
2000 2001 2002 2003 2004 2005 2006 | /* ** Append raw text to zOut */ func (p *Pik) pik_append(zText string) { p.zOut.WriteString(zText) } | | | > > | | | | | | > > > > > > > | > | > > | | < > | > > > | 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 | /* ** Append raw text to zOut */ func (p *Pik) pik_append(zText string) { p.zOut.WriteString(zText) } var re_entity = regexp.MustCompile(`&(#([0-9]{2,});)|([a-zA-Z][a-zA-Z0-9]+;)`) /* ** Append text to zOut with HTML characters escaped. ** ** * The space character is changed into non-breaking space (U+00a0) ** if mFlags has the 0x01 bit set. This is needed when outputting ** text to preserve leading and trailing whitespace. Turns out we ** cannot use as that is an HTML-ism and is not valid in XML. ** ** * The "&" character is changed into "&" if mFlags has the ** 0x02 bit set. This is needed when generating error message text. ** ** * Except for the above, only "<" and ">" are escaped. */ func (p *Pik) pik_append_text(zText string, mFlags int) { bQSpace := mFlags&1 > 0 bQAmp := mFlags&2 > 0 last := 0 var html string for i := 0; i < len(zText); i++ { switch zText[i] { case '<': html = "<" case '>': html = ">" case '&': if !bQAmp { continue } match := re_entity.FindStringSubmatch(zText[i:]) if len(match) > 0 { continue } html = "&" case ' ': if !bQSpace { continue } html = "\u00a0" default: continue } p.pik_append(zText[last:i]) p.pik_append(html) last = i + 1 } p.pik_append(zText[last:]) } /* ** Append error message text. This is either a raw append, or an append ** with HTML escapes, depending on whether the PIKCHR_PLAINTEXT_ERRORS flag ** is set. */ |
︙ | ︙ | |||
3350 3351 3352 3353 3354 3355 3356 | } p.pik_reset_samepath() n := 0 for n < 1 { n = p.pik_next_rpath(pErr) } if pHeading != nil { | | < < < | 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 | } p.pik_reset_samepath() n := 0 for n < 1 { n = p.pik_next_rpath(pErr) } if pHeading != nil { rHdg = math.Mod(rHdg, 360.0) } else if pEdgept.eEdge == CP_C { p.pik_error(pEdgept, "syntax error") return } else { rHdg = pik_hdg_angle[pEdgept.eEdge] } if rHdg <= 45.0 { |
︙ | ︙ | |||
4281 4282 4283 4284 4285 4286 4287 | /* Compute one of the built-in functions */ func (p *Pik) pik_func(pFunc *PToken, x PNum, y PNum) PNum { var v PNum switch pFunc.eCode { case FN_ABS: | > | | | | 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 | /* Compute one of the built-in functions */ func (p *Pik) pik_func(pFunc *PToken, x PNum, y PNum) PNum { var v PNum switch pFunc.eCode { case FN_ABS: if x < 0 { v = -x } else { v = x } case FN_COS: v = math.Cos(x) case FN_INT: v = math.Round(x) case FN_SIN: v = math.Sin(x) |
︙ | ︙ | |||
5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 | n = 0 } fmt.Printf("******** Token %s (%d): \"%s\" **************\n", yyTokenName[token.eType], token.eType, string(token.z[:n])) } // #endif token.n = sz pParser.pik_parser(token.eType, token) } } /* ** Parse the PIKCHR script contained in zText[]. Return a rendering. Or ** if an error is encountered, return the error text. The error message | > > > > > | 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 | n = 0 } fmt.Printf("******** Token %s (%d): \"%s\" **************\n", yyTokenName[token.eType], token.eType, string(token.z[:n])) } // #endif token.n = sz p.nToken++ if p.nToken > PIKCHR_TOKEN_LIMIT { p.pik_error(&token, "script is too complex"); break; } pParser.pik_parser(token.eType, token) } } /* ** Parse the PIKCHR script contained in zText[]. Return a rendering. Or ** if an error is encountered, return the error text. The error message |
︙ | ︙ |
Changes to parser/zettelmark/inline.go.
︙ | ︙ | |||
63 64 65 66 67 68 69 | in, success = cp.parseMark() } case '{': inp.Next() if inp.Ch == '{' { in, success = cp.parseEmbed() } | < < | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | in, success = cp.parseMark() } case '{': inp.Next() if inp.Ch == '{' { in, success = cp.parseEmbed() } case '%': in, success = cp.parseComment() case '_', '*', '>', '~', '^', ',', '"', ':': in, success = cp.parseFormat() case '@', '\'', '`', '=', runeModGrave: in, success = cp.parseLiteral() case '$': |
︙ | ︙ | |||
100 101 102 103 104 105 106 | return cp.parseBackslashRest() } for { inp.Next() switch inp.Ch { // The following case must contain all runes that occur in parseInline! // Plus the closing brackets ] and } and ) and the middle | | | | 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | return cp.parseBackslashRest() } for { inp.Next() switch inp.Ch { // The following case must contain all runes that occur in parseInline! // Plus the closing brackets ] and } and ) and the middle | case input.EOS, '\n', '\r', ' ', '\t', '[', ']', '{', '}', '(', ')', '|', '%', '_', '*', '>', '~', '^', ',', '"', ':', '\'', '@', '`', runeModGrave, '$', '=', '\\', '-', '&': return &ast.TextNode{Text: string(inp.Src[pos:inp.Pos])} } } } func (cp *zmkP) parseBackslash() ast.InlineNode { inp := cp.inp |
︙ | ︙ | |||
380 381 382 383 384 385 386 | } else { inp.Next() } mn := &ast.MarkNode{Mark: string(mark), Inlines: ins} return mn, true } | < < < < < < < < < < < < < < | 378 379 380 381 382 383 384 385 386 387 388 389 390 391 | } else { inp.Next() } mn := &ast.MarkNode{Mark: string(mark), Inlines: ins} return mn, true } func (cp *zmkP) parseComment() (res *ast.LiteralNode, success bool) { inp := cp.inp inp.Next() if inp.Ch != '%' { return nil, false } for inp.Ch == '%' { |
︙ | ︙ |
Changes to parser/zettelmark/post-processor.go.
︙ | ︙ | |||
277 278 279 280 281 282 283 | toPos++ } } for pos := toPos; pos < len(*bs); pos++ { (*bs)[pos] = nil // Allow excess nodes to be garbage collected. } *bs = (*bs)[:toPos:toPos] | < | 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | toPos++ } } for pos := toPos; pos < len(*bs); pos++ { (*bs)[pos] = nil // Allow excess nodes to be garbage collected. } *bs = (*bs)[:toPos:toPos] } // processItemSlice post-processes a slice of items. // It is one of the working horses for post-processing. func (pp *postProcessor) processItemSlice(ins ast.ItemSlice) ast.ItemSlice { if len(ins) == 0 { return nil |
︙ | ︙ |
Changes to parser/zettelmark/zettelmark_test.go.
︙ | ︙ | |||
16 17 18 19 20 21 22 23 24 25 26 27 28 29 | "fmt" "strings" "testing" "zettelstore.de/c/api" "zettelstore.de/c/attrs" "zettelstore.de/z/ast" "zettelstore.de/z/input" "zettelstore.de/z/parser" ) type TestCase struct{ source, want string } type TestCases []TestCase | > | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | "fmt" "strings" "testing" "zettelstore.de/c/api" "zettelstore.de/c/attrs" "zettelstore.de/z/ast" "zettelstore.de/z/config" "zettelstore.de/z/input" "zettelstore.de/z/parser" ) type TestCase struct{ source, want string } type TestCases []TestCase |
︙ | ︙ | |||
41 42 43 44 45 46 47 | func checkTcs(t *testing.T, tcs TestCases) { t.Helper() for tcn, tc := range tcs { t.Run(fmt.Sprintf("TC=%02d,src=%q", tcn, tc.source), func(st *testing.T) { st.Helper() inp := input.NewInput([]byte(tc.source)) | | | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | func checkTcs(t *testing.T, tcs TestCases) { t.Helper() for tcn, tc := range tcs { t.Run(fmt.Sprintf("TC=%02d,src=%q", tcn, tc.source), func(st *testing.T) { st.Helper() inp := input.NewInput([]byte(tc.source)) bns := parser.ParseBlocks(inp, nil, api.ValueSyntaxZmk, config.NoHTML) var tv TestVisitor ast.Walk(&tv, &bns) got := tv.String() if tc.want != got { st.Errorf("\nwant=%q\n got=%q", tc.want, got) } }) |
︙ | ︙ | |||
244 245 246 247 248 249 250 | {"{{\\}\\||a}}", "(PARA (EMBED a }|))"}, {"{{http://a}}", "(PARA (EMBED http://a))"}, {"{{http://a|http://a}}", "(PARA (EMBED http://a http://a))"}, {"{{{{a}}}}", "(PARA (EMBED %7B%7Ba) }})"}, }) } | < < < < < < < < < < < < < | 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | {"{{\\}\\||a}}", "(PARA (EMBED a }|))"}, {"{{http://a}}", "(PARA (EMBED http://a))"}, {"{{http://a|http://a}}", "(PARA (EMBED http://a http://a))"}, {"{{{{a}}}}", "(PARA (EMBED %7B%7Ba) }})"}, }) } func TestMark(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"[!", "(PARA [!)"}, {"[!\n", "(PARA [!)"}, {"[!]", "(PARA (MARK #*))"}, {"[!][!]", "(PARA (MARK #*) (MARK #*-1))"}, |
︙ | ︙ | |||
409 410 411 412 413 414 415 | func TestEntity(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"&", "(PARA &)"}, {"&;", "(PARA &;)"}, {"&#;", "(PARA &#;)"}, | | | | > > > > | | 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 | func TestEntity(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"&", "(PARA &)"}, {"&;", "(PARA &;)"}, {"&#;", "(PARA &#;)"}, {"a;", "(PARA a;)"}, {"&#x;", "(PARA &#x;)"}, {"�z;", "(PARA �z;)"}, {"&1;", "(PARA &1;)"}, {"	", "(PARA 	)"}, // No numeric entities below space are not allowed. {"", "(PARA )"}, // Good cases {"<", "(PARA <)"}, {"0", "(PARA 0)"}, {"J", "(PARA J)"}, {"J", "(PARA J)"}, {"…", "(PARA \u2026)"}, {" ", "(PARA \u00a0)"}, {"E: &,?;c.", "(PARA E: SP &,?;c.)"}, }) } func TestVerbatimZettel(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"@@@\n@@@", "(ZETTEL)"}, |
︙ | ︙ | |||
883 884 885 886 887 888 889 | tv.visitAttributes(n.Attrs) case *ast.BLOBNode: tv.buf.WriteString("(BLOB ") tv.buf.WriteString(n.Syntax) tv.buf.WriteString(")") case *ast.TextNode: tv.buf.WriteString(n.Text) | < < < < | 875 876 877 878 879 880 881 882 883 884 885 886 887 888 | tv.visitAttributes(n.Attrs) case *ast.BLOBNode: tv.buf.WriteString("(BLOB ") tv.buf.WriteString(n.Syntax) tv.buf.WriteString(")") case *ast.TextNode: tv.buf.WriteString(n.Text) case *ast.SpaceNode: if l := n.Count(); l == 1 { tv.buf.WriteString("SP") } else { fmt.Fprintf(&tv.buf, "SP%d", l) } case *ast.BreakNode: |
︙ | ︙ |
Changes to query/query.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 | // Package query provides a query for zettel. package query import ( "math/rand" "sort" | < | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // Package query provides a query for zettel. package query import ( "math/rand" "sort" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) // Searcher is used to select zettel identifier based on search criteria. type Searcher interface { |
︙ | ︙ | |||
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 | } // EnrichNeeded returns true, if the query references a metadata key that // is calculated via metadata enrichments. func (q *Query) EnrichNeeded() bool { if q == nil { return false } for _, term := range q.terms { for key := range term.keys { if meta.IsProperty(key) { return true } } for key := range term.mvals { if meta.IsProperty(key) { return true } } } for _, o := range q.order { if meta.IsProperty(o.key) { return true } } | > > > > < < < < < | 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 | } // EnrichNeeded returns true, if the query references a metadata key that // is calculated via metadata enrichments. func (q *Query) EnrichNeeded() bool { if q == nil { return false } if len(q.actions) > 0 { // Unknown, what an action will use. Example: RSS needs api.KeyPublished. return true } for _, term := range q.terms { for key := range term.keys { if meta.IsProperty(key) { return true } } for key := range term.mvals { if meta.IsProperty(key) { return true } } } for _, o := range q.order { if meta.IsProperty(o.key) { return true } } return false } // RetrieveAndCompile queries the search index and returns a predicate // for its results and returns a matching predicate. func (q *Query) RetrieveAndCompile(searcher Searcher) Compiled { if q == nil { |
︙ | ︙ |
Changes to tests/markdown_test.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 22 23 24 25 26 27 28 | "encoding/json" "fmt" "os" "testing" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" _ "zettelstore.de/z/encoder/htmlenc" _ "zettelstore.de/z/encoder/sexprenc" _ "zettelstore.de/z/encoder/textenc" _ "zettelstore.de/z/encoder/zjsonenc" _ "zettelstore.de/z/encoder/zmkenc" "zettelstore.de/z/input" | > | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | "encoding/json" "fmt" "os" "testing" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/config" "zettelstore.de/z/encoder" _ "zettelstore.de/z/encoder/htmlenc" _ "zettelstore.de/z/encoder/sexprenc" _ "zettelstore.de/z/encoder/textenc" _ "zettelstore.de/z/encoder/zjsonenc" _ "zettelstore.de/z/encoder/zmkenc" "zettelstore.de/z/input" |
︙ | ︙ | |||
63 64 65 66 67 68 69 | } var testcases []markdownTestCase if err = json.Unmarshal(content, &testcases); err != nil { panic(err) } for _, tc := range testcases { | | | | | 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | } var testcases []markdownTestCase if err = json.Unmarshal(content, &testcases); err != nil { panic(err) } for _, tc := range testcases { ast := createMDBlockSlice(tc.Markdown, config.NoHTML) testAllEncodings(t, tc, &ast) testZmkEncoding(t, tc, &ast) } } func createMDBlockSlice(markdown string, hi config.HTMLInsecurity) ast.BlockSlice { return parser.ParseBlocks(input.NewInput([]byte(markdown)), nil, "markdown", hi) } 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) { |
︙ | ︙ | |||
94 95 96 97 98 99 100 | 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() testID = tc.Example*100 + 2 | | | | | 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 | 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() testID = tc.Example*100 + 2 secondAst := parser.ParseBlocks(input.NewInput(buf.Bytes()), nil, api.ValueSyntaxZmk, config.NoHTML) buf.Reset() zmkEncoder.WriteBlocks(&buf, &secondAst) gotSecond := buf.String() // if gotFirst != gotSecond { // st.Errorf("\nCMD: %q\n1st: %q\n2nd: %q", tc.Markdown, gotFirst, gotSecond) // } testID = tc.Example*100 + 3 thirdAst := parser.ParseBlocks(input.NewInput(buf.Bytes()), nil, api.ValueSyntaxZmk, config.NoHTML) buf.Reset() zmkEncoder.WriteBlocks(&buf, &thirdAst) gotThird := buf.String() if gotSecond != gotThird { st.Errorf("\n1st: %q\n2nd: %q", gotSecond, gotThird) } }) } func TestAdditionalMarkdown(t *testing.T) { testcases := []struct { md string exp string }{ {`abc<br>def`, `abc@@<br>@@{="html"}def`}, } zmkEncoder := encoder.Create(api.EncoderZmk) var buf bytes.Buffer for i, tc := range testcases { ast := createMDBlockSlice(tc.md, config.MarkdownHTML) buf.Reset() zmkEncoder.WriteBlocks(&buf, &ast) got := buf.String() if got != tc.exp { t.Errorf("%d: %q -> %q, but got %q", i, tc.md, tc.exp, got) } } } |
Changes to tests/regression_test.go.
︙ | ︙ | |||
21 22 23 24 25 26 27 28 29 30 31 32 33 34 | "path/filepath" "testing" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" "zettelstore.de/z/kernel" "zettelstore.de/z/parser" _ "zettelstore.de/z/box/dirbox" | > | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | "path/filepath" "testing" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" "zettelstore.de/z/kernel" "zettelstore.de/z/parser" _ "zettelstore.de/z/box/dirbox" |
︙ | ︙ | |||
158 159 160 161 162 163 164 | type myConfig struct{} func (*myConfig) Get(context.Context, *meta.Meta, string) string { return "" } func (*myConfig) AddDefaultValues(_ context.Context, m *meta.Meta) *meta.Meta { return m } | > | | | | | | 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | type myConfig struct{} func (*myConfig) Get(context.Context, *meta.Meta, string) string { return "" } func (*myConfig) AddDefaultValues(_ context.Context, m *meta.Meta) *meta.Meta { return m } func (*myConfig) GetHTMLInsecurity() config.HTMLInsecurity { return config.NoHTML } func (*myConfig) GetHomeZettel() id.Zid { return id.Invalid } func (*myConfig) GetListPageSize() int { return 0 } func (*myConfig) GetSiteName() string { return "" } func (*myConfig) GetYAMLHeader() bool { return false } func (*myConfig) GetZettelFileSyntax() []string { return nil } func (*myConfig) GetSimpleMode() bool { return false } func (*myConfig) GetExpertMode() bool { return false } func (*myConfig) GetVisibility(*meta.Meta) meta.Visibility { return meta.VisibilityPublic } func (*myConfig) GetMaxTransclusions() int { return 1024 } var testConfig = &myConfig{} |
︙ | ︙ |
Changes to usecase/create_zettel.go.
︙ | ︙ | |||
53 54 55 56 57 58 59 60 61 62 63 64 65 66 | if readonly, found := m.Get(api.KeyReadOnly); found { m.Set(api.KeyReadOnly, copyReadonly(readonly)) } content := origZettel.Content content.TrimSpace() return domain.Zettel{Meta: m, Content: content} } // PrepareFolge the zettel for further modification. func (*CreateZettel) PrepareFolge(origZettel domain.Zettel) domain.Zettel { origMeta := origZettel.Meta m := meta.New(id.Invalid) if title, found := origMeta.Get(api.KeyTitle); found { m.Set(api.KeyTitle, prependTitle(title, "Folge", "Folge of ")) | > > > > > > > > > > > > > | 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 | if readonly, found := m.Get(api.KeyReadOnly); found { m.Set(api.KeyReadOnly, copyReadonly(readonly)) } content := origZettel.Content content.TrimSpace() return domain.Zettel{Meta: m, Content: content} } // PrepareVersion the zettel for further modification. func (*CreateZettel) PrepareVersion(origZettel domain.Zettel) domain.Zettel { origMeta := origZettel.Meta m := origMeta.Clone() m.Set(api.KeyPredecessor, origMeta.Zid.String()) if readonly, found := m.Get(api.KeyReadOnly); found { m.Set(api.KeyReadOnly, copyReadonly(readonly)) } content := origZettel.Content content.TrimSpace() return domain.Zettel{Meta: m, Content: content} } // PrepareFolge the zettel for further modification. func (*CreateZettel) PrepareFolge(origZettel domain.Zettel) domain.Zettel { origMeta := origZettel.Meta m := meta.New(id.Invalid) if title, found := origMeta.Get(api.KeyTitle); found { m.Set(api.KeyTitle, prependTitle(title, "Folge", "Folge of ")) |
︙ | ︙ | |||
96 97 98 99 100 101 102 | if len(title) > 0 { return s1 + title } return s0 } func copyReadonly(string) string { | | | | 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | if len(title) > 0 { return s1 + title } return s0 } func copyReadonly(string) string { // TODO: Currently, "false" is a safe value. // // If the current user and its role is known, a more elaborative calculation // could be done: set it to a value, so that the current user will be able // to modify it later. return api.ValueFalse } // Run executes the use case. func (uc *CreateZettel) Run(ctx context.Context, zettel domain.Zettel) (id.Zid, error) { |
︙ | ︙ |
Changes to usecase/lists.go.
︙ | ︙ | |||
98 99 100 101 102 103 104 | q := query.Parse(api.KeyRole + api.ExistOperator) // We look for all metadata with an existing role key metas, err := uc.port.SelectMeta(box.NoEnrichContext(ctx), q) if err != nil { return nil, err } return meta.CreateArrangement(metas, api.KeyRole), nil } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 98 99 100 101 102 103 104 | q := query.Parse(api.KeyRole + api.ExistOperator) // We look for all metadata with an existing role key metas, err := uc.port.SelectMeta(box.NoEnrichContext(ctx), q) if err != nil { return nil, err } return meta.CreateArrangement(metas, api.KeyRole), nil } |
Changes to usecase/unlinked_refs.go.
︙ | ︙ | |||
108 109 110 111 112 113 114 | } } syntax := zettel.Meta.GetDefault(api.KeySyntax, "") if !parser.IsTextParser(syntax) { continue } | | | 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | } } syntax := zettel.Meta.GetDefault(api.KeySyntax, "") if !parser.IsTextParser(syntax) { continue } zn, err := parser.ParseZettel(ctx, zettel, syntax, uc.rtConfig), nil if err != nil { continue } evaluator.EvaluateZettel(ctx, uc.port, uc.rtConfig, zn) ast.Walk(&v, &zn.Ast) if v.found { result = append(result, cand) |
︙ | ︙ |
Changes to web/adapter/api/command.go.
︙ | ︙ | |||
21 22 23 24 25 26 27 | // MakePostCommandHandler creates a new HTTP handler to execute certain commands. func (a *API) MakePostCommandHandler( ucIsAuth *usecase.IsAuthenticated, ucRefresh *usecase.Refresh, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() | < < < < < | | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | // MakePostCommandHandler creates a new HTTP handler to execute certain commands. func (a *API) MakePostCommandHandler( ucIsAuth *usecase.IsAuthenticated, ucRefresh *usecase.Refresh, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() switch api.Command(r.URL.Query().Get(api.QueryKeyCommand)) { case api.CommandAuthenticated: handleIsAuthenticated(ctx, w, ucIsAuth) return case api.CommandRefresh: err := ucRefresh.Run(ctx) if err != nil { a.reportUsecaseError(w, err) |
︙ | ︙ |
Deleted web/adapter/api/get_lists.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to web/adapter/api/get_unlinked_refs.go.
︙ | ︙ | |||
39 40 41 42 43 44 45 | if err != nil { a.reportUsecaseError(w, err) return } que := r.URL.Query() phrase := que.Get(api.QueryKeyPhrase) | < < < < | | 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 | if err != nil { a.reportUsecaseError(w, err) return } que := r.URL.Query() phrase := que.Get(api.QueryKeyPhrase) if phrase == "" { if zmkTitle, found := zm.Get(api.KeyTitle); found { isTitle := evaluate.RunMetadata(ctx, zmkTitle) encdr := textenc.Create() var b strings.Builder _, err = encdr.WriteInlines(&b, &isTitle) if err == nil { phrase = b.String() } } } metaList, err := unlinkedRefs.Run(ctx, phrase, adapter.AddUnlinkedRefsToQuery(adapter.GetQuery(que), zm)) if err != nil { a.reportUsecaseError(w, err) return } result := api.ZidMetaRelatedList{ ID: api.ZettelID(zid.String()), |
︙ | ︙ |
Changes to web/adapter/api/get_zettel_context.go.
︙ | ︙ | |||
25 26 27 28 29 30 31 | 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() | < < < < | < < < < < < | 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 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() dir := adapter.GetZCDirection(q.Get(api.QueryKeyDir)) depth, ok := adapter.GetInteger(q, api.QueryKeyDepth) if !ok || depth < 0 { depth = 5 } limit, ok := adapter.GetInteger(q, api.QueryKeyLimit) if !ok || limit < 0 { limit = 200 } ctx := r.Context() metaList, err := getContext.Run(ctx, zid, dir, depth, limit) if err != nil { a.reportUsecaseError(w, err) |
︙ | ︙ |
Changes to web/adapter/api/get_zettel_list.go.
1 2 3 4 5 6 7 8 9 10 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 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) 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 api import ( "bytes" "fmt" "net/http" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeListPlainHandler creates a new HTTP handler for the use case "list some zettel". func (a *API) MakeListPlainHandler(listMeta usecase.ListMeta) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() metaList, err := listMeta.Run(ctx, adapter.GetQuery(r.URL.Query())) if err != nil { a.reportUsecaseError(w, err) |
︙ | ︙ |
Changes to web/adapter/api/query.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 15 16 17 18 19 20 | // under this license. //----------------------------------------------------------------------------- package api import ( "bytes" "io" "net/http" "strconv" "strings" "zettelstore.de/c/api" | > < < < < < | < < < | | | 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 | // under this license. //----------------------------------------------------------------------------- package api import ( "bytes" "context" "io" "net/http" "strconv" "strings" "zettelstore.de/c/api" "zettelstore.de/z/domain/meta" "zettelstore.de/z/query" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeQueryHandler creates a new HTTP handler to perform a query. func (a *API) MakeQueryHandler(listMeta usecase.ListMeta) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() q := adapter.GetQuery(r.URL.Query()) metaList, err := listMeta.Run(ctx, q) if err != nil { a.reportUsecaseError(w, err) return } var buf bytes.Buffer contentType, err := a.queryAction(ctx, &buf, q, metaList) if err != nil { a.reportUsecaseError(w, err) return } err = writeBuffer(w, &buf, contentType) a.log.IfErr(err).Msg("write action") } } func (a *API) queryAction(ctx context.Context, w io.Writer, q *query.Query, ml []*meta.Meta) (string, error) { ap := actionPara{ w: w, q: q, ml: ml, min: -1, max: -1, } |
︙ | ︙ | |||
84 85 86 87 88 89 90 | key := strings.ToLower(act) switch meta.Type(key) { case meta.TypeWord, meta.TypeTagSet: return ap.createMapMeta(key) } } } | > | | 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | key := strings.ToLower(act) switch meta.Type(key) { case meta.TypeWord, meta.TypeTagSet: return ap.createMapMeta(key) } } } err := a.writeQueryMetaList(ctx, w, q, ml) return ctJSON, err } type actionPara struct { w io.Writer q *query.Query ml []*meta.Meta min int |
︙ | ︙ | |||
119 120 121 122 123 124 125 | } mm[tag] = zidList } err := encodeJSONData(ap.w, api.MapListJSON{Map: mm}) return ctJSON, err } | > > > > > > > > > > > > > > > > > > | 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 | } mm[tag] = zidList } err := encodeJSONData(ap.w, api.MapListJSON{Map: mm}) return ctJSON, err } func (a *API) writeQueryMetaList(ctx context.Context, w io.Writer, q *query.Query, ml []*meta.Meta) error { result := make([]api.ZidMetaJSON, 0, len(ml)) for _, m := range ml { result = append(result, api.ZidMetaJSON{ ID: api.ZettelID(m.Zid.String()), Meta: m.Map(), Rights: a.getRights(ctx, m), }) } err := encodeJSONData(w, api.ZettelListJSON{ Query: q.String(), Human: q.Human(), List: result, }) return err } |
Changes to web/adapter/api/request.go.
1 2 3 4 5 6 7 8 9 10 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- | < < < < | 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 | //----------------------------------------------------------------------------- // 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 api import ( "io" "net/http" "net/url" "zettelstore.de/c/api" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" ) // 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 encoding != "" { 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 { |
︙ | ︙ | |||
78 79 80 81 82 83 84 | api.PartZettel: partZettel, } func getPart(q url.Values, defPart partType) partType { if part, ok := partMap[q.Get(api.QueryKeyPart)]; ok { return part } | < < < | 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | api.PartZettel: partZettel, } func getPart(q url.Values, defPart partType) partType { if part, ok := partMap[q.Get(api.QueryKeyPart)]; ok { return part } return defPart } func (p partType) String() string { switch p { case partMeta: return "meta" |
︙ | ︙ |
Changes to web/adapter/request.go.
︙ | ︙ | |||
49 50 51 52 53 54 55 | } return 0, false } // GetQuery retrieves the specified options from a query. func GetQuery(vals url.Values) *query.Query { if exprs, found := vals[api.QueryKeyQuery]; found { | < < < | 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | } return 0, false } // GetQuery retrieves the specified options from a query. func GetQuery(vals url.Values) *query.Query { if exprs, found := vals[api.QueryKeyQuery]; found { return query.Parse(strings.Join(exprs, " ")) } return nil } // GetZCDirection returns a direction value for a given string. func GetZCDirection(s string) usecase.ZettelContextDirection { |
︙ | ︙ |
Changes to web/adapter/webui/const.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 | // WebUI related constants. const queryKeyAction = "action" // Values for queryKeyAction const ( | | | | > > > > | 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 | // WebUI related constants. const queryKeyAction = "action" // Values for queryKeyAction const ( valueActionCopy = "copy" valueActionFolge = "folge" valueActionNew = "new" valueActionVersion = "version" ) // Enumeration for queryKeyAction type createAction uint8 const ( actionCopy createAction = iota actionFolge actionNew actionVersion ) func getCreateAction(s string) createAction { switch s { case valueActionCopy: return actionCopy case valueActionFolge: return actionFolge case valueActionNew: return actionNew case valueActionVersion: return actionVersion default: return actionCopy } } |
Changes to web/adapter/webui/create_zettel.go.
︙ | ︙ | |||
45 46 47 48 49 50 51 52 53 54 55 56 57 58 | return } roleData, syntaxData := retrieveDataLists(ctx, ucListRoles, ucListSyntax) switch op { case actionCopy: wui.renderZettelForm(ctx, w, createZettel.PrepareCopy(origZettel), "Copy Zettel", "Copy Zettel", roleData, syntaxData) case actionFolge: wui.renderZettelForm(ctx, w, createZettel.PrepareFolge(origZettel), "Folge Zettel", "Folgezettel", roleData, syntaxData) case actionNew: m := origZettel.Meta title := parser.ParseMetadata(m.GetTitle()) textTitle, err2 := encodeInlinesText(&title, wui.gentext) if err2 != nil { | > > | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | return } roleData, syntaxData := retrieveDataLists(ctx, ucListRoles, ucListSyntax) switch op { case actionCopy: wui.renderZettelForm(ctx, w, createZettel.PrepareCopy(origZettel), "Copy Zettel", "Copy Zettel", roleData, syntaxData) case actionVersion: wui.renderZettelForm(ctx, w, createZettel.PrepareVersion(origZettel), "Version Zettel", "Versionzettel", roleData, syntaxData) case actionFolge: wui.renderZettelForm(ctx, w, createZettel.PrepareFolge(origZettel), "Folge Zettel", "Folgezettel", roleData, syntaxData) case actionNew: m := origZettel.Meta title := parser.ParseMetadata(m.GetTitle()) textTitle, err2 := encodeInlinesText(&title, wui.gentext) if err2 != nil { |
︙ | ︙ |
Changes to web/adapter/webui/delete_zettel.go.
︙ | ︙ | |||
42 43 44 45 46 47 48 | if err != nil { wui.reportError(ctx, w, err) return } m := ms[0] var shadowedBox string | | < | < | 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 | if err != nil { wui.reportError(ctx, w, err) return } m := ms[0] var shadowedBox string var incomingLinks simpleLinks if len(ms) > 1 { shadowedBox = ms[1].GetDefault(api.KeyBoxNumber, "???") } else { getTextTitle := wui.makeGetTextTitle(createGetMetadataFunc(ctx, getMeta), createEvalMetadataFunc(ctx, evaluate)) incomingLinks = wui.encodeIncoming(m, getTextTitle) } uselessFiles := retrieveUselessFiles(m) user := server.GetUser(ctx) var base baseData wui.makeBaseData(ctx, wui.rtConfig.Get(ctx, m, api.KeyLang), "Delete Zettel "+m.Zid.String(), "", user, &base) wui.renderTemplate(ctx, w, id.DeleteTemplateZid, &base, struct { Zid string MetaPairs []meta.Pair HasShadows bool ShadowedBox string Incoming simpleLinks HasUselessFiles bool UselessFiles []string }{ Zid: zid.String(), MetaPairs: m.ComputedPairs(), HasShadows: shadowedBox != "", ShadowedBox: shadowedBox, Incoming: incomingLinks, HasUselessFiles: len(uselessFiles) > 0, UselessFiles: uselessFiles, }) } } |
︙ | ︙ | |||
101 102 103 104 105 106 107 | wui.reportError(ctx, w, err) return } wui.redirectFound(w, r, wui.NewURLBuilder('/')) } } | | | | 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 | wui.reportError(ctx, w, err) return } wui.redirectFound(w, r, wui.NewURLBuilder('/')) } } func (wui *WebUI) encodeIncoming(m *meta.Meta, getTextTitle getTextTitleFunc) simpleLinks { zidMap := make(strfun.Set) addListValues(zidMap, m, api.KeyBackward) for _, kd := range meta.GetSortedKeyDescriptions() { inverseKey := kd.Inverse if inverseKey == "" { continue } ikd := meta.GetDescription(inverseKey) switch ikd.Type { case meta.TypeID: if val, ok := m.Get(inverseKey); ok { zidMap.Set(val) } case meta.TypeIDSet: addListValues(zidMap, m, inverseKey) } } return createSimpleLinks(wui.encodeZidLinks(maps.Keys(zidMap), 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/get_info.go.
︙ | ︙ | |||
119 120 121 122 123 124 125 | wui.makeBaseData(ctx, wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang), textTitle, "", user, &base) wui.renderTemplate(ctx, w, id.InfoTemplateZid, &base, struct { Zid string WebURL string ContextURL string CanWrite bool EditURL string | | > > | | | < | > > | | | | < | | 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 | wui.makeBaseData(ctx, wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang), textTitle, "", user, &base) wui.renderTemplate(ctx, w, id.InfoTemplateZid, &base, struct { Zid string WebURL string ContextURL string CanWrite bool EditURL string CanCopy bool CopyURL string CanVersion bool VersionURL string CanFolge bool FolgeURL string CanRename bool RenameURL string CanDelete bool DeleteURL string MetaData []metaDataInfo HasLocLinks bool LocLinks []localLink QueryLinks simpleLinks HasExtLinks bool ExtLinks []string ExtNewWindow string UnLinksContent string UnLinksPhrase string QueryKeyPhrase string EvalMatrix []matrixLine ParseMatrix []matrixLine HasShadowLinks bool ShadowLinks []string Endnotes string }{ Zid: zid.String(), WebURL: wui.NewURLBuilder('h').SetZid(apiZid).String(), ContextURL: wui.NewURLBuilder('k').SetZid(apiZid).String(), CanWrite: wui.canWrite(ctx, user, zn.Meta, zn.Content), EditURL: wui.NewURLBuilder('e').SetZid(apiZid).String(), CanCopy: canCreate && !zn.Content.IsBinary(), CopyURL: wui.NewURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionCopy).String(), CanVersion: canCreate, VersionURL: wui.NewURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionVersion).String(), CanFolge: canCreate, FolgeURL: wui.NewURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionFolge).String(), CanRename: wui.canRename(ctx, user, zn.Meta), RenameURL: wui.NewURLBuilder('b').SetZid(apiZid).String(), CanDelete: wui.canDelete(ctx, user, zn.Meta), DeleteURL: wui.NewURLBuilder('d').SetZid(apiZid).String(), MetaData: metaData, HasLocLinks: len(locLinks) > 0, LocLinks: locLinks, QueryLinks: createSimpleLinks(queryLinks), HasExtLinks: len(extLinks) > 0, ExtLinks: extLinks, ExtNewWindow: htmlAttrNewWindow(len(extLinks) > 0), UnLinksContent: unlinkedContent, UnLinksPhrase: phrase, QueryKeyPhrase: api.QueryKeyPhrase, EvalMatrix: wui.infoAPIMatrix('v', zid), |
︙ | ︙ |
Changes to web/adapter/webui/get_zettel.go.
︙ | ︙ | |||
58 59 60 61 62 63 64 | return } if cssZid != id.Invalid { roleCSSURL = wui.NewURLBuilder('z').SetZid(api.ZettelID(cssZid.String())).String() } user := server.GetUser(ctx) roleText := zn.Meta.GetDefault(api.KeyRole, "") | < | | > | | | | | | | | < | | | > > | | > | | | | | | | | | | | | | | | | | | < | | | > > | | > | | | | | | | | < | > | 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 | return } if cssZid != id.Invalid { roleCSSURL = wui.NewURLBuilder('z').SetZid(api.ZettelID(cssZid.String())).String() } user := server.GetUser(ctx) roleText := zn.Meta.GetDefault(api.KeyRole, "") canCreate := wui.canCreate(ctx, user) getTextTitle := wui.makeGetTextTitle(createGetMetadataFunc(ctx, getMeta), evalMetadata) extURL, hasExtURL := zn.Meta.Get(api.KeyURL) folgeLinks := createSimpleLinks(wui.encodeZettelLinks(zn.InhMeta, api.KeyFolge, getTextTitle)) backLinks := createSimpleLinks(wui.encodeZettelLinks(zn.InhMeta, api.KeyBack, getTextTitle)) successorLinks := createSimpleLinks(wui.encodeZettelLinks(zn.InhMeta, api.KeySuccessors, getTextTitle)) apiZid := api.ZettelID(zid.String()) var base baseData wui.makeBaseData(ctx, wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang), textTitle, roleCSSURL, user, &base) base.MetaHeader = enc.MetaString(zn.InhMeta, evalMetadata) wui.renderTemplate(ctx, w, id.ZettelTemplateZid, &base, struct { HTMLTitle string RoleCSS string CanWrite bool EditURL string Zid string InfoURL string RoleText string RoleURL string Tags simpleLinks CanCopy bool CopyURL string CanVersion bool VersionURL string CanFolge bool FolgeURL string PredecessorRefs string PrecursorRefs string HasExtURL bool ExtURL string ExtNewWindow string Author string Content string NeedBottomNav bool FolgeLinks simpleLinks BackLinks simpleLinks SuccessorLinks simpleLinks }{ HTMLTitle: htmlTitle, RoleCSS: roleCSSURL, 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(api.KeyRole + api.SearchOperatorHas + roleText).String(), Tags: createSimpleLinks(wui.buildTagInfos(zn.Meta)), CanCopy: canCreate && !zn.Content.IsBinary(), CopyURL: wui.NewURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionCopy).String(), CanVersion: canCreate, VersionURL: wui.NewURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionVersion).String(), CanFolge: canCreate, FolgeURL: wui.NewURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionFolge).String(), PredecessorRefs: wui.encodeIdentifierSet(zn.InhMeta, api.KeyPredecessor, getTextTitle), PrecursorRefs: wui.encodeIdentifierSet(zn.InhMeta, api.KeyPrecursor, getTextTitle), ExtURL: extURL, HasExtURL: hasExtURL, ExtNewWindow: htmlAttrNewWindow(hasExtURL), Author: zn.Meta.GetDefault(api.KeyAuthor, ""), Content: htmlContent, NeedBottomNav: folgeLinks.Has || backLinks.Has || successorLinks.Has, FolgeLinks: folgeLinks, BackLinks: backLinks, SuccessorLinks: successorLinks, }) } } func encodeInlinesText(is *ast.InlineSlice, enc *textenc.Encoder) (string, error) { if is == nil || len(*is) == 0 { return "", nil |
︙ | ︙ | |||
141 142 143 144 145 146 147 | func (wui *WebUI) buildTagInfos(m *meta.Meta) []simpleLink { var tagInfos []simpleLink if tags, ok := m.GetList(api.KeyTags); ok { ub := wui.NewURLBuilder('h') tagInfos = make([]simpleLink, len(tags)) for i, tag := range tags { | | | 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | func (wui *WebUI) buildTagInfos(m *meta.Meta) []simpleLink { var tagInfos []simpleLink if tags, ok := m.GetList(api.KeyTags); ok { ub := wui.NewURLBuilder('h') tagInfos = make([]simpleLink, len(tags)) for i, tag := range tags { tagInfos[i] = simpleLink{Text: tag, URL: ub.AppendQuery(api.KeyTags + api.SearchOperatorHas + tag).String()} ub.ClearQuery() } } return tagInfos } func (wui *WebUI) encodeIdentifierSet(m *meta.Meta, key string, getTextTitle getTextTitleFunc) string { |
︙ | ︙ |
Changes to web/adapter/webui/htmlgen.go.
︙ | ︙ | |||
45 46 47 48 49 50 51 | gen := &htmlGenerator{ builder: builder, textEnc: textenc.Create(), extMarker: extMarker, env: env, } | < | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | gen := &htmlGenerator{ builder: builder, textEnc: textenc.Create(), extMarker: extMarker, env: env, } env.Builtins.Set(sexpr.SymLinkZettel, sxpf.NewBuiltin("linkZ", true, 2, -1, gen.generateLinkZettel)) env.Builtins.Set(sexpr.SymLinkFound, sxpf.NewBuiltin("linkZ", true, 2, -1, gen.generateLinkZettel)) env.Builtins.Set(sexpr.SymLinkBased, sxpf.NewBuiltin("linkB", true, 2, -1, gen.generateLinkBased)) env.Builtins.Set(sexpr.SymLinkQuery, sxpf.NewBuiltin("linkQ", true, 2, -1, gen.generateLinkQuery)) env.Builtins.Set(sexpr.SymLinkExternal, sxpf.NewBuiltin("linkE", true, 2, -1, gen.generateLinkExternal)) f, err := env.Builtins.LookupForm(sexpr.SymEmbed) |
︙ | ︙ | |||
70 71 72 73 74 75 76 | api.KeyLicense: "license", } func (g *htmlGenerator) MetaString(m *meta.Meta, evalMeta encoder.EvalMetaFunc) string { ignore := strfun.NewSet(api.KeyTitle, api.KeyLang) var buf bytes.Buffer | | < < < < | 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | api.KeyLicense: "license", } func (g *htmlGenerator) MetaString(m *meta.Meta, evalMeta encoder.EvalMetaFunc) string { ignore := strfun.NewSet(api.KeyTitle, api.KeyLang) var buf bytes.Buffer if tags, ok := m.Get(api.KeyTags); ok { writeMetaTags(&buf, tags) ignore.Set(api.KeyTags) } for _, p := range m.ComputedPairs() { key := p.Key if ignore.Has(key) { |
︙ | ︙ | |||
135 136 137 138 139 140 141 | func (g *htmlGenerator) InlinesString(is *ast.InlineSlice) (string, error) { if is == nil || len(*is) == 0 { return "", nil } return html.EvaluateInline(g.env, sexprenc.GetSexpr(is), true, false), nil } | < < < < < < < < < < < < < < < < | 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | func (g *htmlGenerator) InlinesString(is *ast.InlineSlice) (string, error) { if is == nil || len(*is) == 0 { return "", nil } return html.EvaluateInline(g.env, sexprenc.GetSexpr(is), true, false), nil } func (g *htmlGenerator) generateLinkZettel(senv sxpf.Environment, args *sxpf.Pair, _ int) (sxpf.Value, error) { env := senv.(*html.EncEnvironment) if a, refValue, ok := html.PrepareLink(env, args); ok { zid, fragment, hasFragment := strings.Cut(refValue, "#") u := g.builder.NewURLBuilder('h').SetZid(api.ZettelID(zid)) if hasFragment { u = u.SetFragment(fragment) |
︙ | ︙ |
Changes to web/adapter/webui/lists.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- package webui import ( "bytes" "context" | < > > | < | > > > > > | | > | 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 | //----------------------------------------------------------------------------- package webui import ( "bytes" "context" "io" "net/http" "net/url" "strings" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoding/atom" "zettelstore.de/z/encoding/rss" "zettelstore.de/z/encoding/xml" "zettelstore.de/z/evaluator" "zettelstore.de/z/query" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/server" ) // MakeListHTMLMetaHandler creates a HTTP handler for rendering the list of zettel as HTML. func (wui *WebUI) MakeListHTMLMetaHandler(listMeta usecase.ListMeta, evaluate *usecase.Evaluate) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { q := adapter.GetQuery(r.URL.Query()) ctx := r.Context() if !q.EnrichNeeded() { ctx = box.NoEnrichContext(ctx) } metaList, err := listMeta.Run(ctx, q) if err != nil { wui.reportError(ctx, w, err) return } if actions := q.Actions(); len(actions) > 0 { switch actions[0] { case "ATOM": wui.renderAtom(ctx, w, q, metaList) return case "RSS": wui.renderRSS(ctx, w, q, metaList) return } } bns := evaluate.RunBlockNode(ctx, evaluator.QueryAction(ctx, q, metaList, wui.rtConfig)) enc := wui.getSimpleHTMLEncoder() htmlContent, err := enc.BlocksString(&bns) if err != nil { wui.reportError(ctx, w, err) return |
︙ | ︙ | |||
77 78 79 80 81 82 83 | func (wui *WebUI) renderRSS(ctx context.Context, w http.ResponseWriter, q *query.Query, ml []*meta.Meta) { var rssConfig rss.Configuration rssConfig.Setup(ctx, wui.rtConfig) if actions := q.Actions(); len(actions) > 2 && actions[1] == "TITLE" { rssConfig.Title = strings.Join(actions[2:], " ") } | | < < < | > > > > > > > > > > > > > > > > > > > > | 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 | func (wui *WebUI) renderRSS(ctx context.Context, w http.ResponseWriter, q *query.Query, ml []*meta.Meta) { var rssConfig rss.Configuration rssConfig.Setup(ctx, wui.rtConfig) if actions := q.Actions(); len(actions) > 2 && actions[1] == "TITLE" { rssConfig.Title = strings.Join(actions[2:], " ") } data := rssConfig.Marshal(q, ml) adapter.PrepareHeader(w, rss.ContentType) w.WriteHeader(http.StatusOK) var err error if _, err = io.WriteString(w, xml.Header); err == nil { _, err = w.Write(data) } if err != nil { wui.log.IfErr(err).Msg("unable to write RSS data") } } func (wui *WebUI) renderAtom(ctx context.Context, w http.ResponseWriter, q *query.Query, ml []*meta.Meta) { var atomConfig atom.Configuration atomConfig.Setup(ctx, wui.rtConfig) if actions := q.Actions(); len(actions) > 2 && actions[1] == "TITLE" { atomConfig.Title = strings.Join(actions[2:], " ") } data := atomConfig.Marshal(q, ml) adapter.PrepareHeader(w, atom.ContentType) w.WriteHeader(http.StatusOK) var err error if _, err = io.WriteString(w, xml.Header); err == nil { _, err = w.Write(data) } if err != nil { wui.log.IfErr(err).Msg("unable to write Atom data") } } // MakeZettelContextHandler creates a new HTTP handler for the use case "zettel context". func (wui *WebUI) MakeZettelContextHandler(getContext usecase.ZettelContext, evaluate *usecase.Evaluate) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() zid, err := id.Parse(r.URL.Path[1:]) if err != nil { |
︙ | ︙ |
Changes to web/adapter/webui/rename_zettel.go.
︙ | ︙ | |||
38 39 40 41 42 43 44 | m, err := getMeta.Run(ctx, zid) if err != nil { wui.reportError(ctx, w, err) return } getTextTitle := wui.makeGetTextTitle(createGetMetadataFunc(ctx, getMeta), createEvalMetadataFunc(ctx, evaluate)) | < < | < | | 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 | m, err := getMeta.Run(ctx, zid) if err != nil { wui.reportError(ctx, w, err) return } getTextTitle := wui.makeGetTextTitle(createGetMetadataFunc(ctx, getMeta), createEvalMetadataFunc(ctx, evaluate)) uselessFiles := retrieveUselessFiles(m) user := server.GetUser(ctx) var base baseData wui.makeBaseData(ctx, wui.rtConfig.Get(ctx, m, api.KeyLang), "Rename Zettel "+zid.String(), "", user, &base) wui.renderTemplate(ctx, w, id.RenameTemplateZid, &base, struct { Zid string MetaPairs []meta.Pair Incoming simpleLinks HasUselessFiles bool UselessFiles []string }{ Zid: zid.String(), MetaPairs: m.ComputedPairs(), Incoming: wui.encodeIncoming(m, getTextTitle), HasUselessFiles: len(uselessFiles) > 0, UselessFiles: uselessFiles, }) } } // MakePostRenameZettelHandler creates a new HTTP handler to rename an existing zettel. |
︙ | ︙ |
Changes to web/adapter/webui/webui.go.
︙ | ︙ | |||
96 97 98 99 100 101 102 | 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(api.ActionSeparator + api.KeyRole).String(), | | | 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | 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(api.ActionSeparator + api.KeyRole).String(), listTagsURL: ab.NewURLBuilder('h').AppendQuery(api.ActionSeparator + api.KeyTags).String(), refreshURL: ab.NewURLBuilder('g').AppendKVQuery("_c", "r").String(), withAuth: authz.WithAuth(), loginURL: loginoutBase.String(), logoutURL: loginoutBase.AppendKVQuery("logout", "").String(), searchURL: ab.NewURLBuilder('h').String(), } wui.observe(box.UpdateInfo{Box: mgr, Reason: box.OnReload, Zid: id.Invalid}) |
︙ | ︙ | |||
226 227 228 229 230 231 232 233 234 | return t, err } type simpleLink struct { Text string URL string } type baseData struct { | > > > > > > > > > > > > | | | | | | | | | | | | | | | | | | | < | | | | | | < < | | 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 | return t, err } type simpleLink struct { Text string URL string } type simpleLinks struct { Has bool Links []simpleLink } func createSimpleLinks(ls []simpleLink) simpleLinks { return simpleLinks{ Has: len(ls) > 0, Links: ls, } } type baseData struct { Lang string MetaHeader string CSSBaseURL string CSSUserURL string CSSRoleURL 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 NewZettelLinks simpleLinks SearchURL string QueryKeyQuery string Content string FooterHTML string DebugMode bool } func (wui *WebUI) makeBaseData(ctx context.Context, lang, title, roleCSSURL 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, "") } data.Lang = lang data.CSSBaseURL = wui.cssBaseURL data.CSSUserURL = wui.cssUserURL data.CSSRoleURL = roleCSSURL 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.NewZettelLinks = createSimpleLinks(wui.fetchNewTemplates(ctx, user)) data.SearchURL = wui.searchURL data.QueryKeyQuery = api.QueryKeyQuery data.FooterHTML = wui.rtConfig.Get(ctx, nil, config.KeyFooterHTML) data.DebugMode = wui.debug } func (wui *WebUI) getSimpleHTMLEncoder() *htmlGenerator { return createGenerator(wui, "") } |
︙ | ︙ |
Changes to www/changes.wiki.
1 2 3 | <title>Change Log</title> <a name="0_8"></a> | > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | 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 | <title>Change Log</title> <a name="0_9"></a> <h2>Changes for Version 0.9.0 (pending)</h2> <a name="0_8"></a> <h2>Changes for Version 0.8.0 (2022-10-20)</h2> * Remove support for tags within zettel content. Removes also property metadata keys <tt>all-tags</tt> and <tt>computed-tags</tt>. Deprecated in version 0.7.0. (breaking: zettelmarkup, api, webui) * Remove API endpoint <tt>/m</tt>, which retrieve aggregated (tags, roles) zettel identifier. Deprecated in version 0.7.0. (breaking: api) * Remove support for URL query parameter starting with an underscore. Deprecated in version 0.7.0. (breaking: api, webui) * Ignore HTML content by default, and allow HTML gradually by setting startup value <tt>insecure-html</tt>. (breaking: markup) * Endpoint <tt>/q</tt> returns list of full metadata, if no query action is specified. A HTTP call <tt>GET /z</tt> (retrieving metadata of all or some zettel) is now an alias for <tt>GET /q</tt>. (major: api) * Allow to create a zettel that acts as the new version of an existing zettel. Useful if you want to have access to older, outdated content. (minor: webui) * Allow transclusion to reference local image via URL. (minor: zettelmarkup, webui) * Add categories in RSS feed, based on zettel tags. (minor: api, webui) * Add support for creating an Atom 1.0 feed using a query action. (minor: api, webui) * Ignore entities with code point that is not allowed in HTML. (minor: zettelmarkup) * Enhance distribution of tag sizes when show a tag cloud. (minor: webui) * Warn user if zettelstore listens non-locally, but no authentication is enabled. (minor: server) * Fix error that a manual zettel deletion was not always detected. (bug: dirbox) * Some smaller bug fixes and improvements, to the software and to the documentation. <a name="0_7"></a> <h2>Changes for Version 0.7.1 (2022-09-18)</h2> * Produce a RSS feed compatible to Miniflux. (minor) * Make sure to always produce a pubdata in RSS feed. (bug) * Prefix search for data that looks like a zettel identifier may end with a <tt>0</tt>. (bug) * Fix glitch on manual zettel. (bug) <h2>Changes for Version 0.7.0 (2022-09-17)</h2> * Removes support for URL query parameter to search for metadata values, sorting, offset, and limit a zettel list. Deprecated in version 0.6.0 (breaking: api, webui) * Allow to search for the existence / non-existence of a metadata key with the "?" operator: <tt>key?</tt> and <tt>key!?</tt>. Previously, the ":" operator was used for this by specifying an empty search value. Now you can use the ":" operator to find empty / non-empty metadata values. If you |
︙ | ︙ |
Changes to www/download.wiki.
1 2 3 4 5 6 7 8 9 10 11 | <title>Download</title> <h1>Download of Zettelstore Software</h1> <h2>Foreword</h2> * Zettelstore is free/libre open source software, licensed under EUPL-1.2-or-later. * The software is provided as-is. * There is no guarantee that it will not damage your system. * However, it is in use by the main developer since March 2020 without any damage. * It may be useful for you. It is useful for me. * Take a look at the [https://zettelstore.de/manual/|manual] to know how to start and use it. <h2>ZIP-ped Executables</h2> | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <title>Download</title> <h1>Download of Zettelstore Software</h1> <h2>Foreword</h2> * Zettelstore is free/libre open source software, licensed under EUPL-1.2-or-later. * The software is provided as-is. * There is no guarantee that it will not damage your system. * However, it is in use by the main developer since March 2020 without any damage. * It may be useful for you. It is useful for me. * Take a look at the [https://zettelstore.de/manual/|manual] to know how to start and use it. <h2>ZIP-ped Executables</h2> Build: <code>v0.8.0</code> (2022-10-20). * [/uv/zettelstore-0.8.0-linux-amd64.zip|Linux] (amd64) * [/uv/zettelstore-0.8.0-linux-arm.zip|Linux] (arm6, e.g. Raspberry Pi) * [/uv/zettelstore-0.8.0-windows-amd64.zip|Windows] (amd64) * [/uv/zettelstore-0.8.0-darwin-amd64.zip|macOS] (amd64) * [/uv/zettelstore-0.8.0-darwin-arm64.zip|macOS] (arm64, aka Apple silicon) Unzip the appropriate file, install and execute Zettelstore according to the manual. <h2>Zettel for the manual</h2> As a starter, you can download the zettel for the manual [/uv/manual-0.8.0.zip|here]. Just unzip the contained files and put them into your zettel folder or configure a file box to read the zettel directly from the ZIP file. |
Changes to www/index.wiki.
︙ | ︙ | |||
20 21 22 23 24 25 26 | access Zettelstore via its API more easily, [https://zettelstore.de/contrib|Zettelstore Contrib] contains contributed software, which often connects to Zettelstore via its API. Some of the software packages may be experimental. [https://twitter.com/zettelstore|Stay tuned] … <hr> | | | | | | | | 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 | access Zettelstore via its API more easily, [https://zettelstore.de/contrib|Zettelstore Contrib] contains contributed software, which often connects to Zettelstore via its API. Some of the software packages may be experimental. [https://twitter.com/zettelstore|Stay tuned] … <hr> <h3>Latest Release: 0.8.0 (2022-10-20)</h3> * [./download.wiki|Download] * [./changes.wiki#0_8|Change summary] * [/timeline?p=v0.8.0&bt=v0.7.0&y=ci|Check-ins for version 0.8.0], [/vdiff?to=v0.8.0&from=v0.7.0|content diff] * [/timeline?df=v0.8.0&y=ci|Check-ins derived from the 0.8.0 release], [/vdiff?from=v0.8.0&to=trunk|content diff] * [./plan.wiki|Limitations and planned improvements] * [/timeline?t=release|Timeline of all past releases] <hr> <h2>Build instructions</h2> Just install [https://golang.org/dl/|Go] and some Go-based tools. Please read the [./build.md|instructions] for details. * [/dir?ci=trunk|Source code] * [/download|Download the source code] as a tarball or a ZIP file (you must [/login|login] as user "anonymous"). |