Skip to content

Commit

Permalink
✨(front) add upload images in AshleyEditor
Browse files Browse the repository at this point in the history
Decorating posts with images will help students and teachers communicate.
Adding images it's an expected feature in a forum, and it's user-friendly. For
that purpose, this option has been added.
  • Loading branch information
carofun committed Jun 24, 2021
1 parent 62b6d19 commit d97306e
Show file tree
Hide file tree
Showing 32 changed files with 1,676 additions and 957 deletions.
1 change: 1 addition & 0 deletions src/ashley/context_processors.py
Expand Up @@ -3,6 +3,7 @@
"""
import json

from django.conf import settings
from django.middleware.csrf import get_token


Expand Down
9 changes: 7 additions & 2 deletions src/ashley/defaults.py
Expand Up @@ -6,7 +6,7 @@
from draftjs_exporter.defaults import STYLE_MAP as DEFAULT_STYLE_MAP
from draftjs_exporter.dom import DOM

from ashley.editor.decorators import emoji, link, mention
from ashley.editor.decorators import emoji, image, link, mention

_FORUM_ROLE_INSTRUCTOR = "instructor"
_FORUM_ROLE_MODERATOR = "moderator"
Expand Down Expand Up @@ -59,7 +59,12 @@
)()

DEFAULT_DRAFTJS_EXPORTER_CONFIG = {
"entity_decorators": {"LINK": link, "emoji": emoji, "mention": mention},
"entity_decorators": {
"LINK": link,
"emoji": emoji,
"mention": mention,
"IMAGE": image,
},
"composite_decorators": [],
"block_map": DEFAULT_BLOCK_MAP,
"style_map": DEFAULT_STYLE_MAP,
Expand Down
34 changes: 34 additions & 0 deletions src/ashley/editor/decorators.py
Expand Up @@ -59,3 +59,37 @@ def mention(props):
)

return None


def image(props):
"""
Decorator for the `IMAGE` entity in Draft.js ContentState. Image can be decorated with a width,
height, and alignment property. By default draft-js insert data at 40% width of the editor,
we want to keep the same ratio so that the preview is alike.
"""
css_class = ""
alignment = props.get("alignment", None)
if alignment in ("left", "right"):
css_class = f"float-{alignment}"
if alignment == "center":
css_class = "image-center"

# make sure data are number and positive, default is 40% of editor size
width = f'{abs(int(props.get("width", "40")))}%'

height = "auto"
if props.get("height", None) is not None:
height = f'{abs(int(props.get("height")))}%'

if props.get("src", None) is not None:
return DOM.create_element(
"img",
{
"class": css_class,
"src": props.get("src"),
"width": width,
"height": height,
},
)

return None
Binary file modified src/ashley/locale/fr_FR/LC_MESSAGES/django.mo
Binary file not shown.
26 changes: 11 additions & 15 deletions src/ashley/locale/fr_FR/LC_MESSAGES/django.po
Expand Up @@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: ashleyforum\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-04-19 12:27+0000\n"
"PO-Revision-Date: 2021-04-27 14:53\n"
"POT-Creation-Date: 2021-06-02 09:21+0000\n"
"PO-Revision-Date: 2021-06-08 12:39\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr_FR\n"
Expand Down Expand Up @@ -1072,14 +1072,6 @@ msgstr "Il n'y a aucune discussion non lue."
msgid "Forum index"
msgstr "Accueil du forum"

#: editor/widgets.py:14
msgid "Please enter your message here..."
msgstr "Veuillez entrer votre message ici ..."

#: editor/widgets.py:15
msgid "Fill-in or paste your URL here and press enter"
msgstr "Saisissez ou collez votre URL ici et appuyez sur Entrée"

#: machina_extensions/forum_permission/defaults.py:22
msgid "Can rename a forum"
msgstr "Peut renommer un forum"
Expand All @@ -1088,19 +1080,19 @@ msgstr "Peut renommer un forum"
msgid "Can manage a user's moderator status"
msgstr "Permet de gérer le statut de modérateur d'un utilisateur"

#: models.py:36
#: models.py:44
msgid "LTI remote user identifier"
msgstr "Identifiant utilisateur LTI distant"

#: models.py:37
#: models.py:45
msgid "Unique identifier for the user on the tool consumer"
msgstr "Identifiant utilisateur unique sur le Consumer"

#: models.py:43
#: models.py:51
msgid "Public username"
msgstr "Nom d'utilisateur public"

#: models.py:44
#: models.py:52
msgid "This username will be displayed with the user's posts"
msgstr "Ce nom d'utilisateur sera associé aux messages de l'utilisateur"

Expand Down Expand Up @@ -1142,9 +1134,13 @@ msgstr "Renommer le forum"
msgid "Rename"
msgstr "Renommer"

#: templates/forum_conversation/partials/topic_list_header_sort.html:4
msgid "Toggle sorting"
msgstr "Inverser le tri"

#: templates/forum_conversation/post_create.html:51
#: templates/forum_conversation/topic_detail.html:57
#: templates/forum_conversation/topic_list.html:38
#: templates/forum_conversation/topic_list.html:53
#, python-format
msgid "on %(creation_date)s"
msgstr "le %(creation_date)s"
Expand Down
57 changes: 26 additions & 31 deletions src/ashley/machina_extensions/forum_conversation/forms.py
Expand Up @@ -5,46 +5,41 @@
This module defines forms provided by the ``forum_conversation`` application.
"""
import logging

