Merge pull request #109 from Simple2B/svyat/feat/mention_user

issue Add mention user feature without quick search for comments #105…
This commit is contained in:
Костя Столярский 2023-06-01 15:19:26 +03:00 committed by GitHub
commit 0f3471e381
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 80 additions and 58 deletions

View File

@ -70,13 +70,13 @@ def create_app(environment="development"):
# Jinja globals # Jinja globals
from app.controllers.jinja_globals import ( from app.controllers.jinja_globals import (
form_hidden_tag, form_hidden_tag,
display_tags, display_inline_elements,
build_qa_url_using_interpretation, build_qa_url_using_interpretation,
recursive_render, recursive_render,
) )
app.jinja_env.globals["form_hidden_tag"] = form_hidden_tag app.jinja_env.globals["form_hidden_tag"] = form_hidden_tag
app.jinja_env.globals["display_tags"] = display_tags app.jinja_env.globals["display_inline_elements"] = display_inline_elements
app.jinja_env.globals["build_qa_url"] = build_qa_url_using_interpretation app.jinja_env.globals["build_qa_url"] = build_qa_url_using_interpretation
app.jinja_env.globals["recursive_render"] = recursive_render app.jinja_env.globals["recursive_render"] = recursive_render

View File

@ -3,6 +3,7 @@ import re
from flask import current_app from flask import current_app
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from flask import url_for, render_template from flask import url_for, render_template
from sqlalchemy import func
from app import models as m from app import models as m
@ -15,21 +16,45 @@ def form_hidden_tag():
return form.hidden_tag() return form.hidden_tag()
# Using: {{ display_tags("Some text with [tags] here") }} # Using: {{ display_inline_elements("Some text with [tags] here") }}
def display_tags(text: str): def display_inline_elements(text: str):
tags = current_app.config["TAG_REGEX"].findall(text) users_mentions = current_app.config["USER_MENTION_REGEX"].findall(text)
classes = [
"!no-underline",
"cursor-pointer",
"multiple-input-word",
"bg-sky-100",
"border",
"border-sky-300",
"dark:!text-black",
"rounded",
"text-center",
"py-1/2",
"px-1",
]
classes = " ".join(classes)
for users_mention in users_mentions:
username = users_mention.replace("@", "").lower()
user: m.User = m.User.query.filter(
(func.lower(m.User.username) == username)
).first()
if user:
username_to_display = users_mention.replace(username, user.username)
url = url_for("user.profile", user_id=user.id)
text = text.replace(
users_mention,
f"<a href='{url}' class='{classes}'>{username_to_display}</a>",
)
tags = current_app.config["TAG_REGEX"].findall(text)
classes = ["text-orange-500", "!no-underline"] classes = ["text-orange-500", "!no-underline"]
classes = " ".join(classes) classes = " ".join(classes)
for tag in set(tags):
for tag in tags:
url = url_for( url = url_for(
"search.tag_search_interpretations", "search.tag_search_interpretations", tag_name=tag.lower().replace("#", "")
tag_name=tag.lower().replace("[", "").replace("]", ""),
) )
text = text.replace( text = re.sub(
tag, rf"({tag})\b", f"<a href='{url}' class='{classes}'>{tag}</a>", text
f"<a href='{url}' class='{classes}'>{tag}</a>",
) )
return text return text

View File

@ -50,9 +50,9 @@ def set_comment_tags(comment: m.Comment, tags: list[str]):
comment_tags = m.CommentTags.query.filter_by(comment_id=comment.id).all() comment_tags = m.CommentTags.query.filter_by(comment_id=comment.id).all()
for tag in comment_tags: for tag in comment_tags:
db.session.delete(tag) db.session.delete(tag)
tags_names = [tag.lower().replace("[", "").replace("]", "") for tag in tags] tags_names = [tag.lower().replace("#", "") for tag in tags]
for tag_name in tags_names: for tag_name in set(tags_names):
try: try:
tag = get_or_create_tag(tag_name) tag = get_or_create_tag(tag_name)
except ValueError as e: except ValueError as e:
@ -77,7 +77,7 @@ def set_interpretation_tags(interpretation: m.InterpretationTag, tags: list[str]
).all() ).all()
for tag in interpretation_tags: for tag in interpretation_tags:
db.session.delete(tag) db.session.delete(tag)
tags_names = [tag.lower().replace("[", "").replace("]", "") for tag in tags] tags_names = [tag.lower().replace("#", "") for tag in tags]
for tag_name in tags_names: for tag_name in tags_names:
try: try:

