Compare commits

..

1 Commits

Author SHA1 Message Date
Sergio Martínez Portela
6d621ffc3c WIP: Save blog & notes data on FTS search DB. 2023-10-04 00:19:39 +02:00
9 changed files with 169 additions and 331 deletions

View File

@ -1,29 +0,0 @@
# Codigo para llevar's generator
This is the static site generator used to build [Codigo Para Llevar](https://codigoparallevar.com/) (my personal site). It contains:
- A markdown blog (with content ported from acrylamid). Saved as `/blog/`.
- A set of org-mode based notes. Saved as `/notes/`.
It also copies over some static assets (css, js, fonts).
The scripts are hardcoded with the hostnames and paths for my own site, so you might want to update them.
General documentation is in progress and might be replaced little by little by the more interactive [org-web-editor](https://code.codigoparallevar.com/kenkeiras/org-web-editor) once that one (1) supports all the features here and (2) has support for building static sites.
## Instructions
Generally, what you want to do is to run `make` once to prepare the static files, then run this to generate the notes.
```bash
mkdir -p _gen
WATCH_AND_REBUILD=0 python3 scripts/generate.py <path to your notes> _gen/notes [<DEFAULT SUBPATH (usually 'public', '.' to ignore)>]
```
Use `WATCH_AND_REBUILD=1` (or empty) for automatic rebuilds.
## Filtering
This won't render **all** notes, but try to select the PUBLIC ones and skip the PRIVATE ones.
PUBLIC files are contained on the DEFAULT_SUBPATH, PRIVATE headlines have the `:private:` tag.

View File

@ -22,6 +22,7 @@ import shutil
import traceback import traceback
import time import time
import re import re
import sqlite3
from typing import List from typing import List
from bs4 import BeautifulSoup as bs4 from bs4 import BeautifulSoup as bs4
@ -63,6 +64,7 @@ JINJA_ENV = jinja2.Environment(
autoescape=jinja2.select_autoescape() autoescape=jinja2.select_autoescape()
) )
PARSER_NAMESPACE = 'codigoparallevar.com/blog'
WATCH = True WATCH = True
if os.getenv('WATCH_AND_REBUILD', '1') == '0': if os.getenv('WATCH_AND_REBUILD', '1') == '0':
WATCH = False WATCH = False
@ -176,6 +178,12 @@ def get_out_path(front_matter):
return out_path return out_path
def create_db(path):
db = sqlite3.connect(path)
db.execute('CREATE VIRTUAL TABLE IF NOT EXISTS note_search USING fts5(note_id, title, body, top_level_title, is_done, is_todo, parser_namespace, url, tokenize="trigram");')
db.execute('DELETE FROM note_search WHERE parser_namespace = ?;', (PARSER_NAMESPACE,))
return db
def load_all(top_dir_relative): def load_all(top_dir_relative):
top = os.path.abspath(top_dir_relative) top = os.path.abspath(top_dir_relative)
@ -456,10 +464,39 @@ def render_rss(docs, dest_top):
f.write(result) f.write(result)
def regen_all(source_top, dest_top, docs=None): def regen_all(source_top, dest_top, docs=None, db=None):
if docs is None: if docs is None:
docs = load_all(source_top) docs = load_all(source_top)
cur = db.cursor()
cleaned_db = False
try:
cur.execute('DELETE FROM note_search WHERE parser_namespace = ?;', (PARSER_NAMESPACE,))
cleaned_db = True
except sqlite3.OperationalError as err:
if WATCH:
logging.warning("Error pre-cleaning DB, search won't be updated")
else:
raise
# Save posts to DB
for (doc, front_matter, out_path) in docs.values():
cur.execute('''INSERT INTO note_search(note_id, title, body, top_level_title, is_done, is_todo, parser_namespace, url) VALUES (?, ?, ?, ?, ?, ?, ?, ?);''',
(
out_path,
front_matter['title'],
doc,
front_matter['title'],
False,
False,
PARSER_NAMESPACE,
out_path + '/index.html',
))
cur.close()
db.commit()
# Render posts # Render posts
for (doc, front_matter, out_path) in docs.values(): for (doc, front_matter, out_path) in docs.values():
doc_full_path = os.path.join(dest_top, out_path) doc_full_path = os.path.join(dest_top, out_path)
@ -513,7 +550,8 @@ def main(source_top, dest_top):
## Initial load ## Initial load
t0 = time.time() t0 = time.time()
logging.info("Initial load...") logging.info("Initial load...")
docs = regen_all(source_top, dest_top) db = create_db(os.path.join(dest_top, '..', 'db.sqlite3'))
docs = regen_all(source_top, dest_top, db=db)
logging.info("Initial load completed in {:.2f}s".format(time.time() - t0)) logging.info("Initial load completed in {:.2f}s".format(time.time() - t0))
if not WATCH: if not WATCH:
@ -557,7 +595,7 @@ def main(source_top, dest_top):
if is_static_resource: if is_static_resource:
logging.info("Updated static resources in {:.2f}s".format(time.time() - t0)) logging.info("Updated static resources in {:.2f}s".format(time.time() - t0))
else: else:
docs = regen_all(source_top, dest_top, docs) docs = regen_all(source_top, dest_top, docs, db=db)
logging.info("Updated all in {:.2f}s".format(time.time() - t0)) logging.info("Updated all in {:.2f}s".format(time.time() - t0))
else: else:

View File

@ -46,14 +46,14 @@ IMG_EXTENSIONS = set([
"gif", "gif",
]) ])
SKIPPED_TAGS = set(['attach']) SKIPPED_TAGS = set(['attach'])
DEFAULT_SUBPATH = "public" PARSER_NAMESPACE = 'codigoparallevar.com/notes'
WATCH = True WATCH = True
if os.getenv('WATCH_AND_REBUILD', '1') == '0': if os.getenv('WATCH_AND_REBUILD', '1') == '0':
WATCH = False WATCH = False
MIN_HIDDEN_HEADLINE_LEVEL = 2 MIN_HIDDEN_HEADLINE_LEVEL = 2
INDEX_ID = os.getenv("INDEX_ID", "ea48ec1d-f9d4-4fb7-b39a-faa7b6e2ba95") INDEX_ID = "ea48ec1d-f9d4-4fb7-b39a-faa7b6e2ba95"
SITE_NAME = "Código para llevar" SITE_NAME = "Código para llevar"
MONITORED_EVENT_TYPES = ( MONITORED_EVENT_TYPES = (
@ -89,11 +89,9 @@ def is_git_path(path):
return any([chunk == ".git" for chunk in path.split(os.sep)]) return any([chunk == ".git" for chunk in path.split(os.sep)])
def create_db(path): def create_db(path):
if os.path.exists(path):
os.unlink(path)
db = sqlite3.connect(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, tokenize="trigram");') db.execute('CREATE VIRTUAL TABLE IF NOT EXISTS note_search USING fts5(note_id, title, body, top_level_title, is_done, is_todo, parser_namespace, url tokenize="trigram");')
db.execute('DELETE FROM note_search WHERE parser_namespace = ?;', (PARSER_NAMESPACE,))
return db return db
def load_all(top_dir_relative): def load_all(top_dir_relative):
@ -109,9 +107,7 @@ def load_all(top_dir_relative):
path = os.path.join(root, name) path = os.path.join(root, name)
try: try:
doc = load_org(open(path), doc = load_org(open(path), extra_cautious=True)
environment={"org-todo-keywords": "TODO(t) NEXT(n) MEETING(m/!) Q(q) PAUSED(p!/!) EVENT(e/!) SOMETIME(s) WAITING(w@/!) TRACK(r/!) | DISCARDED(x@/!) VALIDATING(v!/!) DONE(d!/!)"},
extra_cautious=True)
docs.append(doc) docs.append(doc)
except Exception as err: except Exception as err:
import traceback import traceback
@ -123,28 +119,13 @@ def load_all(top_dir_relative):
logging.info("Collected {} files".format(len(docs))) logging.info("Collected {} files".format(len(docs)))
return docs return docs
def remove_non_public_headlines(doc: org_rw.OrgDoc | org_rw.Headline): def regen_all(src_top, dest_top, *, docs=None, db=None):
if isinstance(doc, org_rw.OrgDoc):
doc.headlines = list(filter_private_headlines(doc.headlines))
for hl in doc.headlines:
remove_non_public_headlines(hl)
else:
doc.children = list(filter_private_headlines(doc.children))
for hl in doc.children:
remove_non_public_headlines(hl)
def filter_private_headlines(headlines):
for hl in headlines:
if 'private' not in hl.tags:
yield hl
def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
files_generated = 0 files_generated = 0
cur = db.cursor() cur = db.cursor()
cleaned_db = False cleaned_db = False
try: try:
cur.execute('DELETE FROM note_search;') cur.execute('DELETE FROM note_search WHERE parser_namespace = ?;', (PARSER_NAMESPACE,))
cleaned_db = True cleaned_db = True
except sqlite3.OperationalError as err: except sqlite3.OperationalError as err:
if WATCH: if WATCH:
@ -165,12 +146,10 @@ def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
main_headline_to_docid = {} main_headline_to_docid = {}
for doc in docs: for doc in docs:
relpath = os.path.relpath(doc.path, src_top) relpath = os.path.relpath(doc.path, src_top)
remove_non_public_headlines(doc)
changed = False changed = False
headlines = list(doc.getAllHeadlines()) headlines = list(doc.getAllHeadlines())
related = None related = None
if not relpath.startswith(subpath + "/"): if not relpath.startswith("public/"):
# print("Skip:", relpath) # print("Skip:", relpath)
continue continue
@ -282,7 +261,7 @@ def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
topLevelHeadline = topLevelHeadline.parent topLevelHeadline = topLevelHeadline.parent
# Save for full-text-search # Save for full-text-search
cur.execute('''INSERT INTO note_search(note_id, title, body, top_level_title, is_done, is_todo) VALUES (?, ?, ?, ?, ?, ?);''', cur.execute('''INSERT INTO note_search(note_id, title, body, top_level_title, is_done, is_todo, parser_namespace, url) VALUES (?, ?, ?, ?, ?, ?, ?, ?);''',
( (
headline.id, headline.id,
headline.title.get_text(), headline.title.get_text(),
@ -290,6 +269,8 @@ def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
topLevelHeadline.title.get_text(), topLevelHeadline.title.get_text(),
headline.is_done, headline.is_done,
headline.is_todo, headline.is_todo,
PARSER_NAMESPACE,
headline.id + '.node.html',
)) ))
# Update graph, replace document ids with headline ids # Update graph, replace document ids with headline ids
@ -369,15 +350,15 @@ def regen_all(src_top, dest_top, subpath, *, docs=None, db=None):
dirs_exist_ok=True) dirs_exist_ok=True)
def main(src_top, dest_top, subpath): def main(src_top, dest_top):
notifier = inotify.adapters.InotifyTrees([src_top, STATIC_PATH]) notifier = inotify.adapters.InotifyTrees([src_top, STATIC_PATH])
## Initial load ## Initial load
t0 = time.time() t0 = time.time()
os.makedirs(dest_top, exist_ok=True) os.makedirs(dest_top, exist_ok=True)
db = create_db(os.path.join(dest_top, 'db.sqlite3')) db = create_db(os.path.join(dest_top, '..', 'db.sqlite3'))
docs = regen_all(src_top, dest_top, subpath=subpath, db=db) docs = regen_all(src_top, dest_top, db=db)
if not WATCH: if not WATCH:
logging.info("Build completed in {:.2f}s".format(time.time() - t0)) logging.info("Build completed in {:.2f}s".format(time.time() - t0))
@ -395,7 +376,7 @@ def main(src_top, dest_top, subpath):
print("CHANGED: {}".format(filepath)) print("CHANGED: {}".format(filepath))
t0 = time.time() t0 = time.time()
try: try:
docs = regen_all(src_top, dest_top, subpath=subpath, docs=docs, db=db) docs = regen_all(src_top, dest_top, docs=docs, db=db)
except: except:
logging.error(traceback.format_exc()) logging.error(traceback.format_exc())
logging.error("Loading new templates failed 😿") logging.error("Loading new templates failed 😿")
@ -497,7 +478,7 @@ def unindent(content):
len(l) - len(l.lstrip(' ')) len(l) - len(l.lstrip(' '))
for l in content.split('\n') for l in content.split('\n')
if len(l.strip()) > 0 if len(l.strip()) > 0
] or [0]) ])
content_lines = [ content_lines = [
l[base_indentation:] l[base_indentation:]
for l in content.split('\n') for l in content.split('\n')
@ -530,12 +511,6 @@ def render_results_block(element, acc, headline, graph):
if len(content.strip()) > 0: if len(content.strip()) > 0:
render_block(content, acc, _class='results lang-text', is_code=False) render_block(content, acc, _class='results lang-text', is_code=False)
def render_generic_drawer_block(element, acc, headline, graph):
items = [e.get_raw() for e in element.children]
content = '\n'.join(items)
if len(content.strip()) > 0:
render_block(content, acc, _class='generic-drawer {}-drawer lang-text'.format(element.drawer_name), is_code=False)
def render_org_text(element, acc, headline, graph): def render_org_text(element, acc, headline, graph):
as_dom = org_rw.text_to_dom(element.contents, element) as_dom = org_rw.text_to_dom(element.contents, element)
render_text_tokens(as_dom, acc, headline, graph) render_text_tokens(as_dom, acc, headline, graph)
@ -668,7 +643,6 @@ def render_tag(element, acc, headline, graph):
dom.CodeBlock: render_code_block, dom.CodeBlock: render_code_block,
dom.Text: render_text, dom.Text: render_text,
dom.ResultsDrawerNode: render_results_block, dom.ResultsDrawerNode: render_results_block,
dom.GenericDrawerNode: render_generic_drawer_block,
org_rw.Text: render_org_text, org_rw.Text: render_org_text,
}[type(element)](element, acc, headline, graph) }[type(element)](element, acc, headline, graph)
@ -741,11 +715,8 @@ def render_connections(headline_id, content, graph, doc_to_headline_remapping):
# return # return
logging.info("Generating centered graph for {}".format(headline_id)) logging.info("Generating centered graph for {}".format(headline_id))
try:
svg = gen_centered_graph.gen(headline_id, graph['nodes'], doc_to_headline_remapping) svg = gen_centered_graph.gen(headline_id, graph['nodes'], doc_to_headline_remapping)
content.append("<div class='connections'>{}</div>".format(svg)) content.append("<div class='connections'>{}</div>".format(svg))
except:
logging.warning("Broken reference on headline ID={}".format(headline_id))
def render(headline, doc, graph, headlineLevel, doc_to_headline_remapping): def render(headline, doc, graph, headlineLevel, doc_to_headline_remapping):
try: try:
@ -765,10 +736,10 @@ def render(headline, doc, graph, headlineLevel, doc_to_headline_remapping):
content.append(render(child, doc, headlineLevel=headlineLevel+1, graph=graph, content.append(render(child, doc, headlineLevel=headlineLevel+1, graph=graph,
doc_to_headline_remapping=doc_to_headline_remapping)) doc_to_headline_remapping=doc_to_headline_remapping))
if headline.state is None or headline.state.get('name') is None: if headline.state is None:
state = "" state = ""
else: else:
state = f'<span class="state todo-{headline.is_todo} state-{headline.state["name"]}">{headline.state["name"]}</span>' state = f'<span class="state todo-{headline.is_todo} state-{headline.state}">{headline.state}</span>'
if headline.is_todo: if headline.is_todo:
todo_state = "todo" todo_state = "todo"
@ -855,13 +826,9 @@ def save_changes(doc):
if __name__ == "__main__": if __name__ == "__main__":
if len(sys.argv) not in (3, 4): if len(sys.argv) != 3:
print("Usage: {} SOURCE_TOP DEST_TOP <SUBPATH>".format(sys.argv[0])) print("Usage: {} SOURCE_TOP DEST_TOP".format(sys.argv[0]))
exit(0) exit(0)
logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s") logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s")
subpath = DEFAULT_SUBPATH exit(main(sys.argv[1], sys.argv[2]))
if len(sys.argv) == 4:
subpath = sys.argv[3]
exit(main(sys.argv[1], sys.argv[2], subpath=subpath))

