Skip to content

Commit

Permalink
Parse @username links in markdown
Browse files Browse the repository at this point in the history
  • Loading branch information
HebaruSan committed Mar 12, 2022
1 parent b632cb8 commit 8204d09
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 0 deletions.
44 changes: 44 additions & 0 deletions KerbalStuff/kerbdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
from urllib.parse import parse_qs, urlparse
from typing import Dict, Any, Match, Tuple, Optional

from flask import url_for
from markdown import Markdown
from markdown.extensions import Extension
from markdown.inlinepatterns import InlineProcessor
from xml.etree import ElementTree
from markdown.util import AtomicString

from .objects import User


class EmbedInlineProcessor(InlineProcessor):
Expand Down Expand Up @@ -63,6 +67,45 @@ def _embed_youtube(self, vid_id: str) -> ElementTree.Element:
return el


class AtUsernameProcessor(InlineProcessor):
# Don't worry about re.compiling this, markdown.inlinepatterns.Pattern.__init__ does that for us
# Same as blueprints.accounts._username_re
USER_RE = r'@(?P<username>[A-Za-z0-9_]+)'

def __init__(self, md: Markdown, configs: Dict[str, Any]) -> None:
super().__init__(self.USER_RE, md)
self.configs = configs

def handleMatch(self, match: Match[str], data: str) -> Tuple[Optional[ElementTree.Element], Optional[int], Optional[int]]: # type: ignore[override]
username = match.groupdict().get('username')
# Case insensitive lookup
user = User.query.filter(User.username.ilike(username)).first()
return ((self._profileLink(user), match.start(0), match.end(0))
# Keep original text if user not found
if user and user.public else (None, None, None))

@classmethod
def _profileLink(cls, user: User) -> ElementTree.Element:
# Make a link to the user's profile
elt = ElementTree.Element('a', href=url_for('profile.view_profile',
username=user.username),
# Summarize user's profile in tooltip
title='\n'.join((f'{user.username}\'s profile',
f'{cls._profileModCount(user)} mods',
f'Joined {user.created.strftime("%Y-%m-%d")}')))
# Make it bold
strong = ElementTree.SubElement(elt, 'strong')
# AtomicString prevents Markdown from entering an infinite loop by processing the subelement's text again
strong.text = AtomicString(f'@{user.username}')
return elt

@staticmethod
def _profileModCount(user: User) -> int:
return len([m for m in user.mods + [sa.mod for sa in user.shared_authors
if sa.accepted]
if m.published])


class KerbDown(Extension):
def __init__(self, **kwargs: str) -> None:
super().__init__(**kwargs) # type: ignore[arg-type]
Expand All @@ -72,4 +115,5 @@ def __init__(self, **kwargs: str) -> None:
def extendMarkdown(self, md: Markdown) -> None:
# BUG: the base method signature is INVALID, it's a bug in flask-markdown
md.inlinePatterns.register(EmbedInlineProcessor(md, self.config), 'embed', 200)
md.inlinePatterns.register(AtUsernameProcessor(md, self.config), 'atuser', 200)
md.registerExtension(self)
3 changes: 3 additions & 0 deletions templates/markdown.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ <h2>My super cool mod</h2>
</blockquote>
<p>You can use Markdown on your mod descriptions, profile page, and changelogs. The nice thing about using it in
your changelogs is that it looks nice in plaintext, which is how it shows up in your user's email inboxes.</p>
<h2>User profile links</h2>
<p>You can create a link to a user's profile like this:</p>
<pre>@username</pre>
<h2>Embedding videos and images</h2>
<p>You can easily embed a single image like so (this came from the Markdown spec):</p>
<pre>![](http://example.com/image.png)</pre>
Expand Down

0 comments on commit 8204d09

Please sign in to comment.