Compare commits

...

16 Commits

28 changed files with 777 additions and 719 deletions

1
.gitignore vendored
View File

@ -2,4 +2,5 @@
env
n
web
planning.txt
__pycache__

View File

@ -1,15 +1,11 @@
install:
cp n2w_add_uuid.py /usr/local/bin
sed "s/N2W_COMMIT = \"\"/N2W_COMMIT = \"$$(git rev-parse --short HEAD)\"/" notes2web.py > /usr/local/bin/notes2web.py
cp gronk_add_uuid.py /usr/local/bin
sed "s/GRONK_COMMIT = \"dev\"/GRONK_COMMIT = \"$$(git rev-parse --short HEAD)\"/" gronk.py > /usr/local/bin/gronk.py
chmod +x /usr/local/bin/gronk.py
chmod +x /usr/local/bin/gronk_add_uuid.py
mkdir -p /opt/gronk
cp -r templates js css /opt/gronk
pip3 install -r requirements.txt
mkdir -p /opt/notes2web
cp -r templates /opt/notes2web
cp styles.css /opt/notes2web
cp fuse.js /opt/notes2web
cp search.js /opt/notes2web
cp indexsearch.js /opt/notes2web
cp toc_search.js /opt/notes2web
cp permalink.js /opt/notes2web
uninstall:
rm -rf /usr/local/bin/notes2web.py /usr/local/bin/n2w_add_uuid.py /opt/notes2web
rm -rf /usr/local/bin/gronk.py /usr/local/bin/gronk_add_uuid.py /opt/gronk

View File

