diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9606ee5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/__pycache__
+/data/*
+config.py
+tplerr.txt
diff --git a/config.py.example b/config.py.example
new file mode 100644
index 0000000..d170f4d
--- /dev/null
+++ b/config.py.example
@@ -0,0 +1,19 @@
+
+salt = '123'
+title = 'My site'
+
+src = 'data/src'
+dst = 'data/dst'
+hidedst = 'data/hidedst'
+mangle = 'm/'
+
+url = 'https://my.site/files'
+hideurl = 'https://my.site/private'
+
+thumbw = 120
+thumbh = 90
+thumbcolor = 0xffffff
+
+ffprobe = '/usr/bin/ffprobe'
+ffmpeg = '/usr/bin/ffmpeg'
+magick = '/usr/bin/convert'
diff --git a/indexer.py b/indexer.py
new file mode 100755
index 0000000..ccd5c14
--- /dev/null
+++ b/indexer.py
@@ -0,0 +1,240 @@
+#!/usr/bin/python3
+
+# path.thumb.png - user thumb for 'path'
+# path/.thumb.png - user thumb for 'path'
+# path/.thumb..png - default thumb for unknown extension
+# path/.thumb...png - default thumb for subdirectories
+# path/.thumb.ext.png - default thumb for 'ext'
+# path/.thumb/.png - default thumb for unknown extension
+# path/.thumb/..png - default thumb for subdirectories
+# path/.thumb/ext.png - default thumb for 'ext'
+
+
+import os
+import sys
+import time
+import hashlib
+
+import config
+import thumb
+import template
+
+
+thumbext = [ 'png', 'svg', 'jpg' ]
+skipname = [ 'index.html', 'indexd.html' ]
+
+
+def join(*args): return '/'.join(a for a in args if a)
+def isdir(path): return not os.path.islink(path) and os.path.isdir(path)
+def isfile(path): return not os.path.islink(path) and os.path.isfile(path)
+
+def nameext(name):
+ name = os.path.basename(name)
+ i = name.rfind('.')
+ return (name[:i], name[i+1:].lower()) if i >= 0 else (name, '')
+def remove(path):
+ if os.path.isfile(path) or os.path.islink(path):
+ print('remove file:', path)
+ os.remove(path)
+ if os.path.isdir(path):
+ for f in os.listdir(path): remove(join(path, f))
+ print('remove dir:', path)
+ os.rmdir(path)
+def mkdirfor(path):
+ os.makedirs(os.path.dirname(path), exist_ok = True)
+def touch(path, msg = True):
+ if msg: print('create file:', path)
+ assert(not path in files)
+ mkdirfor(path)
+ files.add(path)
+ return path
+def link(src, dst):
+ touch(dst, False)
+ if not force and isfile(dst) and os.path.samefile(src, dst): return
+ remove(dst)
+ print('create link:', dst, '(' + src + ')')
+ os.link(src, dst, follow_symlinks = False)
+def clean(path, root = True):
+ if os.path.islink(path) or (os.path.isfile(path) and not path in files):
+ print('remove file:', path)
+ os.remove(path)
+ elif os.path.isdir(path):
+ for f in os.listdir(path): clean(join(path, f), False)
+ if not root and not os.listdir(path):
+ print('remove dir:', path)
+ os.rmdir(path)
+
+
+
+class Node:
+ def __init__(self, parent, src, name, hide = False):
+ #print('node:', src)
+ self.parent = parent
+ self.src = src
+ self.name = name
+ self.title = name if parent else config.title
+ self.isdir = isdir(src)
+ self.ext = '' if self.isdir else nameext(name)[1]
+ self.path = join(parent.path, name) if parent else name
+ self.date = os.path.getmtime(src)
+ self.mangle = config.mangle + hashlib.md5((config.salt + ':' + self.path).encode()).hexdigest() + '.' + self.ext
+ self.hide = hide or (parent and parent.hide)
+ self.url = join(config.url, self.path)
+ self.mangleurl = join(config.url, self.mangle)
+ self.hideurl = join(config.hideurl, self.path) if self.isdir else (self.mangleurl if self.hide else self.url)
+ self.thumburl = None
+ self.thumbs = parent.thumbs if parent else {}
+ self.subs = {}
+ if parent: parent.touchdate(self.date); parent.subs[name] = self
+
+ def touchdate(self, date):
+ if self.date >= date: return
+ self.date = date
+ if self.parent: self.parent.touchdate(date)
+
+ def scan(self):
+ if not self.isdir: return
+
+ td = join(self.src, '.thumb')
+ if isdir(td):
+ for f in os.listdir(td):
+ n, e = nameext(f); fn = join(td, f)
+ if e in thumbext and isfile(fn): self.thumbs[None if n == '.' else n] = fn
+
+ for f in os.listdir(self.src):
+ fn = join(self.src, f)
+ if f[:1] != '.' or not isfile(fn): continue
+ n, e = nameext(f[1:])
+ if not e in thumbext: continue
+ n, e = nameext(n)
+ if n == 'thumb': self.thumbs[e] = fn
+ if n == 'thumb.': self.thumbs[None] = fn
+
+ for f in os.listdir(self.src): self.sub(join(self.src, f), f, False)
+ hd = join(self.src, '.hide')
+ if isdir(hd):
+ for f in os.listdir(hd): self.sub(join(hd, f), f, True)
+
+ def sub(self, src, name, hide):
+ n, e = nameext(name)
+ if ( self.isdir
+ and name
+ and name not in self.subs
+ and name[0] != '.'
+ and not name.lower() in skipname
+ and (self.parent or not name.lower().startswith(config.mangle))
+ and nameext(n)[1] != 'thumb'
+ and (isfile(src) or isdir(src)) ):
+ Node(self, src, name, hide).scan()
+
+ def makefile(self):
+ if self.isdir: return
+ link(self.src, join(config.dst, self.mangle))
+ if not self.hide: link(self.src, join(config.dst, self.path))
+
+ def makethumb(self):
+ ts = None
+ td = None
+ dst = config.hidedst if self.hide else config.dst
+ url = config.hideurl if self.hide else config.url
+ if self.isdir:
+ dst = join(dst, self.path, '.thumb.')
+ url = join(url, self.path, '.thumb.')
+ else:
+ dst = join(dst, self.path + '.thumb.')
+ url = join(url, self.path + '.thumb.')
+
+ for e in reversed(thumbext):
+ if isfile(join(self.src, '.thumb.' + e)): ts = join(self.src, '.thumb.' + e)
+ for e in reversed(thumbext):
+ if isfile(self.src + '.thumb.' + e): ts = self.src + '.thumb.' + e
+
+ if not ts and not self.isdir:
+ self.path + '.thumb.'
+ if not force:
+ for e in reversed(thumbext):
+ if isfile(dst + e) and os.path.getmtime(dst + e) > self.date: td = dst + e
+ if td: touch(td, False)
+ else:
+ mkdirfor(dst + thumbext[0])
+ td = thumb.make(self.src, dst + thumbext[0]);
+ if td: touch(td)
+
+ if not ts and not td:
+ if self.isdir: ts = self.thumbs.get(None)
+ else: ts = self.thumbs.get(self.ext, self.thumbs.get(''))
+ if not td and ts:
+ td = dst + nameext(ts)[1]
+ link(ts, td)
+ if td: self.thumburl = url + nameext(td)[1]
+
+ def makeinfo(self):
+ return {
+ 'name': self.name,
+ 'title': self.title,
+ 'date': time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(self.date)),
+ 'isdir': self.isdir,
+ 'hide': self.hide,
+ 'url': self.url,
+ 'hideurl': self.hideurl,
+ 'mangleurl': self.mangleurl,
+ 'thumburl': self.thumburl }
+
+ def makepath(self):
+ r = []; p = ''; n = self
+ while n: r.insert(0, {'name': n.name, 'title': n.title, 'url': p}); p = join(p, '..'); n = n.parent
+ return r
+
+ def makeinfoex(self, bydate):
+ r = self.makeinfo()
+ r['bydate'] = bydate
+ r['path'] = self.makepath()
+ r['tw'] = config.thumbw
+ r['th'] = config.thumbh
+ return r
+
+ def makeindex(self, bydate):
+ if not self.isdir or self.hide: return
+ fn = join(config.dst, self.path, ('indexd.html' if bydate else 'index.html'))
+ if not force and isfile(fn) and os.path.getmtime(fn) > self.date: touch(fn, False); return
+ r = self.makeinfoex(bydate)
+ r['subs'] = []
+ for n in sorted(self.subs.values(), key=lambda n: (not n.isdir, (-n.date if bydate else n.name))):
+ if not n.hide: r['subs'].append(n.makeinfo())
+ with open(touch(fn), 'w') as f: tpl.write(f, r)
+
+ def makehideindex(self, bydate):
+ if not self.isdir: return
+ fn = join(config.hidedst, self.path, ('indexd.html' if bydate else 'index.html'))
+ if not force and isfile(fn) and os.path.getmtime(fn) > self.date: touch(fn, False); return
+ r = self.makeinfoex(bydate)
+ r['subs'] = [ n.makeinfo() for n in sorted(self.subs.values(), key=lambda n: (not n.isdir, (-n.date if bydate else n.name), not n.hide)) ]
+ with open(touch(fn), 'w') as f: hidetpl.write(f, r)
+
+ def make(self):
+ #print('make:', self.path)
+ for n in self.subs.values(): n.make()
+ self.makefile()
+ self.makethumb()
+ self.makeindex(False)
+ self.makeindex(True)
+ self.makehideindex(False)
+ self.makehideindex(True)
+
+
+#os.umask(0o0027)
+force = len(sys.argv) > 1 and sys.argv[1] == 'force'
+files = set()
+
+tpl = template.TplLoader.load("tpl/index.tpl")
+hidetpl = template.TplLoader.load("tpl/hide.tpl")
+
+n = Node(None, config.src, '')
+n.scan()
+n.make()
+
+clean(config.dst)
+clean(config.hidedst)
+
+print('done')
+
diff --git a/template.py b/template.py
new file mode 100644
index 0000000..5e58f69
--- /dev/null
+++ b/template.py
@@ -0,0 +1,436 @@
+
+import os
+import html
+#import markdown
+#os.path.dirname(os.path.abspath(__file__))
+
+
+def dictGet(d, path):
+ for k in str(path).split('.'):
+ if type(d) is list or type(d) is tuple:
+ d = d[int(k)]
+ continue
+ elif type(d) is dict:
+ if (not k in d) and ('ex' in d):
+ print("query ex for field: %s (%s)" % (path, k))
+ d = d['ex'](d)
+ if k in d:
+ d = d[k]
+ continue
+ print("variable not found: %s (%s)" % (path, k))
+ return None
+ return d
+
+def tostr(s):
+ return '' if s is None else str(s)
+def text2html(t):
+ return html.escape(tostr(t)).replace("\n", "
")
+def md2html(t):
+ return markdown.markdown(tostr(t))
+
+
+tabsym = ' -- '
+
+
+class TemplateText:
+ def __init__(self, text = ''):
+ self.text = text
+ def write(self, writer, context):
+ writer.write(self.text)
+ def optimize(self):
+ return self if self.text else None
+ def dbgPrint(self, tab):
+ print("%stext(%s)" % (tab, self.text.replace("\n", "\\n")))
+
+class TemplateVarHtml:
+ def __init__(self, varpath = None):
+ self.varpath = varpath
+ def write(self, writer, context):
+ writer.write(tostr(dictGet( context, self.varpath )))
+ def optimize(self):
+ return self
+ def dbgPrint(self, tab):
+ print("%svarHtml(%s)" % (tab, self.varpath))
+
+class TemplateVarMd:
+ def __init__(self, varpath = None):
+ self.varpath = varpath
+ def write(self, writer, context):
+ writer.write(md2html(dictGet( context, self.varpath )))
+ def optimize(self):
+ return self
+ def dbgPrint(self, tab):
+ print("%svarMd(%s)" % (tab, self.varpath))
+
+class TemplateVarText:
+ def __init__(self, varpath = None):
+ self.varpath = varpath
+ def write(self, writer, context):
+ writer.write(text2html(dictGet( context, self.varpath )))
+ def optimize(self):
+ return self
+ def dbgPrint(self, tab):
+ print("%svarText(%s)" % (tab, self.varpath))
+
+class TemplateVarTextMultiline:
+ def __init__(self, varpath = None):
+ self.varpath = varpath
+ def write(self, writer, context):
+ writer.write(text2html(dictGet( context, self.varpath )).replace("\n", "
"))
+ def optimize(self):
+ return self
+ def dbgPrint(self, tab):
+ print("%svarTextMultiline(%s)" % (tab, self.varpath))
+
+class TemplateVarCount:
+ def __init__(self, varpath = None):
+ self.varpath = varpath
+ def write(self, writer, context):
+ v = dictGet( context, self.varpath )
+ writer.write(str(len(v) if hasattr(v, '__len__') else 0))
+ def optimize(self):
+ return self
+ def dbgPrint(self, tab):
+ print("%svarCount(%s)" % (tab, self.varpath))
+
+class TemplateVarField:
+ def __init__(self, varpath = None):
+ self.varpath = varpath
+ def write(self, writer, context):
+ f = self.varpath.split('.')[-1]
+ writer.write("
%s: %s
" % ( + text2html( f ), + text2html( dictGet(context, self.varpath) ) )) + def optimize(self): + return self + def dbgPrint(self, tab): + print("%svarField(%s)" % (tab, self.varpath)) + +class TemplateIf: + def __init__(self, varpath = None, sub = None, alt = None): + self.varpath = varpath + self.sub = sub + self.alt = alt + def write(self, writer, context): + if dictGet(context, self.varpath): + if self.sub: self.sub.write(writer, context) + else: + if self.alt: self.alt.write(writer, context) + def optimize(self): + self.sub = self.sub.optimize() if self.sub else None + self.alt = self.alt.optimize() if self.alt else None + return self if self.sub or self.alt else None + def dbgPrint(self, tab): + print("%sif(%s):" % (tab, self.varpath)) + if self.sub: + self.sub.dbgPrint(tab + tabsym) + if self.alt: + print("%selse:" % tab) + self.alt.dbgPrint(tab + tabsym) + + +class TemplateWith: + def __init__(self, varpath = None, varname = None, sub = None): + self.varpath = varpath + self.varname = varname + self.sub = sub + def write(self, writer, context): + if self.sub: + context = dict(context) + context[self.varname] = dictGet(context, self.varpath) + self.sub.write(writer, context) + def optimize(self): + self.sub = self.sub.optimize() if self.sub else None + return self if self.sub else None + def dbgPrint(self, tab): + print("%swith(%s:%s):" % (tab, self.varpath, self.varname)) + if self.sub: self.sub.dbgPrint(tab + tabsym) + + +class TemplateComment: + def __init__(self, text = None): + self.text = text + def write(self, writer, context): + pass + def optimize(self): + return None + def dbgPrint(self, tab): + print("%scomment(%s)" % (texttab, self.text)) + + +class TemplateLoop: + def __init__(self, varpath = None, keyvarname = None, varname = None, sub = None, sep = None): + self.varpath = varpath + self.keyvarname = keyvarname + self.varname = varname + self.sub = sub + self.sep = sep + def writeItem(self, writer, context, first, k, v): + if self.sep and not first: + self.sep.write(writer, context) + if self.sub: + ctx = dict(context) + ctx[self.keyvarname] = k + ctx[self.varname] = v + self.sub.write(writer, ctx) + def write(self, writer, context): + if self.sep or self.sub: + vv = dictGet(context, self.varpath) + if type(vv) is dict: + f = True + keys = list(vv.keys()) + keys.sort() + for k in keys: + self.writeItem(writer, context, f, k, vv[k]) + f = False + elif hasattr(vv, '__iter__'): + idx = 0 + for v in vv: + self.writeItem(writer, context, not idx, idx, v) + idx = idx + 1 + def optimize(self): + self.sub = self.sub.optimize() if self.sub else None + self.sep = self.sep.optimize() if self.sep else None + return self if self.sub or self.sep else None + def dbgPrint(self, tab): + print("%sloop(%s:%s:%s):" % (tab, self.varpath, self.keyvarname, self.varname)) + if self.sub: + self.sub.dbgPrint(tab + tabsym) + if self.sep: + print("%ssep:" % tab) + self.sep.dbgPrint(tab + tabsym) + +class Template: + def __init__(self): + self.items = [] + def write(self, writer, context): + for i in self.items: + i.write(writer, context) + def optimize(self): + it = [] + for i in self.items: + ii = i.optimize() + if ii: + if it and type(ii) is TemplateText and type(it[-1]) is TemplateText: + it[-1].text = it[-1].text + ii.text + else: + it.append(ii) + self.items = it + if len(self.items) == 1: return self.items[0] + return self if self.items else None + def dbgPrint(self, tab): + print("%stemplate:" % tab) + for i in self.items: + i.dbgPrint(tab + tabsym) + + +class TplLoader: + @staticmethod + def loadtext(filename): + filename = os.path.abspath(filename) + path = os.path.dirname(filename) + res = '' + text = '' + with open(filename, 'r') as f: text = f.read() + ii = 0 + i = 0 + while i < len(text): + if text[i:i+9] == '{include:': + res = res + text[ii:i] + a = i+9 + b = text.find('}', a) + if b<0: raise Exception("include parse error: %s:%d" % (filename, a)) + fn = text[a:b] + res = res + TplLoader.loadtext(path +'/' + fn) + i = b+1 + ii = i + else: + i = i + 1 + return res + text[ii:i] + + def __init__(self, filename): + self.filename = os.path.abspath(filename) + self.text = TplLoader.loadtext(self.filename) + self.ptr = 0 + + def error(self): + with open('tplerr.txt', 'w') as f: f.write(self.text) + raise Exception("parse error: %s:%d" % (self.filename, self.ptr)) + + def readKey(self, k): + if self.text[self.ptr:self.ptr + len(k)] == k: + self.ptr = self.ptr + len(k) + return True + return False + + def readVarname(self): + i = self.ptr + while i < len(self.text) and (self.text[i].isalnum() or self.text[i] in '_'): + i = i + 1 + if i == self.ptr: return None + i, self.ptr = self.ptr, i + return self.text[i:self.ptr] + + def readVarpath(self): + i = self.ptr + varpath = None + while True: + varname = self.readVarname() + if not varname: + if varpath: self.error() + break + varpath = varpath + '.' + varname if varpath else varname + if not self.readKey('.'): break + if not varpath: + self.ptr = i + return varpath + + def loadVarText(self): + if not self.readKey("{:"): return None + varpath = self.readVarpath() + if not varpath or not self.readKey("}"): self.error() + return TemplateVarText(varpath) + + def loadVarTextMultiline(self): + if not self.readKey("{t:"): return None + varpath = self.readVarpath() + if not varpath or not self.readKey("}"): self.error() + return TemplateVarTextMultiline(varpath) + + def loadVarHtml(self): + if not self.readKey("{html:"): return None + varpath = self.readVarpath() + if not varpath or not self.readKey("}"): self.error() + return TemplateVarHtml(varpath) + + def loadVarMd(self): + return None # disabled + if not self.readKey("{md:"): return None + varpath = self.readVarpath() + if not varpath or not self.readKey("}"): self.error() + return TemplateVarMd(varpath) + + def loadVarCount(self): + if not self.readKey("{count:"): return None + varpath = self.readVarpath() + if not varpath or not self.readKey("}"): self.error() + return TemplateVarCount(varpath) + + def loadVarField(self): + if not self.readKey("{f:"): return None + varpath = self.readVarpath() + if not varpath or not self.readKey("}"): self.error() + return TemplateVarField(varpath) + + def loadIf(self): + if not self.readKey("{if:"): return None + varpath = self.readVarpath() + if not varpath or not self.readKey("}"): self.error() + res = TemplateIf(varpath, Template(), Template()) + e = False + while True: + if self.readKey("{endif}"): break + if self.readKey("{else}"): + if e: self.error() + e = True + continue + item = self.loadTemplate() + if not item: self.error() + if e: + res.alt.items.append(item) + else: + res.sub.items.append(item) + return res + + def loadWith(self): + if not self.readKey("{with:"): return None + varpath = self.readVarpath() + if not varpath or not self.readKey(":"): self.error() + varname = self.readVarname() + if not varname or not self.readKey("}"): self.error() + res = TemplateWith(varpath, varname, Template()) + while True: + if self.readKey("{endwith}"): break + item = self.loadTemplate() + if not item: self.error() + res.sub.items.append(item) + return res + + def loadComment(self): + if not self.readKey("{comment}"): return None + res = TemplateComment() + i = self.text.find('{endcomment}', self.ptr) + if i < 0: + i = len(self.text) + res = TemplateComment(self.text[self.ptr:i]) + self.ptr = i + if not self.readKey("{endcomment}"): self.error() + return res + + def loadLoop(self): + if not self.readKey("{loop:"): return None + varpath = self.readVarpath() + if not varpath or not self.readKey(":"): self.error() + keyvarname = self.readVarname() + if not self.readKey(":"): self.error() + varname = self.readVarname() + if not varname or not self.readKey("}"): self.error() + res = TemplateLoop(varpath, keyvarname, varname, Template(), Template()) + s = False + while True: + if self.readKey("{endloop}"): break + if self.readKey("{sep}"): + if s: self.error() + s = True + continue + item = self.loadTemplate() + if not item: self.error() + if s: + res.sep.items.append(item) + else: + res.sub.items.append(item) + return res + + def loadTemplate(self): + t = self.loadVarText() + if t: return t + t = self.loadVarTextMultiline() + if t: return t + t = self.loadVarHtml() + if t: return t + t = self.loadVarMd() + if t: return t + t = self.loadVarCount() + if t: return t + t = self.loadVarField() + if t: return t + t = self.loadIf() + if t: return t + t = self.loadWith() + if t: return t + t = self.loadComment() + if t: return t + t = self.loadLoop() + if t: return t + + i = self.text.find('{', self.ptr) + if i == self.ptr: + i = self.text.find('{', self.ptr+1) + if i < 0: + i = len(self.text) + if i <= self.ptr: + return None + i, self.ptr = self.ptr, i + return TemplateText(self.text[i:self.ptr]) + + @staticmethod + def load(filename): + l = TplLoader(filename) + t = Template() + while True: + tt = l.loadTemplate() + if not tt: break + t.items.append(tt) + if l.ptr < len(l.text): l.error() + t = t.optimize() + return t if t else TemplateText() diff --git a/thumb.py b/thumb.py new file mode 100644 index 0000000..046a1fe --- /dev/null +++ b/thumb.py @@ -0,0 +1,61 @@ + +import os +import subprocess + +import config + + +env = dict(os.environ) +env['LANG']='C' + + +def printerr(r): + print('process returned with error:', r.returncode) + print('command:', r.args) + print('output:', r.stdout) + print('error:', r.stderr) + return + + +def ffmpeg(src, dst): + r = subprocess.run( + [ config.ffprobe, '-v', 'error', '-show_entries', 'format=duration:stream=codec_type', '-of', 'default=noprint_wrappers=1', src ], + capture_output = True, text = True, env = env ) + if r.returncode: return #printerr(r) + video = False; audio = False; duration = 0 + for l in r.stdout.split(): + f = l.split('=', 1) + if len(f) != 2: continue + if l == 'codec_type=video': video = True + if l == 'codec_type=audio': audio = True + if l.startswith('duration='): + try: duration = float(l[9:]) + except ValueError: pass + if not duration: return + if video: + filters = "select='gte(t,%f)',thumbnail,scale=%d:%d:force_original_aspect_ratio=decrease:force_divisible_by=2" % (duration/4, config.thumbw, config.thumbh) + r = subprocess.run( + [ config.ffmpeg, '-i', src, '-vf', filters, '-frames:v', '1', '-y', dst ], + capture_output = True, text = True, env = env ) + #if r.returncode: printerr(r) + if not r.returncode and os.path.isfile(dst): return dst + if audio: + filters = "showwavespic=s=%dx%d:scale=sqrt:colors=%06x" % (config.thumbw, config.thumbh, config.thumbcolor) + r = subprocess.run( + [ config.ffmpeg, '-i', src, '-filter_complex', filters, '-frames:v', '1', '-y', dst ], + capture_output = True, text = True, env = env ) + #if r.returncode: printerr(r) + if not r.returncode and os.path.isfile(dst): return dst + + +def magick(src, dst): + r = subprocess.run( + [ config.magick, src, '-resize', '%dx%d>' % (config.thumbw, config.thumbh), dst ], + capture_output = True, text = True, env = env ) + if r.returncode: return #printerr(r) + if not r.returncode and os.path.isfile(dst): return dst + + +def make(src, dst): + return ffmpeg(src, dst) or magick(src, dst) + diff --git a/tpl/css.tpl b/tpl/css.tpl new file mode 100644 index 0000000..29c1687 --- /dev/null +++ b/tpl/css.tpl @@ -0,0 +1,63 @@ + +body { + font-family: sans; + text-align: center; + font-size: 16px; + background: black; + color: white; +} + +a { font-weight: bold; color: #88f; } +a:visited { color: #66d; } +a:hover { color: #44c; } + +body>div { + text-align: left; + margin: 32px; + border-radius: 16px; + border: 1px solid gray; + display: inline-block; + background: #111; +} +#path { margin: 10px 10px 10px calc({:tw}px + 90px); } + +#head div:first-child { + margin-left: 70px; + width: {:tw}px; + height: {:th}px; + text-align: right; +} +#head div { + font-size: 32px; + font-weight: bold; + vertical-align: middle; + display: inline-block; + margin: 10px; +} +#head img { + display: block; + max-width: {:tw}px; + max-height: {:th}px; + margin: auto; +} + +table { + counter-set: row -1; + border: 0; + border-spacing: 0; + border-radius: 0 0 16px 16px; + overflow: hidden; +} +tr { counter-increment: row; height: calc({:th}px + 10px); } +tr:nth-child(odd) { background-color: #222; } +tr:nth-child(1) { background-color: #333; height: 50px; } +td:first-child::before { content: counter(row); } +th:first-child { padding-left: 20px; padding-right: 10px; width: 30px; text-align: right; } +th:nth-child(3) { padding-left: 10px; } +th:last-child { padding-right: 20px; } +td { padding: 5px; font-size: 16px; } +td:first-child { padding-left: 20px; padding-right: 10px; width: 30px; text-align: right; } +td:nth-child(2) { width: calc({:tw}px + 10px); } +td:nth-child(3) { padding-left: 10px; } +td:last-child { padding-right: 20px; } +td img { max-width: {:tw}px; max-height: {:th}px; display: block; margin: auto; } diff --git a/tpl/hide.tpl b/tpl/hide.tpl new file mode 100644 index 0000000..1e73bc6 --- /dev/null +++ b/tpl/hide.tpl @@ -0,0 +1,25 @@ + + +| # | ++ | {if:bydate}name{else}name{endif} | ++ | {if:bydate}date{else}date{endif} | +
|---|---|---|---|---|
| + | {:n.title} | +mangled | +{:n.date} | +
| # | ++ | {if:bydate}name{else}name{endif} | ++ | {if:bydate}date{else}date{endif} | +
|---|---|---|---|---|
| + | {:n.title} | +mangled | +{:n.date} | +