Updated gallery interfaces and properties
authorThierry Florac <thierry.florac@onf.fr>
Mon, 24 Sep 2018 16:06:40 +0200 (2018-09-24)
changeset 976 5d23f030767c
parent 975 4d55824db030
child 977 4f1a82f040c4
Updated gallery interfaces and properties
src/pyams_content/component/gallery/__init__.py
src/pyams_content/component/gallery/interfaces/__init__.py
src/pyams_content/component/gallery/paragraph.py
src/pyams_content/component/gallery/zmi/__init__.py
src/pyams_content/component/gallery/zmi/file.py
src/pyams_content/component/gallery/zmi/paragraph.py
src/pyams_content/component/gallery/zmi/templates/gallery-media.pt
src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.mo
src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.po
src/pyams_content/locales/pyams_content.pot
src/pyams_content/skin/resources/js/pyams_content.js
src/pyams_content/skin/resources/js/pyams_content.min.js
--- a/src/pyams_content/component/gallery/__init__.py	Mon Sep 24 11:23:45 2018 +0200
+++ b/src/pyams_content/component/gallery/__init__.py	Mon Sep 24 16:06:40 2018 +0200
@@ -12,9 +12,6 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
-
 from pyramid.events import subscriber
 from pyramid.threadlocal import get_current_registry
 from zope.interface import implementer
@@ -25,12 +22,10 @@
 from zope.schema.fieldproperty import FieldProperty
 from zope.traversing.interfaces import ITraversable
 
-# import packages
 from pyams_catalog.utils import index_object
 from pyams_content import _
-# import interfaces
-from pyams_content.component.gallery.interfaces import IBaseGallery, IGallery, IGalleryTarget, IGalleryFile, \
-    GALLERY_CONTAINER_KEY, GALLERY_RENDERERS
+from pyams_content.component.gallery.interfaces import GALLERY_CONTAINER_KEY, GALLERY_RENDERERS, IBaseGallery, IGallery, \
+    IGalleryFile, IGalleryTarget
 from pyams_content.component.paragraph import IBaseParagraph
 from pyams_content.features.checker import BaseContentChecker
 from pyams_content.features.checker.interfaces import IContentChecker
@@ -38,7 +33,7 @@
 from pyams_content.shared.common.interfaces import IWfSharedContent
 from pyams_form.interfaces.form import IFormContextPermissionChecker
 from pyams_i18n.interfaces import II18n
-from pyams_utils.adapter import adapter_config, ContextAdapter, get_annotation_adapter
+from pyams_utils.adapter import ContextAdapter, adapter_config, get_annotation_adapter
 from pyams_utils.container import BTreeOrderedContainer
 from pyams_utils.factory import factory_config
 from pyams_utils.traversing import get_parent
@@ -49,14 +44,12 @@
 # Galleries container
 #
 
-@implementer(IGallery)
+@implementer(IBaseGallery)
 @factory_config(provided=IBaseGallery)
-class Gallery(RenderedContentMixin, BTreeOrderedContainer):
-    """Gallery persistent class"""
+class BaseGallery(RenderedContentMixin, BTreeOrderedContainer):
+    """Base gallery persistent class"""
 
-    title = FieldProperty(IGallery['title'])
-    description = FieldProperty(IGallery['description'])
-    renderer = FieldProperty(IGallery['renderer'])
+    renderer = FieldProperty(IBaseGallery['renderer'])
 
     last_id = 1
 
@@ -75,6 +68,15 @@
         yield from filter(lambda x: IGalleryFile(x).visible, self.values())
 
 
+@implementer(IGallery)
+@factory_config(provided=IGallery)
+class Gallery(BaseGallery):
+    """Gallery persistent class"""
+
+    title = FieldProperty(IGallery['title'])
+    description = FieldProperty(IGallery['description'])
+
+
 @adapter_config(context=IGalleryTarget, provides=IGallery)
 def gallery_factory(target):
     """Galleries container factory"""
@@ -160,7 +162,7 @@
         for media in IGallery(self.context).values():
             if not media.visible:
                 continue