@ -3,6 +3,7 @@
@import url("https://styles.alv.cx/modules/search.css");
@import url("https://styles.alv.cx/modules/buttonlist.css");
@import url("https://styles.alv.cx/modules/darkmode.css");
@import url("/notes/styles.css");
html {
scroll-behavior: smooth;

518
gronk.py Executable file
View File

@ -0,0 +1,518 @@
#!/usr/bin/env python3
"""
gronk --- view your notes as a static html site
"""
import argparse
import os
from pathlib import Path
import shutil
import sys
import subprocess
import copy
import time
import magic
import regex as re
import pprint
import frontmatter
import jinja2
import requests
GRONK_COMMIT = "dev"
PANDOC_SERVER_URL = os.getenv("PANDOC_SERVER_URL", r"http://localhost:3030/")
PANDOC_TIMEOUT = int(os.getenv("PANDOC_TIMEOUT", "120"))
GRONK_CSS_DIR = Path(os.getenv("CSS_DIR", "/opt/gronk/css"))
GRONK_JS_DIR = Path(os.getenv("JS_DIR", "/opt/gronk/js"))
GRONK_TEMPLATES_DIR = Path(os.getenv("TEMPLATES_DIR", "/opt/gronk/templates"))
JINJA_ENV = jinja2.Environment(loader=jinja2.PackageLoader("gronk"),
autoescape=jinja2.select_autoescape)
JINJA_TEMPLATES = {}
JINJA_TEMPLATE_TEXTARTICLE = JINJA_ENV.get_template("article-text.html")
JINJA_TEMPLATE_HOME_INDEX = JINJA_ENV.get_template("home.html")
JINJA_TEMPLATE_INDEX = JINJA_ENV.get_template("index.html")
JINJA_TEMPLATE_ARTICLE = JINJA_ENV.get_template("article.html")
JINJA_TEMPLATE_PERMALINK = JINJA_ENV.get_template("permalink.html")
LICENSE = None
FILEMAP = None
class FileMap:
"""
this class is used to read file properties, inherit properties,
and have a centralised place to access them
"""
def __init__(self, input_dir, output_dir):
self._map = {}
self.input_dir = Path(input_dir)
self.output_dir = Path(output_dir)
@staticmethod
def _path_to_key(path):
return str(path)
@staticmethod
def is_plaintext(filename):
return re.match(r'^text/', magic.from_file(str(filename),
mime=True)) is not None
def add(self, filepath):
filepath = Path(filepath)
if filepath.is_dir():
properties = self._get_directory_properties(filepath)
else:
properties = self._get_file_properties(filepath)
properties['src_path'] = filepath
properties['dst_path'] = self._get_output_filepath(filepath)
self._map[self._path_to_key(filepath)] = properties
def get(self, filepath, default=None, raw=False):
"""
get the properties of a file at a filepath
raw=True to not inherit properties
"""
# TODO maybe store properties of a file once it's in built and mark it
# as built? might save time but also cba
if self._path_to_key(filepath) not in self._map.keys():
self.add(filepath)
properties = copy.deepcopy(
self._map.get(self._path_to_key(filepath), default))
if raw:
return properties
parent = filepath
while True:
parent = parent.parent
if parent == Path('.'):
break
parent_properties = self.get(parent, raw=True)
# TODO inherit any property that isn't defined, append any lists
# that exist
properties['tags'] = properties.get(
'tags', []) + parent_properties.get('tags', [])
if parent == self.input_dir:
break
return properties
def _get_directory_properties(self,
filepath: Path,
include_index_entries=True):
post = {
'title': filepath.name,
'content_after_search': False,
'automatic_index': True,
'search_bar': True,
'tags': [],
}
if 'index.md' in [f.name for f in filepath.iterdir()]:
with open(filepath.joinpath('index.md'),
encoding='utf-8') as file_pointer:
for key, val in frontmatter.load(
file_pointer).to_dict().items():
post[key] = val
if 'content' in post.keys():
post['content'] = render_markdown(post['content'])
post['is_dir'] = True
if include_index_entries:
post['index_entries'] = self._get_index_entries(filepath)
return post
def _get_index_entries(self, filepath):
entries = []
for path in filepath.iterdir():
if path.is_dir():
entry = self._get_directory_properties(
path, include_index_entries=False)
else:
entry = self._get_file_properties(path)
entry['path'] = self._get_output_filepath(path)['web']
entries.append(entry)
entries.sort(key=lambda entry: str(entry.get('title', '')).lower())
entries.sort(key=lambda entry: entry['is_dir'], reverse=True)
return entries
def _get_file_properties(self, filepath):
post = {'title': filepath.name}
if filepath.suffix == '.md':
with open(filepath, encoding='utf-8') as file_pointer:
post = frontmatter.load(file_pointer).to_dict()
# don't store file contents in memory
if 'content' in post.keys():
del post['content']
post['is_dir'] = False
return post
def _get_output_filepath(self, input_filepath):
def webpath(filepath):
return Path('/notes').joinpath(
filepath.relative_to(self.output_dir))
r = {}
r['raw'] = self.output_dir.joinpath(
input_filepath.relative_to(self.input_dir))
r['web'] = webpath(r['raw'])
if input_filepath.is_dir():
return r
if input_filepath.suffix == '.md':
r['html'] = self.output_dir.joinpath(
input_filepath.relative_to(
self.input_dir)).with_suffix('.html')
r['web'] = webpath(r['html'])
elif self.is_plaintext(input_filepath):
r['html'] = self.output_dir.joinpath(
input_filepath.relative_to(
self.input_dir)).with_suffix(input_filepath.suffix +
'.html')
r['raw'] = self.output_dir.joinpath(
input_filepath.relative_to(self.input_dir))
r['web'] = webpath(r['html'])
return r
def to_list(self):
return [val for _, val in self._map.items()]
def to_search_data(self):
"""
returns list of every file in map
"""
r = []
for _, val in self._map.items():
r.append({
'title': val.get('title', ''),
'tags': val.get('tags', []),
'path': str(val['dst_path']['web']),
'is_dir': val['is_dir']
})
return r
def get_uuid_map(self):
d = {}
for _, val in self._map.items():
if 'uuid' not in val.keys():
continue
d[val['uuid']] = str(val['dst_path']['web'])
return d
def update_required(src_filepath, output_filepath):
"""
check if file requires an update,
return boolean
"""
return not output_filepath.exists() or src_filepath.stat(
).st_mtime > output_filepath.stat().st_mtimeme()
def get_args():
""" Get command line arguments """
parser = argparse.ArgumentParser()
parser.add_argument('notes', type=Path)
parser.add_argument('-o', '--output-dir', type=Path, default='web')
parser.add_argument(
'-F',
'--force',
action="store_true",
help=
"Generate new output html even if source file was modified before output html"
)
return parser.parse_args()
def render_markdown_file(input_filepath):
"""
render markdown file to file
write markdown file to args.output_dir in html,
return list of tuple of output filepath, frontmatter post
"""
with open(input_filepath, encoding='utf-8') as file_pointer:
content = frontmatter.load(file_pointer).content
properties = FILEMAP.get(input_filepath)
html = render_markdown(content)
html = JINJA_TEMPLATE_ARTICLE.render(
license=LICENSE,
content=html,
lecture_slides=properties.get("lecture_slides"),
lecture_notes=properties.get("lecture_notes"),
uuid=properties.get("uuid"),
tags=properties.get("tags"),
author=properties.get("author"),
title=properties.get("title"))
properties['dst_path']['html'].write_text(html)
def render_plaintext_file(input_filepath):
"""
render plaintext file to file
copy plaintext file into a html preview, copy raw to output dir
return list of tuple of output filepath, empty dict
"""
raw_content = input_filepath.read_text()
properties = FILEMAP.get(input_filepath)
html = JINJA_TEMPLATE_TEXTARTICLE.render(license=LICENSE, **properties)
properties['dst_path']['raw'].write_text(raw_content)
properties['dst_path']['html'].write_text(html)
def render_generic_file(input_filepath):
"""
render generic file to file
copy generic file into to output_dir
return list of tuple of output filepath, empty dict
"""
properties = FILEMAP.get(input_filepath)
output_filepath = properties['dst_path']['raw']
shutil.copyfile(input_filepath, output_filepath)
def render_file(input_filepath):
"""
render any file by detecting type and applying appropriate type
write input_filepath to correct file in args.output_dir in appropriate formats,
return list of tuples of output filepath, frontmatter post
"""
if input_filepath.suffix == '.md':
return render_markdown_file(input_filepath)
if FileMap.is_plaintext(input_filepath):
return render_plaintext_file(input_filepath)
return render_generic_file(input_filepath)
def render_markdown(content):
"""
render markdown to html
"""
post_body = {
'text': content,
'toc-depth': 6,
'highlight-style': 'pygments',
'html-math-method': 'mathml',
'to': 'html',
'files': {
'data/data/abbreviations': '',
},
'standalone': False,
}
headers = {'Accept': 'application/json'}
response = requests.post(PANDOC_SERVER_URL,
headers=headers,
json=post_body,
timeout=PANDOC_TIMEOUT)
response = response.json()
# TODO look at response['messages'] and log them maybe?
# https://github.com/jgm/pandoc/blob/main/doc/pandoc-server.md#response
return response['output']
def process_home_index(args, notes_git_head_sha1=None):
"""
create home index.html in output_dir
"""
post = {'title': 'gronk', 'content': ''}
custom_content_file = args.notes.joinpath('index.md')
if custom_content_file.is_file():
fmpost = frontmatter.loads(custom_content_file.read_text()).to_dict()
for key, val in fmpost.items():
post[key] = val
post['content'] = render_markdown(post['content'])
html = JINJA_TEMPLATE_HOME_INDEX.render(
gronk_commit=GRONK_COMMIT,
search_data=FILEMAP.to_search_data(),
notes_git_head_sha1=notes_git_head_sha1,
post=post)
args.output_dir.joinpath('index.html').write_text(html)
def generate_permalink_page(output_dir):
"""
create the directory and index.html for redirecting permalinks
"""
dir = output_dir.joinpath('permalink')
dir.mkdir(exist_ok=True)
dir.joinpath('index.html').write_text(
JINJA_TEMPLATE_PERMALINK.render(gronk_commit=GRONK_COMMIT,
data=FILEMAP.get_uuid_map()))
def generate_tag_browser(output_dir):
"""
generate a directory that lets you groub by and browse by any given tag. e.g. tags, authors
"""
tags = {}
for post in FILEMAP.to_list():
post['path'] = post['dst_path']['web']
if 'tags' not in post.keys():
continue
for tag in post['tags']:
if tag not in tags.keys():
tags[tag] = []
tags[tag].append(post)
for tag, index_entries in tags.items():
output_file = output_dir.joinpath(tag, 'index.html')
output_file.parent.mkdir(exist_ok=True, parents=True)
output_file.write_text(
JINJA_TEMPLATE_INDEX.render(
gronk_commit=GRONK_COMMIT,
automatic_index=True,
search_bar=True,
title=tag,
index_entries=[{
'title': entry.get('title', ''),
'is_dir': entry.get('is_dir', False),
'path': str(entry.get('path', Path(''))),
} for entry in index_entries],
))
output_file = output_dir.joinpath('index.html')
output_file.parent.mkdir(exist_ok=True, parents=True)
output_file.write_text(
JINJA_TEMPLATE_INDEX.render(automatic_index=True,
gronk_commit=GRONK_COMMIT,
search_bar=True,
title='tags',
index_entries=[{
'path': tag,
'title': tag,
'is_dir': False,
} for tag in tags.keys()]))
def main(args):
""" Entry point for script """
global LICENSE
global FILEMAP
FILEMAP = FileMap(args.notes, args.output_dir.joinpath('notes'))
# TODO have some sort of 'site rebuild in progress - come back in a minute
# or two!' or auto checking/refreshing page for when site is being built
if args.output_dir.is_file():
print(f"Output directory ({args.output_dir}) cannot be a file.")
args.output_dir.mkdir(parents=True, exist_ok=True)
# attempt to get licensing information
license_path = args.notes.joinpath("LICENSE")
if license_path.exists():
LICENSE = license_path.read_text()
# TODO git commit log integration
for root_str, _, files in os.walk(args.notes):
root = Path(root_str)
if '.git' in root.parts:
continue
root_properties = FILEMAP.get(root)
root_properties['dst_path']['raw'].mkdir(parents=True, exist_ok=True)
pprint.pprint(root_properties)
html = JINJA_TEMPLATE_INDEX.render(
gronk_commit=GRONK_COMMIT,
title=root_properties.get('title', ''),
content=root_properties.get('content', ''),
content_after_search=root_properties['content_after_search'],
automatic_index=root_properties['automatic_index'],
search_bar=root_properties['search_bar'],
index_entries=[{
'title': entry.get('title', ''),
'is_dir': entry.get('is_dir', False),
'path': str(entry.get('path', Path(''))),
} for entry in root_properties.get('index_entries', '')],
)
root_properties['dst_path']['raw'].joinpath('index.html').write_text(
html)
# render each file
for file in files:
# don't render index.md as index as it is used for directory
if file == "index.md":
continue
render_file(root.joinpath(file))
process_home_index(args)
# copy styling and js scripts necessary for function
shutil.copytree(GRONK_CSS_DIR,
args.output_dir.joinpath('css'),
dirs_exist_ok=True)
shutil.copytree(GRONK_JS_DIR,
args.output_dir.joinpath('js'),
dirs_exist_ok=True)
generate_tag_browser(args.output_dir.joinpath('tags'))
generate_permalink_page(args.output_dir)
return 0
# TODO implement useful logging and debug printing
if __name__ == '__main__':
pandoc_process = subprocess.Popen(["/usr/bin/pandoc-server"],
stdout=subprocess.PIPE)
time.sleep(1)
print(pandoc_process.stdout)
try:
sys.exit(main(get_args()))
except KeyboardInterrupt:
sys.exit(0)
finally:
pandoc_process.kill()

28
n2w_add_uuid.py → gronk_add_uuid.py Executable file → Normal file
View File

@ -1,35 +1,41 @@
#!/usr/bin/env python3
import editfrontmatter
import frontmatter
import pathlib
import sys
import uuid
import editfrontmatter
import frontmatter
def get_args():
""" Get command line arguments """
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('filename', type=pathlib.Path)
parser.add_argument('--template',
default=pathlib.Path("/opt/notes2web/templates/n2w_add_uuid_frontmatter_template"),
type=pathlib.Path
)
parser.add_argument('-w', '--write', action='store_true',
help='write to file instead of stdout')
parser.add_argument('-w',
'--write',
action='store_true',
help='write to file instead of stdout')
return parser.parse_args()
def main(args):
""" Entry point for script """
with open(args.template) as fp:
template_str=fp.read()
template_str = "\n".join([
"author: {{ author }}"
"date: {{ date }}"
"title: {{ title }}"
"tags: {{ tags }}"
"uuid: {{ uuid }}"
])
with open(args.filename) as fp:
fm_pre = frontmatter.load(fp)
processor = editfrontmatter.EditFrontMatter(file_path=args.filename, template_str=template_str)
processor = editfrontmatter.EditFrontMatter(file_path=args.filename,
template_str=template_str)
fm_data = fm_pre.metadata
if 'uuid' not in fm_data.keys():
fm_data['uuid'] = str(uuid.uuid4())

View File

View File

@ -1,3 +1,13 @@
/*
* search.js expects an array `data` containing objects with the following keys:
* headers: [string]
* path: string
* tags: [string]
* title: string
* uuid: string
*/
const HEADERS = "headers"
const PATH = "path"
const TAGS = "tags"
@ -6,11 +16,11 @@ const TITLE = "title"
const SEARCH_TIMEOUT_MS = 100
var SEARCH_TIMEOUT_ID = -1
const fuse = new Fuse(data, {
const fuse = new Fuse(search_data, {
keys: [
{
name: HEADERS,
weight: 0.2
weight: 1
},
{
name: PATH,
@ -18,11 +28,11 @@ const fuse = new Fuse(data, {
},
{
name: TAGS,
weight: 0.1
weight: 0.5
},
{
name: TITLE,
weight: 4
weight: 2
}
],
includeMatches: true,

View File

@ -1,428 +0,0 @@
#!/usr/bin/env python3
from bs4 import BeautifulSoup as bs
import subprocess
import frontmatter
import magic
import sys
import pathlib
import pypandoc
import shutil
import os
import regex as re
import json
import yaml
TEXT_ARTICLE_TEMPLATE_FOOT = None
TEXT_ARTICLE_TEMPLATE_HEAD = None
INDEX_TEMPLATE_FOOT = None
INDEX_TEMPLATE_HEAD = None
EXTRA_INDEX_CONTENT = None
N2W_COMMIT = ""
def is_plaintext(filename):
return re.match(r'^text/', magic.from_file(str(filename), mime=True)) is not None
def get_files(folder):
markdown = []
plaintext = []
other = []
for root, folders, files in os.walk(folder):
for filename in files:
if '/.git' in root:
continue
name = os.path.join(root, filename)
if pathlib.Path(name).suffix == '.md':
markdown.append(name)
elif is_plaintext(name):
plaintext.append(name)
other.append(name)
else:
other.append(name)
return markdown, plaintext, other
def get_inherited_tags(file, base_folder):
tags = []
folder = pathlib.Path(file)
while folder != base_folder.parent:
print(f"get_inherited_tags {folder=}")
folder = pathlib.Path(folder).parent
folder_metadata = folder.joinpath('.n2w.yml')
if not folder_metadata.exists():
continue
with open(folder.joinpath('.n2w.yml')) as fp:
folder_properties = yaml.safe_load(fp)
tags += folder_properties.get('itags')
print(f"get_inherited_tags {tags=}")
return list(set(tags))
def git_head_sha1(working_dir):
git_response = subprocess.run(
[ 'git', f"--git-dir={working_dir.joinpath('.git')}", 'rev-parse', '--short', 'HEAD' ],
stdout=subprocess.PIPE
).stdout.decode('utf-8')
return git_response.strip()
def git_filehistory(working_dir, filename):
print(f"{pathlib.Path(filename).relative_to(working_dir)=}")
git_response = subprocess.run(
[
'git',
f"--git-dir={working_dir.joinpath('.git')}",
"log",
"-p",
"--",
pathlib.Path(filename).relative_to(working_dir)
],
stdout=subprocess.PIPE
)
filehistory = [f"File history not available: git log returned code {git_response.returncode}."
"\nIf this is not a git repository, this is not a problem."]
if git_response.returncode == 0:
filehistory = git_response.stdout.decode('utf-8')
temp = re.split(
r'(commit [a-f0-9]{40})',
filehistory,
flags=re.IGNORECASE
)
for t in temp:
if t == '':
temp.remove(t)
filehistory = []
for i in range(0, len(temp)-1, 2):
filehistory.append(f"{temp[i]}{temp[i+1]}")
if filehistory == "":
filehistory = ["This file has no history (it may not be part of the git repository)."]
filehistory = [ x.replace("<", "&lt;").replace(">", "&gt;") for x in filehistory]
filehistory = "<pre>\n" + "</pre><pre>\n".join(filehistory) + "</pre>"
return filehistory
def get_dirs_to_index(folder):
r = []
for root, folders, files in os.walk(folder):
if pathlib.Path(os.path.join(root, folder)).is_relative_to(folder.joinpath('permalink')):
continue
[r.append(os.path.join(root, folder)) for folder in folders]
return r
def update_required(src_filename, output_filename):
return not os.path.exists(output_filename) or os.path.getmtime(src_filename) > os.path.getmtime(output_filename)
def get_args():
""" Get command line arguments """
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('notes', type=pathlib.Path)
parser.add_argument('-o', '--output-dir', type=pathlib.Path, default='web')
parser.add_argument('-t', '--template', type=pathlib.Path, default=pathlib.Path('/opt/notes2web/templates/article.html'))
parser.add_argument('-H', '--template-text-head', type=pathlib.Path, default=pathlib.Path('/opt/notes2web/templates/textarticlehead.html'))
parser.add_argument('-f', '--template-text-foot', type=pathlib.Path, default=pathlib.Path('/opt/notes2web/templates/textarticlefoot.html'))
parser.add_argument('-i', '--template-index-head', type=pathlib.Path, default=pathlib.Path('/opt/notes2web/templates/indexhead.html'))
parser.add_argument('-I', '--template-index-foot', type=pathlib.Path, default=pathlib.Path('/opt/notes2web/templates/indexfoot.html'))
parser.add_argument('-s', '--stylesheet', type=pathlib.Path, default=pathlib.Path('/opt/notes2web/styles.css'))
parser.add_argument('--home_index', type=pathlib.Path, default=pathlib.Path('/opt/notes2web/templates/home_index.html'))
parser.add_argument('--permalink_index', type=pathlib.Path, default=pathlib.Path('/opt/notes2web/templates/permalink_index.html'))
parser.add_argument('-e', '--extra-index-content', type=pathlib.Path, default=pathlib.Path('/opt/notes2web/templates/extra_index_content.html'))
parser.add_argument('-n', '--index-article-names', action='append', default=['index.md'])
parser.add_argument('-F', '--force', action="store_true", help="Generate new output html even if source file was modified before output html")
parser.add_argument('--fuse', type=pathlib.Path, default=pathlib.Path('/opt/notes2web/fuse.js'))
parser.add_argument('--searchjs', type=pathlib.Path, default=pathlib.Path('/opt/notes2web/search.js'))
parser.add_argument('--indexsearchjs', type=pathlib.Path, default=pathlib.Path('/opt/notes2web/indexsearch.js'))
parser.add_argument('--permalinkjs', type=pathlib.Path, default=pathlib.Path('/opt/notes2web/permalink.js'))
parser.add_argument('--tocsearchjs', type=pathlib.Path, default=pathlib.Path('/opt/notes2web/toc_search.js'))
parser.add_argument('--toc-depth', type=int, default=6, dest='toc_depth')
return parser.parse_args()
def main(args):
""" Entry point for script """
with open(args.template_text_foot) as fp:
TEXT_ARTICLE_TEMPLATE_FOOT = fp.read()
with open(args.template_text_head) as fp:
TEXT_ARTICLE_TEMPLATE_HEAD = fp.read()
with open(args.template_index_foot) as fp:
INDEX_TEMPLATE_FOOT = fp.read()
with open(args.template_index_head) as fp:
INDEX_TEMPLATE_HEAD = fp.read()
with open(args.extra_index_content) as fp:
EXTRA_INDEX_CONTENT = fp.read()
if args.output_dir.is_file():
print(f"Output directory ({args.output_dir}) cannot be a file.")
args.output_dir.mkdir(parents=True, exist_ok=True)
notes_license = "This note has no copyright license.",
print(f"{notes_license=}")
license_path = args.notes.joinpath("LICENSE")
if license_path.exists():
with open(license_path) as fp:
notes_license = fp.read()
markdown_files, plaintext_files, other_files = get_files(args.notes)
all_entries=[]
dirs_with_index_article = []
tag_dict = {}
permalink_to_filepath = {}
print(f"{markdown_files=}")
for filename in markdown_files:
print(f"{filename=}")
# calculate output filename
output_filename = args.output_dir.joinpath('notes').joinpath(
pathlib.Path(filename).relative_to(args.notes)
).with_suffix('.html')
if os.path.basename(filename) in args.index_article_names:
output_filename = output_filename.parent.joinpath('index.html')
dirs_with_index_article.append(str(output_filename.parent))
print(f"{output_filename=}")
# extract tags from frontmatter, save to tag_dict
fm = frontmatter.load(filename)
if isinstance(fm.get('tags'), list):
for tag in list(set(fm.get('tags') + get_inherited_tags(filename, args.notes))):
t = {
'path': str(pathlib.Path(output_filename).relative_to(args.output_dir)),
'title': fm.get('title') or pathlib.Path(filename).name
}
if tag in tag_dict.keys():
tag_dict[tag].append(t)
else:
tag_dict[tag] = [t]
# find headers in markdown
with open(filename) as fp:
lines = fp.read().split('\n')
header_lines = []
for line in lines:
if re.match('^#{1,6} \S', line):
header_lines.append(" ".join(line.split(" ")[1:]))
all_entries.append({
'path': '/' + str(pathlib.Path(*pathlib.Path(output_filename).parts[1:])),
'title': fm.get('title') or pathlib.Path(filename).name,
'tags': list(set(fm.get('tags'))),
'headers': header_lines,
'uuid': fm.get('uuid')
})
if 'uuid' in fm.keys():
permalink_to_filepath[fm['uuid']] = all_entries[-1]['path']
# update file if required
if update_required(filename, output_filename) or args.force:
filehistory = git_filehistory(args.notes, filename)
with open(filename) as fp:
article = frontmatter.load(fp)
article['tags'] += get_inherited_tags(filename, args.notes)
article['tags'] = sorted(list(set(article['tags'])))
article['filehistory'] = filehistory
article['licenseFull'] = notes_license
html = pypandoc.convert_text(frontmatter.dumps(article), 'html', format='md', extra_args=[
f'--template={args.template}',
'--mathjax',
'--toc', f'--toc-depth={args.toc_depth}'
])
pathlib.Path(output_filename).parent.mkdir(parents=True, exist_ok=True)
with open(output_filename, 'w+') as fp:
fp.write(html)
print(f"{plaintext_files=}")
for filename in plaintext_files:
filehistory = git_filehistory(args.notes, filename)
title = os.path.basename(filename)
output_filename = str(
args.output_dir.joinpath('notes').joinpath(
pathlib.Path(filename).relative_to(args.notes)
)
) + '.html'
print(f"{output_filename=}")
pathlib.Path(output_filename).parent.mkdir(parents=True, exist_ok=True)
html = re.sub(r'\$title\$', title, TEXT_ARTICLE_TEMPLATE_HEAD)
html = re.sub(r'\$h1title\$', title, html)
html = re.sub(r'\$raw\$', os.path.basename(filename), html)
html = re.sub(r'\$licenseFull\$', notes_license, html)
html = html.replace('$filehistory$', filehistory)
with open(filename) as fp:
html += fp.read().replace("<", "&lt;").replace(">", "&gt;")
html += TEXT_ARTICLE_TEMPLATE_FOOT
with open(output_filename, 'w+') as fp:
fp.write(html)
all_entries.append({
'path': str(pathlib.Path(*pathlib.Path(output_filename).parts[1:])),
'title': title,
'tags': [get_inherited_tags(filename, args.notes)],
'headers': []
})
print(f"{other_files=}")
for filename in other_files:
output_filename = str(
args.output_dir.joinpath('notes').joinpath(
pathlib.Path(filename).relative_to(args.notes)
)
)
title = os.path.basename(filename)
pathlib.Path(output_filename).parent.mkdir(parents=True, exist_ok=True)
all_entries.append({
'path': str(pathlib.Path(*pathlib.Path(output_filename).parts[1:])),
'title': title,
'tags': [get_inherited_tags(filename, args.notes)],
'headers': []
})
shutil.copyfile(filename, output_filename)
tagdir = args.output_dir.joinpath('.tags')
tagdir.mkdir(parents=True, exist_ok=True)
for tag in tag_dict.keys():
html = re.sub(r'\$title\$', f'{tag}', INDEX_TEMPLATE_HEAD)
html = re.sub(r'\$h1title\$', f'tag: {tag}', html)
html = re.sub(r'\$extra_content\$', '', html)
for entry in tag_dict[tag]:
entry['path'] = '/' + entry['path']
html += f"<div class=\"article\"><a href=\"{entry['path']}\">{entry['title']}</a></div>"
html += re.sub('\$data\$', json.dumps(tag_dict[tag]), INDEX_TEMPLATE_FOOT)
with open(tagdir.joinpath(f'{tag}.html'), 'w+') as fp:
fp.write(html)
dirs_to_index = [args.output_dir.name] + get_dirs_to_index(args.output_dir)
print(f"{dirs_to_index=}")
print(f"{dirs_with_index_article=}")
for d in dirs_to_index:
print(f"{d in dirs_with_index_article=} {d=}")
if d in dirs_with_index_article:
continue
directory = pathlib.Path(d)
paths = os.listdir(directory)
#print(f"{paths=}")
indexentries = []
for p in paths:
path = pathlib.Path(p)
#print(f"{path=}")
if p in [ 'index.html', '.git' ]:
continue
fullpath = directory.joinpath(path)
title = path.name
if path.suffix == '.html':
with open(fullpath) as fp:
soup = bs(fp.read(), 'html.parser')
try:
title = soup.find('title').get_text() or pathlib.Path(path).name
except AttributeError:
title = pathlib.Path(path).stem
elif fullpath.is_dir():
title = path
elif is_plaintext(fullpath):
# don't add plaintext files to index, since they have a html wrapper
continue
if str(title).strip() == '':
title = path
indexentries.append({
'title': str(title),
'path': './' + str(path),
'isdirectory': fullpath.is_dir()
})
indexentries.sort(key=lambda entry: str(entry['title']).lower())
indexentries.sort(key=lambda entry: entry['isdirectory'], reverse=True)
html = re.sub(r'\$title\$', str(directory), INDEX_TEMPLATE_HEAD)
html = re.sub(r'\$h1title\$', str(directory), html)
html = re.sub(r'\$extra_content\$',
EXTRA_INDEX_CONTENT if directory == args.notes else '',
html
)
for entry in indexentries:
html += (
'<li class="article">'
f'<a href="{entry["path"]}"><p>'
f'{entry["title"]}{"/" if entry["isdirectory"] else ""}'
'</p></a>'
'</li>'
)
html += re.sub(r'\$data\$', json.dumps(indexentries), INDEX_TEMPLATE_FOOT)
with open(directory.joinpath('index.html'), 'w+') as fp:
fp.write(html)
shutil.copyfile(args.stylesheet, args.output_dir.joinpath('styles.css'))
shutil.copyfile(args.fuse, args.output_dir.joinpath('fuse.js'))
shutil.copyfile(args.searchjs, args.output_dir.joinpath('search.js'))
shutil.copyfile(args.indexsearchjs, args.output_dir.joinpath('indexsearch.js'))
shutil.copyfile(args.tocsearchjs, args.output_dir.joinpath('toc_search.js'))
shutil.copyfile(args.permalinkjs, args.output_dir.joinpath('permalink.js'))
with open(args.output_dir.joinpath('index.html'), 'w+') as fp:
with open(args.home_index) as fp2:
html = re.sub(r'\$title\$', args.output_dir.parts[0], fp2.read())
html = re.sub(r'\$h1title\$', args.output_dir.parts[0], html)
html = re.sub(r'\$n2w_commit\$', N2W_COMMIT, html)
html = re.sub(r'\$notes_git_head_sha1\$', git_head_sha1(args.notes), html)
html = re.sub(r'\$data\$', json.dumps(all_entries), html)
fp.write(html)
permalink_dir = args.output_dir.joinpath('permalink')
permalink_dir.mkdir(exist_ok=True)
with open(args.permalink_index) as fp:
html = re.sub(r'\$data\$', json.dumps(permalink_to_filepath), fp.read())
with open(permalink_dir.joinpath('index.html'), 'w+') as fp:
fp.write(html)
print(tag_dict)
return 0
if __name__ == '__main__':
try:
sys.exit(main(get_args()))
except KeyboardInterrupt:
sys.exit(0)

122
readme.md
View File

@ -1,4 +1,4 @@
# notes2web
# gronk
View your notes as a static html site. Browse a live sample of it [here](https://notes.alv.cx).
@ -9,15 +9,11 @@ Tested with [pandoc v2.19.2](https://github.com/jgm/pandoc/releases/tag/2.19.2).
## Why?
I want to be able to view my notes in a more convenient way.
I was already writing them in Pandoc markdown and could view them as PDFs but that wasn't quite
doing it for me:
- It was inconvenient to flick through multiple files of notes to find the right PDF
- It was annoying to sync to my phone
- PDFs do not scale so they were hard to read on smaller screens
- Probably more reasons I can't think of right now
- Fun
- View notes as a website, on any device
- Write notes with Pandoc markdown
- Easily share notes
- Lightweight HTML generated
- Minimal JavaScript
## Install
@ -31,27 +27,42 @@ doing it for me:
1. Run `make install` as root
## Things to Remember Whilst Writing Notes
## Other Things to Know
- notes2web reads the following YAML [frontmatter](https://jekyllrb.com/docs/front-matter/) variable:
- `author` --- The person(s) who wrote the article
- `tags` --- A YAML list of tags which the article relates to - this is used for browsing and also
searching
- `title` --- The title of the article
- `uuid` --- A unique identifier used for permalinks. More below.
- `lecture_slides` --- a list of paths pointing to lecture slides used while taking notes
- `lecture_notes` --- a list of paths pointing to other notes used while taking notes
- notes2web indexes [ATX-style headings](https://pandoc.org/MANUAL.html#atx-style-headings) for
- gronk indexes [ATX-style headings](https://pandoc.org/MANUAL.html#atx-style-headings) for
searching
- notes2web attempts to display file history through the `git log` command
- notes2web looks for the plaintext file `LICENSE` in the root directory of your notes
- gronk looks for the plaintext file `LICENSE` in the root directory of your notes
This is optional but if you would like to add a license you can find one
[here](https://choosealicense.com).
### Permalinks
## Custom Directory Index
To add custom content to a directory index, put it in a file called `index.md` under the directory.
You can set the following frontmatter variables to customise the directory index of a directory:
| variable | default value | description |
|------------------------|-------------------|--------------------------------------------------------------------------------------------|
| `tags` | `[]` | list of tags, used by search and inherited by any notes and subdirectories |
| `uuid` | none | unique id to reference directory, used for permalinking |
| `content_after_search` | `false` | show custom content in `index.md` after search bar and directory index |
| `automatic_index` | `true` | show the automatically generated directory index. required for search bar to function. |
| `search_bar` | `true` | show search bar to search directory items. requires `automatic_index` (enabled by default) |
## Notes Metadata
gronk reads the following YAML [frontmatter](https://jekyllrb.com/docs/front-matter/) variables for metadata:
| variable | description |
|------------------|---------------------------------------------------------------------------------------|
| `author` | The person(s) who wrote the article |
| `tags` | A YAML list of tags which the article relates to - this is used for browsing and also |
| `title` | The title of the article |
| `uuid` | A unique identifier used for permalinks. |
| `lecture_slides` | a list of paths pointing to lecture slides used while taking notes |
| `lecture_notes` | a list of paths pointing to other notes used while taking notes |
## Permalinks
Permalinks are currently rather basic and requires JavaScript to be enabled on the local computer.
In order to identify documents between file changes, a unique identifier is used to identify a file.
@ -63,59 +74,36 @@ The included `n2w_add_uuid.py` will add a UUID to a markdown file which does not
already.
Combine it with `find` to UUIDify all your markdown files (but make a backup first).
### Inherited Properties
## Custom Styling
Notes can inherit a some properties from their parent folder(s) by creating a `.n2w.yml` file in a
folder.
To completely replace the existing styling, set the environment variable `GRONK_CSS_DIR` to another directory with
a file called `styles.css`.
#### Tags
To add additional styling, the default styling will attempt to import `styles.css` from the root of the notes
directory.
If you have a folder `uni` with all you university notes, you might want all the files in there to
be tagged `uni`:
To add additional content to the homepage, create a file called `index.md` at the top level of your notes directory.
To set the HTML `title` tag, set `title` in the frontmatter of `index.md`:
`NOTES_PATH/uni/.n2w.yaml`:
```markdown
---
title: "alv's notes"
---
```yaml
itags: [ university ]
# alv's notes
these notes are probably wrong
```
## CLI Usage
```
$ notes2web.py notes_directory
$ gronk.py notes_directory
```
Output of `notes2web.py --help`:
Output of `gronk.py --help`:
```
usage: notes2web.py [-h] [-o OUTPUT_DIR] [-t TEMPLATE] [-H TEMPLATE_TEXT_HEAD]
[-f TEMPLATE_TEXT_FOOT] [-i TEMPLATE_INDEX_HEAD]
[-I TEMPLATE_INDEX_FOOT] [-s STYLESHEET]
[--home_index HOME_INDEX] [-e EXTRA_INDEX_CONTENT]
[-n INDEX_ARTICLE_NAMES] [-F] [--fuse FUSE]
[--searchjs SEARCHJS]
notes
positional arguments:
notes
optional arguments:
-h, --help show this help message and exit
-o OUTPUT_DIR, --output-dir OUTPUT_DIR
-t TEMPLATE, --template TEMPLATE
-H TEMPLATE_TEXT_HEAD, --template-text-head TEMPLATE_TEXT_HEAD
-f TEMPLATE_TEXT_FOOT, --template-text-foot TEMPLATE_TEXT_FOOT
-i TEMPLATE_INDEX_HEAD, --template-index-head TEMPLATE_INDEX_HEAD
-I TEMPLATE_INDEX_FOOT, --template-index-foot TEMPLATE_INDEX_FOOT
-s STYLESHEET, --stylesheet STYLESHEET
--home_index HOME_INDEX
-e EXTRA_INDEX_CONTENT, --extra-index-content EXTRA_INDEX_CONTENT
-n INDEX_ARTICLE_NAMES, --index-article-names INDEX_ARTICLE_NAMES
-F, --force Generate new output html even if source file was
modified before output html
--fuse FUSE
--searchjs SEARCHJS
```
TODO add cli output
The command will generate a website in the `output-dir` directory (`./web` by default).
It will then generate a list of all note files and put it in `index.html`.

14
requirements-dev.txt Normal file
View File

@ -0,0 +1,14 @@
beautifulsoup4==4.9.3
editfrontmatter==0.0.1
greenlet==2.0.2
Jinja2==3.0.3
MarkupSafe==2.1.0
msgpack==1.0.5
oyaml==1.0
pynvim==0.4.3
pypandoc==1.5
python-frontmatter==1.0.0
python-magic==0.4.24
regex==2021.11.10
soupsieve==2.2.1
PyYAML==6.0.1

View File

@ -1,11 +1,19 @@
beautifulsoup4==4.9.3
certifi==2023.7.22
charset-normalizer==3.2.0
editfrontmatter==0.0.1
gitdb==4.0.10
GitPython==3.1.36
idna==3.4
Jinja2==3.0.3
MarkupSafe==2.1.0
oyaml==1.0
pypandoc==1.5
python-frontmatter==1.0.0
python-magic==0.4.24
PyYAML==5.4.1
PyYAML==6.0.1
regex==2021.11.10
requests==2.31.0
smmap==5.0.1
soupsieve==2.2.1
urllib3==2.0.4

View File

@ -0,0 +1,9 @@
{% extends "article.html" %}
{% block body_content %}
<p> This file was not rendered by gronk because it is a plaintext file, not a markdown
file.
You access the raw file <a href="{{ raw_link }}">here</a>.
Below is an unformatted representation of the file:
</p>
<pre>{{ raw_content }}</pre>
{% endblock %}

View File

@ -1,8 +1,5 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta charset="utf-8">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" />
<link rel="stylesheet" type="text/css" href="/styles.css" />
{% extends "base.html" %}
{% block head %}
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script>
MathJax = {
@ -12,63 +9,57 @@
}
</script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<title>$title$</title>
</head>
{% endblock %}
<body>
<div id="contentWrapper">
<div id="content">
<p class="smallText metadata">
title: $title$
</p>
$if(lecture_slides)$
<p class="smallText metadata"> lecture_slides: [
$for(lecture_slides)$
<a href="$lecture_slides$">$lecture_slides$</a>$sep$,
$endfor$
]</p>
$endif$
$if(lecture_notes)$
<p class="smallText metadata"> lecture_notes: [
$for(lecture_notes)$
<a href="$lecture_notes$">$lecture_notes$</a>$sep$,
$endfor$
]</p>
$endif$
{% block content %}
<p class="smallText metadata"> title: {{ title }} </p>
{% if lecture_slides %}
<p class="smallText metadata"> lecture_slides: [
{% for slide in lecture_slides %}
<a href="{{ slide }}">{{ slide }}</a>{% if loop.nextitem %},{% endif %}
{% endfor %}
]</p>
{% endif %}
{% if lecture_notes %}
<p class="smallText metadata"> lecture_notes: [
{% for note in lecture_notes %}
<a href="{{ note }}">{{ note }}</a>{% if loop.nextitem %},{% endif %}
{% endfor %}
]</p>
{% endif %}
<p class="smallText metadata">
uuid: $uuid$ (<a href="/permalink?uuid=$uuid$">permalink</a>)
</p>
<p class="smallText metadata"> tags: [
$for(tags)$
<a href="/.tags/$tags$.html">$tags$</a>$sep$,
$endfor$
]</p>
<p class="smallText metadata">
written by $for(author)$$author$$sep$, $endfor$
</p>
<p class="smallText metadata">
syntax highlighting based on <a href="https://pygments.org/">Pygments'</a> default
colors
</p>
<p class="smallText metadata">
page generated by <a href="https://git.alv.cx/alvierahman90/notes2web">notes2web</a>
</p>
<details id="commitLog">
<summary class="smallText">
Commit log (file history)
</summary>
$filehistory$
</details>
<details id="license">
<summary class="smallText">
License
</summary>
<pre>$licenseFull$</pre>
</details>
$body$
</div>
</div>
<script src="/fuse.js"> </script>
<script src="/toc_search.js"> </script>
</body>
{% if uuid %}
<p class="smallText metadata"> uuid: {{ uuid }} (<a href="/permalink?uuid={{ uuid }}">permalink</a>) </p>
{% endif %}
<p class="smallText metadata"> tags: [
{% for tag in tags %}
<a href="/tags/{{ tag }}">{{ tag }}</a>{% if loop.nextitem %},{% endif %}
{% endfor %}
]</p>
<p class="smallText metadata">
{% if author is string %}
written by: {{ author }}
{% elif author is iterable %}
written by: [ {% for auth in author %}{{ auth }}{% if loop.nextitem %}, {% endif %}{% endfor %} ]
{% endif %}
</p>
<p class="smallText metadata">
syntax highlighting based on <a href="https://pygments.org/">Pygments'</a> default
colors
</p>
<p class="smallText metadata">
page generated by <a href="https://git.alv.cx/alvierahman90/gronk">gronk</a>
</p>
{% if license %}
<details id="license">
<summary class="smallText">
License
</summary>
<pre>{{ license }}</pre>
</details>
{% endif %}
{% block body_content %}
{{ content|safe }}
{% endblock %}
{% endblock %}

18
templates/base.html Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta charset="UTF-8">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" />
<link rel="stylesheet" type="text/css" href="/css/styles.css" />
<title>{{ title }}</title>
{% block head %}
{% endblock %}
</head>
<body>
<div id="content">
{% block content %}
{% endblock %}
<p class="smallText"> page generated by <a href="https://github.com/alvierahman90/gronk">gronk</a> (commit {{ gronk_commit }}) {% if notes_git_head_sha1 %}notes commit {{ notes_git_head_sha1 }}{% endif %}</p>
</div>
</body>

View File

@ -1,4 +0,0 @@
<p>
These are my personal notes. Correctness is not guaranteed.
Browse by tag <a href="/.tags">here</a>.
</p>

18
templates/home.html Normal file
View File

@ -0,0 +1,18 @@
{% extends "article.html" %}
{% block content %}
{{ post['content']|safe }}
<p>
Browse <a href="/notes">here</a> or by tag <a href="/tags">here</a>.
</p>
<div id="searchWrapper">
<input autocomplete="off" placeholder="search" id="search" autofocus>
</div>
<p class="smallText" style="margin-top: 0; text-align: center;"> Press <kbd>Enter</kbd> to open first result or <kbd>Shift</kbd>+<kbd>Enter</kbd> to open in new tab</p>
<div id="results">
</div>
<script src="/js/fuse.js"> </script>
<script> const search_data = {{ search_data|tojson }} </script>
<script src="/js/search.js"> </script>
{% endblock %}

View File

@ -1,28 +0,0 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta charset="utf-8">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" />
<link rel="stylesheet" type="text/css" href="/styles.css" />
<title>$title$</title>
</head>
<body>
<div id="content">
<h1>$h1title$</h1>
<p>
These are my personal notes. Correctness is not guaranteed.
Browse <a href="/notes">here</a> or by tag <a href="/.tags">here</a>.
</p>
<div id="searchWrapper">
<input autocomplete="off" placeholder="search" id="search" autofocus>
</div>
<p class="smallText" style="margin-top: 0; text-align: center;"> Press <kbd>Enter</kbd> to open first result or <kbd>Shift</kbd>+<kbd>Enter</kbd> to open in new tab</p>
<div id="results">
</div>
<p class="smallText"> page generated by <a href="https://github.com/alvierahman90/notes2web">notes2web</a> (commit $n2w_commit$) notes commit $notes_git_head_sha1$</p>
</div>
<script src="/fuse.js"> </script>
<script> const data = $data$ </script>
<script src="/search.js"> </script>
</body>

View File

@ -1,14 +1,33 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta charset="utf-8">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" />
<link rel="stylesheet" type="text/css" href="/styles.css" />
<title>$title$</title>
</head>
<body>
<h1>$h1title$</h1>
<div id="content">
$body$
<p style="font-size: 0.7em;"> page generated by <a href="https://github.com/alvierahman90/notes2web">notes2web</a></p>
{% extends "base.html" %}
{% block content %}
<h1>{{ title }}</h1>
{% if not content_after_search %}
{{ content|safe }}
{% endif %}
{% if automatic_index %}
{% if search_bar %}
<div id="searchWrapper">
<input id="search" placeholder="search" autocomplete="off" autofocus>
</div>
</body>
<p class="searchSmallText" style="margin-top: 0; text-align: center">
Press (<kbd>Shift</kbd>+)<kbd>Enter</kbd> to open first result (in new tab)
</p>
{% endif %}
<ul id="searchResults" class="buttonlist">
<li class="article"><a href=".."><p>../</p></a></li>
{% for entry in index_entries %}
<li class="article"><a href="{{ entry['path'] }}"><p>{{ entry['title'] }}{% if entry['is_dir'] %}/{% endif %}</p></a></li>
{% endfor %}
</ul>
{% endif %}
{% if content_after_search %}
{{ content|safe }}
{% endif %}
<script src="/js/fuse.js"> </script>
<script> const search_data = {{ index_entries|tojson }} </script>
<script src="/js/indexsearch.js"> </script>
{% endblock %}

View File

@ -1,7 +0,0 @@
</ul>
<p style="font-size: 0.7em;"> page generated by <a href="https://github.com/alvierahman90/notes2web">notes2web</a></p>
</div>
<script src="/fuse.js"> </script>
<script> const data = $data$ </script>
<script src="/indexsearch.js"> </script>
</body>

View File

@ -1,21 +0,0 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta charset="utf-8">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" />
<link rel="stylesheet" type="text/css" href="/styles.css" />
<title>$title$</title>
</head>
<body>
<div id="content">
<h1>$title$</h1>
$extra_content$
<div id="searchWrapper">
<input id="search" placeholder="search" autocomplete="off" autofocus>
</div>
<p class="searchSmallText" style="margin-top: 0; text-align: center">
Press (<kbd>Shift</kbd>+)<kbd>Enter</kbd> to open first result (in new tab)
</p>
<ul id="searchResults" class="buttonlist">
<li class="article"><a href=".."><p>../</p></a></li>

View File

@ -1,5 +0,0 @@
author: {{ author }}
date: {{ date }}
title: {{ title }}
tags: {{ tags }}
uuid: {{ uuid }}

10
templates/permalink.html Normal file
View File

@ -0,0 +1,10 @@
{% block content %}
<p>
You should be being redirected...
Otherwise, click <a id="manual_redirect">here</a>.
</p>
<p class="smallText"> page generated by <a href="https://github.com/alvierahman90/gronk">gronk</a></p>
<script> const data = {{ data|tojson }} </script>
<script src="/js/permalink.js"> </script>
{% endblock %}

View File

@ -1,19 +0,0 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta charset="utf-8">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" />
<link rel="stylesheet" type="text/css" href="/styles.css" />
<title></title>
</head>
<body>
<div id="content">
<p>
You should be being redirected...
Otherwise, click <a id="manual_redirect">here</a>.
</p>
<p class="smallText"> page generated by <a href="https://github.com/alvierahman90/notes2web">notes2web</a></p>
</div>
<script> const data = $data$ </script>
<script src="/permalink.js"> </script>
</body>

View File

@ -1,3 +0,0 @@
</pre>
</div>
</body>

View File

@ -1,34 +0,0 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta charset="utf-8">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" />
<link rel="stylesheet" type="text/css" href="/styles.css" />
<title>$title$</title>
</head>
<body>
<div id="content">
<div id="header">
<p class="smallText">
page generated by <a href="https://git.alv.cx/alvierahman90/notes2web">notes2web</a>
</p>
<details>
<summary class="smallText">
Commit log (file history)
</summary>
$filehistory$
</details>
<details>
<summary class="smallText">
License
</summary>
<pre>$licenseFull$</pre>
</details>
</div>
<h1>$title$</h1>
<p> This file was not rendered by notes2web because it is a plaintext file, not a markdown
file.
You access the raw file <a href="$raw$">here</a>.
Below is an unformatted representation of the file:
</p>
<pre>