Compare commits

..

No commits in common. "b3470dce7b4e6983cd04b65c13805453d8ee5966" and "9616a1b5148145bf389d69ee8c7862e722667e6e" have entirely different histories.

11 changed files with 26 additions and 551 deletions

8
.gitignore vendored
View File

@ -1,8 +0,0 @@
__pycache__
build/
.eggs
*.egg-info/
fonts/
katex/
highlight/
*.pex

View File

@ -1,9 +0,0 @@
# Functionality
## Client
## Server
## Webpage

View File

@ -4,12 +4,5 @@ import argparse
import sys import sys
def launcher(): def launcher():
parser = argparse.ArgumentParser( print(sys.argv)
prog="MarkdownPreviewer", asyncio.run(main())
description="Server to allow live rendering of markdown")
parser.add_argument('--base', help="base directoy of the server")
result = parser.parse_args()
asyncio.run(main(result.base))

View File

@ -125,19 +125,3 @@ class PString:
't' : 'Str', 't' : 'Str',
'c' : self.content 'c' : self.content
} }
class Image:
def __init__(self, attr, caption, url):
self.attr = attr
self.caption = caption
self.url = url
def toJson(self):
return {
't' : 'Image',
'c' : [
self.attr.toJson(),
self.caption,
[self.url, ""]
]
}

View File

@ -2,14 +2,7 @@
# Allows filtering and mapping based on blocks and meta keys. # Allows filtering and mapping based on blocks and meta keys.
# #
from .pandoc import Pandoc import pandoc
class CallbackClass:
def __init__(self):
pass
def clear(self):
pass
class MetaCallback: class MetaCallback:
def __init__(self, key, callback, replace=False): def __init__(self, key, callback, replace=False):
@ -20,10 +13,7 @@ class MetaCallback:
def __call__(self, data): def __call__(self, data):
return self.callback(data) return self.callback(data)
def clear(self): class MultiCallback:
pass
class MultiCallback(CallbackClass):
def __init__(self, filter): def __init__(self, filter):
self.filter = filter self.filter = filter
self.callbacks = dict() self.callbacks = dict()
@ -42,15 +32,10 @@ class MultiCallback(CallbackClass):
else: else:
return block return block
def clear(self):
for key,value in self.callbacks.items():
if isinstance(value, CallbackClass):
value.clear()
class RenderPipeline: class RenderPipeline:
def __init__(self): def __init__(self):
self.pandoc = Pandoc() self.pandoc = pandoc.Pandoc()
self.metacallbacks = dict() self.metacallbacks = dict()
self.callbacks = dict() self.callbacks = dict()
@ -60,13 +45,6 @@ class RenderPipeline:
def AddCallback(self, key, callback, replace=False): def AddCallback(self, key, callback, replace=False):
self.callbacks[key] = {'cb' : callback, 'replace' : replace} self.callbacks[key] = {'cb' : callback, 'replace' : replace}
def ClearState(self):
for key,value in self.metacallbacks.items():
value.clear()
for key,value in self.callbacks.items():
if isinstance(value['cb'], CallbackClass):
value['cb'].clear()
def ParseBlock(self, block): def ParseBlock(self, block):
if 'c' not in block: if 'c' not in block:
@ -98,7 +76,6 @@ class RenderPipeline:
return output_list return output_list
def __call__(self, data): def __call__(self, data):
self.ClearState()
json_data = self.pandoc.ConvertToJson(data) json_data = self.pandoc.ConvertToJson(data)
for meta_key, meta_value in json_data['meta'].items(): for meta_key, meta_value in json_data['meta'].items():

View File

