markdown: add a MediaExtension that allows attributes on images
Supported attributes: * size=<WIDTH>x<HEIGHT>, both being optional * pixelated In the near future it will also support audio files and videos.
This commit is contained in:
parent
39748667ee
commit
e9c1f04f42
|
@ -45,6 +45,9 @@ a:focus {
|
|||
text-decoration: underline;
|
||||
outline: none;
|
||||
}
|
||||
img.pixelated {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
section p {
|
||||
line-height: 20px;
|
||||
word-wrap: anywhere;
|
||||
|
|
|
@ -40,6 +40,10 @@ a {
|
|||
}
|
||||
}
|
||||
|
||||
img.pixelated {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
section {
|
||||
p {
|
||||
line-height: 20px;
|
||||
|
|
|
@ -15,7 +15,7 @@ markdown_tags = [
|
|||
|
||||
markdown_attrs = {
|
||||
"*": ["id", "class"],
|
||||
"img": ["src", "alt", "title"],
|
||||
"img": ["src", "alt", "title", "width", "height"],
|
||||
"a": ["href", "alt", "title", "rel"],
|
||||
"form": ["action", "method", "enctype"],
|
||||
"input": ["id", "name", "type", "value"],
|
||||
|
|
|
@ -11,6 +11,7 @@ from app.utils.markdown_extensions.pclinks import PCLinkExtension
|
|||
from app.utils.markdown_extensions.hardbreaks import HardBreakExtension
|
||||
from app.utils.markdown_extensions.escape_html import EscapeHtmlExtension
|
||||
from app.utils.markdown_extensions.linkify import LinkifyExtension
|
||||
from app.utils.markdown_extensions.media import MediaExtension
|
||||
|
||||
|
||||
@app.template_filter('md')
|
||||
|
@ -33,6 +34,7 @@ def md(text):
|
|||
LinkifyExtension(),
|
||||
TocExtension(baselevel=2),
|
||||
PCLinkExtension(),
|
||||
MediaExtension(),
|
||||
]
|
||||
|
||||
html = markdown(text, options=options, extensions=extensions)
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
from markdown.extensions import Extension
|
||||
from markdown.inlinepatterns import LinkInlineProcessor
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
class MediaExtension(Extension):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def extendMarkdown(self, md):
|
||||
self.md = md
|
||||
|
||||
# Override image detection
|
||||
MEDIA_RE = r'\!\['
|
||||
media_processor = MediaInlineProcessor(MEDIA_RE)
|
||||
media_processor.md = md
|
||||
md.inlinePatterns.register(media_processor, 'media_link', 155)
|
||||
|
||||
class AttributeLinkInlineProcessor(LinkInlineProcessor):
|
||||
"""
|
||||
A LinkInlineProcessor which additionally supports attributes after links,
|
||||
with the bracket syntax `{ <item>, <item>, ... }` where each item is a
|
||||
key/value pair with either single or double quotes: `key='value'` or
|
||||
`key="value"`.
|
||||
"""
|
||||
|
||||
def getAttributes(self, data, index):
|
||||
current_quote = ""
|
||||
has_closing_brace = False
|
||||
attrs = []
|
||||
current_attr_text = ""
|
||||
|
||||
if index >= len(data) or data[index] != '{':
|
||||
return [], index, True
|
||||
index += 1
|
||||
|
||||
for pos in range(index, len(data)):
|
||||
c = data[pos]
|
||||
index += 1
|
||||
|
||||
# Close quote
|
||||
if current_quote != "" and c == current_quote:
|
||||
current_quote = ""
|
||||
continue
|
||||
# Open new quote
|
||||
if current_quote == "" and c in ["'", '"']:
|
||||
current_quote = c
|
||||
continue
|
||||
# Close brace
|
||||
if current_quote == "" and c == "}":
|
||||
has_closing_brace = True
|
||||
break
|
||||
|
||||
if current_quote == "" and c == " ":
|
||||
if current_attr_text:
|
||||
attrs.append(current_attr_text)
|
||||
current_attr_text = ""
|
||||
else:
|
||||
current_attr_text += c
|
||||
|
||||
if current_attr_text:
|
||||
attrs.append(current_attr_text)
|
||||
|
||||
return attrs, index, has_closing_brace
|
||||
|
||||
@staticmethod
|
||||
def hasAttribute(attrs, name):
|
||||
return name in attrs
|
||||
|
||||
@staticmethod
|
||||
def getStringAttribute(attrs, name):
|
||||
for attr in attrs:
|
||||
if attr.startswith(name + "="):
|
||||
return attr[len(name)+1:]
|
||||
|
||||
@staticmethod
|
||||
def getIntAttribute(attrs, name):
|
||||
try:
|
||||
s = AttributeLinkInlineProcessor.getStringAttribute(attrs, name)
|
||||
return int(s) if s is not None else None
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
class MediaInlineProcessor(AttributeLinkInlineProcessor):
|
||||
""" Return a media element from the given match. """
|
||||
|
||||
def isVideo(self, url):
|
||||
# TODO: YouTube integration
|
||||
return url.endswith(".mp4") or url.endswith(".webm")
|
||||
|
||||
def isAudio(self, url):
|
||||
return url.endswith(".mp3") or url.endswith(".ogg")
|
||||
|
||||
def getSizeAttribute(self, attrs, name):
|
||||
s = self.getStringAttribute(attrs, name)
|
||||
if s is None:
|
||||
return None, None
|
||||
dims = s.split("x", 1)
|
||||
try:
|
||||
if len(dims) == 1:
|
||||
return int(dims[0]), None
|
||||
else:
|
||||
w = int(dims[0]) if dims[0] else None
|
||||
h = int(dims[1]) if dims[1] else None
|
||||
return w, h
|
||||
except ValueError:
|
||||
return None, None
|
||||
|
||||
def handleMatch(self, m, data):
|
||||
text, index, handled = self.getText(data, m.end(0))
|
||||
if not handled:
|
||||
return None, None, None
|
||||
|
||||
src, title, index, handled = self.getLink(data, index)
|
||||
if not handled:
|
||||
return None, None, None
|
||||
|
||||
attrs, index, handled = self.getAttributes(data, index)
|
||||
if not handled:
|
||||
return None, None, None
|
||||
print(attrs, self.getSizeAttribute(attrs, "size"))
|
||||
|
||||
kind = "image"
|
||||
if self.hasAttribute(attrs, "audio") or self.isAudio(src):
|
||||
kind = "audio"
|
||||
elif self.hasAttribute(attrs, "video") or self.isVideo(src):
|
||||
kind = "video"
|
||||
|
||||
# Images
|
||||
|
||||
if kind == "image":
|
||||
w, h = self.getSizeAttribute(attrs, "size")
|
||||
pixelated = self.hasAttribute(attrs, "pixelated")
|
||||
|
||||
el = etree.Element("img")
|
||||
el.set("src", src)
|
||||
|
||||
if title is not None:
|
||||
el.set("title", title)
|
||||
|
||||
if pixelated:
|
||||
el.set("class", "pixelated")
|
||||
if w is not None:
|
||||
el.set("width", str(w))
|
||||
if h is not None:
|
||||
el.set("height", str(h))
|
||||
|
||||
el.set('alt', self.unescape(text))
|
||||
return el, m.start(0), index
|
||||
|
||||
return None, None, None
|
Loading…
Reference in New Issue