File diff suppressed because one or more lines are too long

View File

@ -103,6 +103,8 @@
<!-- DO NOT REMOVE THIS! --> <!-- DO NOT REMOVE THIS! -->
<!-- Adding tailwind classes that are not in html, but will be added by jinja --> <!-- Adding tailwind classes that are not in html, but will be added by jinja -->
<span class="hidden text-orange-500 !no-underline"></span> <span class="hid">
<span class="border-sky-300 bg-sky-100 !dark:text-black hidden text-orange-500 !no-underline !dark:bg-blue-600 !dark:hover:bg-blue-700 !dark:text-white"></span>
</span>
</body> </body>
</html> </html>

View File

@ -24,7 +24,7 @@
{% else %} {% else %}
<div class="ql-snow truncate md:max-w-xl"> <div class="ql-snow truncate md:max-w-xl">
<div class="dark:text-white h-30 ql-editor-readonly !px-0"> <div class="dark:text-white h-30 ql-editor-readonly !px-0">
<p>{{display_tags(section.approved_interpretation.text)|safe }}</p> <p>{{display_inline_elements(section.approved_interpretation.text)|safe }}</p>
</div> </div>
</div> </div>
<div <div
@ -103,9 +103,9 @@
{% for comment in section.approved_comments %} {% for comment in section.approved_comments %}
<div class="p-5 ml-6"> <div class="p-5 ml-6">
<div class="dark:text-white h-30"> <div class="dark:text-white h-30">
<div class="ql-snow mb-2 truncate md:max-w-xl"> <div class="ql-snow mb-2 md:max-w-xl">
<div class="dark:text-white h-30 ql-editor-readonly !px-0"> <div class="dark:text-white h-30 ql-editor-readonly !px-0">
<p>{{ display_tags(comment.text)|safe }}</p> <p>{{ display_inline_elements(comment.text)|safe }}</p>
</div> </div>
</div> </div>
</div> </div>
@ -157,9 +157,9 @@
</div> </div>
</div> </div>
<!-- prettier-ignore --> <!-- prettier-ignore -->
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% else %} {% else %}
<!-- prettier-ignore --> <!-- prettier-ignore -->
{% for section in collection.active_sections %} {% for section in collection.active_sections %}
@ -172,7 +172,7 @@
{% else %} {% else %}
<div class="ql-snow truncate md:max-w-xl mb-3"> <div class="ql-snow truncate md:max-w-xl mb-3">
<div class="dark:text-white h-30 ql-editor-readonly !px-0"> <div class="dark:text-white h-30 ql-editor-readonly !px-0">
<p>{{ display_tags(section.approved_interpretation.text)|safe }}</p> <p>{{ display_inline_elements(section.approved_interpretation.text)|safe }}</p>
</div> </div>
</div> </div>
<!-- prettier-ignore --> <!-- prettier-ignore -->
@ -254,9 +254,9 @@
{% for comment in section.approved_comments %} {% for comment in section.approved_comments %}
<div class="p-5 ml-6"> <div class="p-5 ml-6">
<div class="dark:text-white h-30"> <div class="dark:text-white h-30">
<div class="ql-snow mb-2 truncate md:max-w-xl"> <div class="ql-snow mb-2 md:max-w-xl">
<div class="dark:text-white h-30 ql-editor-readonly !px-0"> <div class="dark:text-white h-30 ql-editor-readonly !px-0">
<p>{{ display_tags(comment.text)|safe }}</p> <p>{{ display_inline_elements(comment.text)|safe }}</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -157,9 +157,9 @@
</div> </div>
<!-- prettier-ignore --> <!-- prettier-ignore -->
<dt class="flex justify-center w-full mb-1 text-gray-500 md:text-lg dark:text-gray-400 flex-col"> <dt class="flex justify-center w-full mb-1 text-gray-500 md:text-lg dark:text-gray-400 flex-col">
<div class="ql-snow mb-2 truncate md:max-w-xl"> <div class="ql-snow mb-2 md:max-w-xl">
<div class="dark:text-white h-30 ql-editor-readonly !px-0"> <div class="dark:text-white h-30 ql-editor-readonly !px-0">
<p>{{ display_tags(interpretation.text)|safe }}</p> <p>{{ display_inline_elements(interpretation.text)|safe }}</p>
</div> </div>
</div> </div>

