Compare commits

..

1 Commits
v2 ... main

Author SHA1 Message Date
0f64ca5849
archive notice 2024-01-02 18:40:17 +00:00
31 changed files with 719 additions and 889 deletions

View File

@ -1,4 +0,0 @@
.git
.env
env
**/__pycache__

4
.gitignore vendored
View File

@ -1,7 +1,5 @@
*.swp
.env
env
n
web
planning.txt
**/__pycache__
__pycache__

View File

@ -1,31 +0,0 @@
FROM python:3.12-bookworm
ARG ARCH=
ENV BUILDARCH=${ARCH}
ENV GRONK_CSS_DIR=./css
ENV GRONK_JS_DIR=./js
ENV GRONK_TEMPLATES_DIR=./templates
WORKDIR /usr/src/app
RUN mkdir /notes
RUN mkdir /web
VOLUME /usr/src/app/notes
VOLUME /usr/src/app/web
RUN apt-get update \
&& wget -O ./pandoc.deb https://github.com/jgm/pandoc/releases/download/3.1.11/pandoc-3.1.11-1-${BUILDARCH}.deb \
&& apt install -y -f ./pandoc.deb \
&& rm ./pandoc.deb
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
RUN useradd -Ms /bin/nologin user
USER user
COPY . .
CMD [ "python3", "-u", "gronk.py", "--output-dir", "./web", "./notes" ]

View File

@ -1,11 +1,16 @@
install:
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
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
pip3 install -r requirements.txt --break-system-packages
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
chmod +x /usr/local/bin/notes2web.py
uninstall:
rm -rf /usr/local/bin/gronk.py /usr/local/bin/gronk_add_uuid.py /opt/gronk
rm -rf /usr/local/bin/notes2web.py /usr/local/bin/n2w_add_uuid.py /opt/notes2web

View File

@ -1,11 +0,0 @@
version: '3'
services:
gronk:
build:
context: '.'
args:
ARCH: ${ARCH}
volumes:
- '${SOURCE}:/usr/src/app/notes'
- '${OUTPUT}:/usr/src/app/web'

View File

552
gronk.py
View File

