From cf524ecee4d4b2658056b95163354b2b90e61fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Fri, 19 Aug 2022 19:36:32 +0200 Subject: [PATCH 001/167] Add styling to link titles. --- static/graph_explorer.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/static/graph_explorer.html b/static/graph_explorer.html index ade81c3..26a8c1c 100644 --- a/static/graph_explorer.html +++ b/static/graph_explorer.html @@ -7,6 +7,12 @@ + From 768065165f71717a44683b03e489268eb8a9b1bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Fri, 19 Aug 2022 19:37:53 +0200 Subject: [PATCH 003/167] Reduce link arrow sizes. --- static/graph_explorer.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/static/graph_explorer.html b/static/graph_explorer.html index e7c68ce..abb6aee 100644 --- a/static/graph_explorer.html +++ b/static/graph_explorer.html @@ -86,15 +86,15 @@ function ForceGraph({ // Add arrowheads svg.append('defs').append('marker') .attr('id', 'arrowhead') - .attr('viewBox', '-0 -5 10 10') - .attr('refX', 13) + .attr('viewBox', '-0 -2.5 5 5') + .attr('refX', 10) .attr('refY', 0) .attr('orient', 'auto') - .attr('markerWidth', 13) - .attr('markerHeight', 13) + .attr('markerWidth', 7) + .attr('markerHeight', 7) .attr('xoverflow', 'visible') .append('svg:path') - .attr('d', 'M 0,-5 L 10 ,0 L 0,5') + .attr('d', 'M 0,-2.5 L 5 ,0 L 0,2.5') .attr('fill', '#999') .style('stroke','none'); From 399f00a54f2cc4d3ee6db51673e7c52f8b971087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Sat, 20 Aug 2022 13:59:15 +0200 Subject: [PATCH 004/167] Add index.html generation. --- scripts/generate.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/generate.py b/scripts/generate.py index 3a6d2f0..37b685b 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -20,6 +20,7 @@ EXTENSIONS = [ ] MIN_HIDDEN_HEADLINE_LEVEL = 2 +INDEX_ID = "ea48ec1d-f9d4-4fb7-b39a-faa7b6e2ba95" def load_all(top_dir_relative): top = os.path.abspath(top_dir_relative) @@ -156,6 +157,12 @@ def main(src_top, dest_top): f.write(as_document(render(headline, doc, headlineLevel=0))) files_generated += 1 + if headline.id == INDEX_ID: + index_endpath = os.path.join(dest_top, "index.html") + with open(index_endpath, "wt") as f: + f.write(as_document(render(headline, doc, headlineLevel=0))) + files_generated += 1 + # Update graph, replace document ids with headline ids for headline_data in graph.values(): for link in headline_data['links']: @@ -318,6 +325,7 @@ def as_document(html): return f""" + + {html} + + + """ diff --git a/scripts/search-server/server.go b/scripts/search-server/server.go index 37f04fa..2f6cbc1 100644 --- a/scripts/search-server/server.go +++ b/scripts/search-server/server.go @@ -49,7 +49,19 @@ func main() { }) }) + api.OPTIONS("/search", func(c *gin.Context) { + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Accept-Encoding, Authorization, accept, origin, Cache-Control, X-Requested-With") + c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") + + c.AbortWithStatus(204) + }) + api.GET("/search", func(c *gin.Context) { + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Accept-Encoding, Authorization, accept, origin, Cache-Control, X-Requested-With") + c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") + query := c.Query("q") stm, err := db.Prepare("SELECT note_id, title FROM note_search WHERE title LIKE ?") diff --git a/static/search-box.js b/static/search-box.js new file mode 100644 index 0000000..fe2e056 --- /dev/null +++ b/static/search-box.js @@ -0,0 +1,55 @@ +function _codigoparallevar_enable_search_box(selector, options) { + const element = document.querySelector(selector); + if ('placeholder' in options) { + element.setAttribute('placeholder', options.placeholder); + } + + // Prepare backdrop + const resultsBoxBackdrop = document.createElement('div'); + resultsBoxBackdrop.setAttribute('class', 'results-box-container hidden'); + const resultsBox = document.createElement('div'); + resultsBox.setAttribute('class', 'results-box'); + + resultsBoxBackdrop.appendChild(resultsBox); + document.body.appendChild(resultsBoxBackdrop); + + + const DEBOUNCE_TIME = 500; // Milliseconds + const MIN_LENGTH = 3; + const SEARCH_ENDPOINT = 'http://localhost:3001/api/search'; + + let debounceWaiter = null; + let currentQuery = null; + + let lastVal = null; + const doQuery = () => { + const val = element.value.trim(); + if ((val.length < MIN_LENGTH) || (val === lastVal)) { + return; + } + lastVal = val; + + const uri = SEARCH_ENDPOINT + '?q=' + encodeURIComponent(val); + let query = fetch(uri); + currentQuery = query; + query + .then(res => res.json()) + .then((body) => { + if (query !== currentQuery) { + console.log("Query out-raced 🤷"); + return; + } + + console.log('=>', body); + }); + }; + element.removeAttribute('disabled'); + + element.onkeyup = (ev) => { + if (debounceWaiter !== null) { + clearTimeout(debounceWaiter); + } + debounceWaiter = setTimeout(doQuery, DEBOUNCE_TIME); + + }; +} diff --git a/static/style.css b/static/style.css index aad771b..bf7f170 100644 --- a/static/style.css +++ b/static/style.css @@ -11,6 +11,28 @@ body { margin: 0 auto; } +body nav { + text-align: center; +} +body nav h1 { + text-align: center; + color: #000; + display: inline-block; +} +body nav input { + background-color: transparent; + color: #000; + border: none; + border-bottom: 1px solid #222; +} +.results-box-container { + z-index: 5; + position: absolute; + width: 100vw; + height: 100vh; + background-color: rgba(0,0,0,0.3); +} + @font-face { font-family: "Atkinson Hyperlegible"; src: url('./fonts/atkinson-hyperlegible/eot/Atkinson-Hyperlegible-Regular-102.eot'); @@ -115,7 +137,7 @@ li .tag::after { pre { overflow: auto; padding: 0.25ex; - box-shadow: 0px 2px 4px 2px rgba(0, 0, 0, 0.26); + box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.25); border-radius: 2px; } pre > code { @@ -212,6 +234,7 @@ tr.__table-separator { background-color: #111; color: #fafafe; } + h2 a { color: #fafafe; } @@ -233,6 +256,15 @@ tr.__table-separator { border-right: 1px solid #fff; } + /* Nav bar */ + body nav h1 { + color: #eee; + } + body nav input { + color: #ddd; + border-bottom: 1px solid #888; + } + /* Code blocks */ pre { padding: 0.5ex; From a616d903fb13073c1bf1a2d07bb620161a613c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Tue, 4 Oct 2022 23:55:57 +0200 Subject: [PATCH 033/167] Implement basic popup creation for note search. --- static/search-box.js | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/static/search-box.js b/static/search-box.js index fe2e056..96c2177 100644 --- a/static/search-box.js +++ b/static/search-box.js @@ -10,9 +10,31 @@ function _codigoparallevar_enable_search_box(selector, options) { const resultsBox = document.createElement('div'); resultsBox.setAttribute('class', 'results-box'); + // Results box contents + const innerSearchBox = document.createElement('input'); + innerSearchBox.setAttribute('type', 'text'); + innerSearchBox.setAttribute('placeholder', element.getAttribute('placeholder')); + resultsBox.appendChild(innerSearchBox); + + const resultsList = document.createElement('ul'); + resultsBox.appendChild(resultsList); + resultsBoxBackdrop.appendChild(resultsBox); document.body.appendChild(resultsBoxBackdrop); + // Popup cancellation + resultsBoxBackdrop.onclick = () => { + resultsBoxBackdrop.classList.add('hidden'); + }; + resultsBox.onclick = (ev) => { + ev.stopPropagation(); + }; + + // Element triggers popup + element.onfocus = () => { + resultsBoxBackdrop.classList.remove('hidden'); + innerSearchBox.focus(); + }; const DEBOUNCE_TIME = 500; // Milliseconds const MIN_LENGTH = 3; @@ -23,7 +45,7 @@ function _codigoparallevar_enable_search_box(selector, options) { let lastVal = null; const doQuery = () => { - const val = element.value.trim(); + const val = innerSearchBox.value.trim(); if ((val.length < MIN_LENGTH) || (val === lastVal)) { return; } @@ -45,11 +67,11 @@ function _codigoparallevar_enable_search_box(selector, options) { }; element.removeAttribute('disabled'); - element.onkeyup = (ev) => { + innerSearchBox.onkeyup = (ev) => { if (debounceWaiter !== null) { clearTimeout(debounceWaiter); } debounceWaiter = setTimeout(doQuery, DEBOUNCE_TIME); - }; } + From d81db05633b7a73889d57bef84fedbd288f39353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Mon, 17 Oct 2022 00:10:06 +0200 Subject: [PATCH 034/167] Show results on notes's search-box. --- scripts/generate.py | 4 +-- static/search-box.js | 27 +++++++++++++++++- static/style.css | 67 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/scripts/generate.py b/scripts/generate.py index 688aabf..2013e4c 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -483,12 +483,12 @@ def as_document(html, title): {html} - + """ diff --git a/static/search-box.js b/static/search-box.js index 96c2177..e2ebd80 100644 --- a/static/search-box.js +++ b/static/search-box.js @@ -34,6 +34,15 @@ function _codigoparallevar_enable_search_box(selector, options) { element.onfocus = () => { resultsBoxBackdrop.classList.remove('hidden'); innerSearchBox.focus(); + + const wasKeyDown = document.onkeydown; + document.onkeydown = (ev) => { + if (ev.key === 'Escape') { + resultsBoxBackdrop.classList.add('hidden'); + document.onkeydown = wasKeyDown; + ev.stopPropagation(); + } + }; }; const DEBOUNCE_TIME = 500; // Milliseconds @@ -62,7 +71,20 @@ function _codigoparallevar_enable_search_box(selector, options) { return; } - console.log('=>', body); + resultsList.innerHTML = ''; + for (const note of body.results.notes) { + const resultCard = document.createElement('li'); + + const resultContents = document.createElement('a'); + resultContents.setAttribute('href', './' + note.id + '.node.html'); + + const resultTitle = document.createElement('h2'); + resultTitle.innerText = note.title; + + resultContents.appendChild(resultTitle); + resultCard.appendChild(resultContents); + resultsList.appendChild(resultCard); + } }); }; element.removeAttribute('disabled'); @@ -75,3 +97,6 @@ function _codigoparallevar_enable_search_box(selector, options) { }; } +// // TODO: Remove this when dev is done +// _codigoparallevar_enable_search_box('#searchbox', {placeholder: 'Search...'}) +// document.querySelector('#searchbox').focus() diff --git a/static/style.css b/static/style.css index bf7f170..aff1b07 100644 --- a/static/style.css +++ b/static/style.css @@ -19,6 +19,8 @@ body nav h1 { color: #000; display: inline-block; } + +/* Search box */ body nav input { background-color: transparent; color: #000; @@ -27,10 +29,56 @@ body nav input { } .results-box-container { z-index: 5; - position: absolute; + position: fixed; width: 100vw; height: 100vh; background-color: rgba(0,0,0,0.3); + top: 0; + left: 0; +} +.results-box-container.hidden { + display: none; +} +.results-box { + min-width: 50vw; + max-width: 90vw; + min-height: 20ex; + max-height: 90vh; + overflow: auto; + border-radius: 4px; + margin: 2rem auto; + box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.25); + background-color: #fff; +} + +.results-box-container .results-box input { + width: 90%; + margin: 0 auto; + padding-top: 2ex; + display: block; + background-color: transparent; + color: #000; + border: none; + border-bottom: 1px solid #444; +} + +/* Search box results */ +.results-box ul { + list-style: none; + padding: 0; +} +.results-box ul li { + padding: 0.25ex; + margin: 1ex; + border-radius: 4px; + box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.25); +} + +.results-box ul li h2 { + font-size: 125%; + padding: 1.25ex; + display: block; + margin: 0; } @font-face { @@ -239,7 +287,7 @@ tr.__table-separator { color: #fafafe; } a { - color: #94dcff; + color: #00fdf2; } h1,h2,h3,h4,h5,h6 { color: #f7da4a; @@ -264,6 +312,21 @@ tr.__table-separator { color: #ddd; border-bottom: 1px solid #888; } + .results-box-container .results-box input { + color: #ddd; + border-bottom: 1px solid #888; + } + .results-box { + box-shadow: none; + background-color: #262826; + } + .results-box ul li { + background-color: #303330; + box-shadow: none; + } + .results-box ul li h2 { + color: white; + } /* Code blocks */ pre { From 7ddf926fa7cb00fcfa59fb8d5bfb7b27e7038f14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Mon, 17 Oct 2022 00:10:40 +0200 Subject: [PATCH 035/167] Lighten dark-mode links on homepage. --- static/homepage.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/homepage.html b/static/homepage.html index 9c53ac1..ade629d 100644 --- a/static/homepage.html +++ b/static/homepage.html @@ -52,7 +52,7 @@ color: #fafafe; } a { - color: #66f; + color: #00fdf2; } #social a { border-color: #fff; From b214a8148a605a453b4bd5971ae6606ed75428c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Tue, 18 Oct 2022 01:16:14 +0200 Subject: [PATCH 036/167] Prepare for note deployment. --- scripts/generate.py | 12 ++++++++++-- scripts/upload-homepage.sh | 8 -------- scripts/upload.sh | 22 ++++++++++++++++++++++ static/search-box.js | 2 +- 4 files changed, 33 insertions(+), 11 deletions(-) delete mode 100644 scripts/upload-homepage.sh create mode 100644 scripts/upload.sh diff --git a/scripts/generate.py b/scripts/generate.py index 2013e4c..fa7a12f 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -24,6 +24,10 @@ EXTENSIONS = [ ".org.txt", ] +WATCH = True +if os.getenv('WATCH_AND_REBUILD', '1') == '0': + WATCH = False + MIN_HIDDEN_HEADLINE_LEVEL = 2 INDEX_ID = "ea48ec1d-f9d4-4fb7-b39a-faa7b6e2ba95" SITE_NAME = "Código para llevar" @@ -237,8 +241,12 @@ def main(src_top, dest_top): db = create_db(os.path.join(dest_top, 'db.sqlite3')) docs = regen_all(src_top, dest_top, db=db) - logging.info("Initial load completed in {:.2f}s".format(time.time() - t0)) + if not WATCH: + logging.info("Build completed in {:.2f}s".format(time.time() - t0)) + return 0 + + logging.info("Initial load completed in {:.2f}s".format(time.time() - t0)) ## Updating for event in notifier.event_gen(yield_nones=False): (ev, types, directory, file) = event @@ -506,4 +514,4 @@ if __name__ == "__main__": exit(0) logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s") - main(sys.argv[1], sys.argv[2]) + exit(main(sys.argv[1], sys.argv[2])) diff --git a/scripts/upload-homepage.sh b/scripts/upload-homepage.sh deleted file mode 100644 index 8d475ef..0000000 --- a/scripts/upload-homepage.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -eu - -cd "$(dirname "$0")/.." - -cd static -scp homepage.html root@codigoparallevar.com:/mnt/vols/misc/codigoparallevar/index.html diff --git a/scripts/upload.sh b/scripts/upload.sh new file mode 100644 index 0000000..ca0a213 --- /dev/null +++ b/scripts/upload.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -eu + +cd "$(dirname "$0")/.." + +# Upload homepage +cd static +scp homepage.html root@codigoparallevar.com:/mnt/vols/misc/codigoparallevar/index.html + +# Build notes +cd ../scripts +WATCH_AND_REBUILD=0 python3 generate.py ~/.logs/brain ../_gen/notes + +# Upload notes +cd ../_gen +rsync -HPaz static/ --delete-after --exclude='*.html' root@codigoparallevar.com:/mnt/vols/misc/codigoparallevar/static/ +rsync -HPaz notes/ --delete-after --exclude='xapian' --exclude='*.sqlite3' root@codigoparallevar.com:/mnt/vols/misc/codigoparallevar/notes/ +rsync -HPaz notes/db.sqlite3 root@codigoparallevar.com:/mnt/vols/misc/codigoparallevar-api/ + +# Restart API server +ssh root@codigoparallevar.com docker restart notes-api-server diff --git a/static/search-box.js b/static/search-box.js index e2ebd80..54e518f 100644 --- a/static/search-box.js +++ b/static/search-box.js @@ -47,7 +47,7 @@ function _codigoparallevar_enable_search_box(selector, options) { const DEBOUNCE_TIME = 500; // Milliseconds const MIN_LENGTH = 3; - const SEARCH_ENDPOINT = 'http://localhost:3001/api/search'; + const SEARCH_ENDPOINT = 'https://api.codigoparallevar.com/api/search'; let debounceWaiter = null; let currentQuery = null; From df7f65fa004129a619226a5d11062c49788c35d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Tue, 18 Oct 2022 01:16:30 +0200 Subject: [PATCH 037/167] Test different background color. --- static/homepage.html | 2 +- static/style.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/static/homepage.html b/static/homepage.html index ade629d..fa748f0 100644 --- a/static/homepage.html +++ b/static/homepage.html @@ -45,7 +45,7 @@ } @media (prefers-color-scheme: dark) { html { - background-color: #0f110e; + background-color: #1d1f21; color: #fafafe; } h2 a { diff --git a/static/style.css b/static/style.css index aff1b07..8e79272 100644 --- a/static/style.css +++ b/static/style.css @@ -279,7 +279,7 @@ tr.__table-separator { /* Dark mode. */ @media (prefers-color-scheme: dark) { html { - background-color: #111; + background-color: #1d1f21; color: #fafafe; } From a313597dcf24e4b0ee142a9e259ce2fea29d6ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Tue, 18 Oct 2022 01:17:11 +0200 Subject: [PATCH 038/167] Mark tags as [tag]. --- static/style.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/static/style.css b/static/style.css index 8e79272..243ea98 100644 --- a/static/style.css +++ b/static/style.css @@ -171,7 +171,12 @@ h1.title .tags .tag { margin-left: 2px; border-radius: 5px; } - +h1.title .tags .tag:before { + content: '['; +} +h1.title .tags .tag:after { + content: ']'; +} /* Lists */ li .tag { font-weight: bold; From 7bad44cfb6334c576df0381337eeb56095a56298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Tue, 18 Oct 2022 22:47:04 +0200 Subject: [PATCH 039/167] Redirect from subnote to parent note. --- scripts/generate.py | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/scripts/generate.py b/scripts/generate.py index fa7a12f..62d39de 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -139,8 +139,8 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): with open(endpath, "wt") as f: doc_to_headline_remapping['id:' + doc.id] = 'id:' + main_headline.id - f.write(as_document(render(main_headline, doc, headlineLevel=0), - org_rw.token_list_to_plaintext(main_headline.title.contents))) + f.write(render_as_document(main_headline, doc, headlineLevel=0, + title=org_rw.token_list_to_plaintext(main_headline.title.contents))) files_generated += 1 elif doc.id is not None: logging.error("Cannot render document from id: {}. {} headlines {} related".format( @@ -201,15 +201,15 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): # Render HTML with open(endpath, "wt") as f: - f.write(as_document(render(headline, doc, headlineLevel=0), - org_rw.token_list_to_plaintext(headline.title.contents))) + f.write(render_as_document(headline, doc, headlineLevel=0, + title=org_rw.token_list_to_plaintext(headline.title.contents))) files_generated += 1 if headline.id == INDEX_ID: index_endpath = os.path.join(dest_top, "index.html") with open(index_endpath, "wt") as f: - f.write(as_document(render(headline, doc, headlineLevel=0), - org_rw.token_list_to_plaintext(headline.title.contents))) + f.write(render_as_document(headline, doc, headlineLevel=0, + title=org_rw.token_list_to_plaintext(headline.title.contents))) files_generated += 1 # Update graph, replace document ids with headline ids @@ -418,6 +418,30 @@ def render_inline(tree, f): return ''.join(acc) +def render_as_document(headline, doc, headlineLevel, title): + if isinstance(headline.parent, org_rw.Headline): + topLevelHeadline = headline.parent + while isinstance(topLevelHeadline.parent, org_rw.Headline): + topLevelHeadline = topLevelHeadline.parent + return f""" + + + + {title} @ {SITE_NAME} + + + + + + Sending you to the main note... [{org_rw.token_list_to_plaintext(topLevelHeadline.title.contents)}] + + + """ + else: + return as_document(render(headline, doc, headlineLevel), title) + def render(headline, doc, headlineLevel): try: dom = headline.as_dom() From d71c28d1e80290231760307347efe012c2dcda16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Tue, 18 Oct 2022 23:13:21 +0200 Subject: [PATCH 040/167] Show top-level title in seach, hide to-do's. --- scripts/generate.py | 11 +++++++++-- scripts/search-server/server.go | 16 ++++++++++++++-- static/search-box.js | 6 +++++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/scripts/generate.py b/scripts/generate.py index 62d39de..5aca805 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -55,7 +55,7 @@ def create_db(path): os.unlink(path) db = sqlite3.connect(path) - db.execute('CREATE VIRTUAL TABLE note_search USING fts5(note_id, title, body);') + db.execute('CREATE VIRTUAL TABLE note_search USING fts5(note_id, title, body, top_level_title, is_done, is_todo);') return db def load_all(top_dir_relative): @@ -191,12 +191,19 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): "depth": headline.depth, } + topLevelHeadline = headline + while isinstance(topLevelHeadline.parent, org_rw.Headline): + topLevelHeadline = topLevelHeadline.parent + # Save for full-text-search - cur.execute('''INSERT INTO note_search(note_id, title, body) VALUES (?, ?, ?);''', + cur.execute('''INSERT INTO note_search(note_id, title, body, top_level_title, is_done, is_todo) VALUES (?, ?, ?, ?, ?, ?);''', ( headline.id, headline.title.get_text(), ''.join(headline.get_contents('raw')), + topLevelHeadline.title.get_text(), + headline.is_done, + headline.is_todo, )) # Render HTML diff --git a/scripts/search-server/server.go b/scripts/search-server/server.go index 2f6cbc1..86a7eb0 100644 --- a/scripts/search-server/server.go +++ b/scripts/search-server/server.go @@ -64,7 +64,7 @@ func main() { query := c.Query("q") - stm, err := db.Prepare("SELECT note_id, title FROM note_search WHERE title LIKE ?") + stm, err := db.Prepare("SELECT note_id, title, top_level_title, is_done, is_todo FROM note_search WHERE title LIKE ?") if err != nil { log.Fatal(err) @@ -90,8 +90,17 @@ func main() { for rows.Next() { var note_id string var note_title string + var note_top_level_title string + var note_is_done string + var note_is_todo string - err = rows.Scan(¬e_id, ¬e_title) + err = rows.Scan( + ¬e_id, + ¬e_title, + ¬e_top_level_title, + ¬e_is_done, + ¬e_is_todo, + ) if err != nil { log.Fatal(err) c.JSON(500, gin.H{ @@ -104,6 +113,9 @@ func main() { item := make(map[string]string) item["id"] = note_id item["title"] = note_title + item["top_level_title"] = note_top_level_title + item["is_done"] = note_is_done + item["is_todo"] = note_is_todo results = append(results, item) } diff --git a/static/search-box.js b/static/search-box.js index 54e518f..13d9f14 100644 --- a/static/search-box.js +++ b/static/search-box.js @@ -73,13 +73,17 @@ function _codigoparallevar_enable_search_box(selector, options) { resultsList.innerHTML = ''; for (const note of body.results.notes) { + if (note.is_todo === "1") { + return; + } + const resultCard = document.createElement('li'); const resultContents = document.createElement('a'); resultContents.setAttribute('href', './' + note.id + '.node.html'); const resultTitle = document.createElement('h2'); - resultTitle.innerText = note.title; + resultTitle.innerText = `${note.title} (${note.top_level_title})`; resultContents.appendChild(resultTitle); resultCard.appendChild(resultContents); From de92f74867ab75c2a78f180b34c4965c2022771f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Tue, 18 Oct 2022 23:18:57 +0200 Subject: [PATCH 041/167] Link to notes index from title. --- scripts/generate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/generate.py b/scripts/generate.py index 5aca805..31b051b 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -440,7 +440,7 @@ def render_as_document(headline, doc, headlineLevel, title): Sending you to the main note... [{org_rw.token_list_to_plaintext(topLevelHeadline.title.contents)}] @@ -521,7 +521,7 @@ def as_document(html, title): {html} From bcfd8b1d934699b9b33c57329c86671e3df131b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Tue, 18 Oct 2022 23:29:12 +0200 Subject: [PATCH 042/167] Reduce unused height on headlines. --- static/style.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/static/style.css b/static/style.css index 243ea98..3053cf5 100644 --- a/static/style.css +++ b/static/style.css @@ -98,7 +98,6 @@ body nav input { } .node .node { - margin: 0.5em 0ex 0.5em 0.25ex; padding: 1ex 0 1ex 1ex; border-left: 1px dashed #2c3e50; } @@ -115,6 +114,10 @@ body nav input { content: "🮦"; } +.node .title { + margin: 0; +} + /* Inhibit

tags inside inlined items */ /* TODO: Remove need for this on generator */ .item p { From fcd9854a17dab0cf9c4aee844f92432671563f74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Thu, 20 Oct 2022 23:44:35 +0200 Subject: [PATCH 043/167] Disable collapsing top-level headline. --- scripts/generate.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/generate.py b/scripts/generate.py index 31b051b..e343401 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -445,7 +445,7 @@ def render_as_document(headline, doc, headlineLevel, title): Sending you to the main note... [{org_rw.token_list_to_plaintext(topLevelHeadline.title.contents)}] - """ + """ else: return as_document(render(headline, doc, headlineLevel), title) @@ -482,13 +482,16 @@ def render(headline, doc, headlineLevel): # display_state = 'expanded' display_state = 'expanded' + title = render_inline(headline.title, render_tag) + + if headlineLevel > 0: + title = f"{title}" + return f"""

{state} - - {render_inline(headline.title, render_tag)} - + {title} {tags}

From f66d69776bbac4087a1e216d6cc9bb384bcfee89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Fri, 21 Oct 2022 00:23:50 +0200 Subject: [PATCH 044/167] Create directory before filling it in. --- scripts/generate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/generate.py b/scripts/generate.py index e343401..b6e0bc4 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -246,6 +246,7 @@ def main(src_top, dest_top): ## Initial load t0 = time.time() + os.makedirs(dest_top, exist_ok=True) db = create_db(os.path.join(dest_top, 'db.sqlite3')) docs = regen_all(src_top, dest_top, db=db) From a18c94dca0b5a4223eca54048d6706d28c90d952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Fri, 21 Oct 2022 20:17:41 +0200 Subject: [PATCH 045/167] Decide on SEARCH_ENDPOINT based on current URL. --- static/search-box.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/static/search-box.js b/static/search-box.js index 13d9f14..50b11c5 100644 --- a/static/search-box.js +++ b/static/search-box.js @@ -47,7 +47,10 @@ function _codigoparallevar_enable_search_box(selector, options) { const DEBOUNCE_TIME = 500; // Milliseconds const MIN_LENGTH = 3; - const SEARCH_ENDPOINT = 'https://api.codigoparallevar.com/api/search'; + const SEARCH_ENDPOINT = (window.location.host.startsWith('localhost') + ? 'http://localhost:3001/api/search' + : 'https://api.codigoparallevar.com/api/search' + ); let debounceWaiter = null; let currentQuery = null; From 8dd624d339f877f2e5d3e328082885501b69a91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Fri, 21 Oct 2022 20:18:01 +0200 Subject: [PATCH 046/167] Cleanup old notes on upload before building new ones. --- scripts/upload.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/upload.sh b/scripts/upload.sh index ca0a213..482106c 100644 --- a/scripts/upload.sh +++ b/scripts/upload.sh @@ -10,6 +10,7 @@ scp homepage.html root@codigoparallevar.com:/mnt/vols/misc/codigoparallevar/inde # Build notes cd ../scripts +rm -Rf ../_gen/notes WATCH_AND_REBUILD=0 python3 generate.py ~/.logs/brain ../_gen/notes # Upload notes From ce8fd431b63809180c9cb5458bcc29e2145d9686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Sun, 23 Oct 2022 18:22:05 +0200 Subject: [PATCH 047/167] Allow rendering of links that require graph knowledge. - Fix rendering of `./filename.org` links. --- scripts/generate.py | 255 +++++++++++++++++++++++++------------------- 1 file changed, 147 insertions(+), 108 deletions(-) diff --git a/scripts/generate.py b/scripts/generate.py index b6e0bc4..48d803f 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -92,12 +92,19 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): doc_to_headline_remapping = {} os.makedirs(dest_top, exist_ok=True) - graph = {} + + ## Build headline list + # This includes a virtual headline for ID-referenced documents. + all_headlines = [] + main_headlines_by_path = {} for doc in docs: relpath = os.path.relpath(doc.path, src_top) changed = False headlines = list(doc.getAllHeadlines()) related = None + if not relpath.startswith("public/"): + # print("Skip:", relpath) + continue i = len(headlines) while i > 0: @@ -123,10 +130,7 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): print("Updated", relpath) save_changes(doc) - if not relpath.startswith("public/"): - # print("Skip:", relpath) - continue - + all_headlines.extend(headlines) main_headline = None topHeadlines = doc.getTopHeadlines() @@ -134,14 +138,10 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): or (len(topHeadlines) == 2 and related is not None)): main_headline = [h for h in topHeadlines if h != related][0] + main_headlines_by_path[doc.path] = main_headline if doc.id is not None: - endpath = os.path.join(dest_top, doc.id + ".node.html") - with open(endpath, "wt") as f: - doc_to_headline_remapping['id:' + doc.id] = 'id:' + main_headline.id - - f.write(render_as_document(main_headline, doc, headlineLevel=0, - title=org_rw.token_list_to_plaintext(main_headline.title.contents))) - files_generated += 1 + doc_to_headline_remapping['id:' + doc.id] = 'id:' + main_headline.id + files_generated += 1 elif doc.id is not None: logging.error("Cannot render document from id: {}. {} headlines {} related".format( relpath, @@ -149,75 +149,62 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): 'with' if related is not None else 'without' )) - for headline in headlines: - endpath = os.path.join(dest_top, headline.id + ".node.html") + graph = {} + # Build graph + for headline in all_headlines: + links = [] + headline_links = list(headline.get_links()) + if headline == main_headline and related is not None: + headline_links.extend(list(related.get_links())) - links = [] - headline_links = list(headline.get_links()) - if headline == main_headline and related is not None: - headline_links.extend(list(related.get_links())) + for l in headline_links: + if l.value.startswith('http://') or l.value.startswith('https://'): + pass # Ignore for now, external URL + elif l.value.startswith('id:'): + links.append({'target': l.value}) + elif l.value.startswith('attachment:'): + pass # Ignore, attachment + elif l.value.startswith('file:'): + pass # Ignore, attachment + elif l.value.startswith('notmuch:'): + pass # Ignore, mail + elif l.value.startswith('orgit-rev:'): + pass # Ignore, mail + elif l.value.startswith('*'): + pass # Ignore, internal + elif not ':' in l.value.split()[0]: + pass # Ignore, internal + elif l.value.startswith('./'): + pass # TODO: Properly handle + else: + logging.warning('On document {}, unknown link to {}'.format(doc.path, l.value)) - for l in headline_links: - if l.value.startswith('http://') or l.value.startswith('https://'): - pass # Ignore for now, external URL - elif l.value.startswith('id:'): - links.append({'target': l.value}) - elif l.value.startswith('attachment:'): - pass # Ignore, attachment - elif l.value.startswith('file:'): - pass # Ignore, attachment - elif l.value.startswith('notmuch:'): - pass # Ignore, mail - elif l.value.startswith('orgit-rev:'): - pass # Ignore, mail - elif l.value.startswith('*'): - pass # Ignore, internal - elif not ':' in l.value.split()[0]: - pass # Ignore, internal - elif l.value.startswith('./'): - pass # TODO: Properly handle - else: - logging.warning('On document {}, unknown link to {}'.format(doc.path, l.value)) + if headline.parent: + if isinstance(headline.parent, org_rw.Headline): + links.append({ + "target": headline.parent.id, + "relation": "in" + }) + graph[headline.id] = { + "title": org_rw.org_rw.token_list_to_plaintext(headline.title.contents).strip(), + "links": links, + "depth": headline.depth, + } - if headline.parent: - if isinstance(headline.parent, org_rw.Headline): - links.append({ - "target": headline.parent.id, - "relation": "in" - }) - graph[headline.id] = { - "title": org_rw.org_rw.token_list_to_plaintext(headline.title.contents).strip(), - "links": links, - "depth": headline.depth, - } + topLevelHeadline = headline + while isinstance(topLevelHeadline.parent, org_rw.Headline): + topLevelHeadline = topLevelHeadline.parent - topLevelHeadline = headline - while isinstance(topLevelHeadline.parent, org_rw.Headline): - topLevelHeadline = topLevelHeadline.parent - - # Save for full-text-search - cur.execute('''INSERT INTO note_search(note_id, title, body, top_level_title, is_done, is_todo) VALUES (?, ?, ?, ?, ?, ?);''', - ( - headline.id, - headline.title.get_text(), - ''.join(headline.get_contents('raw')), - topLevelHeadline.title.get_text(), - headline.is_done, - headline.is_todo, - )) - - # Render HTML - with open(endpath, "wt") as f: - f.write(render_as_document(headline, doc, headlineLevel=0, - title=org_rw.token_list_to_plaintext(headline.title.contents))) - files_generated += 1 - - if headline.id == INDEX_ID: - index_endpath = os.path.join(dest_top, "index.html") - with open(index_endpath, "wt") as f: - f.write(render_as_document(headline, doc, headlineLevel=0, - title=org_rw.token_list_to_plaintext(headline.title.contents))) - files_generated += 1 + # Save for full-text-search + cur.execute('''INSERT INTO note_search(note_id, title, body, top_level_title, is_done, is_todo) VALUES (?, ?, ?, ?, ?, ?);''', + ( + headline.id, + headline.title.get_text(), + ''.join(headline.get_contents('raw')), + topLevelHeadline.title.get_text(), + headline.is_done, + headline.is_todo, + )) # Update graph, replace document ids with headline ids for headline_data in graph.values(): @@ -225,6 +212,34 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): if link['target'] in doc_to_headline_remapping: link['target'] = doc_to_headline_remapping[link['target']] + # Render docs after we've built the graph + # Render main headlines + full_graph_info = { "nodes": graph, "main_headlines": main_headlines_by_path } + for _docpath, main_headline in main_headlines_by_path.items(): + if main_headline.doc.id: + endpath = os.path.join(dest_top, main_headline.doc.id + ".node.html") + with open(endpath, "wt") as f: + f.write(render_as_document(main_headline, main_headline.doc, headlineLevel=0, graph=full_graph_info, + title=org_rw.token_list_to_plaintext(main_headline.title.contents))) + + + # Render all headlines + for headline in all_headlines: + endpath = os.path.join(dest_top, headline.id + ".node.html") + + # Render HTML + with open(endpath, "wt") as f: + f.write(render_as_document(headline, headline.doc, headlineLevel=0, graph=full_graph_info, + title=org_rw.token_list_to_plaintext(headline.title.contents))) + files_generated += 1 + + if headline.id == INDEX_ID: + index_endpath = os.path.join(dest_top, "index.html") + with open(index_endpath, "wt") as f: + f.write(render_as_document(headline, headline.doc, headlineLevel=0, graph=full_graph_info, + title=org_rw.token_list_to_plaintext(headline.title.contents))) + files_generated += 1 + # Output graph files graphpath = os.path.join(dest_top, "graph.json") graph_explorer_path = os.path.join(dest_top, "graph.html") @@ -297,29 +312,29 @@ def print_element(element, indentation, headline): print_tree(element, indentation, headline) -def render_property_drawer(element, acc): +def render_property_drawer(element, acc, headline, graph): pass -def render_logbook_drawer(element, acc): +def render_logbook_drawer(element, acc, headline, graph): pass -def render_property_node(element, acc): +def render_property_node(element, acc, headline, graph): pass -def render_list_group(element, acc): +def render_list_group(element, acc, headline, graph): acc.append("
    ") - render_tree(element.children, acc) + render_tree(element.children, acc, headline, graph) acc.append("
") -def render_table(element, acc): +def render_table(element, acc, graph, headline): acc.append("") - render_tree(element.children, acc) + render_tree(element.children, acc, headline, graph) acc.append("
") -def render_table_row(element, acc): +def render_table_row(element, acc, headline, graph): acc.append("") for cell in element.cells: acc.append("") @@ -327,22 +342,22 @@ def render_table_row(element, acc): acc.append("") acc.append("") -def render_table_separator_row(element, acc): +def render_table_separator_row(element, acc, headline, graph): acc.append("") -def render_list_item(element, acc): +def render_list_item(element, acc, headline, graph): acc.append("
  • ") if element.tag is not None: acc.append("") - render_text_tokens(element.tag, acc) + render_text_tokens(element.tag, acc, headline, graph) acc.append("") acc.append("") - render_text_tokens(element.content, acc) + render_text_tokens(element.content, acc, headline, graph) acc.append("
  • ") -def render_code_block(element, acc): +def render_code_block(element, acc, headline, graph): acc.append('
    '.format(element.subtype.lower()))
         content = html.escape(element.lines)
     
    @@ -360,23 +375,23 @@ def render_code_block(element, acc):
         acc.append('\n'.join(content_lines))
         acc.append('
    ') -def render_results_block(element, acc): +def render_results_block(element, acc, headline, graph): # TODO: # acc.append('
    ')
         # render_tree(element.children, acc)
         # acc.append('
    ') pass -def render_org_text(element, acc): +def render_org_text(element, acc, headline, graph): as_dom = org_rw.text_to_dom(element.contents, element) - render_text_tokens(as_dom, acc) + render_text_tokens(as_dom, acc, headline, graph) -def render_text(element, acc): +def render_text(element, acc, headline, graph): acc.append('
    ') - render_text_tokens(element.content, acc) + render_text_tokens(element.content, acc, headline, graph) acc.append('
    ') -def render_text_tokens(tokens, acc): +def render_text_tokens(tokens, acc, headline, graph): acc.append('

    ') for chunk in tokens: if isinstance(chunk, str): @@ -386,6 +401,30 @@ def render_text_tokens(tokens, acc): link_target = chunk.value if link_target.startswith('id:'): link_target = './' + link_target[3:] + '.node.html' + elif link_target.startswith('./') or link_target.startswith('../'): + if '::' in link_target: + logging.warn('Not implemented headline links to other files. Used on {}'.format(link_target)) + + else: + target_path = os.path.abspath(os.path.join(os.path.dirname(headline.doc.path), link_target)) + if target_path not in graph['main_headlines']: + logging.warn('Link to doc not in graph: {}'.format(target_path)) + else: + link_target = './' + graph['main_headlines'][target_path].id + '.node.html' + elif link_target.startswith('attachment:'): + logging.warn('Not implemented `attachment:` links. Used on {}'.format(link_target)) + elif link_target.startswith('git://'): + logging.warn('Not implemented `git://`. Used on {}'.format(link_target)) + elif link_target.startswith('* '): + logging.warn('Not implemented `* Headline` links. Used on {}'.format(link_target)) + else: + if not ( + link_target.startswith('https://') + or link_target.startswith('http://') + or link_target.startswith('/') + ): + raise NotImplementedError('Unknown link type: {}' + .format(link_target)) description = chunk.description if description is None: description = chunk.value @@ -399,7 +438,7 @@ def render_text_tokens(tokens, acc): acc.append('

    ') -def render_tag(element, acc): +def render_tag(element, acc, headline, graph): return { dom.PropertyDrawerNode: render_property_drawer, dom.LogbookDrawerNode: render_logbook_drawer, @@ -413,20 +452,20 @@ def render_tag(element, acc): dom.Text: render_text, dom.ResultsDrawerNode: render_results_block, org_rw.Text: render_org_text, - }[type(element)](element, acc) + }[type(element)](element, acc, headline, graph) -def render_tree(tree, acc): +def render_tree(tree, acc, headline, graph): for element in tree: - render_tag(element, acc) + render_tag(element, acc, headline, graph) -def render_inline(tree, f): +def render_inline(tree, f, headline, graph): acc = [] - f(tree, acc) + f(tree, acc, headline, graph) return ''.join(acc) -def render_as_document(headline, doc, headlineLevel, title): +def render_as_document(headline, doc, headlineLevel, graph, title): if isinstance(headline.parent, org_rw.Headline): topLevelHeadline = headline.parent while isinstance(topLevelHeadline.parent, org_rw.Headline): @@ -448,9 +487,9 @@ def render_as_document(headline, doc, headlineLevel, title): """ else: - return as_document(render(headline, doc, headlineLevel), title) + return as_document(render(headline, doc, graph=graph, headlineLevel=headlineLevel), title) -def render(headline, doc, headlineLevel): +def render(headline, doc, graph, headlineLevel): try: dom = headline.as_dom() except: @@ -459,9 +498,9 @@ def render(headline, doc, headlineLevel): print_tree(dom, indentation=2, headline=headline) content = [] - render_tree(dom, content) + render_tree(dom, content, headline, graph) for child in headline.children: - content.append(render(child, doc, headlineLevel=headlineLevel+1)) + content.append(render(child, doc, headlineLevel=headlineLevel+1, graph=graph)) if headline.state is None: state = "" @@ -483,7 +522,7 @@ def render(headline, doc, headlineLevel): # display_state = 'expanded' display_state = 'expanded' - title = render_inline(headline.title, render_tag) + title = render_inline(headline.title, render_tag, headline, graph) if headlineLevel > 0: title = f"{title}" From 4d3997bce1d308c6288565bbca71ae93d60ecd53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Sun, 23 Oct 2022 18:28:08 +0200 Subject: [PATCH 048/167] Render `* Target` links. --- scripts/generate.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/scripts/generate.py b/scripts/generate.py index 48d803f..14c2043 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -288,6 +288,13 @@ def main(src_top, dest_top): continue logging.info("Updated all in {:.2f}s".format(time.time() - t0)) +def get_headline_with_name(target_name, doc): + target_name = target_name.strip() + for headline in doc.getAllHeadlines(): + if headline.title.get_text().strip() == target_name: + return headline + + return None def print_tree(tree, indentation=0, headline=None): # if headline and headline.id != INDEX_ID: @@ -416,7 +423,11 @@ def render_text_tokens(tokens, acc, headline, graph): elif link_target.startswith('git://'): logging.warn('Not implemented `git://`. Used on {}'.format(link_target)) elif link_target.startswith('* '): - logging.warn('Not implemented `* Headline` links. Used on {}'.format(link_target)) + target_headline = get_headline_with_name(link_target.lstrip('* '), headline.doc) + if target_headline is None: + logging.warn('No headline found corresponding to {}. On file {}'.format(link_target, headline.doc.path)) + else: + link_target = './' + target_headline.id + '.node.html' else: if not ( link_target.startswith('https://') From 3e9f323b56f844cbcdd748e714a8cd0f639629cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Sun, 23 Oct 2022 18:33:00 +0200 Subject: [PATCH 049/167] Remove reference to `git://` links. --- scripts/generate.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/generate.py b/scripts/generate.py index 14c2043..1833389 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -420,8 +420,6 @@ def render_text_tokens(tokens, acc, headline, graph): link_target = './' + graph['main_headlines'][target_path].id + '.node.html' elif link_target.startswith('attachment:'): logging.warn('Not implemented `attachment:` links. Used on {}'.format(link_target)) - elif link_target.startswith('git://'): - logging.warn('Not implemented `git://`. Used on {}'.format(link_target)) elif link_target.startswith('* '): target_headline = get_headline_with_name(link_target.lstrip('* '), headline.doc) if target_headline is None: From a6607ba0f35872385fb3f64a651150010f440d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Sun, 23 Oct 2022 21:16:22 +0200 Subject: [PATCH 050/167] WIP: Support bare links (no []). --- scripts/generate.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/scripts/generate.py b/scripts/generate.py index 1833389..fc40371 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -10,6 +10,7 @@ import sys import uuid from datetime import datetime import traceback +import re import inotify.adapters @@ -43,6 +44,8 @@ MONITORED_EVENT_TYPES = ( 'IN_MOVE_SELF', ) +WHITESPACE_RE = re.compile(r'\s') + ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) STATIC_PATH = os.path.join(ROOT_DIR, 'static') @@ -402,8 +405,29 @@ def render_text_tokens(tokens, acc, headline, graph): acc.append('

    ') for chunk in tokens: if isinstance(chunk, str): - lines = chunk.replace('\n\n', '

    ') - acc.append('{}'.format(lines)) + lines = chunk.split('\n\n') + contents = [] + for line in lines: + line_chunks = [] + for word in WHITESPACE_RE.split(line): + if ':/' in word and not (word.startswith('org-protocol://')): + if not (word.startswith('http://') + or word.startswith('https://') + or word.startswith('ftp://') + or word.startswith('ftps://') + ): + print("Line:", line) + print("Chunks:", WHITESPACE_RE.split(line)) + raise Exception('Is this a link? {} (on {})'.format(word, headline.doc.path)) + line_chunks.append('{description}' + .format(url=word, + description=html.escape(word))) + else: + line_chunks.append(html.escape(word)) + contents.append(' '.join(line_chunks)) + + acc.append('{}'.format('

    '.join(contents))) + elif isinstance(chunk, Link): link_target = chunk.value if link_target.startswith('id:'): From 8d8dcbfdcecf60eff43cf8bbb2af55f0c9aba525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Sun, 23 Oct 2022 21:30:09 +0200 Subject: [PATCH 051/167] Fix bare links next to `[` or `]` characters. --- scripts/generate.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/scripts/generate.py b/scripts/generate.py index fc40371..f40ca64 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -44,7 +44,7 @@ MONITORED_EVENT_TYPES = ( 'IN_MOVE_SELF', ) -WHITESPACE_RE = re.compile(r'\s') +TEXT_OR_LINK_RE = re.compile(r'([^\s\[\]]+|.)') ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -409,22 +409,20 @@ def render_text_tokens(tokens, acc, headline, graph): contents = [] for line in lines: line_chunks = [] - for word in WHITESPACE_RE.split(line): + for word in TEXT_OR_LINK_RE.findall(line): if ':/' in word and not (word.startswith('org-protocol://')): if not (word.startswith('http://') or word.startswith('https://') or word.startswith('ftp://') or word.startswith('ftps://') ): - print("Line:", line) - print("Chunks:", WHITESPACE_RE.split(line)) - raise Exception('Is this a link? {} (on {})'.format(word, headline.doc.path)) + raise Exception('Is this a link? {} (on {})\nLine: {}\nChunks: {}'.format(word, headline.doc.path, line, chunks)) line_chunks.append('{description}' .format(url=word, description=html.escape(word))) else: line_chunks.append(html.escape(word)) - contents.append(' '.join(line_chunks)) + contents.append(''.join(line_chunks)) acc.append('{}'.format('

    '.join(contents))) From 20b945aa316959fb2da29bd8cf02f8cc186cceb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Sun, 23 Oct 2022 21:30:44 +0200 Subject: [PATCH 052/167] Add usual task states to the default settings. --- scripts/generate.py | 7 +++++++ static/style.css | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/generate.py b/scripts/generate.py index f40ca64..5730e9e 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -20,6 +20,13 @@ from org_rw import dump as dump_org from org_rw import load as load_org from org_rw import token_list_to_raw +# Set custom states +for state in ("NEXT", "MEETING", "Q", "PAUSED", "SOMETIME", "TRACK", "WAITING"): + org_rw.DEFAULT_TODO_KEYWORDS.append(state) + +for state in ("DISCARDED", "VALIDATING"): + org_rw.DEFAULT_DONE_KEYWORDS.append(state) + EXTENSIONS = [ ".org", ".org.txt", diff --git a/static/style.css b/static/style.css index 3053cf5..2c6c6c2 100644 --- a/static/style.css +++ b/static/style.css @@ -155,10 +155,10 @@ h1.title .state { border-radius: 5px; } -h1.title .state.state-TODO { +h1.title .state.todo-True { background-color: rgba(255,0,0,0.5); } -h1.title .state.state-DONE { +h1.title .state.todo-False { background-color: rgba(0,255,0,0.25); } From 2eab1b43516fd908005ec3556cee3969b011ab94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Sun, 23 Oct 2022 21:40:10 +0200 Subject: [PATCH 053/167] Fix: don't hide text until custom font is loaded. --- static/style.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/static/style.css b/static/style.css index 2c6c6c2..9f6e141 100644 --- a/static/style.css +++ b/static/style.css @@ -88,8 +88,11 @@ body nav input { url('./fonts/atkinson-hyperlegible/woff2/Atkinson-Hyperlegible-Regular-102a.woff2') format('woff2'), url('./fonts/atkinson-hyperlegible/woff/Atkinson-Hyperlegible-Regular-102.woff') format('woff'), url('./fonts/atkinson-hyperlegible/ttf/Atkinson-Hyperlegible-Regular-102.ttf') format('truetype'); + + /* Make sure text is displayed ASAP, even if this font is not ready. */ + font-display: swap; } -} + /* Node styling */ .node { From 955dc433dfd0e60d2bcb6874172013faf379b042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Sun, 23 Oct 2022 21:50:33 +0200 Subject: [PATCH 054/167] Visually identify internal links. --- scripts/generate.py | 7 +++++-- static/style.css | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/scripts/generate.py b/scripts/generate.py index 5730e9e..2c4cae8 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -424,7 +424,7 @@ def render_text_tokens(tokens, acc, headline, graph): or word.startswith('ftps://') ): raise Exception('Is this a link? {} (on {})\nLine: {}\nChunks: {}'.format(word, headline.doc.path, line, chunks)) - line_chunks.append('{description}' + line_chunks.append('{description}' .format(url=word, description=html.escape(word))) else: @@ -435,6 +435,7 @@ def render_text_tokens(tokens, acc, headline, graph): elif isinstance(chunk, Link): link_target = chunk.value + is_internal_link = True if link_target.startswith('id:'): link_target = './' + link_target[3:] + '.node.html' elif link_target.startswith('./') or link_target.startswith('../'): @@ -456,6 +457,7 @@ def render_text_tokens(tokens, acc, headline, graph): else: link_target = './' + target_headline.id + '.node.html' else: + is_internal_link = False if not ( link_target.startswith('https://') or link_target.startswith('http://') @@ -467,8 +469,9 @@ def render_text_tokens(tokens, acc, headline, graph): if description is None: description = chunk.value - acc.append('{}'.format( + acc.append('{}'.format( html.escape(link_target), + 'internal' if is_internal_link else 'external', html.escape(description), )) # else: diff --git a/static/style.css b/static/style.css index 9f6e141..97ef9fc 100644 --- a/static/style.css +++ b/static/style.css @@ -300,6 +300,12 @@ tr.__table-separator { a { color: #00fdf2; } + a.internal::before { + content: '{ '; + } + a.internal::after { + content: ' }'; + } h1,h2,h3,h4,h5,h6 { color: #f7da4a; } From 34d0d2ead3db9bde69d9c1a19c96ed694c11fbba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Tue, 25 Oct 2022 23:35:46 +0200 Subject: [PATCH 055/167] Detect local links that result in a non-public note. --- scripts/generate.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/scripts/generate.py b/scripts/generate.py index 2c4cae8..d7ded60 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -107,6 +107,7 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): # This includes a virtual headline for ID-referenced documents. all_headlines = [] main_headlines_by_path = {} + main_headline_to_docid = {} for doc in docs: relpath = os.path.relpath(doc.path, src_top) changed = False @@ -151,6 +152,7 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): main_headlines_by_path[doc.path] = main_headline if doc.id is not None: doc_to_headline_remapping['id:' + doc.id] = 'id:' + main_headline.id + main_headline_to_docid[main_headline.id] = doc.id files_generated += 1 elif doc.id is not None: logging.error("Cannot render document from id: {}. {} headlines {} related".format( @@ -159,8 +161,8 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): 'with' if related is not None else 'without' )) - graph = {} # Build graph + graph = {} for headline in all_headlines: links = [] headline_links = list(headline.get_links()) @@ -200,6 +202,8 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): "links": links, "depth": headline.depth, } + if headline.id in main_headline_to_docid: + graph[main_headline_to_docid[headline.id]] = graph[headline.id] topLevelHeadline = headline while isinstance(topLevelHeadline.parent, org_rw.Headline): @@ -306,6 +310,13 @@ def get_headline_with_name(target_name, doc): return None +def assert_id_exists(id, src_headline, graph): + if id not in graph["nodes"]: + raise AssertionError("Cannot follow link to '{}' on headline '{}' ({})" + .format(id, + src_headline.id, + src_headline.title.get_text())) + def print_tree(tree, indentation=0, headline=None): # if headline and headline.id != INDEX_ID: # return @@ -437,6 +448,7 @@ def render_text_tokens(tokens, acc, headline, graph): link_target = chunk.value is_internal_link = True if link_target.startswith('id:'): + assert_id_exists(link_target[3:], headline, graph) link_target = './' + link_target[3:] + '.node.html' elif link_target.startswith('./') or link_target.startswith('../'): if '::' in link_target: @@ -447,6 +459,7 @@ def render_text_tokens(tokens, acc, headline, graph): if target_path not in graph['main_headlines']: logging.warn('Link to doc not in graph: {}'.format(target_path)) else: + assert_id_exists(graph['main_headlines'][target_path].id, headline, graph) link_target = './' + graph['main_headlines'][target_path].id + '.node.html' elif link_target.startswith('attachment:'): logging.warn('Not implemented `attachment:` links. Used on {}'.format(link_target)) @@ -455,6 +468,7 @@ def render_text_tokens(tokens, acc, headline, graph): if target_headline is None: logging.warn('No headline found corresponding to {}. On file {}'.format(link_target, headline.doc.path)) else: + assert_id_exists(target_headline.id, headline, graph) link_target = './' + target_headline.id + '.node.html' else: is_internal_link = False From 250bcde6d5630d8590e15ccf2d336b195d06d725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Tue, 25 Oct 2022 23:36:10 +0200 Subject: [PATCH 056/167] Set site language on homepage. --- static/homepage.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/homepage.html b/static/homepage.html index fa748f0..74811bb 100644 --- a/static/homepage.html +++ b/static/homepage.html @@ -1,5 +1,5 @@ - + Código para llevar From d023955ee030e4c48f906c85b678d0d11808544b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Tue, 25 Oct 2022 23:45:30 +0200 Subject: [PATCH 057/167] Skip links to private notes. --- scripts/generate.py | 96 ++++++++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 41 deletions(-) diff --git a/scripts/generate.py b/scripts/generate.py index d7ded60..e80be56 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -57,6 +57,18 @@ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) STATIC_PATH = os.path.join(ROOT_DIR, 'static') +class NonExistingLocalNoteError(AssertionError): + def __init__(self, note_id, src_headline): + AssertionError.__init__(self) + self.note_id = note_id + self.src_headline = src_headline + + def get_message(self): + return ("Cannot follow link to '{}' on headline '{}' ({})" + .format(self.note_id, + self.src_headline.id, + self.src_headline.title.get_text().strip())) + def is_git_path(path): return any([chunk == ".git" for chunk in path.split(os.sep)]) @@ -312,10 +324,7 @@ def get_headline_with_name(target_name, doc): def assert_id_exists(id, src_headline, graph): if id not in graph["nodes"]: - raise AssertionError("Cannot follow link to '{}' on headline '{}' ({})" - .format(id, - src_headline.id, - src_headline.title.get_text())) + raise NonExistingLocalNoteError(id, src_headline) def print_tree(tree, indentation=0, headline=None): # if headline and headline.id != INDEX_ID: @@ -447,47 +456,52 @@ def render_text_tokens(tokens, acc, headline, graph): elif isinstance(chunk, Link): link_target = chunk.value is_internal_link = True - if link_target.startswith('id:'): - assert_id_exists(link_target[3:], headline, graph) - link_target = './' + link_target[3:] + '.node.html' - elif link_target.startswith('./') or link_target.startswith('../'): - if '::' in link_target: - logging.warn('Not implemented headline links to other files. Used on {}'.format(link_target)) - - else: - target_path = os.path.abspath(os.path.join(os.path.dirname(headline.doc.path), link_target)) - if target_path not in graph['main_headlines']: - logging.warn('Link to doc not in graph: {}'.format(target_path)) - else: - assert_id_exists(graph['main_headlines'][target_path].id, headline, graph) - link_target = './' + graph['main_headlines'][target_path].id + '.node.html' - elif link_target.startswith('attachment:'): - logging.warn('Not implemented `attachment:` links. Used on {}'.format(link_target)) - elif link_target.startswith('* '): - target_headline = get_headline_with_name(link_target.lstrip('* '), headline.doc) - if target_headline is None: - logging.warn('No headline found corresponding to {}. On file {}'.format(link_target, headline.doc.path)) - else: - assert_id_exists(target_headline.id, headline, graph) - link_target = './' + target_headline.id + '.node.html' - else: - is_internal_link = False - if not ( - link_target.startswith('https://') - or link_target.startswith('http://') - or link_target.startswith('/') - ): - raise NotImplementedError('Unknown link type: {}' - .format(link_target)) description = chunk.description if description is None: description = chunk.value - acc.append('{}'.format( - html.escape(link_target), - 'internal' if is_internal_link else 'external', - html.escape(description), - )) + try: + if link_target.startswith('id:'): + assert_id_exists(link_target[3:], headline, graph) + link_target = './' + link_target[3:] + '.node.html' + elif link_target.startswith('./') or link_target.startswith('../'): + if '::' in link_target: + logging.warn('Not implemented headline links to other files. Used on {}'.format(link_target)) + + else: + target_path = os.path.abspath(os.path.join(os.path.dirname(headline.doc.path), link_target)) + if target_path not in graph['main_headlines']: + logging.warn('Link to doc not in graph: {}'.format(target_path)) + else: + assert_id_exists(graph['main_headlines'][target_path].id, headline, graph) + link_target = './' + graph['main_headlines'][target_path].id + '.node.html' + elif link_target.startswith('attachment:'): + logging.warn('Not implemented `attachment:` links. Used on {}'.format(link_target)) + elif link_target.startswith('* '): + target_headline = get_headline_with_name(link_target.lstrip('* '), headline.doc) + if target_headline is None: + logging.warn('No headline found corresponding to {}. On file {}'.format(link_target, headline.doc.path)) + else: + assert_id_exists(target_headline.id, headline, graph) + link_target = './' + target_headline.id + '.node.html' + else: + is_internal_link = False + if not ( + link_target.startswith('https://') + or link_target.startswith('http://') + or link_target.startswith('/') + ): + raise NotImplementedError('Unknown link type: {}' + .format(link_target)) + + acc.append('{}'.format( + html.escape(link_target), + 'internal' if is_internal_link else 'external', + html.escape(description), + )) + except NonExistingLocalNoteError as err: + logging.warning(err.get_message()) + acc.append(html.escape(description)) # else: # raise NotImplementedError('TextToken: {}'.format(chunk)) acc.append('

    ') From c8b3a99e7ae470f74b1c3fdd0e7ad95fef7a11ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Tue, 25 Oct 2022 23:52:54 +0200 Subject: [PATCH 058/167] Mark internal links on light style too. --- static/style.css | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/static/style.css b/static/style.css index 97ef9fc..ba2d073 100644 --- a/static/style.css +++ b/static/style.css @@ -192,6 +192,13 @@ li .tag::after { content: ":: "; } +a.internal::before { + content: '{ '; +} +a.internal::after { + content: ' }'; +} + /* Code blocks */ pre { overflow: auto; @@ -300,12 +307,6 @@ tr.__table-separator { a { color: #00fdf2; } - a.internal::before { - content: '{ '; - } - a.internal::after { - content: ' }'; - } h1,h2,h3,h4,h5,h6 { color: #f7da4a; } From 25a65253dd87a341153e486d244b706ed87f0e69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Wed, 26 Oct 2022 22:42:24 +0200 Subject: [PATCH 059/167] Show message when no results are found on search. Also, lower debounce time to 250ms. --- static/search-box.js | 13 ++++++++++++- static/style.css | 8 ++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/static/search-box.js b/static/search-box.js index 50b11c5..c661ede 100644 --- a/static/search-box.js +++ b/static/search-box.js @@ -19,6 +19,11 @@ function _codigoparallevar_enable_search_box(selector, options) { const resultsList = document.createElement('ul'); resultsBox.appendChild(resultsList); + const noResultsBox = document.createElement('div'); + noResultsBox.setAttribute('class', 'no-results-box hidden'); + noResultsBox.innerText = 'No results 🤷'; + resultsBox.appendChild(noResultsBox); + resultsBoxBackdrop.appendChild(resultsBox); document.body.appendChild(resultsBoxBackdrop); @@ -45,7 +50,7 @@ function _codigoparallevar_enable_search_box(selector, options) { }; }; - const DEBOUNCE_TIME = 500; // Milliseconds + const DEBOUNCE_TIME = 250; // Milliseconds const MIN_LENGTH = 3; const SEARCH_ENDPOINT = (window.location.host.startsWith('localhost') ? 'http://localhost:3001/api/search' @@ -92,6 +97,12 @@ function _codigoparallevar_enable_search_box(selector, options) { resultCard.appendChild(resultContents); resultsList.appendChild(resultCard); } + if (body.results.notes.length == 0) { + noResultsBox.classList.remove('hidden'); + } + else { + noResultsBox.classList.add('hidden'); + } }); }; element.removeAttribute('disabled'); diff --git a/static/style.css b/static/style.css index ba2d073..5e631e6 100644 --- a/static/style.css +++ b/static/style.css @@ -81,6 +81,14 @@ body nav input { margin: 0; } +.no-results-box { + padding: 1rem; +} + +.no-results-box.hidden { + display: none; +} + @font-face { font-family: "Atkinson Hyperlegible"; src: url('./fonts/atkinson-hyperlegible/eot/Atkinson-Hyperlegible-Regular-102.eot'); From 9053bf30f69ee320baffcac86759a3d6e6dfecd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Wed, 26 Oct 2022 22:45:50 +0200 Subject: [PATCH 060/167] Fix premature return when TODOs found. --- static/search-box.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/search-box.js b/static/search-box.js index c661ede..6531fce 100644 --- a/static/search-box.js +++ b/static/search-box.js @@ -82,7 +82,7 @@ function _codigoparallevar_enable_search_box(selector, options) { resultsList.innerHTML = ''; for (const note of body.results.notes) { if (note.is_todo === "1") { - return; + continue; } const resultCard = document.createElement('li'); From c80ada2a406ef396890ff42e81430a7411047752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Wed, 26 Oct 2022 23:12:44 +0200 Subject: [PATCH 061/167] Simple style changes around search box. --- static/style.css | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/static/style.css b/static/style.css index 5e631e6..a4ad7ac 100644 --- a/static/style.css +++ b/static/style.css @@ -25,7 +25,7 @@ body nav input { background-color: transparent; color: #000; border: none; - border-bottom: 1px solid #222; + border-bottom: 1px solid #888; } .results-box-container { z-index: 5; @@ -59,7 +59,13 @@ body nav input { background-color: transparent; color: #000; border: none; - border-bottom: 1px solid #444; + border-bottom: 1px solid #888; + outline: none; + font-size: 100%; +} + +.results-box-container .results-box input:focus { + border-bottom: 1px solid #000; } /* Search box results */ @@ -71,11 +77,11 @@ body nav input { padding: 0.25ex; margin: 1ex; border-radius: 4px; - box-shadow: 0px 1px 2px 1px rgba(0, 0, 0, 0.25); + box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25); } .results-box ul li h2 { - font-size: 125%; + font-size: 110%; padding: 1.25ex; display: block; margin: 0; @@ -353,6 +359,9 @@ tr.__table-separator { .results-box ul li h2 { color: white; } + .results-box-container .results-box input:focus { + border-bottom: 1px solid #fff; + } /* Code blocks */ pre { From 486c88c583abc4f5822abcdf857d8a73b7c20bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Sun, 30 Oct 2022 23:27:47 +0100 Subject: [PATCH 062/167] Take full headline body for search function. Using just `get_contents` doesn't get list items or tables. --- scripts/generate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate.py b/scripts/generate.py index e80be56..6c990c0 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -226,7 +226,7 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): ( headline.id, headline.title.get_text(), - ''.join(headline.get_contents('raw')), + '\n'.join(headline.doc.dump_headline(headline, recursive=False)), topLevelHeadline.title.get_text(), headline.is_done, headline.is_todo, From 7d209417659f8f6f975dfb456ead884e10b57495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Sun, 30 Oct 2022 23:38:01 +0100 Subject: [PATCH 063/167] Use experimental trigram tokenizer in SQLite FTS. --- scripts/generate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate.py b/scripts/generate.py index 6c990c0..2ca6882 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -77,7 +77,7 @@ def create_db(path): os.unlink(path) db = sqlite3.connect(path) - db.execute('CREATE VIRTUAL TABLE note_search USING fts5(note_id, title, body, top_level_title, is_done, is_todo);') + db.execute('CREATE VIRTUAL TABLE note_search USING fts5(note_id, title, body, top_level_title, is_done, is_todo, tokenize="trigram");') return db def load_all(top_dir_relative): From b676e2f94943184ff881c2a93076495d64b431a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Sun, 30 Oct 2022 23:44:31 +0100 Subject: [PATCH 064/167] Do search on all indexed fields. --- scripts/search-server/server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/search-server/server.go b/scripts/search-server/server.go index 86a7eb0..8914956 100644 --- a/scripts/search-server/server.go +++ b/scripts/search-server/server.go @@ -64,7 +64,7 @@ func main() { query := c.Query("q") - stm, err := db.Prepare("SELECT note_id, title, top_level_title, is_done, is_todo FROM note_search WHERE title LIKE ?") + stm, err := db.Prepare("SELECT note_id, title, top_level_title, is_done, is_todo FROM note_search(?)") if err != nil { log.Fatal(err) @@ -77,7 +77,7 @@ func main() { results := make([]map[string]string, 0) - rows, err := stm.Query(fmt.Sprintf("%%%v%%", query)) + rows, err := stm.Query(query) if err != nil { log.Fatal(err) c.JSON(500, gin.H{ From 208a9b2e97d6c79218799cd7995d4d83a2bc5e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Sun, 30 Oct 2022 23:58:44 +0100 Subject: [PATCH 065/167] Show notes TO-DO on search, mark them as such. --- static/search-box.js | 37 ++++++++++++++++++++++--------------- static/style.css | 5 +++++ 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/static/search-box.js b/static/search-box.js index 6531fce..3bac4a5 100644 --- a/static/search-box.js +++ b/static/search-box.js @@ -80,22 +80,29 @@ function _codigoparallevar_enable_search_box(selector, options) { } resultsList.innerHTML = ''; - for (const note of body.results.notes) { - if (note.is_todo === "1") { - continue; + for (const list of [ + body.results.notes.filter(n => n.is_todo !== "1"), + body.results.notes.filter(n => n.is_todo === "1"), + ]){ + for (const note of list) { + const resultCard = document.createElement('li'); + + const resultContents = document.createElement('a'); + resultContents.setAttribute('href', './' + note.id + '.node.html'); + + const resultTitle = document.createElement('h2'); + resultTitle.innerText = `${note.title} (${note.top_level_title})`; + if (note.is_todo === "1") { + resultTitle.setAttribute('class', 'is-todo'); + } + else if (note.is_done === "1") { + resultTitle.setAttribute('class', 'is-done'); + } + + resultContents.appendChild(resultTitle); + resultCard.appendChild(resultContents); + resultsList.appendChild(resultCard); } - - const resultCard = document.createElement('li'); - - const resultContents = document.createElement('a'); - resultContents.setAttribute('href', './' + note.id + '.node.html'); - - const resultTitle = document.createElement('h2'); - resultTitle.innerText = `${note.title} (${note.top_level_title})`; - - resultContents.appendChild(resultTitle); - resultCard.appendChild(resultContents); - resultsList.appendChild(resultCard); } if (body.results.notes.length == 0) { noResultsBox.classList.remove('hidden'); diff --git a/static/style.css b/static/style.css index a4ad7ac..8c9550b 100644 --- a/static/style.css +++ b/static/style.css @@ -87,6 +87,11 @@ body nav input { margin: 0; } +.results-box li h2.is-todo::before { + content: ' [TODO] '; + color: #D00; +} + .no-results-box { padding: 1rem; } From 964e2501eeac04c2ec2f91653da2393796d59002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Tue, 1 Nov 2022 12:19:20 +0100 Subject: [PATCH 066/167] Mark focused links on click. --- static/style.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/static/style.css b/static/style.css index 8c9550b..1dab804 100644 --- a/static/style.css +++ b/static/style.css @@ -20,6 +20,11 @@ body nav h1 { display: inline-block; } +a:focus { + background-color: rgb(0, 0, 238); + color: white; +} + /* Search box */ body nav input { background-color: transparent; @@ -326,6 +331,10 @@ tr.__table-separator { a { color: #00fdf2; } + a:focus { + background-color: #00fdf2; + color: black; + } h1,h2,h3,h4,h5,h6 { color: #f7da4a; } From 613aa4c88f44038fc2a7abd0de12d7db034efafe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Tue, 1 Nov 2022 12:19:34 +0100 Subject: [PATCH 067/167] Make result-bot TODO tag more badge-like. --- static/style.css | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/static/style.css b/static/style.css index 1dab804..6c43e2d 100644 --- a/static/style.css +++ b/static/style.css @@ -93,8 +93,13 @@ body nav input { } .results-box li h2.is-todo::before { - content: ' [TODO] '; - color: #D00; + content: 'TODO'; + display: inline-block; + background-color: #D00; + padding: 0.25ex; + border-radius: 4px; + font-size: 90%; + margin-right: 0.5ex; } .no-results-box { From 847e2cfd7403ddef7eacf311e9cdee97e6904cc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Tue, 1 Nov 2022 12:19:52 +0100 Subject: [PATCH 068/167] Don't crash when failing to update note_search DB. --- scripts/generate.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/generate.py b/scripts/generate.py index 2ca6882..e9bed85 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -108,7 +108,13 @@ def load_all(top_dir_relative): def regen_all(src_top, dest_top, *, docs=None, db=None): files_generated = 0 cur = db.cursor() - cur.execute('DELETE FROM note_search;') + cleaned_db = False + + try: + cur.execute('DELETE FROM note_search;') + cleaned_db = True + except sqlite3.OperationalError as err: + logging.warn("Error pre-cleaning DB, search won't be updated") docs = load_all(src_top) doc_to_headline_remapping = {} From 0ebda876f7df78ddd9d66e5e2d4a41574d31d1f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Tue, 1 Nov 2022 18:42:59 +0100 Subject: [PATCH 069/167] Update headline expansion based on document STARTUP config. --- scripts/generate.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/generate.py b/scripts/generate.py index e9bed85..8e27fde 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -592,10 +592,14 @@ def render(headline, doc, graph, headlineLevel): tag_list.append(f'{html.escape(tag)}') tags = f'{"".join(tag_list)}' - # display_state = 'collapsed' - # if headlineLevel < MIN_HIDDEN_HEADLINE_LEVEL: - # display_state = 'expanded' display_state = 'expanded' + # Update display based on document STARTUP config + visual_level = doc.get_keywords('STARTUP', 'showall') + if visual_level.startswith('show') and visual_level.endswith('levels'): + visual_level_num = int(visual_level[len('show'):-len('levels')]) - 1 + # Note that level is 0 indexed inside this loop + if headlineLevel >= visual_level_num: + display_state = 'collapsed' title = render_inline(headline.title, render_tag, headline, graph) From 38e5f57eab089fb362d32467bad868de74be0a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Fri, 4 Nov 2022 00:30:21 +0100 Subject: [PATCH 070/167] Use node collapse markers with better font compatibility. These are temporal. This way of working moves the titles around when they get collapsed/expanded, so I should probably change it at some point. --- static/style.css | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/static/style.css b/static/style.css index 6c43e2d..f2ee9b8 100644 --- a/static/style.css +++ b/static/style.css @@ -139,11 +139,14 @@ body nav input { } .node .node.collapsed > .title::before { - content: "🮥"; + content: "›"; + vertical-align: text-bottom; } .node .node.expanded > .title::before { - content: "🮦"; + content: "›"; + transform: rotate(90deg); + display: inline-block; } .node .title { From 1f286a0a54c897073632ba76c44b991a5be52b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Fri, 4 Nov 2022 00:31:32 +0100 Subject: [PATCH 071/167] Add simple style for results box. --- static/style.css | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/static/style.css b/static/style.css index f2ee9b8..ae213bd 100644 --- a/static/style.css +++ b/static/style.css @@ -261,6 +261,16 @@ code { font-size: 85%; } +/* Results */ +.results.lang-text { + border-radius: 4px; + border: 1px solid rgba(255,255,255,0.5); + padding: 1ex; + max-height: 80vh; + overflow-y: auto; +} + + .content { margin: 1ex; } @@ -405,6 +415,11 @@ tr.__table-separator { font-family: Menlo, Monaco, "Courier New", monospace; } + /* Results */ + .results.lang-text { + border: 1px solid rgba(255,255,255,0.5); + } + /* Tables. */ table, th, td, tr { border: 1px solid #eee; From 212c41d848199f93c57f654ffdbdf269031872a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Fri, 4 Nov 2022 00:31:53 +0100 Subject: [PATCH 072/167] Avoid having Headlines look like links when clicked. --- static/style.css | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/static/style.css b/static/style.css index ae213bd..60f8b3a 100644 --- a/static/style.css +++ b/static/style.css @@ -25,6 +25,17 @@ a:focus { color: white; } +h1 a:focus, +h2 a:focus, +h3 a:focus, +h4 a:focus, +h5 a:focus, +h6 a:focus +{ + background-color: inherit; + color: #2c3e50; +} + /* Search box */ body nav input { background-color: transparent; @@ -353,6 +364,16 @@ tr.__table-separator { background-color: #00fdf2; color: black; } + h1 a:focus, + h2 a:focus, + h3 a:focus, + h4 a:focus, + h5 a:focus, + h6 a:focus + { + background-color: inherit; + color: #f7da4a; + } h1,h2,h3,h4,h5,h6 { color: #f7da4a; } From 76531e3cfcb4692e2d2a2f2898bd686c8580d74e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Fri, 4 Nov 2022 00:33:05 +0100 Subject: [PATCH 073/167] Add result box rendering. --- scripts/generate.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/scripts/generate.py b/scripts/generate.py index 8e27fde..0820493 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -11,6 +11,7 @@ import uuid from datetime import datetime import traceback import re +from itertools import chain import inotify.adapters @@ -399,10 +400,10 @@ def render_list_item(element, acc, headline, graph): render_text_tokens(element.content, acc, headline, graph) acc.append("") - -def render_code_block(element, acc, headline, graph): - acc.append('
    '.format(element.subtype.lower()))
    -    content = html.escape(element.lines)
    +def render_block(content, acc, _class, is_code):
    +    acc.append('
    '.format(_class))
    +    if is_code:
    +        acc.append('')
     
         # Remove indentation common to all lines
         base_indentation = min([
    @@ -416,14 +417,21 @@ def render_code_block(element, acc, headline, graph):
         ]
     
         acc.append('\n'.join(content_lines))
    -    acc.append('
    ') + if is_code: + acc.append('
    ') + acc.append('
    ') + +def render_code_block(element, acc, headline, graph): + content = html.escape(element.lines) + render_block(content, acc, _class='code ' + element.subtype.lower(), is_code=True) + def render_results_block(element, acc, headline, graph): - # TODO: - # acc.append('
    ')
    -    # render_tree(element.children, acc)
    -    # acc.append('
    ') - pass + items = [e.get_raw() for e in element.children] + print(items) + content = '\n'.join(items) + if len(content.strip()) > 0: + render_block(content, acc, _class='results lang-text', is_code=False) def render_org_text(element, acc, headline, graph): as_dom = org_rw.text_to_dom(element.contents, element) @@ -436,6 +444,8 @@ def render_text(element, acc, headline, graph): def render_text_tokens(tokens, acc, headline, graph): acc.append('

    ') + if isinstance(tokens, org_rw.Text): + tokens = tokens.contents for chunk in tokens: if isinstance(chunk, str): lines = chunk.split('\n\n') From 08d35fc0b531ded2fc9f0d0ed8d42e7b7e37c540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Fri, 4 Nov 2022 00:33:16 +0100 Subject: [PATCH 074/167] Handle more softly words with :// that don't match known link types. --- scripts/generate.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/generate.py b/scripts/generate.py index 0820493..e300386 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -453,16 +453,18 @@ def render_text_tokens(tokens, acc, headline, graph): for line in lines: line_chunks = [] for word in TEXT_OR_LINK_RE.findall(line): - if ':/' in word and not (word.startswith('org-protocol://')): + if '://' in word and not (word.startswith('org-protocol://')): if not (word.startswith('http://') or word.startswith('https://') or word.startswith('ftp://') or word.startswith('ftps://') ): - raise Exception('Is this a link? {} (on {})\nLine: {}\nChunks: {}'.format(word, headline.doc.path, line, chunks)) - line_chunks.append('{description}' - .format(url=word, - description=html.escape(word))) + logging.warning('Is this a link? {} (on {})\nLine: {}\nChunks: {}'.format(word, headline.doc.path, line, line_chunks)) + line_chunks.append(html.escape(word)) + else: + line_chunks.append('{description}' + .format(url=word, + description=html.escape(word))) else: line_chunks.append(html.escape(word)) contents.append(''.join(line_chunks)) From 4849128fcdeaf7b5b8032493c8fb5eb82d431b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Wed, 9 Nov 2022 01:01:34 +0100 Subject: [PATCH 075/167] Add simple backlink connections. --- scripts/generate.py | 31 +++++++++++++++++++++++++++++-- static/style.css | 4 ++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/scripts/generate.py b/scripts/generate.py index e300386..ae8e554 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -182,6 +182,7 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): # Build graph graph = {} + backlink_graph = {} for headline in all_headlines: links = [] headline_links = list(headline.get_links()) @@ -216,6 +217,19 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): "target": headline.parent.id, "relation": "in" }) + for backlink in links: + if 'relation' in backlink and backlink['relation'] == 'in': + continue + + target = backlink['target'] + if target.startswith('id:'): + target = target[len('id:'):] + + if target not in backlink_graph: + backlink_graph[target] = set() + + backlink_graph[target].add(headline.id) + graph[headline.id] = { "title": org_rw.org_rw.token_list_to_plaintext(headline.title.contents).strip(), "links": links, @@ -247,7 +261,7 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): # Render docs after we've built the graph # Render main headlines - full_graph_info = { "nodes": graph, "main_headlines": main_headlines_by_path } + full_graph_info = { "nodes": graph, "backlinks": backlink_graph, "main_headlines": main_headlines_by_path } for _docpath, main_headline in main_headlines_by_path.items(): if main_headline.doc.id: endpath = os.path.join(dest_top, main_headline.doc.id + ".node.html") @@ -428,7 +442,6 @@ def render_code_block(element, acc, headline, graph): def render_results_block(element, acc, headline, graph): items = [e.get_raw() for e in element.children] - print(items) content = '\n'.join(items) if len(content.strip()) > 0: render_block(content, acc, _class='results lang-text', is_code=False) @@ -576,6 +589,17 @@ def render_as_document(headline, doc, headlineLevel, graph, title): else: return as_document(render(headline, doc, graph=graph, headlineLevel=headlineLevel), title) +def render_connections(headline_id, content, graph): + if headline_id not in graph['backlinks']: + return + + content.append("

    These notes link here:
      ") + for backlink in sorted(graph['backlinks'][headline_id], key=lambda x: graph['nodes'][x]['title']): + link = graph["nodes"][backlink] + title = link["title"] + content.append(f"
    • {html.escape(title)}
    • ") + content.append("
    ") + def render(headline, doc, graph, headlineLevel): try: dom = headline.as_dom() @@ -586,6 +610,9 @@ def render(headline, doc, graph, headlineLevel): content = [] render_tree(dom, content, headline, graph) + if headline.id: + render_connections(headline.id, content, graph) + for child in headline.children: content.append(render(child, doc, headlineLevel=headlineLevel+1, graph=graph)) diff --git a/static/style.css b/static/style.css index 60f8b3a..b9e6afd 100644 --- a/static/style.css +++ b/static/style.css @@ -173,6 +173,10 @@ h1 p,h2 p,h3 p,h4 p,h5 p,h6 p, li p { display: inline; } +.connections ul { + margin-top: 0; +} + /* Headers */ body > .node > h1 { text-align: center; From ac445d2e7c6a386653074e9a51de087f1e06aad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Wed, 9 Nov 2022 01:01:49 +0100 Subject: [PATCH 076/167] Use `logging.warning` instead of deprecated `logging.warn`. --- scripts/generate.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/generate.py b/scripts/generate.py index ae8e554..0741e9e 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -115,7 +115,7 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): cur.execute('DELETE FROM note_search;') cleaned_db = True except sqlite3.OperationalError as err: - logging.warn("Error pre-cleaning DB, search won't be updated") + logging.warning("Error pre-cleaning DB, search won't be updated") docs = load_all(src_top) doc_to_headline_remapping = {} @@ -497,21 +497,21 @@ def render_text_tokens(tokens, acc, headline, graph): link_target = './' + link_target[3:] + '.node.html' elif link_target.startswith('./') or link_target.startswith('../'): if '::' in link_target: - logging.warn('Not implemented headline links to other files. Used on {}'.format(link_target)) + logging.warning('Not implemented headline links to other files. Used on {}'.format(link_target)) else: target_path = os.path.abspath(os.path.join(os.path.dirname(headline.doc.path), link_target)) if target_path not in graph['main_headlines']: - logging.warn('Link to doc not in graph: {}'.format(target_path)) + logging.warning('Link to doc not in graph: {}'.format(target_path)) else: assert_id_exists(graph['main_headlines'][target_path].id, headline, graph) link_target = './' + graph['main_headlines'][target_path].id + '.node.html' elif link_target.startswith('attachment:'): - logging.warn('Not implemented `attachment:` links. Used on {}'.format(link_target)) + logging.warning('Not implemented `attachment:` links. Used on {}'.format(link_target)) elif link_target.startswith('* '): target_headline = get_headline_with_name(link_target.lstrip('* '), headline.doc) if target_headline is None: - logging.warn('No headline found corresponding to {}. On file {}'.format(link_target, headline.doc.path)) + logging.warning('No headline found corresponding to {}. On file {}'.format(link_target, headline.doc.path)) else: assert_id_exists(target_headline.id, headline, graph) link_target = './' + target_headline.id + '.node.html' From 87e4a8aa7d894b57faeec2281304dc80a88d89ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Wed, 9 Nov 2022 19:53:56 +0100 Subject: [PATCH 077/167] Remap document ids backlinks to main headlines. --- scripts/generate.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scripts/generate.py b/scripts/generate.py index 0741e9e..b2e0ec2 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -259,6 +259,17 @@ def regen_all(src_top, dest_top, *, docs=None, db=None): if link['target'] in doc_to_headline_remapping: link['target'] = doc_to_headline_remapping[link['target']] + # Remap document ids backlinks to main headlines + for doc_id, main_headline_id in doc_to_headline_remapping.items(): + if doc_id.startswith('id:'): + doc_id = doc_id[len('id:'):] + if main_headline_id.startswith('id:'): + main_headline_id = main_headline_id[len('id:'):] + for backlink in backlink_graph.get(doc_id, []): + if main_headline_id not in backlink_graph: + backlink_graph[main_headline_id] = set() + backlink_graph[main_headline_id].add(backlink) + # Render docs after we've built the graph # Render main headlines full_graph_info = { "nodes": graph, "backlinks": backlink_graph, "main_headlines": main_headlines_by_path } From e7dc8ad1e7d57c2c1014d90cac472ee3a8d2437a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Fri, 11 Nov 2022 18:45:28 +0100 Subject: [PATCH 078/167] Make indentation lines clearer on dark mode. --- static/style.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/static/style.css b/static/style.css index b9e6afd..9c547b7 100644 --- a/static/style.css +++ b/static/style.css @@ -358,6 +358,11 @@ tr.__table-separator { color: #fafafe; } + .node .node { + border-color: #8c9ea0; + } + + h2 a { color: #fafafe; } From 9a883d90dd1d8caa89dda26e6795828aa77f7b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mart=C3=ADnez=20Portela?= Date: Tue, 15 Nov 2022 21:11:36 +0100 Subject: [PATCH 079/167] Apply Syntax Highlight on code blocks. --- scripts/generate.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/scripts/generate.py b/scripts/generate.py index b2e0ec2..8bf0f3d 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -21,6 +21,10 @@ from org_rw import dump as dump_org from org_rw import load as load_org from org_rw import token_list_to_raw +import pygments +import pygments.lexers +import pygments.formatters + # Set custom states for state in ("NEXT", "MEETING", "Q", "PAUSED", "SOMETIME", "TRACK", "WAITING"): org_rw.DEFAULT_TODO_KEYWORDS.append(state) @@ -447,7 +451,21 @@ def render_block(content, acc, _class, is_code): acc.append('') def render_code_block(element, acc, headline, graph): - content = html.escape(element.lines) + code = element.lines + if element.arguments is not None and len(element.arguments) > 0 : + try: + lexer = pygments.lexers.get_lexer_by_name(element.arguments.split()[0], stripall=True) + content = pygments.highlight(code, + lexer, + pygments.formatters.HtmlFormatter() + ) + acc.append(content) + return + + except pygments.util.ClassNotFound: + pass + logging.error("Cannot find lexer for {}".format(element.subtype.lower())) + content = html.escape(code) render_block(content, acc, _class='code ' + element.subtype.lower(), is_code=True) @@ -588,6 +606,7 @@ def render_as_document(headline, doc, headlineLevel, graph, title): {title} @ {SITE_NAME} +