View File

@ -12,4 +12,4 @@ cd ../../_gen/notes/
set -x set -x
exec docker run -it --rm -p $PORT:80 -e SNIPPET_SIZE=256 -e PORT=80 -e DB_PATH=/db.sqlite3 -v `pwd`/db.sqlite3:/db.sqlite3:ro search-server exec docker run -it --rm -p $PORT:80 -e PORT=80 -e DB_PATH=/db.sqlite3 -v `pwd`/db.sqlite3:/db.sqlite3:ro search-server

View File

@ -33,22 +33,6 @@ func main() {
port = port_num port = port_num
} }
snippet_size := 128
snippet_size_str, ok := os.LookupEnv("SNIPPET_SIZE")
if ok {
snippet_size_num, err := strconv.Atoi(snippet_size_str)
if err != nil {
log.Fatal(err)
os.Exit(1)
}
if (snippet_size_num < 64) {
log.Fatal("Environment variale $SNIPPET_SIZE must be >= 64.")
os.Exit(1)
}
snippet_size = snippet_size_num
}
db, err := sql.Open("sqlite3", database_path) db, err := sql.Open("sqlite3", database_path)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -79,23 +63,8 @@ func main() {
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
query := c.Query("q") query := c.Query("q")
body_type := c.Query("body")
if ((body_type != "all") && (body_type != "none") && (body_type != "snippet")) {
body_type = "none"
}
var stm *sql.Stmt
var err error
if (body_type == "snippet") {
stm, err = db.Prepare("SELECT note_id, highlight(note_search, 1, '<span class=\"match\">', '</span>'), top_level_title, is_done, is_todo, snippet(note_search, 2, '<span class=\"match\">', '</span>', '<span class=\"search-result-break\"></span>', ?) FROM note_search(?)")
} else if (body_type == "all") {
stm, err = db.Prepare("SELECT note_id, highlight(note_search, 1, '<span class=\"match\">', '</span>'), top_level_title, is_done, is_todo, highlight(note_search, 2, '<span class=\"match\">', '</span>') FROM note_search(?)")
} else if (body_type == "none") {
stm, err = db.Prepare("SELECT note_id, highlight(note_search, 1, '<span class=\"match\">', '</span>'), top_level_title, is_done, is_todo FROM note_search(?)")
}
stm, err := db.Prepare("SELECT note_id, title, top_level_title, is_done, is_todo FROM note_search(?)")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -107,13 +76,8 @@ func main() {
} }
results := make([]map[string]string, 0) results := make([]map[string]string, 0)
var rows *sql.Rows
if (body_type == "snippet") { rows, err := stm.Query(query)
rows, err = stm.Query(snippet_size, query)
} else {
rows, err = stm.Query(query)
}
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
c.JSON(500, gin.H{ c.JSON(500, gin.H{
@ -130,23 +94,6 @@ func main() {
var note_is_done string var note_is_done string
var note_is_todo string var note_is_todo string
item := make(map[string]string)
if (body_type != "none") {
var note_highlight string
err = rows.Scan(
&note_id,
&note_title,
&note_top_level_title,
&note_is_done,
&note_is_todo,
&note_highlight,
)
if (body_type != "none") {
item["highlight"] = note_highlight
}
} else {
err = rows.Scan( err = rows.Scan(
&note_id, &note_id,
&note_title, &note_title,
@ -154,7 +101,6 @@ func main() {
&note_is_done, &note_is_done,
&note_is_todo, &note_is_todo,
) )
}
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
c.JSON(500, gin.H{ c.JSON(500, gin.H{
@ -164,6 +110,7 @@ func main() {
return return
} }
item := make(map[string]string)
item["id"] = note_id item["id"] = note_id
item["title"] = note_title item["title"] = note_title
item["top_level_title"] = note_top_level_title item["top_level_title"] = note_top_level_title

View File

@ -1,85 +1,82 @@
/* Dark mode. */ pre { line-height: 125%; }
@media (prefers-color-scheme: dark) { td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
pre { line-height: 125%; } span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } .hll { background-color: #49483e }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } .c { color: #75715e } /* Comment */
.hll { background-color: #49483e } .err { color: #960050; background-color: #1e0010 } /* Error */
.c { color: #75715e } /* Comment */ .esc { color: #f8f8f2 } /* Escape */
.err { color: #960050; background-color: #1e0010 } /* Error */ .g { color: #f8f8f2 } /* Generic */
.esc { color: #f8f8f2 } /* Escape */ .k { color: #66d9ef } /* Keyword */
.g { color: #f8f8f2 } /* Generic */ .l { color: #ae81ff } /* Literal */
.k { color: #66d9ef } /* Keyword */ .n { color: #f8f8f2 } /* Name */
.l { color: #ae81ff } /* Literal */ .o { color: #f92672 } /* Operator */
.n { color: #f8f8f2 } /* Name */ .x { color: #f8f8f2 } /* Other */
.o { color: #f92672 } /* Operator */ .p { color: #f8f8f2 } /* Punctuation */
.x { color: #f8f8f2 } /* Other */ .ch { color: #75715e } /* Comment.Hashbang */
.p { color: #f8f8f2 } /* Punctuation */ .cm { color: #75715e } /* Comment.Multiline */
.ch { color: #75715e } /* Comment.Hashbang */ .cp { color: #75715e } /* Comment.Preproc */
.cm { color: #75715e } /* Comment.Multiline */ .cpf { color: #75715e } /* Comment.PreprocFile */
.cp { color: #75715e } /* Comment.Preproc */ .c1 { color: #75715e } /* Comment.Single */
.cpf { color: #75715e } /* Comment.PreprocFile */ .cs { color: #75715e } /* Comment.Special */
.c1 { color: #75715e } /* Comment.Single */ .gd { color: #f92672 } /* Generic.Deleted */
.cs { color: #75715e } /* Comment.Special */ .ge { color: #f8f8f2; font-style: italic } /* Generic.Emph */
.gd { color: #f92672 } /* Generic.Deleted */ .gr { color: #f8f8f2 } /* Generic.Error */
.ge { color: #f8f8f2; font-style: italic } /* Generic.Emph */ .gh { color: #f8f8f2 } /* Generic.Heading */
.gr { color: #f8f8f2 } /* Generic.Error */ .gi { color: #a6e22e } /* Generic.Inserted */
.gh { color: #f8f8f2 } /* Generic.Heading */ .go { color: #66d9ef } /* Generic.Output */
.gi { color: #a6e22e } /* Generic.Inserted */ .gp { color: #f92672; font-weight: bold } /* Generic.Prompt */
.go { color: #66d9ef } /* Generic.Output */ .gs { color: #f8f8f2; font-weight: bold } /* Generic.Strong */
.gp { color: #f92672; font-weight: bold } /* Generic.Prompt */ .gu { color: #75715e } /* Generic.Subheading */
.gs { color: #f8f8f2; font-weight: bold } /* Generic.Strong */ .gt { color: #f8f8f2 } /* Generic.Traceback */
.gu { color: #75715e } /* Generic.Subheading */ .kc { color: #66d9ef } /* Keyword.Constant */
.gt { color: #f8f8f2 } /* Generic.Traceback */ .kd { color: #66d9ef } /* Keyword.Declaration */
.kc { color: #66d9ef } /* Keyword.Constant */ .kn { color: #f92672 } /* Keyword.Namespace */
.kd { color: #66d9ef } /* Keyword.Declaration */ .kp { color: #66d9ef } /* Keyword.Pseudo */
.kn { color: #f92672 } /* Keyword.Namespace */ .kr { color: #66d9ef } /* Keyword.Reserved */
.kp { color: #66d9ef } /* Keyword.Pseudo */ .kt { color: #66d9ef } /* Keyword.Type */
.kr { color: #66d9ef } /* Keyword.Reserved */ .ld { color: #e6db74 } /* Literal.Date */
.kt { color: #66d9ef } /* Keyword.Type */ .m { color: #ae81ff } /* Literal.Number */
.ld { color: #e6db74 } /* Literal.Date */ .s { color: #e6db74 } /* Literal.String */
.m { color: #ae81ff } /* Literal.Number */ .na { color: #a6e22e } /* Name.Attribute */
.s { color: #e6db74 } /* Literal.String */ .nb { color: #f8f8f2 } /* Name.Builtin */
.na { color: #a6e22e } /* Name.Attribute */ .nc { color: #a6e22e } /* Name.Class */
.nb { color: #f8f8f2 } /* Name.Builtin */ .no { color: #66d9ef } /* Name.Constant */
.nc { color: #a6e22e } /* Name.Class */ .nd { color: #a6e22e } /* Name.Decorator */
.no { color: #66d9ef } /* Name.Constant */ .ni { color: #f8f8f2 } /* Name.Entity */
.nd { color: #a6e22e } /* Name.Decorator */ .ne { color: #a6e22e } /* Name.Exception */
.ni { color: #f8f8f2 } /* Name.Entity */ .nf { color: #a6e22e } /* Name.Function */
.ne { color: #a6e22e } /* Name.Exception */ .nl { color: #f8f8f2 } /* Name.Label */
.nf { color: #a6e22e } /* Name.Function */ .nn { color: #f8f8f2 } /* Name.Namespace */
.nl { color: #f8f8f2 } /* Name.Label */ .nx { color: #a6e22e } /* Name.Other */
.nn { color: #f8f8f2 } /* Name.Namespace */ .py { color: #f8f8f2 } /* Name.Property */
.nx { color: #a6e22e } /* Name.Other */ .nt { color: #f92672 } /* Name.Tag */
.py { color: #f8f8f2 } /* Name.Property */ .nv { color: #f8f8f2 } /* Name.Variable */
.nt { color: #f92672 } /* Name.Tag */ .ow { color: #f92672 } /* Operator.Word */
.nv { color: #f8f8f2 } /* Name.Variable */ .w { color: #f8f8f2 } /* Text.Whitespace */
.ow { color: #f92672 } /* Operator.Word */ .mb { color: #ae81ff } /* Literal.Number.Bin */
.w { color: #f8f8f2 } /* Text.Whitespace */ .mf { color: #ae81ff } /* Literal.Number.Float */
.mb { color: #ae81ff } /* Literal.Number.Bin */ .mh { color: #ae81ff } /* Literal.Number.Hex */
.mf { color: #ae81ff } /* Literal.Number.Float */ .mi { color: #ae81ff } /* Literal.Number.Integer */
.mh { color: #ae81ff } /* Literal.Number.Hex */ .mo { color: #ae81ff } /* Literal.Number.Oct */
.mi { color: #ae81ff } /* Literal.Number.Integer */ .sa { color: #e6db74 } /* Literal.String.Affix */
.mo { color: #ae81ff } /* Literal.Number.Oct */ .sb { color: #e6db74 } /* Literal.String.Backtick */
.sa { color: #e6db74 } /* Literal.String.Affix */ .sc { color: #e6db74 } /* Literal.String.Char */
.sb { color: #e6db74 } /* Literal.String.Backtick */ .dl { color: #e6db74 } /* Literal.String.Delimiter */
.sc { color: #e6db74 } /* Literal.String.Char */ .sd { color: #e6db74 } /* Literal.String.Doc */
.dl { color: #e6db74 } /* Literal.String.Delimiter */ .s2 { color: #e6db74 } /* Literal.String.Double */
.sd { color: #e6db74 } /* Literal.String.Doc */ .se { color: #ae81ff } /* Literal.String.Escape */
.s2 { color: #e6db74 } /* Literal.String.Double */ .sh { color: #e6db74 } /* Literal.String.Heredoc */
.se { color: #ae81ff } /* Literal.String.Escape */ .si { color: #e6db74 } /* Literal.String.Interpol */
.sh { color: #e6db74 } /* Literal.String.Heredoc */ .sx { color: #e6db74 } /* Literal.String.Other */
.si { color: #e6db74 } /* Literal.String.Interpol */ .sr { color: #e6db74 } /* Literal.String.Regex */
.sx { color: #e6db74 } /* Literal.String.Other */ .s1 { color: #e6db74 } /* Literal.String.Single */
.sr { color: #e6db74 } /* Literal.String.Regex */ .ss { color: #e6db74 } /* Literal.String.Symbol */
.s1 { color: #e6db74 } /* Literal.String.Single */ .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */
.ss { color: #e6db74 } /* Literal.String.Symbol */ .fm { color: #a6e22e } /* Name.Function.Magic */
.bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ .vc { color: #f8f8f2 } /* Name.Variable.Class */
.fm { color: #a6e22e } /* Name.Function.Magic */ .vg { color: #f8f8f2 } /* Name.Variable.Global */
.vc { color: #f8f8f2 } /* Name.Variable.Class */ .vi { color: #f8f8f2 } /* Name.Variable.Instance */
.vg { color: #f8f8f2 } /* Name.Variable.Global */ .vm { color: #f8f8f2 } /* Name.Variable.Magic */
.vi { color: #f8f8f2 } /* Name.Variable.Instance */ .il { color: #ae81ff } /* Literal.Number.Integer.Long */
.vm { color: #f8f8f2 } /* Name.Variable.Magic */
.il { color: #ae81ff } /* Literal.Number.Integer.Long */
}

View File

@ -6,13 +6,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<style> <style>
body { body {
background-color: white;
font-family: sans-serif; font-family: sans-serif;
margin: 0 auto; margin: 0 auto;
width: fit-content; width: fit-content;
max-width: 100ex; max-width: 100ex;
padding: 0 1ex; padding: 0 1ex;
color: black;
} }
.header h1 { .header h1 {
text-align: center; text-align: center;
@ -47,7 +45,7 @@
border-right: 1px solid #000; border-right: 1px solid #000;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
body { html {
background-color: #1d1f21; background-color: #1d1f21;
color: #fafafe; color: #fafafe;
} }
@ -74,12 +72,6 @@
</div> </div>
<div class="links"> <div class="links">
<a href="/notes">
<section>
<h2>Notes</h2>
<p>Some publicly-visible notes from a sort of knowledge graph that I use as information dump.</p>
</section>
</a>
<section> <section>
<h2><a href="/blog">Blog</a></h2> <h2><a href="/blog">Blog</a></h2>
<p> <p>
@ -108,24 +100,12 @@
</ul> </ul>
</p> </p>
</section> </section>
<a href="/notes">
<section> <section>
<h2>Talks / Slides</h2> <h2>Notes</h2>
<p> <p>Some publicly-visible notes from a sort of knowledge graph that I use as information dump.</p>
<ul>
<li>
Malleable Software
(<a href="/slides/hackliza2024/software-maleable/software-maleable.odp">galician, </a>
for <a href="https://hackliza.gal">Hackliza</a>
<a href="/slides/hackliza2024/software-maleable/software-maleable.pdf">[PDF]</a>
<a href="/slides/hackliza2024/software-maleable/software-maleable.odp">[ODP]</a>)
(<a href="/slides/eslibre2024/software-maleable.odp">spanish,</a>
for <a href="https://eslib.re/2024/">esLibre 2024</a>
<a href="/slides/eslibre2024/software-maleable.pdf">[PDF]</a>
<a href="/slides/eslibre2024/software-maleable.odp">[ODP]</a>).
</li>
</ul>
</p>
</section> </section>
</a>
<!-- section> <!-- section>
<h2>Projects</h2> <h2>Projects</h2>
<p> <p>
@ -136,7 +116,7 @@
<section id="social"> <section id="social">
<h2>Find me</h2> <h2>Find me</h2>
<p> <p>
<a href="https://social.codigoparallevar.com/@kenkeiras">ActivityPub</a> <a href="https://social.codigoparallevar.com/@kenkeiras">Mastodon</a>
<a href="https://github.com/kenkeiras">GitHub</a> <a href="https://github.com/kenkeiras">GitHub</a>
<a href="https://gitlab.com/kenkeiras">GitLab</a> <a href="https://gitlab.com/kenkeiras">GitLab</a>
<a href="https://programaker.com/users/kenkeiras">PrograMaker</a> <a href="https://programaker.com/users/kenkeiras">PrograMaker</a>

View File

@ -1,9 +1,4 @@
function _codigoparallevar_enable_search_box(selector, options) { function _codigoparallevar_enable_search_box(selector, options) {
const unescape = (str, tag) => {
const prev = tag.replaceAll('<', '&lt;').replaceAll('>', '&gt;');
return str.replaceAll(prev, tag);
}
const element = document.querySelector(selector); const element = document.querySelector(selector);
if ('placeholder' in options) { if ('placeholder' in options) {
element.setAttribute('placeholder', options.placeholder); element.setAttribute('placeholder', options.placeholder);
@ -74,7 +69,7 @@ function _codigoparallevar_enable_search_box(selector, options) {
lastVal = val; lastVal = val;
resultsBox.classList.add('loading'); resultsBox.classList.add('loading');
const uri = SEARCH_ENDPOINT + '?q=' + encodeURIComponent(val) + '&body=snippet'; const uri = SEARCH_ENDPOINT + '?q=' + encodeURIComponent(val);
let query = fetch(uri); let query = fetch(uri);
currentQuery = query; currentQuery = query;
query query
@ -99,32 +94,14 @@ function _codigoparallevar_enable_search_box(selector, options) {
const resultTitle = document.createElement('h2'); const resultTitle = document.createElement('h2');
resultTitle.innerText = `${note.title} (${note.top_level_title})`; resultTitle.innerText = `${note.title} (${note.top_level_title})`;
resultTitle.innerHTML = unescape(unescape(
resultTitle.innerHTML,
'</span>'), '<span class="match">'
);
if (note.is_todo === "1") { if (note.is_todo === "1") {
resultCard.setAttribute('class', 'is-todo'); resultTitle.setAttribute('class', 'is-todo');
} }
else if (note.is_done === "1") { else if (note.is_done === "1") {
resultCard.setAttribute('class', 'is-done'); resultTitle.setAttribute('class', 'is-done');
} }
resultContents.appendChild(resultTitle); resultContents.appendChild(resultTitle);
if (note.highlight) {
const resultBody = document.createElement('p');
resultBody.innerText = note.highlight;
resultBody.innerHTML = unescape(
unescape(
unescape(
resultBody.innerHTML,
'</span>'),
'<span class="search-result-break">'),
'<span class="match">');
resultContents.appendChild(resultBody);
}
resultCard.appendChild(resultContents); resultCard.appendChild(resultContents);
resultsList.appendChild(resultCard); resultsList.appendChild(resultCard);
} }

View File

@ -10,8 +10,6 @@ body {
max-width: 80ex; max-width: 80ex;
margin: 0 auto; margin: 0 auto;
padding: 0.5ex 1ex; padding: 0.5ex 1ex;
background-color: white;
color: black;
} }
body.blog { body.blog {
@ -126,30 +124,13 @@ body nav input {
box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25); box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.25);
} }
.results-box ul li a {
text-decoration: none;
}
.results-box ul li h2 { .results-box ul li h2 {
font-size: 110%; font-size: 110%;
padding: 1.25ex; padding: 1.25ex;
display: block; display: block;
margin: 0; margin: 0;
text-decoration: underline;
} }
.results-box ul li p {
padding: 1.25ex;
color: black;
}
.results-box ul li span.match {
background: yellow;
}
.results-box ul li .search-result-break::before {
content: '…';
color: #777;
}
.results-box li h2.is-todo::before { .results-box li h2.is-todo::before {
content: 'TODO'; content: 'TODO';
display: inline-block; display: inline-block;
@ -263,11 +244,6 @@ img {
border-left: 2px solid var(--tree-color); border-left: 2px solid var(--tree-color);
} }
.node .contents ul ul ul li,
.global-table-of-contents ul ul ul li {
margin-left: calc(0px + var(--tree-radius) * 2 + .5ex);
}
.node .contents ul ul li::marker, .node .contents ul ul li::marker,
.global-table-of-contents ul ul li::marker { .global-table-of-contents ul ul li::marker {
content: ''; content: '';
@ -356,11 +332,6 @@ h1.title .state.todo-True {
h1.title .state.todo-False { h1.title .state.todo-False {
background-color: rgba(0,255,0,0.25); background-color: rgba(0,255,0,0.25);
} }
h1.title .state.todo-True.state-SOMETIME {
background-color: #ddd;
color: black;
}
h1.title .tags { h1.title .tags {
float: right; float: right;
@ -397,7 +368,6 @@ a.internal::after {
} }
a.external::after { a.external::after {
content: ' ↗'; content: ' ↗';
vertical-align: top;
} }
/* Markup */ /* Markup */
@ -610,7 +580,7 @@ tr.__table-separator {
/* Dark mode. */ /* Dark mode. */
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
html, body { html {
background-color: #1d1f21; background-color: #1d1f21;
color: #fafafe; color: #fafafe;
} }
@ -681,19 +651,10 @@ tr.__table-separator {
.results-box ul li h2 { .results-box ul li h2 {
color: white; color: white;
} }
.results-box ul li p {
padding: 1.25ex;
color: white;
}
.results-box-container .results-box input:focus { .results-box-container .results-box input:focus {
border-bottom: 1px solid #fff; border-bottom: 1px solid #fff;
} }
.results-box ul li span.match {
background: #886600;
}
/* Code blocks */ /* Code blocks */
.highlight pre { .highlight pre {
padding: 1ex; padding: 1ex;