From 05b5d1d17beb307042fde946dcf1ea89657afe94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Antoine=20Dupr=C3=A9?= Date: Wed, 20 Mar 2024 17:27:41 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20[Feature]=20Add=20styling=20and=20e?= =?UTF-8?q?mbedding=20tools=20for=20FlatPages=20content=20(refs=20#3921,?= =?UTF-8?q?=20#3922,=20#4019)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issues: - [[PageStatiqueV2] Proposer des styles prédéfinis #3921](https://github.com/GeotrekCE/Geotrek-admin/issues/3921) - [[PageStatiqueV2] Alignement des images #3922](https://github.com/GeotrekCE/Geotrek-admin/issues/3922) - [Intégration composant "Suggestions" #4019](https://github.com/GeotrekCE/Geotrek-admin/issues/4019) The following styling possibilities have been added: - a blockquote section - an information section - multiple heading levels (from H2 to H6) Added embedding tools : - decorated link with a button style (from tool "Lien Bouton") - the "Suggestions" tool allow to embed an element referencing Geotrek objects (available categories: treks, touristic content, touristic event, outdoor site). It takes the form of an invisible DIV in the HTML content which can be retrieved through the API and displayed nicely (with cards in an article for instance). --- docs/changelog.rst | 1 + docs/usage/static-pages.rst | 36 ++++- geotrek/flatpages/forms.py | 8 +- .../flatpages/css/flatpage_custom_formats.css | 51 +++++++ .../flatpages/js/additional_tinymce_plugin.js | 129 ++++++++++++++++++ geotrek/flatpages/widgets.py | 93 +++++++++++++ geotrek/settings/base.py | 6 + 7 files changed, 321 insertions(+), 3 deletions(-) create mode 100644 geotrek/flatpages/static/flatpages/css/flatpage_custom_formats.css create mode 100644 geotrek/flatpages/static/flatpages/js/additional_tinymce_plugin.js create mode 100644 geotrek/flatpages/widgets.py diff --git a/docs/changelog.rst b/docs/changelog.rst index 4b31426c9f..1b22349c1a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -8,6 +8,7 @@ CHANGELOG **Features** - Add new menu headers and flat pages with tree hierarchies +- Add styling and embedding tools for FlatPages content (refs #3921, #3922, #4019) **Bug fixes** diff --git a/docs/usage/static-pages.rst b/docs/usage/static-pages.rst index d7dbace11c..3ea7def7d0 100644 --- a/docs/usage/static-pages.rst +++ b/docs/usage/static-pages.rst @@ -74,18 +74,52 @@ Les champs Titre, Publiée et Contenu peuvent recevoir une valeur différente po Ces champs sont une alternative au glisser-déposer sur la liste des Pages Statiques et permettent de déplacer les pages dans l'arborescence (voir `Arborescence des pages statiques`_). -**Mise en forme et médias** +Mise en forme et médias +----------------------- Le champ contenu expose un éditeur de texte riche (TinyMCE) permettant d'ajouter de la mise en forme et des médias dans le contenu de la page. - mise en forme du texte : titres, styles du texte, couleur du texte - insertion de listes +- encart "Information" +- lien sous forme de bouton +- citation Médias : - insérer une image - insérer une vidéo YouTube - insérer un lien vers une autre page +- encart de suggestion de contenu Geotrek + +Insérer une image +----------------- + +L'outil *Insérer/modifier* une image permet d'insérer une image dans le contenu. Les champs suivants sont à renseigner : + +- Source +- Description alternative : non-affichée, pour l'accessibilité et les formes de consultation alternatives du contenu +- Largeur et Hauteur de présentation de l'image en pixels +- checkbox Afficher le sous-titrage insère une zone de texte collée à l'image pour présenter un titre (le titre est à saisir dans le contenu une fois le formulaire validé) + +Insérer des suggestions de contenu Geotrek +------------------------------------------ + +Avec l'outil *Suggestions*, les champs suivants sont à renseigner : + +- le type de contenu (itinéraires, contenu touristique, événements ou site d'activités de plein nature) +- les identifiants des contenus (séparés par des virgules. Par exemple : 12,8,73) +- un titre pour l'encart de suggestions + +Après la validation du formulaire une zone récapitulant les informations saisies sous forme textuelle est placée dans le contenu de la page. Le site portail enrichira la présentation des suggestions avec les titres des contenus suggérés à la place des identifiants et les images associées. + +Vérifier la mise en page du contenu +----------------------------------- + +Les outils suivants sont disponibles : + +- *Afficher les blocs* : permet de contrôler finement la séparation du contenu en blocs (pratique pour les paragraphes de texte) +- *Code source* : affiche et permet de modifier directement le contenu au format HTML (pour les utilisateurs avertis) Publier une page ================ diff --git a/geotrek/flatpages/forms.py b/geotrek/flatpages/forms.py index 1ae8dcdec2..005bf0438e 100644 --- a/geotrek/flatpages/forms.py +++ b/geotrek/flatpages/forms.py @@ -1,6 +1,6 @@ from django.conf import settings from django.core import validators -from tinymce.widgets import TinyMCE +from geotrek.flatpages.widgets import FlatPageTinyMCE from treebeard.forms import MoveNodeForm @@ -26,7 +26,7 @@ def __init__(self, *args, **kwargs): # are passed along as they contain modeltranslation CSS classes. for fieldname, formfield in self.fields.items(): if fieldname.startswith('content_'): - self.fields[fieldname].widget = TinyMCE(attrs=self.fields[fieldname].widget.attrs) + self.fields[fieldname].widget = FlatPageTinyMCE(attrs=self.fields[fieldname].widget.attrs) if self.instance.pk: page = Attachment.objects.filter( @@ -49,6 +49,10 @@ class Meta: 'content', ) + class Media: + + js = ('flatpages/js/additional_tinymce_plugins.js',) + def save_cover_image(self): page = self.instance if self.cleaned_data['cover_image']: diff --git a/geotrek/flatpages/static/flatpages/css/flatpage_custom_formats.css b/geotrek/flatpages/static/flatpages/css/flatpage_custom_formats.css new file mode 100644 index 0000000000..8a8792c44d --- /dev/null +++ b/geotrek/flatpages/static/flatpages/css/flatpage_custom_formats.css @@ -0,0 +1,51 @@ +h1, h2, h3 { + clear: both; +} + +.align-left { + float: left; + margin-right: 1rem; + margin-bottom: 1rem; +} + +.align-right { + float: right; + margin-left: 1rem; + margin-bottom: 1rem; +} + +.button-link { + display: inline-block; + padding: 0.5rem 1rem; + border-radius: 1rem; + border: 1px solid; + text-decoration: none; +} + +.information { + clear: both; + padding: 1rem; + background: lightgray; +} + +.suggestions { + clear: both; + display: block !important; + background: lightgreen; + color: green; + padding: 2rem; + border-radius: 1rem; + box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + clear: both; +} + +.suggestions::before { + content: '' attr(data-type) ' suggestions : ' attr(data-label) ''; + font-size: 1.2rem; + display: block; + margin-bottom: .3rem; +} + +.suggestions::after { + content: 'Identifiants : ' attr(data-ids) ''; +} diff --git a/geotrek/flatpages/static/flatpages/js/additional_tinymce_plugin.js b/geotrek/flatpages/static/flatpages/js/additional_tinymce_plugin.js new file mode 100644 index 0000000000..64278c14c6 --- /dev/null +++ b/geotrek/flatpages/static/flatpages/js/additional_tinymce_plugin.js @@ -0,0 +1,129 @@ +/* + Note: We have included the plugin in the same JavaScript file as the TinyMCE + instance for display purposes only. Tiny recommends not maintaining the plugin + with the TinyMCE instance and using the `external_plugins` option. +*/ + +document.addEventListener("DOMContentLoaded", function () { + tinymce.PluginManager.add('button-link', function (editor, url) { + var openDialogButtonLink = function () { + return editor.windowManager.open({ + title: 'Lien bouton', + body: { + type: 'panel', + items: [ + { + type: 'input', + name: 'label', + label: 'Intitulé', + }, + { + type: 'input', + name: 'link', + label: 'Lien', + }, + { + type: 'checkbox', + name: 'target', + label: 'Ouvrir dans un nouvel onglet' + } + ] + }, + buttons: [ + { + type: 'cancel', + text: 'Close' + }, + { + type: 'submit', + text: 'Save', + primary: true + } + ], + initialData: { + label: tinymce.activeEditor.selection.getContent(), + }, + onSubmit: function (api) { + var data = api.getData(); + if (!data.link || !data.label) { + return; + } + var target = data.target ? ' target="_blank" rel="noopener noreferrer" ' : ''; + editor.insertContent('' + data.label + ''); + api.close(); + } + }); + }; + + var openDialogSuggestion = function () { + return editor.windowManager.open({ + title: 'Suggestions', + body: { + type: 'panel', + items: [ + { + type: 'listbox', + name: 'type', + label: 'Type', + items: [ + {value: 'trek', text: 'Trek'}, + {value: 'touristicContent', text: 'Touristic content'}, + {value: 'touristicEvent', text: 'Touristic event'}, + {value: 'outdoorSite', text: 'Outdoor site'}, + ] + }, + { + type: 'input', + name: 'label', + label: "Intitulé de l'encart", + }, + { + type: 'input', + name: 'ids', + label: "Liste d'ID (séparés par des virgules)", + } + ] + }, + buttons: [ + { + type: 'cancel', + text: 'Close' + }, + { + type: 'submit', + text: 'Save', + primary: true + } + ], + initialData: { + label: tinymce.activeEditor.selection.getContent(), + }, + onSubmit: function (api) { + var data = api.getData(); + if (!data.type || !data.ids) { + return; + } + editor.insertContent(''); + api.close(); + } + }); + }; + /* Add a button that opens a window */ + editor.ui.registry.addButton('button-link', { + text: 'Lien bouton', + onAction: function () { + /* Open window */ + openDialogButtonLink(); + } + }); + /* Add a button that opens a window */ + editor.ui.registry.addButton('suggestions', { + text: 'Suggestions', + onAction: function () { + /* Open window */ + openDialogSuggestion(); + } + }); + }) + +}); // end of document DOMContentLoaded event handler diff --git a/geotrek/flatpages/widgets.py b/geotrek/flatpages/widgets.py new file mode 100644 index 0000000000..266ea3aa5e --- /dev/null +++ b/geotrek/flatpages/widgets.py @@ -0,0 +1,93 @@ +from django.templatetags.static import static +from tinymce.widgets import TinyMCE + +FLATPAGE_TINYMCE_CONFIG = { + "height": 500, + "plugins": [ + 'autolink', + 'lists', + 'link', + 'image', + 'media', + 'mediaembed', + 'button-link', + 'blockquote', + 'media', + 'table', + 'paste', + 'imagetools', + 'wordcount', + 'image_caption', + 'suggestions', + 'visualblocks', + 'code', + 'help', + ], + "menubar": False, + 'image_title': False, + 'image_caption': True, + 'automatic_uploads': False, + 'convert_urls': False, + 'file_picker_types': None, + 'images_upload_url': None, + "toolbar": 'undo redo | styleselect | blockquote | bold italic forecolor |' + 'alignleft aligncenter alignright alignjustify | bullist numlist | link image media |' + 'button-link suggestions | removeformat visualblocks code | wordcount | help', + "formats": { + "informationFormat": { + "block": 'div', "classes": 'information' + } + }, + "style_formats": [ + {"title": 'Headings', "items": [ + {"title": 'Headings 2', "format": 'h2'}, + {"title": 'Headings 3', "format": 'h3'}, + {"title": 'Headings 4', "format": 'h4'}, + {"title": 'Headings 5', "format": 'h5'}, + {"title": 'Headings 6', "format": 'h6'} + ]}, + {"title": 'Inline', "items": [ + {"title": 'Bold', "format": 'bold'}, + {"title": 'Italic', "format": 'italic'}, + {"title": 'Underline', "format": 'underline'}, + {"title": 'Strikethrough', "format": 'strikethrough'}, + ]}, + {"title": 'Blocks', "items": [ + {"title": 'Paragraph', "format": 'p'}, + {"title": 'Blockquote', "format": 'blockquote'}, + {"title": 'Information', "format": 'informationFormat'}, + ]}, + {"title": 'Alignment', "items": [ + {"title": 'Left', "format": 'alignleft'}, + {"title": 'Center', "format": 'aligncenter'}, + {"title": 'Right', "format": 'alignright'}, + {"title": 'Justify', "format": 'alignjustify'} + ]} + ], + "newline_behavior": '', + "default_font_stack": ['-apple-system', 'Helvetica', 'Arial', 'sans-serif'], + "theme": "silver", + 'paste_auto_cleanup_on_paste': True, + 'paste_as_text': True, + "forced_root_block": "p", + "width": "95%", + "resize": "both", + "browser_spellcheck": True, + "contextmenu": False, + 'valid_elements': ('@[id|class|style|title|dir