View File

@ -69,7 +69,7 @@
<p>{{ interpretation.section.label }}</p> <p>{{ interpretation.section.label }}</p>
</a> </a>
<div class="dark:text-white h-30 ql-editor-readonly"> <div class="dark:text-white h-30 ql-editor-readonly">
<p>{{ display_tags(interpretation.text)|safe }}</p> <p>{{ display_inline_elements(interpretation.text)|safe }}</p>
</div> </div>
</div> </div>
<div class="flex mt-auto align-center justify-between md:w-full"> <div class="flex mt-auto align-center justify-between md:w-full">

View File

@ -20,7 +20,7 @@
{{ section.label }} {{ section.label }}
</h1> </h1>
<div class="ql-editor-readonly text-lg dark:text-white p-3"> <div class="ql-editor-readonly text-lg dark:text-white p-3">
{{display_tags(interpretation.text)|safe}} {{display_inline_elements(interpretation.text)|safe}}
</div> </div>
</div> </div>
<div class="p-1"> <div class="p-1">
@ -131,9 +131,9 @@
<dt class="flex justify-center w-full mb-1 text-gray-500 md:text-lg dark:text-gray-400 flex-col"> <dt class="flex justify-center w-full mb-1 text-gray-500 md:text-lg dark:text-gray-400 flex-col">
<div> <div>
<div class="dark:text-white h-30"> <div class="dark:text-white h-30">
<div class="ql-snow mb-2 truncate md:max-w-xl"> <div class="ql-snow mb-2 md:max-w-xl">
<div class="dark:text-white h-30 ql-editor-readonly !px-0"> <div class="dark:text-white h-30 ql-editor-readonly !px-0">
<p>{{ display_tags(comment.text)|safe }}</p> <p>{{ display_inline_elements(comment.text)|safe }}</p>
</div> </div>
</div> </div>
</div> </div>
@ -194,7 +194,7 @@
<div class="p-5 mb-2 flex justify-between items-end bg-slate-100 dark:bg-slate-600 rounded-lg"> <div class="p-5 mb-2 flex justify-between items-end bg-slate-100 dark:bg-slate-600 rounded-lg">
<div class="ql-snow"> <div class="ql-snow">
<div class="inline-block mb-4 ql-editor-readonly !p-0"> <div class="inline-block mb-4 ql-editor-readonly !p-0">
{{display_tags(child.text)|safe}} {{display_inline_elements(child.text)|safe}}
</div> </div>
<span> <span>
<div> <div>

View File

@ -71,7 +71,7 @@
<p>{{ interpretation.section.label }}</p> <p>{{ interpretation.section.label }}</p>
</a> </a>
<div class="dark:text-white h-30 ql-editor-readonly"> <div class="dark:text-white h-30 ql-editor-readonly">
<p>{{ display_tags(interpretation.text)|safe }}</p> <p>{{ display_inline_elements(interpretation.text)|safe }}</p>
</div> </div>
</div> </div>
<div class="flex mt-auto align-center justify-between md:w-full border-t border-gray-200 dark:border-gray-700"> <div class="flex mt-auto align-center justify-between md:w-full border-t border-gray-200 dark:border-gray-700">

View File

@ -92,7 +92,7 @@
<p>{{ interpretation.section.label }}</p> <p>{{ interpretation.section.label }}</p>
</a> </a>
<div class="dark:text-white h-30 ql-editor-readonly"> <div class="dark:text-white h-30 ql-editor-readonly">
<p>{{ display_tags(interpretation.text)|safe }}</p> <p>{{ display_inline_elements(interpretation.text)|safe }}</p>
</div> </div>
</div> </div>
<div class="flex mt-auto align-center justify-between md:w-full"> <div class="flex mt-auto align-center justify-between md:w-full">

View File

