Skip to content

Commit

Permalink
✨ [Feature] Add styling and embedding tools for FlatPages content (refs
Browse files Browse the repository at this point in the history
#3921, #3922, #4019)

Issues:

- [[PageStatiqueV2] Proposer des styles prédéfinis #3921](#3921)
- [[PageStatiqueV2] Alignement des images #3922](#3922)
- [Intégration composant "Suggestions" #4019](#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).
  • Loading branch information
marcantoinedupre committed Apr 1, 2024
1 parent f2b2c2d commit 05b5d1d
Show file tree
Hide file tree
Showing 7 changed files with 321 additions and 3 deletions.
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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**

Expand Down
36 changes: 35 additions & 1 deletion docs/usage/static-pages.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
================
Expand Down
8 changes: 6 additions & 2 deletions geotrek/flatpages/forms.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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(
Expand All @@ -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']:
Expand Down
51 changes: 51 additions & 0 deletions geotrek/flatpages/static/flatpages/css/flatpage_custom_formats.css
Original file line number Diff line number Diff line change
@@ -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) '';
}
129 changes: 129 additions & 0 deletions geotrek/flatpages/static/flatpages/js/additional_tinymce_plugin.js
Original file line number Diff line number Diff line change
@@ -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('<a class="button-link"' + target + 'href="' + data.link + '">' + data.label + '</a>');
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('<div class="suggestions" data-label="' + data.label + '" data-type="' + data.type + '" data-ids="' + data.ids + '" style="display: none" contenteditable="false"></div>');
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
93 changes: 93 additions & 0 deletions geotrek/flatpages/widgets.py
Original file line number Diff line number Diff line change
@@ -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<ltr?rtl|lang|xml::lang],'
'a[rel|rev|charset|hreflang|tabindex|accesskey|type|name|href|target|title|class],'
'img[longdesc|usemap|src|border|alt=|title|hspace|vspace|width|height|align],'
'p,em/i,strong/b,div[align],br,ul,li,ol,span[style],'
'iframe[src|frameborder=0|alt|title|width|height|align|name],'
'h2,h3,h4,h5,h6,figure,figcaption'),
"setup": "tinyMceInit",
}


class FlatPageTinyMCE(TinyMCE):

def __init__(self, *args, **kwargs):
mce_attrs = FLATPAGE_TINYMCE_CONFIG.copy()
mce_attrs["content_css"] = static("flatpages/css/flatpage_custom_formats.css")
kwargs["mce_attrs"] = mce_attrs
super().__init__(*args, **kwargs)
6 changes: 6 additions & 0 deletions geotrek/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,12 @@ def api_bbox(bbox, buffer):
"paste_as_text": True
}

TINYMCE_EXTRA_MEDIA = {
"css": {
"all": ("flatpages/css/flatpage_custom_formats.css", ),
},
}

SYNC_MOBILE_ROOT = os.path.join(VAR_DIR, 'mobile')
SYNC_MOBILE_OPTIONS = {'skip_tiles': False}

Expand Down

0 comments on commit 05b5d1d

Please sign in to comment.