@ -1,552 +0,0 @@
#!/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("GRONK_CSS_DIR", "/opt/gronk/css"))
GRONK_JS_DIR = Path(os.getenv("GRONK_JS_DIR", "/opt/gronk/js"))
GRONK_TEMPLATES_DIR = Path(
os.getenv("GRONK_TEMPLATES_DIR", "/opt/gronk/templates/"))
JINJA_ENV = jinja2.Environment(
loader=jinja2.FileSystemLoader(searchpath=GRONK_TEMPLATES_DIR),
autoescape=jinja2.select_autoescape)
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 'readme.md' in [f.name for f in filepath.iterdir()]:
with open(filepath.joinpath('readme.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 '.git' in path.parts:
continue
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('readme.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 """
start_time = time.time()
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 readme.md as index as it is used for directory
if file == "readme.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)
elapsed_time = time.time() - start_time
print(f"generated notes {elapsed_time=}")
return 0
def start_pandoc_server():
"""
attempt to get the version of pandoc server in a loop until it is
successful and return version as string
"""
start_time = time.time()
process = subprocess.Popen(["/usr/bin/pandoc-server"],
stdout=subprocess.PIPE)
version = None
while True:
try:
resp = requests.get(f"{PANDOC_SERVER_URL}/version")
version = resp.content.decode('utf-8')
break
except requests.ConnectionError:
time.sleep(0.1)
rc = process.poll()
if rc is not None:
print(f"PANDOC SERVER FAILED TO START: {rc=}")
print(process.stdout.read().decode("utf-8"))
raise Exception("Pandoc server failed to start")
elapsed_time = time.time() - start_time
print(f"pandoc-server started {version=} {elapsed_time=}")
return process
# TODO implement useful logging and debug printing
if __name__ == '__main__':
pandoc_process = start_pandoc_server()
try:
sys.exit(main(get_args()))
except KeyboardInterrupt:
sys.exit(0)
finally:
pandoc_process.kill()

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

@ -1,12 +1,10 @@
#!/usr/bin/env python3
import pathlib
import sys
import uuid
import editfrontmatter
import frontmatter
import pathlib
import sys
import uuid
def get_args():
""" Get command line arguments """
@ -14,28 +12,24 @@ def get_args():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('filename', type=pathlib.Path)
parser.add_argument('-w',
'--write',
action='store_true',
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')
return parser.parse_args()
def main(args):
""" Entry point for script """
template_str = "\n".join([
"author: {{ author }}"
"date: {{ date }}"
"title: {{ title }}"
"tags: {{ tags }}"
"uuid: {{ uuid }}"
])
with open(args.template) as fp:
template_str=fp.read()
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())

428
notes2web.py Executable file
View File

@ -0,0 +1,428 @@
#!/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)

144
readme.md
View File

@ -1,4 +1,6 @@
# gronk
> notes2web is now called [gronk](https://github.com/alvierahman90/gronk) and development has moved
# notes2web
View your notes as a static html site. Browse a live sample of it [here](https://notes.alv.cx).
@ -9,77 +11,43 @@ Tested with [pandoc v2.19.2](https://github.com/jgm/pandoc/releases/tag/2.19.2).
## Why?
- View notes as a website, on any device
- Easily share notes
- Powered by Pandoc, and therefore supports [Pandoc's markdown](https://pandoc.org/MANUAL.html#pandocs-markdown) (I mainly care about equations)
- [flatnotes](https://github.com/Dullage/flatnotes) is cool but I really would rather type my notes in Vim
- Lightweight HTML generated
- Minimal JavaScript
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
## Install
### Docker
Run the following, modifing the `-v` arguments as needed to mount the correct folders and
setting the value of `ARCH` to either `amd64` or `arm64` as appropriate.
```
docker build . -t gronk --build-arg ARCH=amd64
docker run -v ./n:/usr/src/app/notes -v ./web:/usr/src/app/web gronk
```
#### Compose
A [docker compose file](./docker-compose.yml) file has been provided.
Set the following environment variables (or create a .env file) and run `docker compose up`:
- `ARCH`
- `SOURCE`
- `OUTPUT`
### Locally
0. Install [Pandoc](https://pandoc.org/index.html) and [Pip](https://github.com/pypa/pip), python3-dev, and a C compiler
1. `sudo make install`
1. Run `make install` as root
## Other Things to Know
## Things to Remember Whilst Writing Notes
- gronk indexes [ATX-style headings](https://pandoc.org/MANUAL.html#atx-style-headings) for
- 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
- gronk looks for the plaintext file `LICENSE` in the root directory of your notes
- `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
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
## Custom Directory Index and Metadata
This is optional but if you would like to add a license you can find one
[here](https://choosealicense.com).
To add custom content to a directory index, put it in a file called `readme.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 `readme.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
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.
@ -91,46 +59,58 @@ 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).
## Custom Styling
### Inherited Properties
To completely replace the existing styling, set the environment variable `GRONK_CSS_DIR` to another directory with
a file called `styles.css`.
Notes can inherit a some properties from their parent folder(s) by creating a `.n2w.yml` file in a
folder.
To add additional styling, the default styling will attempt to import `styles.css` from the root of the notes
directory.
#### Tags
To add additional content to the homepage, create a file called `readme.md` at the top level of your notes directory.
To set the HTML `title` tag, set `title` in the frontmatter of `readme.md`:
If you have a folder `uni` with all you university notes, you might want all the files in there to
be tagged `uni`:
```markdown
---
title: "alv's notes"
---
`NOTES_PATH/uni/.n2w.yaml`:
# alv's notes
these notes are probably wrong
```yaml
itags: [ university ]
```
## CLI Usage
```
$ gronk.py notes_directory
$ notes2web.py notes_directory
```
Output of `gronk.py --help`:
Output of `notes2web.py --help`:
```
usage: gronk.py [-h] [-o OUTPUT_DIR] [-F] notes
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
options:
optional arguments:
-h, --help show this help message and exit
-o OUTPUT_DIR, --output-dir OUTPUT_DIR
-F, --force Generate new output html even if source file was modified before output
html
-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
```
The command will generate a website in the `output-dir` directory (`./web` by default).
@ -149,3 +129,5 @@ Then you just have to point a webserver at `output-dir`.
Default synatx highlighting is based off [Pygments](https://pygments.org/)' default theme and
made using Pandoc v2.7.2.
I found the theme [here](https://github.com/tajmone/pandoc-goodies/blob/master/skylighting/css/built-in-styles/pygments.css).
Pretty sure the link colours are taken from [thebestmotherfucking.website](https://thebestmotherfucking.website/).

View File

@ -1,14 +0,0 @@
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,10 +1,5 @@
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
@ -14,8 +9,3 @@ python-magic==0.4.24
PyYAML==5.3.1
regex==2021.11.10
soupsieve==2.2.1
regex==2021.11.10
requests==2.31.0
smmap==5.0.1
soupsieve==2.2.1
urllib3==2.0.4

View File

@ -1,13 +1,3 @@
/*
* 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"
@ -16,11 +6,11 @@ const TITLE = "title"
const SEARCH_TIMEOUT_MS = 100
var SEARCH_TIMEOUT_ID = -1
const fuse = new Fuse(search_data, {
const fuse = new Fuse(data, {
keys: [
{
name: HEADERS,
weight: 1
weight: 0.2
},
{
name: PATH,
@ -28,11 +18,11 @@ const fuse = new Fuse(search_data, {
},
{
name: TAGS,
weight: 0.5
weight: 0.1
},
{
name: TITLE,
weight: 2
weight: 4
}
],
includeMatches: true,

View File

@ -3,7 +3,6 @@
@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;

View File

@ -1,9 +0,0 @@
{% 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,5 +1,8 @@
{% extends "base.html" %}
{% block head %}
<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" />
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script>
MathJax = {
@ -9,57 +12,63 @@
}
</script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
{% endblock %}
<title>$title$</title>
</head>
{% block content %}
<p class="smallText metadata"> title: {{ title }} </p>
{% if lecture_slides %}
<body>
<div id="contentWrapper">
<div id="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 %}
$for(lecture_slides)$
<a href="$lecture_slides$">$lecture_slides$</a>$sep$,
$endfor$
]</p>
{% endif %}
{% if lecture_notes %}
$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 %}
$for(lecture_notes)$
<a href="$lecture_notes$">$lecture_notes$</a>$sep$,
$endfor$
]</p>
{% endif %}
{% if uuid %}
<p class="smallText metadata"> uuid: {{ uuid }} (<a href="/permalink?uuid={{ uuid }}">permalink</a>) </p>
{% endif %}
$endif$
<p class="smallText metadata">
uuid: $uuid$ (<a href="/permalink?uuid=$uuid$">permalink</a>)
</p>
<p class="smallText metadata"> tags: [
{% for tag in tags %}
<a href="/tags/{{ tag }}">{{ tag }}</a>{% if loop.nextitem %},{% endif %}
{% endfor %}
$for(tags)$
<a href="/.tags/$tags$.html">$tags$</a>$sep$,
$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 %}
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/gronk">gronk</a>
page generated by <a href="https://git.alv.cx/alvierahman90/notes2web">notes2web</a>
</p>
{% if license %}
<details id="commitLog">
<summary class="smallText">
Commit log (file history)
</summary>
$filehistory$
</details>
<details id="license">
<summary class="smallText">
License
</summary>
<pre>{{ license }}</pre>
<pre>$licenseFull$</pre>
</details>
{% endif %}
{% block body_content %}
{{ content|safe }}
{% endblock %}
{% endblock %}
$body$
</div>
</div>
<script src="/fuse.js"> </script>
<script src="/toc_search.js"> </script>
</body>

View File

@ -1,18 +0,0 @@
<!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

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

View File

@ -1,18 +0,0 @@
{% 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 %}

28
templates/home_index.html Normal file
View File

@ -0,0 +1,28 @@
<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,33 +1,14 @@
{% 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>
<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>
</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>
{% 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 %}
</body>

7
templates/indexfoot.html Normal file
View File

@ -0,0 +1,7 @@
</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>

21
templates/indexhead.html Normal file
View File

@ -0,0 +1,21 @@
<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

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

View File

@ -1,10 +0,0 @@
{% 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

@ -0,0 +1,19 @@
<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

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

View File

@ -0,0 +1,34 @@
<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>