@ -1,396 +0,0 @@
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler, StaticFileHandler
from tornado.websocket import WebSocketHandler
from tornado.template import Loader
import sys
import asyncio
import msgpack
import json
import re
import tempfile
import subprocess
import os
import time
from datetime import datetime
from .render_pipeline import RenderPipeline, MultiCallback, CallbackClass
from .pandoc import Pandoc, Div, Span, Header, Attr, Plain, PString, Inline, Image
blockquote_re = re.compile('!\\[([a-z]+)\\]')
panel_content = Attr("", classes=["panel-content"])
header_attr = Attr("", classes=["panel-title"])
warning_icon = Attr("", classes=["fas", "fa-exclamation-triangle", "panel-icon"])
information_icon = Attr("", classes=["fas", "fa-info-circle", "panel-icon"])
header_title_attr = Attr("", classes=["panel-header"])
outer_attr = Attr("", classes=["panel", "panel-warning"])
outer_info_attr = Attr("", classes=["panel", "panel-info"])
def blockquote_filter(block):
data = block['c']
if data[0]['t'] != 'Para':
return None
paragraph_content = data[0]['c']
if paragraph_content[0]['t'] != 'Str':
return None
string_content = paragraph_content[0]['c']
m = blockquote_re.match(string_content)
if m is None:
return None
return m.group(1)
def create_warning(content):
internal_content = content['c']
internal_content[0]['c'].pop(0)
internal_content[0]['c'].pop(0)
content_div = Div(panel_content, internal_content).toJson()
label = PString("Warning").toJson()
header = Header(header_attr, 3, [label]).toJson()
icon = Plain(Span(warning_icon, []).toJson()).toJson()
header_div = Div(header_title_attr,
[
icon,
header
]).toJson()
outer_div = Div(outer_attr,
[
header_div,
content_div
]).toJson()
return outer_div
def create_information(content):
internal_content = content['c']
internal_content[0]['c'].pop(0)
internal_content[0]['c'].pop(0)
content_div = Div(panel_content, internal_content).toJson()
label = PString("Information").toJson()
header = Header(header_attr, 3, [label]).toJson()
icon = Plain(Span(information_icon, []).toJson()).toJson()
header_div = Div(header_title_attr,
[
icon,
header
]).toJson()
outer_div = Div(outer_info_attr,
[
header_div,
content_div
]).toJson()
return outer_div
def parse_title(content):
if content['t'] == 'MetaString':
data = content['c']
elif content['t'] == 'MetaInlines':
data = ""
for inline in content['c']:
if inline['t'] == 'Str':
data += inline['c']
elif inline['t'] == 'Space':
data += " "
Publisher.publish("title", data)
def parse_course(content):
if content['t'] == 'MetaString':
data = content['c']
elif content['t'] == 'MetaInlines':
data = ""
for inline in content['c']:
if inline['t'] == 'Str':
data += inline['c']
elif inline['t'] == 'Space':
data += " "
Publisher.publish("course", data)
def parse_date(content):
if content['t'] == 'MetaString':
data = content['c']
elif content['t'] == 'MetaInlines':
data = ""
for inline in content['c']:
if inline['t'] == 'Str':
data += inline['c']
elif inline['t'] == 'Space':
data += " "
Publisher.publish("date", data)
class Theorem(CallbackClass):
def __init__(self):
self.counter = 1
def __call__(self, content):
internal_content = content['c']
internal_content[0]['c'].pop(0)
internal_content[0]['c'].pop(0)
outer_div_attr = Attr("", classes=["theorem"])
inner_div_attr = Attr("", classes=["theorem-content"])
bold_attr = Attr("", classes=["theorem-title"])
span_attr = Attr("")
theorem_string = "Theorem {}. ".format(self.counter)
title_content = Inline("Emph", [PString(theorem_string).toJson()]).toJson()
title = Span(span_attr, [title_content]).toJson()
internal_content[0]['c'].insert(0, title)
content_div = Div(inner_div_attr, internal_content).toJson()
outer_div = Div(outer_div_attr, [content_div]).toJson()
self.counter += 1
return outer_div
def clear(self):
self.counter = 1
class Proof(CallbackClass):
def __init__(self):
self.counter = 1
def clear(self):
self.counter = 1
def __call__(self, content):
internal_content = content['c']
internal_content[0]['c'].pop(0)
internal_content[0]['c'].pop(0)
outer_div_attr = Attr("", classes=["proof"])
inner_div_attr = Attr("", classes=["proof-content"])
span_attr = Attr("", classes=["proof-title"])
qed_attr = Attr("", classes=["proof-qed"])
proof_string = "Proof {}. ".format(self.counter)
title_content = Span(span_attr, [PString(proof_string).toJson()]).toJson()
title = Inline("Emph", [title_content]).toJson()
internal_content[0]['c'].insert(0, title)
qed_string = Plain(PString("").toJson()).toJson()
qed = Div(qed_attr, [qed_string]).toJson()
inner_div = Div(inner_div_attr, internal_content).toJson()
outer_div = Div(outer_div_attr, [inner_div, qed]).toJson()
return outer_div
class Penrose(CallbackClass):
def __init__(self, base_path):
self.data_path = base_path + "/data/penrose/"
def run(self, input_file_name, domain, style):
domain_path = self.data_path + domain + ".domain"
style_path = self.data_path + domain + ".style"
return subprocess.run(
["roger", "trio", input_file_name, domain_path, style_path, '--path', "/"],
text=True,
capture_output=True)
def __call__(self, content):
handle, file_path = tempfile.mkstemp(suffix=".substance", text=True)
text = content['c'][1]
with os.fdopen(handle, 'w') as f:
f.write(text)
data = self.run(file_path, "set", "set")
handle, file_path = tempfile.mkstemp(suffix=".svg", text=True)
with os.fdopen(handle, 'w') as f:
f.write(data.stdout)
img_attr = Attr("")
new_content = Image(img_attr, [{'t' : 'Str', 'c' : 'Penrose'}], "/generated/{}".format(file_path[5:])).toJson()
wrapper = Plain(new_content).toJson()
return wrapper
class Publisher:
topics = dict()
@classmethod
def publish(cls, topic, content):
if topic in Publisher.topics:
for client in Publisher.topics[topic]:
client["callback"](content)
@classmethod
def subscribe(cls, id, topic, callback):
if topic in Publisher.topics:
Publisher.topics[topic].append({"id":id, "callback":callback})
else:
Publisher.topics[topic] = [{"id":id, "callback":callback}]
class MainBodyHandler(RequestHandler):
body = ""
title = ""
date = ""
course = ""
@classmethod
def update_body(cls, content):
MainBodyHandler.body = content
@classmethod
def update_title(cls, content):
MainBodyHandler.title = content
@classmethod
def update_date(cls, content):
MainBodyHandler.date = content
@classmethod
def update_course(cls, content):
MainBodyHandler.course = content
def initialize(self, loader):
self.loader = loader
self.template = loader.load("index.html")
def get(self):
self.write(
self.template.generate(body_content=MainBodyHandler.body,
title=MainBodyHandler.title,
date=MainBodyHandler.date,
course=MainBodyHandler.course)
)
websockets = []
class PushPull(WebSocketHandler):
def check_origin(self, origin):
return True
@classmethod
def update_body(cls, content):
for socket in websockets:
socket.write_message({"show" : content})
@classmethod
def update_title(cls, content):
for socket in websockets:
socket.write_message({"title" : content})
@classmethod
def update_date(cls, content):
for socket in websockets:
socket.write_message({"date" : content})
@classmethod
def update_course(cls, content):
for socket in websockets:
socket.write_message({"course" : content})
def open(self):
if self not in websockets:
websockets.append(self)
def on_message(self, message):
print(message)
def on_close(self):
if self in websockets:
websockets.remove(self)
def on_stdin(fd, pipeline):
res = fd.read(4)
size = res[3] + (res[2]<<8) + (res[1]<<16) + (res[0]<<24)
read_data = fd.read(size)
data = msgpack.unpackb(read_data)
for key,value in data.items():
if key == "show":
Publisher.publish(key, pipeline(value))
else:
Publisher.publish(key, value)
def route_handler(base_path, loader):
return [
(r"/", MainBodyHandler, {"loader" : loader}),
(r"/ws", PushPull),
(r"/css/(.*)", StaticFileHandler, {"path" : r"{}/data/css".format(base_path)}),
(r"/lib/(.*)", StaticFileHandler, {"path" : r"{}/data/lib".format(base_path)}),
(r"/generated/(.*)", StaticFileHandler, {"path" : r"/tmp"})
]
def codeblock_filter(block):
return block['c'][0][1][0]
async def main(base_path):
loader = Loader("{}/template".format(base_path))
pipeline = RenderPipeline()
pipeline.AddMetaCallback('title', parse_title)
pipeline.AddMetaCallback('course', parse_course)
pipeline.AddMetaCallback('date', parse_date)
blockquote = MultiCallback(blockquote_filter)
blockquote.AddCallback('warning', create_warning)
blockquote.AddCallback('information', create_information)
theo = Theorem()
proo = Proof()
blockquote.AddCallback('theorem', theo)
blockquote.AddCallback('proof', proo)
codeblock = MultiCallback(codeblock_filter)
pen = Penrose(base_path)
codeblock.AddCallback("penrose", pen)
pipeline.AddCallback('BlockQuote', blockquote, replace=True)
pipeline.AddCallback('CodeBlock', codeblock, replace=True)
loop = asyncio.get_event_loop()
fd = open(sys.stdin.fileno(), 'rb', buffering=0)
loop.add_reader(fd, on_stdin, fd, pipeline)
application = Application(route_handler(base_path, loader))
Publisher.subscribe("MainbodyHandler", "show", MainBodyHandler.update_body)
Publisher.subscribe("MainbodyHandler", "title", MainBodyHandler.update_title)
Publisher.subscribe("MainbodyHandler", "course", MainBodyHandler.update_course)
Publisher.subscribe("MainbodyHandler", "date", MainBodyHandler.update_date)
Publisher.subscribe("PushPull", "show", PushPull.update_body)
Publisher.subscribe("PushPull", "title", PushPull.update_title)
Publisher.subscribe("PushPull", "course", PushPull.update_course)
Publisher.subscribe("PushPull", "date", PushPull.update_date)
application.listen(8888)
await asyncio.Event().wait()

