From 4a2e29f1a00bbc08385d8ef55aced1467e7cf31d Mon Sep 17 00:00:00 2001 From: Mara Karagianni Date: Wed, 28 Feb 2024 18:15:10 +0200 Subject: [PATCH 01/14] Update ckeditor5 django fields --- adhocracy-plus/config/settings/base.py | 274 +++++++++++++----- adhocracy-plus/config/urls.py | 3 + apps/activities/admin.py | 4 +- .../a4_candy_contrib/item_detail.html | 6 +- apps/documents/assets/ParagraphForm.jsx | 44 ++- apps/documents/models.py | 8 +- .../a4_candy_documents/chapter_detail.html | 6 +- .../a4_candy_documents/paragraph_detail.html | 2 +- .../documents/templatetags/react_documents.py | 4 +- apps/newsletters/forms.py | 10 + apps/newsletters/models.py | 10 +- apps/offlineevents/forms.py | 14 + apps/offlineevents/models.py | 16 +- .../offlineevent_detail.html | 4 +- apps/organisations/admin.py | 4 +- apps/organisations/forms.py | 9 +- .../a4_candy_projects/project_detail.html | 6 +- apps/topicprio/forms.py | 30 +- apps/topicprio/models.py | 9 +- .../a4_candy_topicprio/topic_detail.html | 4 +- package.json | 2 +- requirements/base.txt | 3 +- tests/documents/test_document_api.py | 65 +++++ tests/newsletters/test_newsletter_views.py | 65 +++++ .../test_views_project_offlineevents.py | 74 +++++ .../test_views_module_topics.py | 60 ++++ 26 files changed, 601 insertions(+), 135 deletions(-) diff --git a/adhocracy-plus/config/settings/base.py b/adhocracy-plus/config/settings/base.py index 046a9ddc3..139688ec6 100644 --- a/adhocracy-plus/config/settings/base.py +++ b/adhocracy-plus/config/settings/base.py @@ -25,6 +25,7 @@ "django.contrib.messages", "django.contrib.staticfiles", "django.contrib.humanize", + "django_ckeditor_5", "widget_tweaks", "rest_framework", "rest_framework.authtoken", @@ -305,120 +306,110 @@ ], } -# CKEditor - -CKEDITOR_UPLOAD_PATH = "uploads/" -CKEDITOR_RESTRICT_BY_USER = "username" -CKEDITOR_ALLOW_NONIMAGE_FILES = True - -CKEDITOR_CONFIGS = { +BLEACH_LIST = { "default": { - "width": "100%", - "toolbar": "Custom", - "toolbar_Custom": [ - ["Bold", "Italic", "Underline"], - ["NumberedList", "BulletedList"], - ["Link", "Unlink"], + "tags": [ + "p", + "strong", + "em", + "u", + "ol", + "li", + "ul", + "a", + "img", + "iframe", + "div", ], + "attributes": { + "a": ["href", "rel", "target"], + "img": ["src", "alt", "style"], + "div": ["class"], + "iframe": ["src", "alt", "style"], + }, }, "image-editor": { - "width": "100%", - "toolbar": "Custom", - "toolbar_Custom": [ - ["Bold", "Italic", "Underline"], - ["Image"], - ["NumberedList", "BulletedList"], - ["Link", "Unlink"], - ], - "removeDialogTabs": "image:Link", - }, - "collapsible-image-editor": { - "width": "100%", - "title": _("Rich text editor"), - "toolbar": "Custom", - "toolbar_Custom": [ - ["Bold", "Italic", "Underline"], - ["Image"], - ["NumberedList", "BulletedList"], - ["Link", "Unlink"], - ["CollapsibleItem"], - ["Embed", "EmbedBase"], + "tags": [ + "a", + "em", + "figcaption", + "figure", + "img", + "li", + "ol", + "p", + "span", + "strong", + "u", + "ul", ], - "removePlugins": "stylesheetparser", - "extraAllowedContent": "iframe[*]; div[*]", - "removeDialogTabs": "image:Link", - }, - "video-editor": { - "width": "100%", - "title": _("Rich text editor"), - "toolbar": "Custom", - "toolbar_Custom": [["Embed", "EmbedBase"]], - "removePlugins": "stylesheetparser", - "extraAllowedContent": "iframe[*]; div[*]", - }, -} - -BLEACH_LIST = { - "default": { - "tags": ["p", "strong", "em", "u", "ol", "li", "ul", "a"], "attributes": { "a": ["href", "rel", "target"], + "figure": ["class", "style"], + "figcaption": ["class"], + "img": ["class", "src", "alt", "style", "height", "width"], + "span": ["class", "style"], }, - }, - "image-editor": { - "tags": ["p", "strong", "em", "u", "ol", "li", "ul", "a", "img"], - "attributes": {"a": ["href", "rel", "target"], "img": ["src", "alt", "style"]}, "styles": [ + "aspect-ratio", "float", - "margin", - "padding", - "width", "height", + "margin", "margin-bottom", - "margin-top", "margin-left", "margin-right", + "margin-top", + "padding", + "width", ], }, "collapsible-image-editor": { "tags": [ + "a", + "div", + "em", + "figcaption", + "figure", + "iframe", + "img", + "li", + "ol", "p", + "span", "strong", - "em", "u", - "ol", - "li", "ul", - "a", - "img", - "div", - "iframe", ], "attributes": { "a": ["href", "rel", "target"], - "img": ["src", "alt", "style"], - "div": ["class"], - "iframe": ["src", "alt", "style"], + "div": ["class", "data-oembed-url"], + "figure": ["class", "style"], + "figcaption": ["class"], + "iframe": ["src", "alt"], + "img": ["class", "src", "alt", "style", "height", "width"], + "span": ["class", "style"], }, "styles": [ + "aspect-ratio", "float", - "margin", - "padding", - "width", "height", + "margin", "margin-bottom", - "margin-top", "margin-left", "margin-right", + "margin-top", + "padding", + "width", ], }, "video-editor": { - "tags": ["a", "img", "div", "iframe"], + "tags": ["a", "img", "div", "iframe", "figure"], "attributes": { "a": ["href", "rel", "target"], "img": ["src", "alt", "style"], - "div": ["class"], - "iframe": ["src", "alt", "style"], + "div": ["class", "data-oembed-url"], + "iframe": ["src", "alt"], + "figure": ["class", "div", "iframe"], }, }, } @@ -560,3 +551,134 @@ CELERY_RESULT_BACKEND = "redis://localhost:6379" CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True CELERY_RESULT_EXTENDED = True + +# CKEditor config +CKEDITOR_UPLOAD_PATH = "uploads/" # to be removed after django upgrade and ckeditor_uploader removal +CKEDITOR_5_FILE_STORAGE = "adhocracy4.ckeditor.storage.CustomStorage" +CKEDITOR_5_PATH_FROM_USERNAME = True +CKEDITOR_5_ALLOW_ALL_FILE_TYPES = True +CKEDITOR_5_UPLOAD_FILE_TYPES = ["jpg", "jpeg", "png", "gif", "pdf", "docx"] +CKEDITOR_5_CONFIGS = { + "default": { + "language": "de", + "toolbar": [ + "bold", + "italic", + "underline", + "|", + "link", + "bulletedList", + "numberedList", + ], + "list": { + "properties": { + "styles": "true", + "startIndex": "true", + "reversed": "true", + } + }, + "link": {"defaultProtocol": "https://"}, + }, + "image-editor": { + "language": "de", + "toolbar": { + "items": [ + "bold", + "italic", + "underline", + "bulletedList", + "numberedList", + "link", + "imageUpload", + "fileUpload", + ], + "shouldNotGroupWhenFull": "true", + }, + "image": { + "toolbar": [ + "imageUpload", + "imageTextAlternative", + "toggleImageCaption", + "imageStyle:inline", + "imageStyle:wrapText", + "imageStyle:breakText", + "imageStyle:alignLeft", + "imageStyle:alignRight", + ], + "insert": {"type": "auto"}, + }, + "list": { + "properties": { + "styles": "true", + "startIndex": "true", + "reversed": "true", + } + }, + "link": {"defaultProtocol": "https://"}, + }, + "collapsible-image-editor": { + "language": "de", + "toolbar": [ + "bold", + "italic", + "underline", + "bulletedList", + "numberedList", + "link", + "imageUpload", + "fileUpload", + "mediaEmbed", + "accordionButton", + "fontSize", + ], + "image": { + "toolbar": [ + "imageUpload", + "imageTextAlternative", + "toggleImageCaption", + "imageStyle:inline", + "imageStyle:wrapText", + "imageStyle:breakText", + "imageStyle:alignLeft", + "imageStyle:alignRight", + ], + "insert": {"type": "auto"}, + }, + "list": { + "properties": { + "styles": "true", + "startIndex": "true", + "reversed": "true", + } + }, + "link": {"defaultProtocol": "https://"}, + "mediaEmbed": { + "removeProviders": [ + "dailymotion", + "spotify", + "facebook", + "flickr", + "googleMaps", + "instagram", + "twitter", + ], + "previewsInData": True, + }, + }, + "video-editor": { + "language": "de", + "toolbar": ["mediaEmbed"], + "mediaEmbed": { + "removeProviders": [ + "dailymotion", + "spotify", + "facebook", + "flickr", + "googleMaps", + "instagram", + "twitter", + ], + "previewsInData": True, + }, + }, +} diff --git a/adhocracy-plus/config/urls.py b/adhocracy-plus/config/urls.py index 501076c0f..1dcf99880 100644 --- a/adhocracy-plus/config/urls.py +++ b/adhocracy-plus/config/urls.py @@ -176,6 +176,9 @@ ] ), ), + path( + "ckeditor5/", include("django_ckeditor_5.urls"), name="ck_editor_5_upload_file" + ), path("sitemap.xml", static_sitemap_index, name="static-sitemap-index"), path("sitemap-wagtail.xml", wagtail_sitemap, name="wagtail-sitemap"), path( diff --git a/apps/activities/admin.py b/apps/activities/admin.py index 783ef6e14..7c31a246c 100644 --- a/apps/activities/admin.py +++ b/apps/activities/admin.py @@ -1,6 +1,6 @@ -from ckeditor_uploader.widgets import CKEditorUploadingWidget from django import forms from django.contrib import admin +from django_ckeditor_5.widgets import CKEditor5Widget from . import models @@ -9,7 +9,7 @@ class ActivityAdminForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields["description"].widget = CKEditorUploadingWidget( + self.fields["description"].widget = CKEditor5Widget( config_name="collapsible-image-editor", ) diff --git a/apps/contrib/templates/a4_candy_contrib/item_detail.html b/apps/contrib/templates/a4_candy_contrib/item_detail.html index ec0144b6d..3f2980327 100644 --- a/apps/contrib/templates/a4_candy_contrib/item_detail.html +++ b/apps/contrib/templates/a4_candy_contrib/item_detail.html @@ -57,7 +57,9 @@