-            for name, checker in sorted(registry.getAdapters((media, ), IContentChecker),
+            for name, checker in sorted(registry.getAdapters((media,), IContentChecker),
                                         key=lambda x: x[1].weight):
                 output.append('- {0} : '.format(checker.label or
                                                 II18n(media).query_attribute('title', request=request)))
--- a/src/pyams_content/component/gallery/interfaces/__init__.py	Mon Sep 24 11:23:45 2018 +0200
+++ b/src/pyams_content/component/gallery/interfaces/__init__.py	Mon Sep 24 16:06:40 2018 +0200
@@ -80,14 +80,6 @@
 class IBaseGallery(IOrderedContainer, IAttributeAnnotatable, IRenderedContent):
     """Base gallery interface"""
 
-    title = I18nTextLineField(title=_("Title"),
-                              description=_("Gallery title, as shown in front-office"),
-                              required=False)
-
-    description = I18nTextField(title=_("Description"),
-                                description=_("Gallery description displayed by front-office template"),
-                                required=False)
-
     renderer = Choice(title=_("Gallery template"),
                       description=_("Presentation template used for this gallery"),
                       vocabulary=GALLERY_RENDERERS,
@@ -110,6 +102,14 @@
 
     contains(IGalleryItem)
 
+    title = I18nTextLineField(title=_("Title"),
+                              description=_("Gallery title, as shown in front-office"),
+                              required=False)
+
+    description = I18nTextField(title=_("Description"),
+                                description=_("Gallery description displayed by front-office template"),
+                                required=False)
+
 
 class IGalleryTarget(IAttributeAnnotatable):
     """Gallery container target marker interface"""
@@ -119,5 +119,5 @@
 GALLERY_PARAGRAPH_NAME = _("Medias gallery")
 
 
-class IGalleryParagraph(IGallery, IBaseParagraph):
+class IGalleryParagraph(IBaseGallery, IBaseParagraph):
     """Gallery paragraph"""
--- a/src/pyams_content/component/gallery/paragraph.py	Mon Sep 24 11:23:45 2018 +0200
+++ b/src/pyams_content/component/gallery/paragraph.py	Mon Sep 24 16:06:40 2018 +0200
@@ -12,22 +12,19 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
+from zope.interface import implementer
 
-# import interfaces
-from pyams_content.component.gallery.interfaces import IGalleryParagraph, GALLERY_PARAGRAPH_TYPE, GALLERY_PARAGRAPH_NAME
+from pyams_content import _
+from pyams_content.component.gallery import BaseGallery
+from pyams_content.component.gallery.interfaces import GALLERY_PARAGRAPH_NAME, GALLERY_PARAGRAPH_TYPE, IGalleryParagraph
+from pyams_content.component.paragraph import BaseParagraph, BaseParagraphContentChecker, BaseParagraphFactory
 from pyams_content.component.paragraph.interfaces import IParagraphFactory
-from pyams_content.features.checker.interfaces import IContentChecker, MISSING_VALUE, MISSING_LANG_VALUE
-from pyams_i18n.interfaces import II18n, INegotiator, II18nManager
-
-# import packages
-from pyams_content.component.gallery import Gallery as BaseGallery
-from pyams_content.component.paragraph import BaseParagraph, BaseParagraphContentChecker, BaseParagraphFactory
+from pyams_content.features.checker.interfaces import IContentChecker, MISSING_LANG_VALUE, MISSING_VALUE
+from pyams_i18n.interfaces import II18n, II18nManager, INegotiator
 from pyams_utils.adapter import adapter_config
-from pyams_utils.registry import utility_config, get_utility
+from pyams_utils.registry import get_utility, utility_config
+from pyams_utils.request import check_request
 from pyams_utils.traversing import get_parent
-from zope.interface import implementer
 
 
 @implementer(IGalleryParagraph)
@@ -37,6 +34,16 @@
     icon_class = 'fa-picture-o'
     icon_hint = GALLERY_PARAGRAPH_NAME
 
+    @property
+    def title(self):
+        request = check_request()
+        translate = request.localizer.translate
+        nb_medias = len(self)
+        if nb_medias > 0:
+            return translate(_("({0} medias)")).format(nb_medias)
+        else:
+            return translate(_("(empty gallery)"))
+
 
 @utility_config(name=GALLERY_PARAGRAPH_TYPE, provides=IParagraphFactory)
 class GalleryFactory(BaseParagraphFactory):
@@ -58,7 +65,7 @@
             langs = manager.get_languages()
         else:
             negotiator = get_utility(INegotiator)
-            langs = (negotiator.server_language, )
+            langs = (negotiator.server_language,)
         i18n = II18n(self.context)
         for lang in langs:
             value = i18n.get_attribute('title', lang, request)
--- a/src/pyams_content/component/gallery/zmi/__init__.py	Mon Sep 24 11:23:45 2018 +0200
+++ b/src/pyams_content/component/gallery/zmi/__init__.py	Mon Sep 24 16:06:40 2018 +0200
@@ -12,41 +12,35 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
 import json
 import zipfile
-
 from io import BytesIO
 
-# import interfaces
-from pyams_content.component.gallery.interfaces import IGallery
-from pyams_content.component.gallery.zmi.interfaces import IGalleryContentsView
-from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
-from pyams_file.interfaces import IFileInfo
-from pyams_form.interfaces.form import IWidgetsPrefixViewletsManager
-from pyams_i18n.interfaces import II18n
-from pyams_skin.layer import IPyAMSLayer
-from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
+from pyramid.exceptions import NotFound
 from pyramid.interfaces import IView
-
-# import packages
-from pyams_content.shared.common.zmi import WfSharedContentPermissionMixin
-from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
-from pyams_form.form import ajax_config
-from pyams_pagelet.pagelet import pagelet_config
-from pyams_template.template import template_config
-from pyams_utils.url import absolute_url
-from pyams_viewlet.viewlet import viewlet_config, Viewlet
-from pyams_zmi.form import AdminDialogEditForm, AdminDialogDisplayForm
-from pyramid.exceptions import NotFound
 from pyramid.renderers import render_to_response
 from pyramid.response import Response
 from pyramid.view import view_config
 from z3c.form import field
-from zope.interface import implementer, Interface
+from zope.interface import Interface, implementer
 
 from pyams_content import _
+from pyams_content.component.gallery.interfaces import IBaseGallery, IGallery
+from pyams_content.component.gallery.zmi.interfaces import IGalleryContentsView
+from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
+from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
+from pyams_content.shared.common.zmi import WfSharedContentPermissionMixin
+from pyams_file.interfaces import IFileInfo
+from pyams_form.form import ajax_config
+from pyams_form.interfaces.form import IWidgetsPrefixViewletsManager
+from pyams_i18n.interfaces import II18n
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.layer import IPyAMSLayer
+from pyams_template.template import template_config
+from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
+from pyams_utils.url import absolute_url
+from pyams_viewlet.viewlet import Viewlet, viewlet_config
+from pyams_zmi.form import AdminDialogDisplayForm, AdminDialogEditForm
 
 
 #
@@ -82,7 +76,7 @@
 # Gallery contents
 #
 
-@pagelet_config(name='contents.html', context=IGallery, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@pagelet_config(name='contents.html', context=IBaseGallery, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
 @implementer(IGalleryContentsView)
 class GalleryContentForm(AdminDialogDisplayForm):
     """Gallery contents form"""
@@ -94,7 +88,7 @@
     show_widget_title = True
 
 
-@viewlet_config(name='gallery-medias', context=IGallery, view=IGalleryContentsView,
+@viewlet_config(name='gallery-medias', context=IBaseGallery, view=IGalleryContentsView,
                 manager=IWidgetsPrefixViewletsManager)
 @template_config(template='templates/gallery-medias.pt', layer=IPyAMSLayer)
 @implementer(IGalleryContentsView)
@@ -105,7 +99,7 @@
         return II18n(media).query_attribute('title', request=self.request)
 
 
-@view_config(name='get-gallery-medias.html', context=IGallery, request_type=IPyAMSLayer,
+@view_config(name='get-gallery-medias.html', context=IBaseGallery, request_type=IPyAMSLayer,
              permission=VIEW_SYSTEM_PERMISSION)
 @implementer(IGalleryContentsView)
 class GalleryMediasView(WfSharedContentPermissionMixin):
@@ -128,7 +122,7 @@
             return absolute_url(media, self.request, 'preview.html')
 
 
-@view_config(name='set-medias-order.json', context=IGallery, request_type=IPyAMSLayer,
+@view_config(name='set-medias-order.json', context=IBaseGallery, request_type=IPyAMSLayer,
              permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
 def set_medias_order(request):
     """Set gallery medias order"""
@@ -137,11 +131,11 @@
     return {'status': 'success'}
 
 
-@view_config(name='set-media-visibility.json', context=IGallery, request_type=IPyAMSLayer,
+@view_config(name='set-media-visibility.json', context=IBaseGallery, request_type=IPyAMSLayer,
              permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
 def set_media_visibility(request):
     """Set gallery media visibility"""
-    gallery = IGallery(request.context)
+    gallery = IBaseGallery(request.context)
     media = gallery.get(str(request.params.get('object_name')))
     if media is None:
         raise NotFound()
@@ -153,13 +147,13 @@
 # Gallery medias downloader
 #
 
-@view_config(name='get-medias.zip', context=IGallery, request_type=IPyAMSLayer,
+@view_config(name='get-medias.zip', context=IBaseGallery, request_type=IPyAMSLayer,
              permission=VIEW_SYSTEM_PERMISSION)
 def get_medias_archive(request):
     """Get all gallery medias as ZIP file"""
     zip_data = BytesIO()
     zip_file = zipfile.ZipFile(zip_data, mode='w')
-    for media in IGallery(request.context).values():
+    for media in IBaseGallery(request.context).values():
         zip_file.writestr(IFileInfo(media.data).filename, media.data.data)
     zip_file.close()
     zip_data.seek(0)
--- a/src/pyams_content/component/gallery/zmi/file.py	Mon Sep 24 11:23:45 2018 +0200
+++ b/src/pyams_content/component/gallery/zmi/file.py	Mon Sep 24 16:06:40 2018 +0200
@@ -15,6 +15,7 @@
 from pyramid.renderers import render
 from z3c.form import field
 from z3c.form.interfaces import NOT_CHANGED
+from zope.interface import Interface
 from zope.lifecycleevent import ObjectCreatedEvent
 from zope.location import locate
 from zope.schema.interfaces import WrongType
@@ -25,7 +26,7 @@
     IGalleryParagraph
 from pyams_content.component.gallery.zmi.interfaces import IGalleryContentsView, IGalleryMediasAddFields
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
-from pyams_content.shared.common import IWfSharedContent
+from pyams_content.shared.common.interfaces import IWfSharedContent
 from pyams_content.shared.common.zmi import WfSharedContentPermissionMixin
 from pyams_file.file import get_magic_content_type
 from pyams_file.interfaces.archive import IArchiveExtractor
@@ -110,7 +111,7 @@
             for media in medias:
                 media.author = data.get('author')
                 self.context.append(media)
-        return None
+        return medias
 
     def get_ajax_output(self, changes):
         return {
@@ -120,7 +121,7 @@
         }
 
 
-@viewlet_config(name='file.showhide.action', context=IGalleryFile, layer=IPyAMSLayer, view=IGalleryContentsView,
+@viewlet_config(name='file.showhide.action', context=IGalleryFile, layer=IPyAMSLayer, view=Interface,
                 manager=IContextActions, permission=VIEW_SYSTEM_PERMISSION, weight=1)
 class GalleryFileShowHideAction(WfSharedContentPermissionMixin, JsToolbarActionItem):
     """Gallery file show/hide action"""
@@ -156,7 +157,7 @@
             return self.url
 
 
-@viewlet_config(name='file.properties.action', context=IGalleryFile, layer=IPyAMSLayer, view=IGalleryContentsView,
+@viewlet_config(name='file.properties.action', context=IGalleryFile, layer=IPyAMSLayer, view=Interface,
                 manager=IContextActions, permission=VIEW_SYSTEM_PERMISSION, weight=5)
 class GalleryFilePropertiesAction(FilePropertiesAction):
     """Media properties action"""
@@ -216,7 +217,7 @@
         return output
 
 
-@viewlet_config(name='gallery-file-remover.action', context=IGalleryFile, layer=IPyAMSLayer, view=IGalleryContentsView,
+@viewlet_config(name='gallery-file-remover.action', context=IGalleryFile, layer=IPyAMSLayer, view=Interface,
                 manager=IContextActions, weight=90)
 class GalleryFileRemoverAction(WfSharedContentPermissionMixin, JsToolbarActionItem):
     """Gallery file remover action"""
--- a/src/pyams_content/component/gallery/zmi/paragraph.py	Mon Sep 24 11:23:45 2018 +0200
+++ b/src/pyams_content/component/gallery/zmi/paragraph.py	Mon Sep 24 16:06:40 2018 +0200
@@ -12,41 +12,43 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
+from pyramid.interfaces import IView
+from pyramid.renderers import render
+from pyramid.view import view_config
+from transaction.interfaces import ITransactionManager
+from z3c.form import button, field
+from z3c.form.interfaces import INPUT_MODE
+from zope.interface import Interface, implementer
 
-# import interfaces
-from pyams_content.component.gallery.interfaces import IGalleryParagraph, IBaseGallery, GALLERY_PARAGRAPH_TYPE
+from pyams_content import _
+from pyams_content.component.gallery.interfaces import GALLERY_PARAGRAPH_TYPE, IGalleryParagraph
+from pyams_content.component.gallery.paragraph import Gallery
+from pyams_content.component.gallery.zmi.file import GalleryMediaAddForm
 from pyams_content.component.gallery.zmi.interfaces import IGalleryContentsView
-from pyams_content.component.paragraph.interfaces import IParagraphContainerTarget, IParagraphContainer, \
-    PARAGRAPH_HIDDEN_FIELDS
-from pyams_content.component.paragraph.zmi.interfaces import IParagraphInnerEditor, IParagraphContainerView
+from pyams_content.component.paragraph.interfaces import IParagraphContainer, IParagraphContainerTarget
+from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
+    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, IParagraphEditFormButtons, ParagraphContainerTable, \
+    get_json_paragraph_refresh_event
+from pyams_content.component.paragraph.zmi.interfaces import IParagraphContainerView, IParagraphInnerEditor
+from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
+from pyams_content.shared.common.zmi import WfSharedContentPermissionMixin
+from pyams_form.form import AJAXAddForm, ajax_config
 from pyams_form.interfaces.form import IInnerForm, IInnerSubForm
 from pyams_i18n.interfaces import II18n
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.container import delete_container_element
 from pyams_skin.interfaces.viewlet import IToolbarAddingMenu, IWidgetTitleViewletManager
 from pyams_skin.layer import IPyAMSLayer
-from pyramid.interfaces import IView
-from z3c.form.interfaces import INPUT_MODE
-
-# import packages
-from pyams_content.component.gallery.paragraph import Gallery
-from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \
-    BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, get_json_paragraph_refresh_event, IParagraphEditFormButtons
-from pyams_content.features.renderer.zmi.widget import RendererFieldWidget
-from pyams_content.shared.common.zmi import WfSharedContentPermissionMixin
-from pyams_form.form import ajax_config
-from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.table import get_table_id
 from pyams_skin.viewlet.toolbar import ToolbarAction
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
+from pyams_utils.interfaces import ICacheKeyValue
+from pyams_utils.traversing import get_parent
 from pyams_utils.url import absolute_url
 from pyams_viewlet.viewlet import viewlet_config
 from pyams_zmi.form import AdminDialogAddForm, InnerAdminDisplayForm
-from z3c.form import field, button
-from zope.interface import implementer, Interface
-
-from pyams_content import _
 
 
 @viewlet_config(name='add-gallery.menu', context=IParagraphContainerTarget, view=IParagraphContainerView,
@@ -70,7 +72,7 @@
     legend = _("Add new gallery")
     icon_css_class = 'fa fa-fw fa-picture-o'
 
-    fields = field.Fields(IGalleryParagraph).omit(*PARAGRAPH_HIDDEN_FIELDS)
+    fields = field.Fields(IGalleryParagraph).select('renderer')
     edit_permission = MANAGE_CONTENT_PERMISSION
 
     def create(self, data):
@@ -92,21 +94,11 @@
     legend = _("Edit gallery properties")
     icon_css_class = 'fa fa-fw fa-picture-o'
 
-    fields = field.Fields(IGalleryParagraph).omit(*PARAGRAPH_HIDDEN_FIELDS)
+    fields = field.Fields(IGalleryParagraph).select('renderer')
     fields['renderer'].widgetFactory = RendererFieldWidget
 
     edit_permission = MANAGE_CONTENT_PERMISSION
 
-    def get_ajax_output(self, changes):
-        updated = changes.get(IBaseGallery, ())
-        if 'title' in updated:
-            return {
-                'status': 'success',
-                'events': [get_json_paragraph_refresh_event(self.context, self.request), ]
-            }
-        else:
-            return super(self.__class__, self).get_ajax_output(changes)
-
 
 @adapter_config(context=(IGalleryParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
 @implementer(IInnerForm)
@@ -149,7 +141,7 @@
 
 @viewlet_config(name='add-media.menu', context=IGalleryParagraph, view=GalleryContentsView,
                 manager=IWidgetTitleViewletManager)
-class GalleryMediaAddMenu(WfSharedContentPermissionMixin, ToolbarAction):
+class GalleryParagraphMediaAddMenu(WfSharedContentPermissionMixin, ToolbarAction):
     """Gallery media add menu"""
 
     label = _("Add media(s)")
@@ -157,3 +149,47 @@
     url = 'add-media.html'
     modal_target = True
     stop_propagation = True
+
+
+@pagelet_config(name='add-media.html', context=IGalleryParagraph, layer=IPyAMSLayer,
+                permission=MANAGE_CONTENT_PERMISSION)
+@ajax_config(name='add-media.json', context=IGalleryParagraph, layer=IPyAMSLayer, base=AJAXAddForm)
+class GalleryParagraphMediaAddForm(GalleryMediaAddForm):
+    """Gallery media add form"""
+
+    def get_ajax_output(self, changes):
+        ITransactionManager(self.context).commit()
+        medias = []
+        for media in changes:
+            medias.append(render('templates/gallery-media.pt',
+                                 {'media': media}, request=self.request))
+        output = {
+            'status': 'success',
+            'message': self.request.localizer.translate(_("Media(s) successfully added")),
+            'events': [
+                get_json_paragraph_refresh_event(self.context, self.request)
+            ],
+            'callback': 'PyAMS_content.galleries.addMediaCallback',
+            'options': {
+                'parent': '{0}::{1}'.format(get_table_id(ParagraphContainerTable,
+                                                         context=get_parent(self.context, IParagraphContainerTarget)),
+                                            ICacheKeyValue(self.context)),
+                'medias': medias
+            }
+        }
+        return output
+
+
+@view_config(name='delete-element.json', context=IGalleryParagraph, request_type=IPyAMSLayer,
+             renderer='json', xhr=True)
+def delete_media(request):
+    """Delete media from container"""
+    result = delete_container_element(request, IGalleryParagraph)
+    if result.get('status') == 'success':
+        result.update({
+            'handle_json': True,
+            'events': [
+                get_json_paragraph_refresh_event(request.context, request)
+            ]
+        })
+    return result
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_content/component/gallery/zmi/templates/gallery-media.pt	Mon Sep 24 16:06:40 2018 +0200
@@ -0,0 +1,58 @@
+<div class="media margin-5 margin-bottom-10 radius-4 padding-5 pull-left text-center"
+	 style="position: relative;"
+	 tal:attributes="data-ams-element-name media.__name__">
+	<tal:var define="thumbnails tales:thumbnails(media.data);">
+		<tal:if condition="thumbnails">
+			<tal:if condition="media.data.content_type.startswith('image/')">
+				<a class="fancyimg hint" data-toggle
+				   data-ams-hint-gravity="sw"
+				   title="Zoom image" i18n:attributes="title"
+				   tal:define="target thumbnails.get_thumbnail('800x600')"
+				   tal:attributes="class 'fancyimg hint {0}'.format('not-visible' if not media.visible else '');
+								   href tales:absolute_url(target);
+								   rel string:gallery_${context.__name__};">
+					<img class="thumbnail"
+						 data-ams-hint-gravity="s"
+						 tal:define="thumbnail thumbnails.get_thumbnail('128x128');
+									 image_size thumbnail.get_image_size();
+									 margin_left 64 - image_size[0] / 2;
+									 margin_top 64 - image_size[1] / 2;
+									 title i18n:media.title;
+									 src tales:absolute_url(thumbnail);"
+						 tal:attributes="src string:${src}?_=${tales:timestamp(thumbnail)};
+										 id 'media_{0}_{1}'.format(context.__name__, media.__name__);
+										 original-title title or '--';
+										 style string:margin-left: ${margin_left}px;; margin-right: ${margin_left}px;; margin-top: ${margin_top}px;; margin-bottom: ${margin_top}px;;" />
+				</a>
+			</tal:if>
+			<tal:if condition="not:media.data.content_type.startswith('image/')">
+				<a tal:define="thumbnail thumbnails.get_thumbnail('128x128');
+							   target view.get_thumbnail_target(media.data);
+							   image_size thumbnail.get_image_size();
+							   margin_left 64 - image_size[0] / 2;
+							   margin_top 64 - image_size[1] / 2;
+							   title i18n:media.title;"
+				   tal:omit-tag="not:target"
+				   tal:attributes="href target" data-toggle="modal">
+					<img class="thumbnail no-border"
+						 data-ams-hint-gravity="s"
+						 tal:attributes="src tales:absolute_url(thumbnail);
+										 id 'media_{0}_{1}'.format(context.__name__, media.__name__);
+										 original-title title or '--';
+										 style string:margin-left: ${margin_left}px;; margin-right: ${margin_left}px;; margin-top: ${margin_top}px;; margin-bottom: ${margin_top}px;;" />
+				</a>
+			</tal:if>
+		</tal:if>
+		<tal:if condition="not:thumbnails">
+			<img class="thumbnail hint" src="/--static--/pyams_skin/img/mimetypes/unknown.png"
+				 tal:attributes="title i18n:media.title"
+				 style="padding: 48px;" />
+		</tal:if>
+	</tal:var>
+	<div class="btn-group margin-top-10"
+		 tal:define="actions tales:context_actions(media);">
+		<tal:loop repeat="viewlet actions.viewlets"
+				  content="structure viewlet.render()" />
+	</div>
+	<span class="clearfix"></span>
+</div>
Binary file src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.mo has changed
--- a/src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.po	Mon Sep 24 11:23:45 2018 +0200
+++ b/src/pyams_content/locales/fr/LC_MESSAGES/pyams_content.po	Mon Sep 24 16:06:40 2018 +0200
@@ -5,7 +5,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2018-09-21 14:52+0200\n"
+"POT-Creation-Date: 2018-09-24 16:03+0200\n"
 "PO-Revision-Date: 2015-09-10 10:42+0200\n"
 "Last-Translator: Thierry Florac <tflorac@ulthar.net>\n"
 "Language-Team: French\n"
@@ -80,64 +80,77 @@
 msgid "Guest user (role)"
 msgstr "Invité (rôle)"
 
-#: src/pyams_content/component/gallery/__init__.py:153
+#: src/pyams_content/component/gallery/paragraph.py:46
+msgid "(empty gallery)"
+msgstr "(cette galerie ne contient aucun média)"
+
+#: src/pyams_content/component/gallery/paragraph.py:44
+#, python-format
+msgid "({0} medias)"
+msgstr "({0} médias dans la galerie)"
+
+#: src/pyams_content/component/gallery/__init__.py:156
 msgid "Gallery"
 msgstr "Galerie de médias"
 
-#: src/pyams_content/component/gallery/zmi/file.py:60
-#: src/pyams_content/component/gallery/zmi/file.py:72
-#: src/pyams_content/component/gallery/zmi/paragraph.py:155
+#: src/pyams_content/component/gallery/zmi/file.py:56
+#: src/pyams_content/component/gallery/zmi/file.py:68
+#: src/pyams_content/component/gallery/zmi/paragraph.py:148
 msgid "Add media(s)"
 msgstr "Ajouter des médias"
 
-#: src/pyams_content/component/gallery/zmi/file.py:182
+#: src/pyams_content/component/gallery/zmi/file.py:177
 msgid "Update media properties"
 msgstr "Propriétés du média"
 
-#: src/pyams_content/component/gallery/zmi/file.py:236
+#: src/pyams_content/component/gallery/zmi/file.py:226
 msgid "Remove media..."
 msgstr "Supprimer le média"
 
-#: src/pyams_content/component/gallery/zmi/file.py:146
+#: src/pyams_content/component/gallery/zmi/file.py:141
 msgid "Show/hide media"
 msgstr "Cliquez pour rendre le média visible ou non"
 
-#: src/pyams_content/component/gallery/zmi/file.py:203
+#: src/pyams_content/component/gallery/zmi/file.py:193
 msgid "Audio content"
 msgstr "Contenu audio associé"
 
-#: src/pyams_content/component/gallery/zmi/paragraph.py:57
+#: src/pyams_content/component/gallery/zmi/paragraph.py:60
 msgid "Medias gallery..."
 msgstr "Galerie de médias"
 
-#: src/pyams_content/component/gallery/zmi/paragraph.py:70
+#: src/pyams_content/component/gallery/zmi/paragraph.py:73
 msgid "Add new gallery"
 msgstr "Ajout d'une galerie de médias"
 
-#: src/pyams_content/component/gallery/zmi/paragraph.py:92
+#: src/pyams_content/component/gallery/zmi/paragraph.py:95
 msgid "Edit gallery properties"
 msgstr "Propriétés de la galerie de médias"
 
-#: src/pyams_content/component/gallery/zmi/__init__.py:63
+#: src/pyams_content/component/gallery/zmi/paragraph.py:169
+msgid "Media(s) successfully added"
+msgstr "Les médias ont été ajoutés dans la galerie."
+
+#: src/pyams_content/component/gallery/zmi/__init__.py:58
 msgid "Update gallery properties"
 msgstr "Propriétés de la galerie de médias"
 
-#: src/pyams_content/component/gallery/zmi/__init__.py:90
+#: src/pyams_content/component/gallery/zmi/__init__.py:85
 msgid "Update gallery contents"
 msgstr "Contenu de la galerie de médias"
 
-#: src/pyams_content/component/gallery/zmi/interfaces.py:36
+#: src/pyams_content/component/gallery/zmi/interfaces.py:35
 msgid "Images or videos data"
 msgstr "Fichier"
 
-#: src/pyams_content/component/gallery/zmi/interfaces.py:37
+#: src/pyams_content/component/gallery/zmi/interfaces.py:36
 msgid "You can upload a single file or choose to upload a whole ZIP archive"
 msgstr ""
 "Vous pouvez déposer les médias un par un, ou en nombre en les réunissant au "
 "préalable dans un fichier ZIP"
 
-#: src/pyams_content/component/gallery/zmi/interfaces.py:40
-#: src/pyams_content/component/gallery/interfaces/__init__.py:61
+#: src/pyams_content/component/gallery/zmi/interfaces.py:39
+#: src/pyams_content/component/gallery/interfaces/__init__.py:55
 #: src/pyams_content/component/extfile/interfaces/__init__.py:44
 #: src/pyams_content/component/illustration/interfaces/__init__.py:54
 #: src/pyams_content/component/paragraph/interfaces/video.py:46
@@ -147,8 +160,8 @@
 msgid "Author"
 msgstr "Auteur"
 
-#: src/pyams_content/component/gallery/zmi/interfaces.py:41
-#: src/pyams_content/component/gallery/interfaces/__init__.py:62
+#: src/pyams_content/component/gallery/zmi/interfaces.py:40
+#: src/pyams_content/component/gallery/interfaces/__init__.py:56
 #: src/pyams_content/component/extfile/interfaces/__init__.py:45
 #: src/pyams_content/component/paragraph/interfaces/video.py:47
 #: src/pyams_content/component/paragraph/interfaces/audio.py:47
@@ -156,16 +169,10 @@
 msgid "Name of document's author"
 msgstr "Sous la forme \"Prénom Nom / Organisme\""
 
-#: src/pyams_content/component/gallery/zmi/interfaces.py:44
-msgid "Author comments"
-msgstr "À propos de l'auteur"
-
-#: src/pyams_content/component/gallery/zmi/interfaces.py:45
-#: src/pyams_content/component/gallery/interfaces/__init__.py:66
-msgid "Comments relatives to author's rights management"
-msgstr ""
-"Commentaires (non publiés mais à conserver) relatifs à l'auteur et à la "
-"gestion de ses droits"
+#: src/pyams_content/component/gallery/zmi/templates/gallery-media.pt:9
+#: src/pyams_content/component/gallery/zmi/templates/gallery-medias.pt:42
+msgid "Zoom image"
+msgstr "Agrandir l'image"
 
 #: src/pyams_content/component/gallery/zmi/templates/gallery-medias.pt:12
 msgid "Gallery medias"
@@ -175,28 +182,24 @@
 msgid "Download medias"
 msgstr "Enregistrer tous les médias sous..."
 
-#: src/pyams_content/component/gallery/zmi/templates/gallery-medias.pt:42
-msgid "Zoom image"
-msgstr "Agrandir l'image"
-
-#: src/pyams_content/component/gallery/interfaces/__init__.py:133
+#: src/pyams_content/component/gallery/interfaces/__init__.py:119
 msgid "Medias gallery"
 msgstr "Galerie de médias"
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:47
+#: src/pyams_content/component/gallery/interfaces/__init__.py:41
 #: src/pyams_content/component/illustration/interfaces/__init__.py:40
 #: src/pyams_content/component/illustration/interfaces/__init__.py:103
 msgid "Image or video data"
 msgstr "Fichier"
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:48
+#: src/pyams_content/component/gallery/interfaces/__init__.py:42
 #: src/pyams_content/component/illustration/interfaces/__init__.py:41
 #: src/pyams_content/component/illustration/interfaces/__init__.py:104
 msgid "Image or video content"
 msgstr ""
 "Cliquez sur le bouton 'Parcourir...' pour sélectionner un nouveau contenu..."
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:51
+#: src/pyams_content/component/gallery/interfaces/__init__.py:45
 #: src/pyams_content/component/illustration/interfaces/__init__.py:47
 #: src/pyams_content/component/paragraph/interfaces/video.py:43
 #: src/pyams_content/component/paragraph/interfaces/audio.py:43
@@ -204,13 +207,13 @@
 msgid "Legend"
 msgstr "Légende"
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:54
+#: src/pyams_content/component/gallery/interfaces/__init__.py:48
 #: src/pyams_content/component/illustration/interfaces/__init__.py:50
 #: src/pyams_content/reference/pictograms/interfaces/__init__.py:48
 msgid "Accessibility title"
 msgstr "Alternative (accessibilité)"
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:55
+#: src/pyams_content/component/gallery/interfaces/__init__.py:49
 msgid "Alternate title used to describe media content"
 msgstr ""
 "Ce texte est affiché lorsque le média ne peut être téléchargé ou affiché ; "
@@ -218,8 +221,8 @@
 "déficiences visuelles. Il doit donc décrire le contenu du média, pour se "
 "conformer aux normes d'accessibilité."
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:58
-#: src/pyams_content/component/gallery/interfaces/__init__.py:101
+#: src/pyams_content/component/gallery/interfaces/__init__.py:52
+#: src/pyams_content/component/gallery/interfaces/__init__.py:109
 #: src/pyams_content/component/extfile/interfaces/__init__.py:40
 #: src/pyams_content/component/paragraph/interfaces/audio.py:50
 #: src/pyams_content/component/links/interfaces/__init__.py:39
@@ -227,55 +230,52 @@
 msgid "Description"
 msgstr "Description"
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:65
-msgid "Author's comments"
-msgstr "À propos de l'auteur"
-
-#: src/pyams_content/component/gallery/interfaces/__init__.py:69
-msgid "Source ID"
-msgstr "ID source"
-
-#: src/pyams_content/component/gallery/interfaces/__init__.py:70
-msgid "Number used to identify media into it's original source"
-msgstr ""
-"Identifiant ou référence de ce média dans la base de données dont elle est "
-"issue ; au besoin, préciser le nom de cette base"
-
-#: src/pyams_content/component/gallery/interfaces/__init__.py:73
+#: src/pyams_content/component/gallery/interfaces/__init__.py:59
 #: src/pyams_content/component/extfile/interfaces/__init__.py:89
 #: src/pyams_content/component/paragraph/interfaces/audio.py:39
 msgid "Audio data"
 msgstr "Fichier"
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:74
+#: src/pyams_content/component/gallery/interfaces/__init__.py:60
 msgid "Sound file associated with the current media"
 msgstr "Vous pouvez associer un fichier audio à ce média"
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:77
+#: src/pyams_content/component/gallery/interfaces/__init__.py:63
 msgid "Sound title"
 msgstr "Titre"
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:78
+#: src/pyams_content/component/gallery/interfaces/__init__.py:64
 msgid "Title of associated sound file"
 msgstr "Titre du fichier audio associé à ce média"
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:81
+#: src/pyams_content/component/gallery/interfaces/__init__.py:67
 msgid "Sound description"
 msgstr "Description"
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:82
+#: src/pyams_content/component/gallery/interfaces/__init__.py:68
 msgid "Short description of associated sound file"
 msgstr "Courte description du fichier audio associé à ce média"
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:85
+#: src/pyams_content/component/gallery/interfaces/__init__.py:71
 msgid "Visible media?"
 msgstr "Média visible ?"
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:86
+#: src/pyams_content/component/gallery/interfaces/__init__.py:72
 msgid "If 'no', this media won't be displayed in front office"
 msgstr "Si 'non', ce média ne sera pas présenté aux internautes"
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:97
+#: src/pyams_content/component/gallery/interfaces/__init__.py:83
+msgid "Gallery template"
+msgstr "Mode de rendu"
+
+#: src/pyams_content/component/gallery/interfaces/__init__.py:84
+msgid "Presentation template used for this gallery"
+msgstr ""
+"<span>Modèle de présentation utilisé par cette galerie.<br /"
+"><strong>ATTENTION :</strong> certains modes de rendu ne prennent en charge "
+"que certains types de médias !!</span>"
+
+#: src/pyams_content/component/gallery/interfaces/__init__.py:105
 #: src/pyams_content/component/keynumber/portlet/interfaces/__init__.py:31
 #: src/pyams_content/component/paragraph/zmi/milestone.py:238
 #: src/pyams_content/component/paragraph/zmi/container.py:270
@@ -295,25 +295,14 @@
 msgid "Title"
 msgstr "Titre"
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:98
+#: src/pyams_content/component/gallery/interfaces/__init__.py:106
 msgid "Gallery title, as shown in front-office"
 msgstr "Titre de la galerie présenté aux internautes"
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:102
+#: src/pyams_content/component/gallery/interfaces/__init__.py:110
 msgid "Gallery description displayed by front-office template"
 msgstr "Description de la galerie de médias présentée aux internautes"
 
-#: src/pyams_content/component/gallery/interfaces/__init__.py:105
-msgid "Gallery template"
-msgstr "Mode de rendu"
-
-#: src/pyams_content/component/gallery/interfaces/__init__.py:106
-msgid "Presentation template used for this gallery"
-msgstr ""
-"<span>Modèle de présentation utilisé par cette galerie.<br /"
-"><strong>ATTENTION :</strong> certains modes de rendu ne prennent en charge "
-"que certains types de médias !!</span>"
-
 #: src/pyams_content/component/extfile/__init__.py:172
 #: src/pyams_content/component/extfile/__init__.py:176
 msgid "Standard file"
@@ -4558,8 +4547,8 @@
 "be used if none is specified"
 msgstr ""
 "Libellé utilisé en lieu et place du titre dans les composants de navigation "
-"(notamment dans les pages carrefour) ; si rien n'est indiqué, le titre original "
-"du contenu référencé sera utilisé"
+"(notamment dans les pages carrefour) ; si rien n'est indiqué, le titre "
+"original du contenu référencé sera utilisé"
 
 #: src/pyams_content/shared/site/interfaces/__init__.py:157
 msgid "If 'no', link is not visible"
@@ -5968,6 +5957,25 @@
 msgid "Hidden header"
 msgstr "Ne pas afficher d'en-tête de pages"
 
+#~ msgid "Author comments"
+#~ msgstr "À propos de l'auteur"
+
+#~ msgid "Comments relatives to author's rights management"
+#~ msgstr ""
+#~ "Commentaires (non publiés mais à conserver) relatifs à l'auteur et à la "
+#~ "gestion de ses droits"
+
+#~ msgid "Author's comments"
+#~ msgstr "À propos de l'auteur"
+
+#~ msgid "Source ID"
+#~ msgstr "ID source"
+
+#~ msgid "Number used to identify media into it's original source"
+#~ msgstr ""
+#~ "Identifiant ou référence de ce média dans la base de données dont elle "
+#~ "est issue ; au besoin, préciser le nom de cette base"
+
 #~ msgid "Context content"
 #~ msgstr "Contenu partagé"
 
--- a/src/pyams_content/locales/pyams_content.pot	Mon Sep 24 11:23:45 2018 +0200
+++ b/src/pyams_content/locales/pyams_content.pot	Mon Sep 24 16:06:40 2018 +0200
@@ -6,7 +6,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2018-09-21 14:52+0200\n"
+"POT-Creation-Date: 2018-09-24 16:03+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -81,62 +81,75 @@
 msgid "Guest user (role)"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/__init__.py:153
+#: ./src/pyams_content/component/gallery/paragraph.py:46
+msgid "(empty gallery)"
+msgstr ""
+
+#: ./src/pyams_content/component/gallery/paragraph.py:44
+#, python-format
+msgid "({0} medias)"
+msgstr ""
+
+#: ./src/pyams_content/component/gallery/__init__.py:156
 msgid "Gallery"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/zmi/file.py:60
-#: ./src/pyams_content/component/gallery/zmi/file.py:72
-#: ./src/pyams_content/component/gallery/zmi/paragraph.py:155
+#: ./src/pyams_content/component/gallery/zmi/file.py:56
+#: ./src/pyams_content/component/gallery/zmi/file.py:68
+#: ./src/pyams_content/component/gallery/zmi/paragraph.py:148
 msgid "Add media(s)"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/zmi/file.py:182
+#: ./src/pyams_content/component/gallery/zmi/file.py:177
 msgid "Update media properties"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/zmi/file.py:236
+#: ./src/pyams_content/component/gallery/zmi/file.py:226
 msgid "Remove media..."
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/zmi/file.py:146
+#: ./src/pyams_content/component/gallery/zmi/file.py:141
 msgid "Show/hide media"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/zmi/file.py:203
+#: ./src/pyams_content/component/gallery/zmi/file.py:193
 msgid "Audio content"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/zmi/paragraph.py:57
+#: ./src/pyams_content/component/gallery/zmi/paragraph.py:60
 msgid "Medias gallery..."
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/zmi/paragraph.py:70
+#: ./src/pyams_content/component/gallery/zmi/paragraph.py:73
 msgid "Add new gallery"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/zmi/paragraph.py:92
+#: ./src/pyams_content/component/gallery/zmi/paragraph.py:95
 msgid "Edit gallery properties"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/zmi/__init__.py:63
+#: ./src/pyams_content/component/gallery/zmi/paragraph.py:169
+msgid "Media(s) successfully added"
+msgstr ""
+
+#: ./src/pyams_content/component/gallery/zmi/__init__.py:58
 msgid "Update gallery properties"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/zmi/__init__.py:90
+#: ./src/pyams_content/component/gallery/zmi/__init__.py:85
 msgid "Update gallery contents"
 msgstr ""
 
+#: ./src/pyams_content/component/gallery/zmi/interfaces.py:35
+msgid "Images or videos data"
+msgstr ""
+
 #: ./src/pyams_content/component/gallery/zmi/interfaces.py:36
-msgid "Images or videos data"
-msgstr ""
-
-#: ./src/pyams_content/component/gallery/zmi/interfaces.py:37
 msgid "You can upload a single file or choose to upload a whole ZIP archive"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/zmi/interfaces.py:40
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:61
+#: ./src/pyams_content/component/gallery/zmi/interfaces.py:39
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:55
 #: ./src/pyams_content/component/extfile/interfaces/__init__.py:44
 #: ./src/pyams_content/component/illustration/interfaces/__init__.py:54
 #: ./src/pyams_content/component/paragraph/interfaces/video.py:46
@@ -146,8 +159,8 @@
 msgid "Author"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/zmi/interfaces.py:41
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:62
+#: ./src/pyams_content/component/gallery/zmi/interfaces.py:40
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:56
 #: ./src/pyams_content/component/extfile/interfaces/__init__.py:45
 #: ./src/pyams_content/component/paragraph/interfaces/video.py:47
 #: ./src/pyams_content/component/paragraph/interfaces/audio.py:47
@@ -155,13 +168,9 @@
 msgid "Name of document's author"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/zmi/interfaces.py:44
-msgid "Author comments"
-msgstr ""
-
-#: ./src/pyams_content/component/gallery/zmi/interfaces.py:45
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:66
-msgid "Comments relatives to author's rights management"
+#: ./src/pyams_content/component/gallery/zmi/templates/gallery-media.pt:9
+#: ./src/pyams_content/component/gallery/zmi/templates/gallery-medias.pt:42
+msgid "Zoom image"
 msgstr ""
 
 #: ./src/pyams_content/component/gallery/zmi/templates/gallery-medias.pt:12
@@ -172,27 +181,23 @@
 msgid "Download medias"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/zmi/templates/gallery-medias.pt:42
-msgid "Zoom image"
-msgstr ""
-
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:133
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:119
 msgid "Medias gallery"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:47
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:41
 #: ./src/pyams_content/component/illustration/interfaces/__init__.py:40
 #: ./src/pyams_content/component/illustration/interfaces/__init__.py:103
 msgid "Image or video data"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:48
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:42
 #: ./src/pyams_content/component/illustration/interfaces/__init__.py:41
 #: ./src/pyams_content/component/illustration/interfaces/__init__.py:104
 msgid "Image or video content"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:51
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:45
 #: ./src/pyams_content/component/illustration/interfaces/__init__.py:47
 #: ./src/pyams_content/component/paragraph/interfaces/video.py:43
 #: ./src/pyams_content/component/paragraph/interfaces/audio.py:43
@@ -200,18 +205,18 @@
 msgid "Legend"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:54
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:48
 #: ./src/pyams_content/component/illustration/interfaces/__init__.py:50
 #: ./src/pyams_content/reference/pictograms/interfaces/__init__.py:48
 msgid "Accessibility title"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:55
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:49
 msgid "Alternate title used to describe media content"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:58
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:101
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:52
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:109
 #: ./src/pyams_content/component/extfile/interfaces/__init__.py:40
 #: ./src/pyams_content/component/paragraph/interfaces/audio.py:50
 #: ./src/pyams_content/component/links/interfaces/__init__.py:39
@@ -219,53 +224,49 @@
 msgid "Description"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:65
-msgid "Author's comments"
-msgstr ""
-
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:69
-msgid "Source ID"
-msgstr ""
-
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:70
-msgid "Number used to identify media into it's original source"
-msgstr ""
-
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:73
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:59
 #: ./src/pyams_content/component/extfile/interfaces/__init__.py:89
 #: ./src/pyams_content/component/paragraph/interfaces/audio.py:39
 msgid "Audio data"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:74
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:60
 msgid "Sound file associated with the current media"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:77
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:63
 msgid "Sound title"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:78
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:64
 msgid "Title of associated sound file"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:81
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:67
 msgid "Sound description"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:82
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:68
 msgid "Short description of associated sound file"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:85
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:71
 msgid "Visible media?"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:86
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:72
 msgid "If 'no', this media won't be displayed in front office"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:97
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:83
+msgid "Gallery template"
+msgstr ""
+
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:84
+msgid "Presentation template used for this gallery"
+msgstr ""
+
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:105
 #: ./src/pyams_content/component/keynumber/portlet/interfaces/__init__.py:31
 #: ./src/pyams_content/component/paragraph/zmi/milestone.py:238
 #: ./src/pyams_content/component/paragraph/zmi/container.py:270
@@ -285,22 +286,14 @@
 msgid "Title"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:98
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:106
 msgid "Gallery title, as shown in front-office"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:102
+#: ./src/pyams_content/component/gallery/interfaces/__init__.py:110
 msgid "Gallery description displayed by front-office template"
 msgstr ""
 
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:105
-msgid "Gallery template"
-msgstr ""
-
-#: ./src/pyams_content/component/gallery/interfaces/__init__.py:106
-msgid "Presentation template used for this gallery"
-msgstr ""
-
 #: ./src/pyams_content/component/extfile/__init__.py:172
 #: ./src/pyams_content/component/extfile/__init__.py:176
 msgid "Standard file"
--- a/src/pyams_content/skin/resources/js/pyams_content.js	Mon Sep 24 11:23:45 2018 +0200
+++ b/src/pyams_content/skin/resources/js/pyams_content.js	Mon Sep 24 16:06:40 2018 +0200
@@ -164,6 +164,21 @@
 		 */
 		galleries: {
 
+			addMediaCallback: function(options) {
+				var gallery = $('.gallery', $('[id="' + options.parent + '"]'));
+				var switcher = gallery.siblings('.switcher');
+				if (switcher.parents('fieldset:first').hasClass('switched')) {
+					switcher.click();
+				}
+				for (var i=0; i < options.medias.length; i++) {
+					gallery.append(options.medias[i]);
+				}
+				gallery.sortable({
+					helper: 'clone',
+					stop: PyAMS_content.galleries.setOrder
+				})
+			},
+
 			updateMediaTitle: function(options) {
 				$('img[id="' + options.media_id + '"]').attr('original-title', options.title);
 			},
@@ -215,6 +230,9 @@
 											{object_name: object_name},
 											function(result, status) {
 												media.remove();
+												if (result.handle_json) {
+													MyAMS.ajax.handleJSON(result);
+												}
 											});
 						}
 					});
--- a/src/pyams_content/skin/resources/js/pyams_content.min.js	Mon Sep 24 11:23:45 2018 +0200
+++ b/src/pyams_content/skin/resources/js/pyams_content.min.js	Mon Sep 24 16:06:40 2018 +0200
@@ -1,1 +1,1 @@
-!function(t,e){"use strict";var a=e.MyAMS,i={widget:{treeview:{selectFolder:function(e,a){t(e.target).siblings('input[type="hidden"]').val(a.id)},unselectFolder:function(e,a){t(e.target).siblings('input[type="hidden"]').val(null)}}},TinyMCE:{initEditor:function(t){return tinyMCE.addI18n("fr",{"Link list":"Liste de liens","Toggle h3 header":"En-tête H3","Toggle h4 header":"En-tête H4","Insert internal link":"Insérer un lien interne","Link title":"Texte à afficher","Internal number":"N° interne"}),tinymce.PluginManager.add("internal_links",function(t,e){t.addButton("internal_links",{icon:"cloud-check",tooltip:"Insert internal link",image:"/--static--/pyams_content/img/internal-link.png",onclick:function(){t.windowManager.open({title:"Insert internal link",body:[{type:"textbox",name:"oid",label:"Internal number"},{type:"textbox",name:"title",label:"Link title",value:t.selection.getContent()}],onsubmit:function(e){t.insertContent('<a href="oid://'+e.data.oid+'">'+e.data.title+"</a>")}})}})}),tinyMCE.PluginManager.add("headers",function(t,e){["h3","h4"].forEach(function(e){t.addButton("header-"+e,{tooltip:"Toggle "+e+" header",text:e.toUpperCase(),onClick:function(){t.execCommand("mceToggleFormat",!1,e)},onPostRender:function(){var a=this,i=function(){t.formatter.formatChanged(e,function(t){a.active(t)})};t.formatter?i():t.on("init",i)}})})}),t.image_list=i.TinyMCE.getImagesList,t.link_list=i.TinyMCE.getLinksList,t.style_formats=[{title:"Inline",items:[{title:"Bold",icon:"bold",format:"bold"},{title:"Italic",icon:"italic",format:"italic"},{title:"Underline",icon:"underline",format:"underline"},{title:"Strikethrough",icon:"strikethrough",format:"strikethrough"},{title:"Superscript",icon:"superscript",format:"superscript"},{title:"Subscript",icon:"subscript",format:"subscript"},{title:"Code",icon:"code",format:"code"}]},{title:"Blocks",items:[{title:"Paragraph",format:"p"},{title:"Blockquote",format:"blockquote"},{title:"Div",format:"div"},{title:"Pre",format:"pre"}]},{title:"Alignment",items:[{title:"Left",icon:"alignleft",format:"alignleft"},{title:"Center",icon:"aligncenter",format:"aligncenter"},{title:"Right",icon:"alignright",format:"alignright"},{title:"Justify",icon:"alignjustify",format:"alignjustify"}]}],t.plugins+=" internal_links headers",t.toolbar1&&(t.toolbar1="undo redo | header-h3 header-h4 styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent"),t.toolbar2&&(t.toolbar2="forecolor backcolor | charmap internal_links link | fullscreen preview print | code"),t},getImagesList:function(e){var i=t(document.activeElement).parents("form");if(i.exists()){var n=i.attr("data-ams-form-handler")||i.attr("action"),s=n.substr(0,n.lastIndexOf("/")+1);return a.ajax.post(s+"get-images-list.json",{},e)}},getLinksList:function(e){var i=t(document.activeElement).parents("form");if(i.exists()){var n=i.attr("data-ams-form-handler")||i.attr("action"),s=n.substr(0,n.lastIndexOf("/")+1);return a.ajax.post(s+"get-links-list.json",{},e)}}},galleries:{updateMediaTitle:function(e){t('img[id="'+e.media_id+'"]').attr("original-title",e.title)},switchMediaVisibility:function(e){return function(){var e=t(this),i=e.parents(".media"),n=i.parents(".gallery");t("i",e).attr("class","fa fa-fw fa-spinner fa-pulse"),a.ajax.post(n.data("ams-location")+"/set-media-visibility.json",{object_name:i.data("ams-element-name")},function(a,i){a.visible?(t("i",e).attr("class","fa fa-fw fa-eye"),e.parents(".btn-group").siblings("a.fancyimg").removeClass("not-visible")):(t("i",e).attr("class","fa fa-fw fa-eye-slash text-danger"),e.parents(".btn-group").siblings("a.fancyimg").addClass("not-visible"))})}},setOrder:function(e,i){if(!i||!i.item.hasClass("already-dropped")){var n=i.item.parents(".gallery"),s=t(".media",n).listattr("data-ams-element-name");a.ajax.post(n.data("ams-location")+"/set-medias-order.json",{medias:JSON.stringify(s)})}},removeMedia:function(e){return function(){var e=t(this);a.skin.bigBox({title:a.i18n.WARNING,content:'<i class="text-danger fa fa-2x fa-bell shake animated"></i>&nbsp; '+a.i18n.DELETE_WARNING,buttons:a.i18n.BTN_OK_CANCEL},function(t){if(t===a.i18n.BTN_OK){var i=e.parents(".gallery").data("ams-location"),n=e.parents(".media"),s=n.data("ams-element-name");a.ajax.post(i+"/delete-element.json",{object_name:s},function(t,e){n.remove()})}})}},afterFancyboxLoad:function(t,e){t.element.hasClass("not-visible")&&t.inner.prepend('<div class="hidden-mask"></div>')}},paragraphs:{preReload:function(){i.paragraphs.switched=t("i.switch.fa-minus-square-o","#paragraphs_list").parents("tr").listattr("id")},postReload:function(){t(i.paragraphs.switched).each(function(){t("i.switch.fa-plus-square-o",'[id="'+this+'"]').parents("div").first().click()}),delete i.paragraphs.switched},refreshParagraph:function(e){var a=t('tr[id="'+e.object_id+'"]');t("span.title",a).html(e.title||" - - - - - - - -")},switchEditor:function(e){var i=t(this),n=t("i.switch",i),s=i.parents("td"),r=t(".editor",s),o=i.parents("tr");if(n.hasClass("fa-plus-square-o")){var l=o.parents("table");r.html('<h1 class="loading"><i class="fa fa-2x fa-gear fa-spin"></i></h1>'),a.ajax.post(l.data("ams-location")+"/get-paragraph-editor.json",{object_name:o.data("ams-element-name")},function(t){r.html(t),t&&(a.initContent(r),n.removeClass("fa-plus-square-o").addClass("fa-minus-square-o"),o.data("ams-disabled-handlers",!0),a.skin.scrollTo(r,{offset:r.height()-o.height()}))})}else a.skin.cleanContainer(r),r.empty(),n.removeClass("fa-minus-square-o").addClass("fa-plus-square-o"),o.removeData("ams-disabled-handlers")},switchLastEditor:function(e){var a=t('table[id="'+e+'"]'),i=t("tr:last",a);t('[data-ams-click-handler="PyAMS_content.paragraphs.switchEditor"]',i).click()},switchAllEditors:function(e){var i=t(this),n=t("i",i),s=i.parents("table");n.hasClass("fa-plus-square-o")?(n.removeClass("fa-plus-square-o").addClass("fa-cog fa-spin"),a.ajax.post(s.data("ams-location")+"/get-paragraphs-editors.json",{},function(e){for(var i in e)if(e.hasOwnProperty(i)){var r=t('tr[data-ams-element-name="'+i+'"]',s),o=t(".editor",r);o.is(":empty")&&o.html(e[i]),t(".fa-plus-square-o",r).removeClass("fa-plus-square-o").addClass("fa-minus-square-o"),r.data("ams-disabled-handlers",!0)}t("i.fa-plus-square-o",t("tbody",s)).exists()||n.removeClass("fa-cog fa-spin").addClass("fa-minus-square-o"),a.initContent(s)})):(t(".editor",s).each(function(){a.skin.cleanContainer(t(this)),t(this).empty()}),t(".fa-minus-square-o",s).removeClass("fa-minus-square-o").addClass("fa-plus-square-o"),t("tr",s).removeData("ams-disabled-handlers"))},updateToolbar:function(e){var i=t('tr[id="'+e.object_id+'"]'),n=t(".title-toolbar",i);n.replaceWith(e.toolbar_tag),n=t(".title-toolbar",i),a.initContent(n)},updateMarkers:function(e){var i=t('tr[id="'+e.object_id+'"]'),n=t(".title-toolbar",i),s=t("DIV.action."+e.marker_type,n);s.exists()?s.replaceWith(e.marker_tag):t(e.marker_tag).appendTo(n),e.marker_tag&&(s=t("DIV.action."+e.marker_type,n),a.initContent(s)),a.helpers.sort(n,"weight")}},pictograms:{initManagerSelection:function(){var e=t(this),a=t('input[type="hidden"]',t(".selected-pictograms",e)).listattr("value");return{selected:JSON.stringify(a)}},switchPictogram:function(){var e=t(this),a=e.parents(".pictograms"),i=a.parents(".pictograms-manager");a.hasClass("available-pictograms")?t(".selected-pictograms",i).append(e):t(".available-pictograms",i).append(e)}},themes:{initExtracts:function(e){var i=t('select[name="manager_themes.widgets.thesaurus_name:list"]',e).val(),n=t('select[name="manager_themes.widgets.extract_name:list"]',e),s=n.val();i&&a.jsonrpc.post("getExtracts",{thesaurus_name:i},{url:"/api/thesaurus/json"},function(e){n.empty(),t(e.result).each(function(){t("<option></option>").attr("value",this.id).attr("selected",this.id===s).text(this.text).appendTo(n)})})},getExtracts:function(e){var i=t(e.currentTarget).parents("form"),n=t('select[name="manager_themes.widgets.thesaurus_name:list"]',i).val(),s=t('select[name="manager_themes.widgets.extract_name:list"]',i),r=s.data("select2");n&&"--NOVALUE--"!==n?a.jsonrpc.post("getExtracts",{thesaurus_name:n},{url:"/api/thesaurus/json"},function(t){r.results.empty(),r.opts.populateResults.call(r,r.results,t.result,{term:""})}):(s.select2("data",null),r.results.empty(),r.opts.populateResults.call(r,r.results,[],{term:""}))}},fields:{refreshField:function(e){var a=t('table[id="form_fields_list"]'),i=t('tr[data-ams-element-name="'+e.object_name+'"]',a);t("td:nth-child(4)",i).html(e.title)}},imgmap:{init:function(){var e=t(this);a.ajax.check(t.fn.canvasAreaDraw,"/--static--/pyams_content/js/jquery-canvasAreaDraw"+a.devext+".js",function(){e.canvasAreaDraw({imageUrl:e.data("ams-image-url")})})},initPreview:function(){var e=t(this);a.ajax.check(t.fn.mapster,"/--static--/pyams_content/js/jquery-imagemapster-1.2.10"+a.devext+".js",function(){e.mapster({fillColor:"ff0000",fillOpacity:.35,selected:!0,highlight:!0,staticState:!0})})}},site:{switchVisibility:function(){return function(){var e=t(this),i=e.parents("tr").first();a.ajax.post(i.data("ams-location")+"/switch-content-visibility.json",{object_name:i.data("ams-element-name")},function(a,i){var n="fa-eye";a.visible||(n+="-slash"),a.published||(n+=" text-danger"),t("i",e).attr("class","fa fa-fw "+n)})}}},review:{timer:null,timer_duration:{general:3e4,chat:5e3},initComments:function(e){var n=t(".chat-body",e);n.animate({scrollTop:n[0].scrollHeight},1e3),clearInterval(i.review.timer),i.review.timer=setInterval(i.review.updateComments,i.review.timer_duration.chat),a.skin.registerCleanCallback(i.review.cleanCommentsCallback)},cleanCommentsCallback:function(){clearInterval(i.review.timer),i.review.timer=setInterval(i.review.updateComments,i.review.timer_duration.general)},updateComments:function(){var e,i=t(".badge",'nav a[href="#review-comments.html"]'),n=t(".chat-body",".widget-body");e=n.exists()?t(".message",n).length:parseInt(i.text()),a.ajax.post("get-last-review-comments.json",{count:e},function(a){n.exists()&&i.removeClass("bg-color-danger").addClass("bg-color-info"),e!==a.count&&(i.text(a.count).removeClass("hidden"),n.exists()&&(t(".messages",n).append(a.content),n.animate({scrollTop:n[0].scrollHeight},1e3)),n.exists()||i.removeClass("bg-color-info").addClass("bg-color-danger").animate({padding:"3px 12px 2px","margin-right":"9px"},"slow",function(){t(this).animate({padding:"3px 6px 2px","margin-right":"15px"},"slow")}))})},initCommentData:function(e){var a=t(".chat-body",".widget-body");return{count:t(".message",a).length}},addCommentAction:function(){return function(){t('textarea[name="comment"]').focus()}},addCommentCallback:function(e){var a=t(this),i=a.parents(".widget-body");t(".messages",i).append(e.content),t('textarea[name="comment"]',a).val("");var n=t(".chat-body",i);n.animate({scrollTop:n[0].scrollHeight},1e3),t(".badge",'nav a[href="#review-comments.html"]').text(e.count).removeClass("hidden")}},header:{submitEditForm:function(){var e=t(this).parents("form").first();a.form.submit(e,{form_data:{autosubmit:!0}})}},footer:{submitEditForm:function(){var e=t(this).parents("form").first();a.form.submit(e,{form_data:{autosubmit:!0}})}},profile:{switchFavorite:function(){var e=t(this),i=e.data("sequence-oid");a.ajax.post("switch-user-favorite.json",{oid:i},function(t,a){t.favorite?e.removeClass("fa-star-o").addClass("fa-star"):e.removeClass("fa-star").addClass("fa-star-o")})}}};t(".badge",'nav a[href="#review-comments.html"]').exists()&&(i.review.timer=setInterval(i.review.updateComments,i.review.timer_duration.general)),e.PyAMS_content=i}(jQuery,this);
+!function(t,e){"use strict";var a=e.MyAMS,i={widget:{treeview:{selectFolder:function(e,a){t(e.target).siblings('input[type="hidden"]').val(a.id)},unselectFolder:function(e,a){t(e.target).siblings('input[type="hidden"]').val(null)}}},TinyMCE:{initEditor:function(t){return tinyMCE.addI18n("fr",{"Link list":"Liste de liens","Toggle h3 header":"En-tête H3","Toggle h4 header":"En-tête H4","Insert internal link":"Insérer un lien interne","Link title":"Texte à afficher","Internal number":"N° interne"}),tinymce.PluginManager.add("internal_links",function(t,e){t.addButton("internal_links",{icon:"cloud-check",tooltip:"Insert internal link",image:"/--static--/pyams_content/img/internal-link.png",onclick:function(){t.windowManager.open({title:"Insert internal link",body:[{type:"textbox",name:"oid",label:"Internal number"},{type:"textbox",name:"title",label:"Link title",value:t.selection.getContent()}],onsubmit:function(e){t.insertContent('<a href="oid://'+e.data.oid+'">'+e.data.title+"</a>")}})}})}),tinyMCE.PluginManager.add("headers",function(t,e){["h3","h4"].forEach(function(e){t.addButton("header-"+e,{tooltip:"Toggle "+e+" header",text:e.toUpperCase(),onClick:function(){t.execCommand("mceToggleFormat",!1,e)},onPostRender:function(){var a=this,i=function(){t.formatter.formatChanged(e,function(t){a.active(t)})};t.formatter?i():t.on("init",i)}})})}),t.image_list=i.TinyMCE.getImagesList,t.link_list=i.TinyMCE.getLinksList,t.style_formats=[{title:"Inline",items:[{title:"Bold",icon:"bold",format:"bold"},{title:"Italic",icon:"italic",format:"italic"},{title:"Underline",icon:"underline",format:"underline"},{title:"Strikethrough",icon:"strikethrough",format:"strikethrough"},{title:"Superscript",icon:"superscript",format:"superscript"},{title:"Subscript",icon:"subscript",format:"subscript"},{title:"Code",icon:"code",format:"code"}]},{title:"Blocks",items:[{title:"Paragraph",format:"p"},{title:"Blockquote",format:"blockquote"},{title:"Div",format:"div"},{title:"Pre",format:"pre"}]},{title:"Alignment",items:[{title:"Left",icon:"alignleft",format:"alignleft"},{title:"Center",icon:"aligncenter",format:"aligncenter"},{title:"Right",icon:"alignright",format:"alignright"},{title:"Justify",icon:"alignjustify",format:"alignjustify"}]}],t.plugins+=" internal_links headers",t.toolbar1&&(t.toolbar1="undo redo | header-h3 header-h4 styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent"),t.toolbar2&&(t.toolbar2="forecolor backcolor | charmap internal_links link | fullscreen preview print | code"),t},getImagesList:function(e){var i=t(document.activeElement).parents("form");if(i.exists()){var n=i.attr("data-ams-form-handler")||i.attr("action"),s=n.substr(0,n.lastIndexOf("/")+1);return a.ajax.post(s+"get-images-list.json",{},e)}},getLinksList:function(e){var i=t(document.activeElement).parents("form");if(i.exists()){var n=i.attr("data-ams-form-handler")||i.attr("action"),s=n.substr(0,n.lastIndexOf("/")+1);return a.ajax.post(s+"get-links-list.json",{},e)}}},galleries:{addMediaCallback:function(e){var a=t(".gallery",t('[id="'+e.parent+'"]')),n=a.siblings(".switcher");n.parents("fieldset:first").hasClass("switched")&&n.click();for(var s=0;s<e.medias.length;s++)a.append(e.medias[s]);a.sortable({helper:"clone",stop:i.galleries.setOrder})},updateMediaTitle:function(e){t('img[id="'+e.media_id+'"]').attr("original-title",e.title)},switchMediaVisibility:function(e){return function(){var e=t(this),i=e.parents(".media"),n=i.parents(".gallery");t("i",e).attr("class","fa fa-fw fa-spinner fa-pulse"),a.ajax.post(n.data("ams-location")+"/set-media-visibility.json",{object_name:i.data("ams-element-name")},function(a,i){a.visible?(t("i",e).attr("class","fa fa-fw fa-eye"),e.parents(".btn-group").siblings("a.fancyimg").removeClass("not-visible")):(t("i",e).attr("class","fa fa-fw fa-eye-slash text-danger"),e.parents(".btn-group").siblings("a.fancyimg").addClass("not-visible"))})}},setOrder:function(e,i){if(!i||!i.item.hasClass("already-dropped")){var n=i.item.parents(".gallery"),s=t(".media",n).listattr("data-ams-element-name");a.ajax.post(n.data("ams-location")+"/set-medias-order.json",{medias:JSON.stringify(s)})}},removeMedia:function(e){return function(){var e=t(this);a.skin.bigBox({title:a.i18n.WARNING,content:'<i class="text-danger fa fa-2x fa-bell shake animated"></i>&nbsp; '+a.i18n.DELETE_WARNING,buttons:a.i18n.BTN_OK_CANCEL},function(t){if(t===a.i18n.BTN_OK){var i=e.parents(".gallery").data("ams-location"),n=e.parents(".media"),s=n.data("ams-element-name");a.ajax.post(i+"/delete-element.json",{object_name:s},function(t,e){n.remove(),t.handle_json&&a.ajax.handleJSON(t)})}})}},afterFancyboxLoad:function(t,e){t.element.hasClass("not-visible")&&t.inner.prepend('<div class="hidden-mask"></div>')}},paragraphs:{preReload:function(){i.paragraphs.switched=t("i.switch.fa-minus-square-o","#paragraphs_list").parents("tr").listattr("id")},postReload:function(){t(i.paragraphs.switched).each(function(){t("i.switch.fa-plus-square-o",'[id="'+this+'"]').parents("div").first().click()}),delete i.paragraphs.switched},refreshParagraph:function(e){var a=t('tr[id="'+e.object_id+'"]');t("span.title",a).html(e.title||" - - - - - - - -")},switchEditor:function(e){var i=t(this),n=t("i.switch",i),s=i.parents("td"),r=t(".editor",s),o=i.parents("tr");if(n.hasClass("fa-plus-square-o")){var l=o.parents("table");r.html('<h1 class="loading"><i class="fa fa-2x fa-gear fa-spin"></i></h1>'),a.ajax.post(l.data("ams-location")+"/get-paragraph-editor.json",{object_name:o.data("ams-element-name")},function(t){r.html(t),t&&(a.initContent(r),n.removeClass("fa-plus-square-o").addClass("fa-minus-square-o"),o.data("ams-disabled-handlers",!0),a.skin.scrollTo(r,{offset:r.height()-o.height()}))})}else a.skin.cleanContainer(r),r.empty(),n.removeClass("fa-minus-square-o").addClass("fa-plus-square-o"),o.removeData("ams-disabled-handlers")},switchLastEditor:function(e){var a=t('table[id="'+e+'"]'),i=t("tr:last",a);t('[data-ams-click-handler="PyAMS_content.paragraphs.switchEditor"]',i).click()},switchAllEditors:function(e){var i=t(this),n=t("i",i),s=i.parents("table");n.hasClass("fa-plus-square-o")?(n.removeClass("fa-plus-square-o").addClass("fa-cog fa-spin"),a.ajax.post(s.data("ams-location")+"/get-paragraphs-editors.json",{},function(e){for(var i in e)if(e.hasOwnProperty(i)){var r=t('tr[data-ams-element-name="'+i+'"]',s),o=t(".editor",r);o.is(":empty")&&o.html(e[i]),t(".fa-plus-square-o",r).removeClass("fa-plus-square-o").addClass("fa-minus-square-o"),r.data("ams-disabled-handlers",!0)}t("i.fa-plus-square-o",t("tbody",s)).exists()||n.removeClass("fa-cog fa-spin").addClass("fa-minus-square-o"),a.initContent(s)})):(t(".editor",s).each(function(){a.skin.cleanContainer(t(this)),t(this).empty()}),t(".fa-minus-square-o",s).removeClass("fa-minus-square-o").addClass("fa-plus-square-o"),t("tr",s).removeData("ams-disabled-handlers"))},updateToolbar:function(e){var i=t('tr[id="'+e.object_id+'"]'),n=t(".title-toolbar",i);n.replaceWith(e.toolbar_tag),n=t(".title-toolbar",i),a.initContent(n)},updateMarkers:function(e){var i=t('tr[id="'+e.object_id+'"]'),n=t(".title-toolbar",i),s=t("DIV.action."+e.marker_type,n);s.exists()?s.replaceWith(e.marker_tag):t(e.marker_tag).appendTo(n),e.marker_tag&&(s=t("DIV.action."+e.marker_type,n),a.initContent(s)),a.helpers.sort(n,"weight")}},pictograms:{initManagerSelection:function(){var e=t(this),a=t('input[type="hidden"]',t(".selected-pictograms",e)).listattr("value");return{selected:JSON.stringify(a)}},switchPictogram:function(){var e=t(this),a=e.parents(".pictograms"),i=a.parents(".pictograms-manager");a.hasClass("available-pictograms")?t(".selected-pictograms",i).append(e):t(".available-pictograms",i).append(e)}},themes:{initExtracts:function(e){var i=t('select[name="manager_themes.widgets.thesaurus_name:list"]',e).val(),n=t('select[name="manager_themes.widgets.extract_name:list"]',e),s=n.val();i&&a.jsonrpc.post("getExtracts",{thesaurus_name:i},{url:"/api/thesaurus/json"},function(e){n.empty(),t(e.result).each(function(){t("<option></option>").attr("value",this.id).attr("selected",this.id===s).text(this.text).appendTo(n)})})},getExtracts:function(e){var i=t(e.currentTarget).parents("form"),n=t('select[name="manager_themes.widgets.thesaurus_name:list"]',i).val(),s=t('select[name="manager_themes.widgets.extract_name:list"]',i),r=s.data("select2");n&&"--NOVALUE--"!==n?a.jsonrpc.post("getExtracts",{thesaurus_name:n},{url:"/api/thesaurus/json"},function(t){r.results.empty(),r.opts.populateResults.call(r,r.results,t.result,{term:""})}):(s.select2("data",null),r.results.empty(),r.opts.populateResults.call(r,r.results,[],{term:""}))}},fields:{refreshField:function(e){var a=t('table[id="form_fields_list"]'),i=t('tr[data-ams-element-name="'+e.object_name+'"]',a);t("td:nth-child(4)",i).html(e.title)}},imgmap:{init:function(){var e=t(this);a.ajax.check(t.fn.canvasAreaDraw,"/--static--/pyams_content/js/jquery-canvasAreaDraw"+a.devext+".js",function(){e.canvasAreaDraw({imageUrl:e.data("ams-image-url")})})},initPreview:function(){var e=t(this);a.ajax.check(t.fn.mapster,"/--static--/pyams_content/js/jquery-imagemapster-1.2.10"+a.devext+".js",function(){e.mapster({fillColor:"ff0000",fillOpacity:.35,selected:!0,highlight:!0,staticState:!0})})}},site:{switchVisibility:function(){return function(){var e=t(this),i=e.parents("tr").first();a.ajax.post(i.data("ams-location")+"/switch-content-visibility.json",{object_name:i.data("ams-element-name")},function(a,i){var n="fa-eye";a.visible||(n+="-slash"),a.published||(n+=" text-danger"),t("i",e).attr("class","fa fa-fw "+n)})}}},review:{timer:null,timer_duration:{general:3e4,chat:5e3},initComments:function(e){var n=t(".chat-body",e);n.animate({scrollTop:n[0].scrollHeight},1e3),clearInterval(i.review.timer),i.review.timer=setInterval(i.review.updateComments,i.review.timer_duration.chat),a.skin.registerCleanCallback(i.review.cleanCommentsCallback)},cleanCommentsCallback:function(){clearInterval(i.review.timer),i.review.timer=setInterval(i.review.updateComments,i.review.timer_duration.general)},updateComments:function(){var e,i=t(".badge",'nav a[href="#review-comments.html"]'),n=t(".chat-body",".widget-body");e=n.exists()?t(".message",n).length:parseInt(i.text()),a.ajax.post("get-last-review-comments.json",{count:e},function(a){n.exists()&&i.removeClass("bg-color-danger").addClass("bg-color-info"),e!==a.count&&(i.text(a.count).removeClass("hidden"),n.exists()&&(t(".messages",n).append(a.content),n.animate({scrollTop:n[0].scrollHeight},1e3)),n.exists()||i.removeClass("bg-color-info").addClass("bg-color-danger").animate({padding:"3px 12px 2px","margin-right":"9px"},"slow",function(){t(this).animate({padding:"3px 6px 2px","margin-right":"15px"},"slow")}))})},initCommentData:function(e){var a=t(".chat-body",".widget-body");return{count:t(".message",a).length}},addCommentAction:function(){return function(){t('textarea[name="comment"]').focus()}},addCommentCallback:function(e){var a=t(this),i=a.parents(".widget-body");t(".messages",i).append(e.content),t('textarea[name="comment"]',a).val("");var n=t(".chat-body",i);n.animate({scrollTop:n[0].scrollHeight},1e3),t(".badge",'nav a[href="#review-comments.html"]').text(e.count).removeClass("hidden")}},header:{submitEditForm:function(){var e=t(this).parents("form").first();a.form.submit(e,{form_data:{autosubmit:!0}})}},footer:{submitEditForm:function(){var e=t(this).parents("form").first();a.form.submit(e,{form_data:{autosubmit:!0}})}},profile:{switchFavorite:function(){var e=t(this),i=e.data("sequence-oid");a.ajax.post("switch-user-favorite.json",{oid:i},function(t,a){t.favorite?e.removeClass("fa-star-o").addClass("fa-star"):e.removeClass("fa-star").addClass("fa-star-o")})}}};t(".badge",'nav a[href="#review-comments.html"]').exists()&&(i.review.timer=setInterval(i.review.updateComments,i.review.timer_duration.general)),e.PyAMS_content=i}(jQuery,this);