View File

@ -17,7 +17,6 @@
--secondary-color: #34A853; --secondary-color: #34A853;
--accent-color: #EA4335; --accent-color: #EA4335;
--background-color: white; --background-color: white;
--quote-bg-color: #F5F5F5;
--text-color: #222222; --text-color: #222222;
--light-gray: #ECF0F1; --light-gray: #ECF0F1;
--dark-gray: #303333; --dark-gray: #303333;
@ -41,13 +40,9 @@
--spacing-medium: 1rem; --spacing-medium: 1rem;
--spacing-large: 2rem; --spacing-large: 2rem;
--quote-blue: #0726b0;
--border-radius: 4px; --border-radius: 4px;
--border-width: 2px; --border-width: 2px;
--blockquote-width: 8px;
--shadow-small: 0 1px 3px rgba(0, 0, 0, 0.12); --shadow-small: 0 1px 3px rgba(0, 0, 0, 0.12);
--shadow-medium: 0 2px 6px rgba(0,0,0,0.12); --shadow-medium: 0 2px 6px rgba(0,0,0,0.12);
@ -67,17 +62,6 @@ body {
padding: 0; padding: 0;
} }
blockquote {
border-left: var(--blockquote-width) solid var(--quote-blue);
border-radius: var(--border-radius);
margin-left: 0px;
padding-left: 20px;
padding-top: 5px;
padding-bottom: 5px;
padding-right: 20px;
background-color: var(--quote-bg-color);
}
.page-header { .page-header {
margin-bottom: var(--spacing-large); margin-bottom: var(--spacing-large);
text-align: center; text-align: center;

View File

@ -1,21 +1,21 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<link rel="stylesheet" href="lib/highlight/styles/tokyo-night-dark.min.css"> <link rel="stylesheet" href="highlight/styles/tokyo-night-dark.min.css">
<script src="lib/highlight/highlight.min.js"></script> <script src="highlight/highlight.min.js"></script>
<link href="lib/katex/katex.min.css" rel="stylesheet" type="text/css"> <link href="katex/katex.min.css" rel="stylesheet" type="text/css">
<script src="lib/katex/katex.min.js"></script> <script src="katex/katex.min.js"></script>
<script src="lib/katex/contrib/auto-render.min.js"></script> <script src="katex/contrib/auto-render.min.js"></script>
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head> </head>
<body> <body>
<div class="page-container"> <div class="page-container">
<header class="page-header"> <header class="page-header">
<h1 id="page-title" class="page-title">{{ title }}</h1> <h1 class="page-title">Your Note Title</h1>
<div class="page-subtitle"> <div class="page-subtitle">
<div id="page-author" class="page-author">{{ course }}</div> <div class="page-author">Author: Your Name</div>
<div class="page-date">Date: <span id="date">{{ date }}</span></div> <div class="page-date">Date: August 1, 2025</div>
</div> </div>
</header> </header>
@ -42,10 +42,11 @@
socket.send("ping"); socket.send("ping");
}); });
function change_body(v) { socket.addEventListener("message", (event) => {
const node = document.getElementById("page-content"); var data = JSON.parse(event.data);
const wrapper = document.createElement("div"); var node = document.getElementById("page-content");
wrapper.innerHTML = v; var wrapper = document.createElement('div');
wrapper.innerHTML = data['show'];
node.replaceChildren(wrapper); node.replaceChildren(wrapper);
hljs.highlightAll(); hljs.highlightAll();
@ -59,34 +60,6 @@
{left: "\\(", right: "\\)", display: false} {left: "\\(", right: "\\)", display: false}
] ]
}); });
}
function change_header(k,v) {
const dict = {
'title' : 'page-title',
'date' : 'date',
'course' : 'page-author'
}
const node = document.getElementById(dict[k])
node.replaceChildren(v);
}
socket.addEventListener("message", (event) => {
var data = JSON.parse(event.data);
Object.entries(data).forEach(([k,v]) => {
console.log(k,v);
switch (k) {
case "show":
change_body(v)
break;
case "title":
case "date":
case "course":
change_header(k,v);
break;
}
})
}); });
</script> </script>
</body> </body>