{{ object.name }}

- {{ object.description | richtext }} +
+ {{ object.description | richtext }} +
{% block additional_content %}{% endblock %} @@ -144,7 +146,7 @@

{% translate 'Official Feedback' %}

{% html_date object.moderator_feedback_text.created %}
-
+
{{ object.moderator_feedback_text.feedback_text | safe }}
diff --git a/apps/documents/assets/ParagraphForm.jsx b/apps/documents/assets/ParagraphForm.jsx index a4206e310..46ae752f7 100644 --- a/apps/documents/assets/ParagraphForm.jsx +++ b/apps/documents/assets/ParagraphForm.jsx @@ -10,6 +10,21 @@ const ckReplace = function (id, config) { return window.CKEDITOR.replace(id, config) } +// translations +const translations = { + headline: django.gettext('Headline'), + paragraph: django.gettext('Paragraph'), + moveUp: django.gettext('Move up'), + moveDown: django.gettext('Move down'), + delete: django.gettext('Delete'), + helpText: django.gettext( + 'If you add an image, please provide an ' + + 'alternate text. It serves as a textual description of the image ' + + 'content and is read out by screen readers. Describe the image in ' + + 'approx. 80 characters. Example: A busy square with people in summer.' + ) +} + class ParagraphForm extends React.Component { handleNameChange (e) { const name = e.target.value @@ -68,7 +83,7 @@ class ParagraphForm extends React.Component {