from django.contrib.auth import get_user_model
from machina.apps.forum_conversation.forms import PostForm as MachinaPostForm
from machina.core.loading import get_class

get_forum_member_display_name = get_class(
"forum_member.shortcuts", "get_forum_member_display_name"
)
User = get_user_model()
logger = logging.getLogger(__name__)
from machina.apps.forum_conversation.forms import TopicForm as MachinaTopicForm


class PostForm(MachinaPostForm):
""" Overload Machina PostForm to send extra variables to the widget editor """

def __init__(self, *args, **kwargs):

super().__init__(*args, **kwargs)

# collect active users for this topic
active_post_users = (
User.objects.filter(
is_active=True, posts__topic=self.topic, posts__approved=True
)
.exclude(pk=self.user.pk)
.distinct()
)

list_active_users = [
# collect extra informations used by the editor
self.fields["content"].widget.attrs.update(
{
"name": get_forum_member_display_name(user),
"user": user.pk,
"mentions": self.topic.get_active_users(self.user),
"forum": self.forum.id,
}
for user in active_post_users
]
self.fields["content"].widget.attrs["mention_users"] = sorted(
list_active_users, key=lambda i: i["name"]
)

logger.debug(
"List active users %s", self.fields["content"].widget.attrs["mention_users"]
)
# remove unused machina placeholder, placeholder is created in our component AshleyEditor
self.fields["content"].widget.attrs.pop("placeholder")


class TopicForm(MachinaTopicForm):
"""Overload Machina TopicForm to send extra variables to the widget editor for the
Topic creation. TopicForm is loaded only for the initial post of the topic."""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.topic is not None and self.topic.posts_count > 1:
# will be useful only if this first post is edited after answers
self.fields["content"].widget.attrs[
"mentions"
] = self.topic.get_active_users(self.user)

# send extra informations used by the editor
self.fields["content"].widget.attrs.update({"forum": self.forum.id})

# remove unused machina placeholder, placeholder is created in our component AshleyEditor
self.fields["content"].widget.attrs.pop("placeholder")
26 changes: 25 additions & 1 deletion src/ashley/machina_extensions/forum_conversation/models.py
@@ -1,11 +1,17 @@
"""Declare the models related to the forum_conversation app ."""

from django.contrib.auth import get_user_model
from django.db import models # pylint: disable=all
from django.utils.translation import gettext_lazy as _
from machina.apps.forum_conversation.abstract_models import (
AbstractTopic as MachinaAbstractTopic,
)
from machina.core.db.models import model_factory
from machina.core.loading import get_class

get_forum_member_display_name = get_class(
"forum_member.shortcuts", "get_forum_member_display_name"
)
User = get_user_model()


class AbstractTopic(MachinaAbstractTopic):
Expand All @@ -32,6 +38,24 @@ class AbstractTopic(MachinaAbstractTopic):
class Meta(MachinaAbstractTopic.Meta):
abstract = True

def get_active_users(self, user):
# collect active users for this topic and exclude current user
active_post_users = (
User.objects.filter(is_active=True, posts__topic=self, posts__approved=True)
.exclude(pk=user.pk)
.distinct()
)

list_active_users = [
{
"name": get_forum_member_display_name(user),
"user": user.pk,
}
for user in active_post_users
]

return sorted(list_active_users, key=lambda i: i["name"])


Topic = model_factory(AbstractTopic)

Expand Down
24 changes: 24 additions & 0 deletions src/frontend/i18n/locales/fr_FR.json
@@ -1,4 +1,28 @@
{
"components.AshleyEditor.ImageAdd.addImageFileUploadTitle": {
"description": "Title for the modal to upload an image.",
"message": "Charger une image"
},
"components.AshleyEditor.ImageAdd.addImageFromComputer": {
"description": "Title for the button to upload an image.",
"message": "Ajouter une image"
},
"components.AshleyEditor.ImageAdd.addImageFromComputerError": {
"description": "Error message on uploading image",
"message": "Une erreur est survenue lors du téléchargement de l'image, veuillez réessayer ou contacter le support"
},
"components.AshleyEditor.ImageAdd.addImageFromComputerErrorMaxUpload": {
"description": "Error message on uploading image, max size reached",
"message": "Une erreur est survenue lors du téléchargement de l'image, la taille du fichier est supérieure à {maxSize}Mo."
},
"components.AshleyEditor.linkPlaceholderEditor": {
"description": "Placeholder when adding a link in the editor",
"message": "Saisissez ou collez votre URL ici et appuyez sur Entrée"
},
"components.AshleyEditor.placeholderEditor": {
"description": "Placeholder for new posts when editor is empty",
"message": "Veuillez entrer votre message ici ..."
},
"components.DashboardModerators.ButtonChangeRoleCta.modalConfirmation": {
"description": "Body for the modal to promote or revoke the defined user to moderator, to ask the user to confirm this action.",
"message": "Etes-vous certains que vous souhaitez poursuivre cette action ?"
Expand Down

0 comments on commit d97306e

Please sign in to comment.