View File

@ -1,3 +0,0 @@
type Set
predicate SubSet(Set s1, Set s2)

View File

@ -1,17 +0,0 @@
canvas {
width = 100
height = 100
}
forall Set A; Set B
where SubSet(A, B) {
ensure contains(A.icon, B.icon, 5.0)
A.icon above B.icon
}
forall Set x {
x.icon = Circle {
strokeWidth : 0.0
}
ensure x.icon.r > 25
}

View File

@ -20,8 +20,7 @@ end
app = { app = {
cmd = nil, cmd = nil,
channel = nil, channel = nil
cwd = nil
} }
function app:init(on_exit) function app:init(on_exit)
@ -29,9 +28,9 @@ function app:init(on_exit)
return return
end end
self.cwd = debug.getinfo(1, 'S').source:sub(2):match('(.*[/\\])') local cwd = debug.getinfo(1, 'S').source:sub(2):match('(.*[/\\])')
self.channel = vim.fn.jobstart(self.cmd, { self.channel = vim.fn.jobstart(self.cmd, {
cwd = self.cwd, cwd = cwd,
stderr_buffered = true, stderr_buffered = true,
on_exit = function() on_exit = function()
vim.fn.chanclose(self.channel) vim.fn.chanclose(self.channel)
@ -76,13 +75,11 @@ function module.setup()
setmetatable(o, app) setmetatable(o, app)
app.__index = app app.__index = app
local cwd = debug.getinfo(1, 'S').source:sub(2):match('(.*[/\\])')
local base_location = cwd:gsub("lua/MarkdownPreviewer", "")
o.cmd = { o.cmd = {
base_location .. "Server/test.pex", "../../Server/run.sh",
"--base", "../../Server/venv",
base_location 'python3',
'../../Server/server.py'
} }
return o return o