@ -80,7 +80,7 @@
<p>{{ interpretation.section.label }}</p> <p>{{ interpretation.section.label }}</p>
</a> </a>
<div class="dark:text-white h-30 ql-editor-readonly"> <div class="dark:text-white h-30 ql-editor-readonly">
<p>{{ display_tags(interpretation.text)|safe }}</p> <p>{{ display_inline_elements(interpretation.text)|safe }}</p>
</div> </div>
</div> </div>
<div class="flex mt-auto align-center justify-between md:w-full"> <div class="flex mt-auto align-center justify-between md:w-full">

View File

@ -121,7 +121,7 @@
<p>{{ interpretation.section.label }}</p> <p>{{ interpretation.section.label }}</p>
</a> </a>
<div class="dark:text-white h-30 ql-editor-readonly"> <div class="dark:text-white h-30 ql-editor-readonly">
<p>{{ display_tags(interpretation.text)|safe }}</p> <p>{{ display_inline_elements(interpretation.text)|safe }}</p>
</div> </div>
</div> </div>
<div class="flex mt-auto align-center justify-between md:w-full"> <div class="flex mt-auto align-center justify-between md:w-full">

View File

@ -61,6 +61,8 @@ def create_comment(
tags = current_app.config["TAG_REGEX"].findall(text) tags = current_app.config["TAG_REGEX"].findall(text)
set_comment_tags(comment, tags) set_comment_tags(comment, tags)
# TODO Send notifications
# users_mentions = current_app.config["USER_MENTION_REGEX"].findall(text)
flash("Success!", "success") flash("Success!", "success")
return redirect(redirect_url) return redirect(redirect_url)

View File

@ -69,7 +69,7 @@ def interpretation_create(
plain_text = clean_html(text).lower() plain_text = clean_html(text).lower()
tags = current_app.config["TAG_REGEX"].findall(text) tags = current_app.config["TAG_REGEX"].findall(text)
for tag in tags: for tag in tags:
word = tag.lower().replace("[", "").replace("]", "") word = tag.lower().replace("#", "")
plain_text = plain_text.replace(tag.lower(), word) plain_text = plain_text.replace(tag.lower(), word)
interpretation: m.Interpretation = m.Interpretation( interpretation: m.Interpretation = m.Interpretation(
@ -135,7 +135,7 @@ def interpretation_edit(
plain_text = clean_html(text).lower() plain_text = clean_html(text).lower()
tags = current_app.config["TAG_REGEX"].findall(text) tags = current_app.config["TAG_REGEX"].findall(text)
for tag in tags: for tag in tags:
word = tag.lower().replace("[", "").replace("]", "") word = tag.lower().replace("#", "")
plain_text = plain_text.replace(tag.lower(), word) plain_text = plain_text.replace(tag.lower(), word)
interpretation.plain_text = plain_text interpretation.plain_text = plain_text

View File

@ -30,7 +30,8 @@ class BaseConfig(BaseSettings):
HTTP_PROVIDER_URL: str HTTP_PROVIDER_URL: str
# regex # regex
TAG_REGEX = re.compile(r"\[.*?\]") TAG_REGEX = re.compile(r"(#+[a-zA-Z0-9(_)]{1,})") # #tag
USER_MENTION_REGEX = re.compile(r"(?<!\w)@\w+") # @word
@staticmethod @staticmethod
def configure(app: Flask): def configure(app: Flask):

View File

@ -1135,8 +1135,8 @@ def test_interpretation_in_home_last_inter_section(
version_id=book.last_version.id, version_id=book.last_version.id,
).save() ).save()
label_1 = "Test Interpretation #1 Label" label_1 = "Test Interpretation no1 Label"
text_1 = "Test Interpretation #1 Text" text_1 = "Test Interpretation no1 Text"
response: Response = client.post( response: Response = client.post(
f"/book/{book.id}/{section_in_subcollection.id}/create_interpretation", f"/book/{book.id}/{section_in_subcollection.id}/create_interpretation",

View File

@ -86,7 +86,7 @@ def test_create_tags_on_comment_create_and_edit(client: FlaskClient):
section = db.session.get(m.Section, 1) section = db.session.get(m.Section, 1)
interpretation = db.session.get(m.Interpretation, 1) interpretation = db.session.get(m.Interpretation, 1)
tags = "[tag1] [tag2] [tag3]" tags = "#tag1 #tag2 #tag3"
response: Response = client.post( response: Response = client.post(
f"/book/{book.id}/{interpretation.id}/create_comment", f"/book/{book.id}/{interpretation.id}/create_comment",
data=dict( data=dict(
@ -102,9 +102,7 @@ def test_create_tags_on_comment_create_and_edit(client: FlaskClient):
assert comment assert comment
assert comment.tags assert comment.tags
splitted_tags = [ splitted_tags = [tag.lower().replace("#", "") for tag in tags.split()]
tag.lower().replace("[", "").replace("]", "") for tag in tags.split()
]
assert len(comment.tags) == 3 assert len(comment.tags) == 3
for tag in comment.tags: for tag in comment.tags:
tag: m.Tag tag: m.Tag
@ -113,7 +111,7 @@ def test_create_tags_on_comment_create_and_edit(client: FlaskClient):
tags_from_db: m.Tag = m.Tag.query.all() tags_from_db: m.Tag = m.Tag.query.all()
assert len(tags_from_db) == 3 assert len(tags_from_db) == 3
tags = "[tag1] [tag5] [tag7]" tags = "#tag1 #tag5 #tag7"
response: Response = client.post( response: Response = client.post(
f"/book/{book.id}/{interpretation.id}/comment_edit", f"/book/{book.id}/{interpretation.id}/comment_edit",
data=dict(text="some text" + tags, comment_id=comment.id), data=dict(text="some text" + tags, comment_id=comment.id),
@ -125,9 +123,7 @@ def test_create_tags_on_comment_create_and_edit(client: FlaskClient):
assert comment assert comment
assert comment.tags assert comment.tags
splitted_tags = [ splitted_tags = [tag.lower().replace("#", "") for tag in tags.split()]
tag.lower().replace("[", "").replace("]", "") for tag in tags.split()
]
assert len(comment.tags) == 3 assert len(comment.tags) == 3
for tag in comment.tags: for tag in comment.tags:
tag: m.Tag tag: m.Tag
@ -144,8 +140,8 @@ def test_create_tags_on_interpretation_create_and_edit(client: FlaskClient):
book = db.session.get(m.Book, 1) book = db.session.get(m.Book, 1)
section = db.session.get(m.Section, 1) section = db.session.get(m.Section, 1)
tags = "[tag1] [tag2] [tag3]" tags = "#tag1 #tag2 #tag3"
text_1 = "Test Interpretation #1 Text" text_1 = "Test Interpretation no1 Text"
response: Response = client.post( response: Response = client.post(
f"/book/{book.id}/{section.id}/create_interpretation", f"/book/{book.id}/{section.id}/create_interpretation",
@ -160,9 +156,7 @@ def test_create_tags_on_interpretation_create_and_edit(client: FlaskClient):
assert interpretation assert interpretation
assert interpretation.tags assert interpretation.tags
splitted_tags = [ splitted_tags = [tag.lower().replace("#", "") for tag in tags.split()]
tag.lower().replace("[", "").replace("]", "") for tag in tags.split()
]
assert len(interpretation.tags) == 3 assert len(interpretation.tags) == 3
for tag in interpretation.tags: for tag in interpretation.tags:
tag: m.Tag tag: m.Tag
@ -171,7 +165,7 @@ def test_create_tags_on_interpretation_create_and_edit(client: FlaskClient):
tags_from_db: m.Tag = m.Tag.query.all() tags_from_db: m.Tag = m.Tag.query.all()
assert len(tags_from_db) == 3 assert len(tags_from_db) == 3
tags = "[tag-4] [tag5] [tag3]" tags = "#tag4 #tag3 #tag5"
response: Response = client.post( response: Response = client.post(
f"/book/{book.id}/{interpretation.id}/edit_interpretation", f"/book/{book.id}/{interpretation.id}/edit_interpretation",
data=dict(interpretation_id=interpretation.id, text=text_1 + tags), data=dict(interpretation_id=interpretation.id, text=text_1 + tags),
@ -184,9 +178,7 @@ def test_create_tags_on_interpretation_create_and_edit(client: FlaskClient):
).first() ).first()
assert interpretation assert interpretation
splitted_tags = [ splitted_tags = [tag.lower().replace("#", "") for tag in tags.split()]
tag.lower().replace("[", "").replace("]", "") for tag in tags.split()
]
assert len(interpretation.tags) == 3 assert len(interpretation.tags) == 3
for tag in interpretation.tags: for tag in interpretation.tags:
tag: m.Tag tag: m.Tag