110 lines
3.1 KiB
Python
110 lines
3.1 KiB
Python
# ContentDB
|
|
# Copyright (C) rubenwardy
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
import re
|
|
|
|
from flask import url_for
|
|
from markdown_it import MarkdownIt
|
|
from markdown_it.token import Token
|
|
from markdown_it.rules_core.state_core import StateCore
|
|
from typing import Sequence, List
|
|
|
|
|
|
def render_user_mention(self, tokens: Sequence[Token], idx, options, env):
|
|
token = tokens[idx]
|
|
username = token.content
|
|
url = url_for("users.profile", username=username)
|
|
return f"<a href=\"{url}\" data-username=\"{username}\">@{username}</a>"
|
|
|
|
|
|
def render_package_mention(self, tokens: Sequence[Token], idx, options, env):
|
|
token = tokens[idx]
|
|
username = token.content
|
|
name = token.attrs["name"]
|
|
url = url_for("packages.view", author=username, name=name)
|
|
return f"<a href=\"{url}\">@{username}/{name}</a>"
|
|
|
|
|
|
def parse_mentions(state: StateCore):
|
|
for block_token in state.tokens:
|
|
if block_token.type != "inline" or block_token.children is None:
|
|
continue
|
|
|
|
link_depth = 0
|
|
html_link_depth = 0
|
|
|
|
children = []
|
|
for token in block_token.children:
|
|
if token.type == "link_open":
|
|
link_depth += 1
|
|
elif token.type == "link_close":
|
|
link_depth -= 1
|
|
elif token.type == "html_inline":
|
|
# is link open / close?
|
|
pass
|
|
|
|
if link_depth > 0 or html_link_depth > 0 or token.type != "text":
|
|
children.append(token)
|
|
else:
|
|
children.extend(split_tokens(token, state))
|
|
|
|
block_token.children = children
|
|
|
|
|
|
RE_PARTS = dict(
|
|
USER=r"[A-Za-z0-9._-]*\b",
|
|
NAME=r"[A-Za-z0-9_]+\b"
|
|
)
|
|
MENTION_RE = r"(@({USER})(?:\/({NAME}))?)".format(**RE_PARTS)
|
|
|
|
|
|
def split_tokens(token: Token, state: StateCore) -> List[Token]:
|
|
tokens = []
|
|
content = token.content
|
|
pos = 0
|
|
for match in re.finditer(MENTION_RE, content):
|
|
username = match.group(2)
|
|
package_name = match.group(3)
|
|
(start, end) = match.span(0)
|
|
|
|
if start > pos:
|
|
token_text = Token("text", "", 0)
|
|
token_text.content = content[pos:start]
|
|
token_text.level = token.level
|
|
tokens.append(token_text)
|
|
|
|
mention = Token("package_mention" if package_name else "user_mention", "", 0)
|
|
mention.content = username
|
|
mention.attrSet("name", package_name)
|
|
mention.level = token.level
|
|
tokens.append(mention)
|
|
|
|
pos = end
|
|
|
|
if pos < len(content):
|
|
token_text = Token("text", "", 0)
|
|
token_text.content = content[pos:]
|
|
token_text.level = token.level
|
|
tokens.append(token_text)
|
|
|
|
return tokens
|
|
|
|
|
|
def init_mention(md: MarkdownIt):
|
|
md.add_render_rule("user_mention", render_user_mention, "html")
|
|
md.add_render_rule("package_mention", render_package_mention, "html")
|
|
md.core.ruler.after("inline", "mention", parse_mentions)
|