Added renderers
authorThierry Florac <thierry.florac@onf.fr>
Wed, 07 Mar 2018 16:32:24 +0100
changeset 9 e81e39878694
parent 8 3968b35afde8
child 10 04df9069f10c
Added renderers
src/pyams_default_theme/component/__init__.py
src/pyams_default_theme/component/association/__init__.py
src/pyams_default_theme/component/association/templates/association-default.pt
src/pyams_default_theme/component/gallery/__init__.py
src/pyams_default_theme/component/gallery/templates/renderer-default.pt
src/pyams_default_theme/component/illustration/__init__.py
src/pyams_default_theme/component/illustration/interfaces/__init__.py
src/pyams_default_theme/component/illustration/templates/illustration-default.pt
src/pyams_default_theme/component/illustration/templates/illustration-left.pt
src/pyams_default_theme/component/illustration/templates/illustration-right.pt
src/pyams_default_theme/component/paragraph/__init__.py
src/pyams_default_theme/component/paragraph/contact.py
src/pyams_default_theme/component/paragraph/frame.py
src/pyams_default_theme/component/paragraph/header.py
src/pyams_default_theme/component/paragraph/interfaces/__init__.py
src/pyams_default_theme/component/paragraph/interfaces/contact.py
src/pyams_default_theme/component/paragraph/interfaces/frame.py
src/pyams_default_theme/component/paragraph/interfaces/verbatim.py
src/pyams_default_theme/component/paragraph/keypoint.py
src/pyams_default_theme/component/paragraph/templates/contact-default.pt
src/pyams_default_theme/component/paragraph/templates/frame-default.pt
src/pyams_default_theme/component/paragraph/templates/frame-left.pt
src/pyams_default_theme/component/paragraph/templates/frame-right.pt
src/pyams_default_theme/component/paragraph/templates/header-default.pt
src/pyams_default_theme/component/paragraph/templates/keypoints-default.pt
src/pyams_default_theme/component/paragraph/templates/verbatim-default.pt
src/pyams_default_theme/component/paragraph/templates/verbatim-left.pt
src/pyams_default_theme/component/paragraph/templates/verbatim-right.pt
src/pyams_default_theme/component/paragraph/templates/video-default.pt
src/pyams_default_theme/component/paragraph/verbatim.py
src/pyams_default_theme/component/paragraph/video.py
src/pyams_default_theme/shared/__init__.py
src/pyams_default_theme/shared/logo/__init__.py
src/pyams_default_theme/shared/logo/templates/logos-default.pt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/__init__.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,19 @@
+#
+# Copyright (c) 2008-2017 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+# import standard library
+
+# import interfaces
+
+# import packages
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/association/__init__.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,49 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.component.association.interfaces import IAssociationParagraph, IAssociationInfo, \
+    IAssociationContainer
+from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_skin.layer import IPyAMSLayer
+
+# import packages
+from pyams_content.features.renderer.zmi import BaseContentRenderer
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+
+from pyams_default_theme import _
+
+
+#
+# Associations paragraph default renderer
+#
+
+@adapter_config(name='default', context=(IAssociationParagraph, IPyAMSLayer), provides=IContentRenderer)
+@template_config(template='templates/association-default.pt', layer=IPyAMSLayer)
+class AssociationParagraphDefaultRenderer(BaseContentRenderer):
+    """Associations paragraph default renderer"""
+
+    label = _("Default associations renderer")
+
+    i18n_context_attrs = ('title', )
+
+    def update(self):
+        super(AssociationParagraphDefaultRenderer, self).update()
+        self.associations = [{'url': item.get_url(self.request),
+                              'title': IAssociationInfo(item).user_title}
+                             for item in IAssociationContainer(self.context).values() if item.visible]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/association/templates/association-default.pt	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,9 @@
+<i18n:var domain="pyams_default_theme">
+	<h3 tal:content="view.title">ยง title</h3>
+	<ul class="inside">
+		<li tal:repeat="item view.associations">
+			<a tal:attributes="href item['url']"
+			   tal:content="item['title']" target="_blank">Link</a>
+		</li>
+	</ul>
+</i18n:var>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/gallery/__init__.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,41 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.component.gallery import IGallery
+from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_skin.layer import IPyAMSLayer
+
+# import packages
+from pyams_content.features.renderer.zmi import BaseContentRenderer
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+
+from pyams_default_theme import _
+
+
+class BaseGalleryRenderer(BaseContentRenderer):
+    """Base gallery renderer"""
+
+
+@adapter_config(name='default', context=(IGallery, IPyAMSLayer), provides=IContentRenderer)
+@template_config(template='templates/renderer-default.pt', layer=IPyAMSLayer)
+class DefaultGalleryRenderer(BaseGalleryRenderer):
+    """Default gallery renderer"""
+
+    label = _("Default gallery renderer")
+    weight = 1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/gallery/templates/renderer-default.pt	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,24 @@
+<div i18n:domain="pyams_content">
+	<tal:loop repeat="image context.get_visible_medias()">
+		<picture tal:define="image_data i18n:image.data;
+							 image_url extension:absolute_url(image_data);
+							 base_width 100 / 12;
+							 width 12;">
+			<source media="(min-width: 1200px)"
+					tal:attributes="srcset string:${image_url}/++thumb++lg:w1200 1200w, ${image_url}/++thumb++lg:w512 512w, ${image_url}/++thumb++lg:w256 256w, ${image_url}/++thumb++lg:w128 128w;
+									sizes string:${round(base_width * width)}vw" />
+			<source media="(min-width: 992px)"
+					tal:attributes="srcset string:${image_url}/++thumb++md:w992 992w, ${image_url}/++thumb++md:w512 512w, ${image_url}/++thumb++md:w256 256w, ${image_url}/++thumb++md:w128 128w;
+									sizes string:${round(base_width * width)}vw" />
+			<source media="(min-width: 768px)"
+					tal:condition="width"
+					tal:attributes="srcset string:${image_url}/++thumb++sm:w768 768w, ${image_url}/++thumb++sm:w512 512w, ${image_url}/++thumb++sm:w256 256w, ${image_url}/++thumb++sm:w128 128w;
+									sizes string:${round(base_width * width)}vw" />
+			<source media="(max-width: 767px)"
+					tal:condition="width"
+					tal:attributes="srcset string:${image_url}/++thumb++xs:w768 768w, ${image_url}/++thumb++xs:w512 512w, ${image_url}/++thumb++xs:w256 256w, ${image_url}/++thumb++xs:w128 128w;
+									sizes string:${round(base_width * width)}vw" />
+			<img style="width: 100%;" tal:attributes="src image_url" />
+		</picture>
+	</tal:loop>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/illustration/__init__.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,101 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+from persistent import Persistent
+
+# import interfaces
+from pyams_content.component.illustration.interfaces import IIllustration
+from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_default_theme.component.illustration.interfaces import IIllustrationWithZoomSettings
+from pyams_skin.layer import IPyAMSLayer
+from zope.annotation.interfaces import IAnnotations
+
+# import packages
+from pyams_content.features.renderer.zmi import BaseContentRenderer
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+from zope.interface import implementer
+from zope.location import locate, Location
+from zope.schema.fieldproperty import FieldProperty
+
+from pyams_default_theme import _
+
+
+#
+# Illustration renderer with zoom settings
+#
+
+ILLUSTRATION_ZOOM_RENDERER_SETTINGS_KEY = 'pyams_content.illustration.renderer:zoom'
+
+
+@implementer(IIllustrationWithZoomSettings)
+class IllustrationZoomSettings(Persistent, Location):
+    """Illustration zoom renderer settings"""
+
+    zoom_on_click = FieldProperty(IIllustrationWithZoomSettings['zoom_on_click'])
+
+
+@adapter_config(context=IIllustration, provides=IIllustrationWithZoomSettings)
+def IllustrationWithZoomSettingsFactory(context):
+    """Illustration zoom renderer settings factory"""
+    annotations = IAnnotations(context)
+    settings = annotations.get(ILLUSTRATION_ZOOM_RENDERER_SETTINGS_KEY)
+    if settings is None:
+        settings = annotations[ILLUSTRATION_ZOOM_RENDERER_SETTINGS_KEY] = IllustrationZoomSettings()
+        locate(settings, context)
+    return settings
+
+
+#
+# Illustration renderers
+#
+
+class BaseIllustrationRenderer(BaseContentRenderer):
+    """Base illustration renderer"""
+
+    context_attrs = ('author', )
+    i18n_context_attrs = ('title', 'alt_title', 'description', 'data')
+
+
+@adapter_config(name='default', context=(IIllustration, IPyAMSLayer), provides=IContentRenderer)
+@template_config(template='templates/illustration-default.pt', layer=IPyAMSLayer)
+class DefaultIllustrationRenderer(BaseIllustrationRenderer):
+    """Default illustration renderer"""
+
+    label = _("Centered illustration")
+    weight = 1
+
+
+@adapter_config(name='left+zoom', context=(IIllustration, IPyAMSLayer), provides=IContentRenderer)
+@template_config(template='templates/illustration-left.pt', layer=IPyAMSLayer)
+class LeftIllustrationWithZoomRenderer(BaseIllustrationRenderer):
+    """Illustration renderer with small image and zoom"""
+
+    label = _("Small illustration on the left")
+    weight = 2
+
+    settings_interface = IIllustrationWithZoomSettings
+
+
+@adapter_config(name='right+zoom', context=(IIllustration, IPyAMSLayer), provides=IContentRenderer)
+@template_config(template='templates/illustration-right.pt', layer=IPyAMSLayer)
+class RightIllustrationWithZoomRenderer(BaseIllustrationRenderer):
+    """Illustration renderer with small image and zoom"""
+
+    label = _("Small illustration on the right")
+    weight = 3
+
+    settings_interface = IIllustrationWithZoomSettings
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/illustration/interfaces/__init__.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,33 @@
+#
+# Copyright (c) 2008-2017 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+
+# import packages
+from zope.interface import Interface
+from zope.schema import Bool
+
+from pyams_default_theme import _
+
+
+class IIllustrationWithZoomSettings(Interface):
+    """Illustration with zoom interface"""
+
+    zoom_on_click = Bool(title=_("Zoom on click?"),
+                         description=_("If 'yes', a click on illustration thumbnail is required to zoom"),
+                         required=True,
+                         default=True)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/illustration/templates/illustration-default.pt	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,20 @@
+<div class="text-center margin-y-5">
+	<picture tal:define="image_url extension:absolute_url(view.data);
+						 base_width 100 / 12;
+						 width 12;">
+		<source media="(min-width: 1200px)"
+				tal:attributes="srcset string:${image_url}/++thumb++lg:w1200 1200w, ${image_url}/++thumb++lg:w512 512w, ${image_url}/++thumb++lg:w256 256w, ${image_url}/++thumb++lg:w128 128w;
+								sizes string:${round(base_width * width)}vw" />
+		<source media="(min-width: 992px)"
+				tal:attributes="srcset string:${image_url}/++thumb++md:w992 992w, ${image_url}/++thumb++md:w512 512w, ${image_url}/++thumb++md:w256 256w, ${image_url}/++thumb++md:w128 128w;
+								sizes string:${round(base_width * width)}vw" />
+		<source media="(min-width: 768px)"
+				tal:attributes="srcset string:${image_url}/++thumb++sm:w768 768w, ${image_url}/++thumb++sm:w512 512w, ${image_url}/++thumb++sm:w256 256w, ${image_url}/++thumb++sm:w128 128w;
+								sizes string:${round(base_width * width)}vw" />
+		<source media="(max-width: 767px)"
+				tal:attributes="srcset string:${image_url}/++thumb++xs:w768 768w, ${image_url}/++thumb++xs:w512 512w, ${image_url}/++thumb++xs:w256 256w, ${image_url}/++thumb++xs:w128 128w;
+								sizes string:${round(base_width * width)}vw" />
+		<img style="width: 100%;" tal:attributes="src image_url; alt view.alt_title;" />
+	</picture>
+	<span tal:content="view.title">legend</span>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/illustration/templates/illustration-left.pt	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,13 @@
+<div class="pull-left margin-10" tal:condition="view.data">
+	<a class="fancybox" data-toggle
+	   data-ams-fancybox-type="image"
+	   tal:omit-tag="not:view.settings.zoom_on_click"
+	   tal:define="thumbnails extension:thumbnails(view.data);
+				   target thumbnails.get_thumbnail('800x600');
+				   thumb thumbnails.get_thumbnail('300x200');"
+	   tal:attributes="href extension:absolute_url(target)">
+		<img tal:attributes="src extension:absolute_url(thumb);
+							 alt view.alt_title;" /><br />
+		<span tal:content="view.title">legend</span>
+	</a><br />
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/illustration/templates/illustration-right.pt	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,13 @@
+<div class="pull-right margin-10" tal:condition="view.data">
+	<a class="fancybox" data-toggle
+	   data-ams-fancybox-type="image"
+	   tal:omit-tag="not:view.settings.zoom_on_click"
+	   tal:define="thumbnails extension:thumbnails(view.data);
+				   target thumbnails.get_thumbnail('800x600');
+				   thumb thumbnails.get_thumbnail('300x200');"
+	   tal:attributes="href extension:absolute_url(target)">
+		<img tal:attributes="src extension:absolute_url(thumb);
+							 alt view.alt_title" /><br />
+		<span tal:content="view.title">legend</span>
+	</a><br />
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/__init__.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,19 @@
+#
+# Copyright (c) 2008-2017 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+# import standard library
+
+# import interfaces
+
+# import packages
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/contact.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,91 @@
+#
+# Copyright (c) 2008-2017 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+from persistent import Persistent
+
+# import interfaces
+from pyams_content.component.paragraph.interfaces.contact import IContactParagraph
+from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_default_theme.component.paragraph.interfaces.contact import IContactParagraphDefaultRendererSettings
+from pyams_skin.layer import IPyAMSLayer
+from zope.annotation.interfaces import IAnnotations
+
+# import packages
+from pyams_content.features.renderer.zmi import BaseContentRenderer
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+from zope.interface import implementer
+from zope.location import locate, Location
+from zope.schema.fieldproperty import FieldProperty
+
+from pyams_default_theme import _
+
+
+#
+# Contact paragraph default renderer settings
+#
+
+CONTACT_DEFAULT_RENDERER_SETTINGS_KEY = 'pyams_content.contact.renderer:default'
+
+
+@implementer(IContactParagraphDefaultRendererSettings)
+class ContactParagraphDefaultRendererSettings(Persistent, Location):
+    """Contact paragraph default renderer settings"""
+
+    display_photo = FieldProperty(IContactParagraphDefaultRendererSettings['display_photo'])
+    photo_position = FieldProperty(IContactParagraphDefaultRendererSettings['photo_position'])
+    display_map = FieldProperty(IContactParagraphDefaultRendererSettings['display_map'])
+    map_position = FieldProperty(IContactParagraphDefaultRendererSettings['map_position'])
+
+    def can_display_photo(self):
+        contact = IContactParagraph(self.__parent__)
+        if not contact.photo:
+            return False
+        return self.display_photo
+
+    def can_display_map(self):
+        contact = IContactParagraph(self.__parent__)
+        if not contact.gps_location:
+            return False
+        return self.display_map
+
+
+@adapter_config(context=IContactParagraph, provides=IContactParagraphDefaultRendererSettings)
+def ContactParagraphDefaultRendererSettingsFactory(context):
+    """Contact paragraph default renderer settings factory"""
+    annotations = IAnnotations(context)
+    settings = annotations.get(CONTACT_DEFAULT_RENDERER_SETTINGS_KEY)
+    if settings is None:
+        settings = annotations[CONTACT_DEFAULT_RENDERER_SETTINGS_KEY] = ContactParagraphDefaultRendererSettings()
+        locate(settings, context)
+    return settings
+
+
+#
+# Contact paragraph default renderer
+#
+
+@adapter_config(name='default', context=(IContactParagraph, IPyAMSLayer), provides=IContentRenderer)
+@template_config(template='templates/contact-default.pt', layer=IPyAMSLayer)
+class ContactParagraphDefaultRenderer(BaseContentRenderer):
+    """Context paragraph default renderer"""
+
+    label = _("Default contact renderer")
+
+    settings_interface = IContactParagraphDefaultRendererSettings
+
+    context_attrs = ('name', 'photo', 'gps_location', 'address')
+    i18n_context_attrs = ('title', 'charge', )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/frame.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,157 @@
+#
+# Copyright (c) 2008-2017 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+from persistent import Persistent
+
+# import interfaces
+from pyams_content.component.association.interfaces import IAssociationContainer
+from pyams_content.component.illustration.interfaces import IIllustration
+from pyams_content.component.paragraph.interfaces.frame import IFrameParagraph
+from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_default_theme.component.paragraph.interfaces.frame import IFrameParagraphRendererSettings, \
+    ILateralFrameParagraphRendererSettings, IDefaultFrameParagraphRendererSettings
+from pyams_i18n.interfaces import II18n
+from pyams_skin.layer import IPyAMSLayer
+from zope.annotation.interfaces import IAnnotations
+
+# import packages
+from pyams_content.features.renderer.zmi import BaseContentRenderer
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+from zope.interface import implementer
+from zope.location import locate, Location
+from zope.schema.fieldproperty import FieldProperty
+
+from pyams_default_theme import _
+
+
+#
+# Framed text paragraph default renderer settings
+#
+
+FRAME_PARAGRAPH_RENDERER_SETTINGS_KEY = 'pyams_content.frame.renderer:default'
+
+
+@implementer(IFrameParagraphRendererSettings)
+class BaseFrameParagraphRendererSettings(Persistent, Location):
+    """Base frame text paragraph renderer settings"""
+
+    display_illustration = FieldProperty(IFrameParagraphRendererSettings['display_illustration'])
+    display_associations = FieldProperty(IFrameParagraphRendererSettings['display_associations'])
+
+    def can_display_illustration(self):
+        if not self.display_illustration:
+            return False
+        frame = IFrameParagraph(self.__parent__)
+        illustration = IIllustration(frame, None)
+        return (illustration is not None) and bool(II18n(illustration).query_attribute('data'))
+
+    def can_display_associations(self):
+        if not self.display_associations:
+            return False
+        frame = IFrameParagraph(self.__parent__)
+        associations = IAssociationContainer(frame, None)
+        return (associations is not None) and (len(associations.get_visible_items()) > 0)
+
+
+@implementer(IDefaultFrameParagraphRendererSettings)
+class DefaultFrameParagraphRendererSettings(BaseFrameParagraphRendererSettings):
+    """Framed text paragraph lateral renderer settings"""
+
+
+@adapter_config(context=IFrameParagraph, provides=IDefaultFrameParagraphRendererSettings)
+def DefaultFrameParagraphRendererSettingsFactory(context):
+    """Frame paragraph default renderer settings factory"""
+    annotations = IAnnotations(context)
+    settings = annotations.get(FRAME_PARAGRAPH_RENDERER_SETTINGS_KEY)
+    if settings is None:
+        settings = annotations[FRAME_PARAGRAPH_RENDERER_SETTINGS_KEY] = DefaultFrameParagraphRendererSettings()
+        locate(settings, context)
+    return settings
+
+
+@implementer(ILateralFrameParagraphRendererSettings)
+class LateralFrameParagraphRendererSettings(BaseFrameParagraphRendererSettings):
+    """Framed text paragraph lateral renderer settings"""
+
+    relative_width = FieldProperty(ILateralFrameParagraphRendererSettings['relative_width'])
+
+
+LATERAL_FRAME_PARAGRAPH_RENDERER_SETTINGS_KEY = 'pyams_content.frame.renderer:lateral'
+
+
+@adapter_config(context=IFrameParagraph, provides=ILateralFrameParagraphRendererSettings)
+def LateralFrameParagraphRendererSettingsFactory(context):
+    """Frame text paragraph lateral renderer settings factory"""
+    annotations = IAnnotations(context)
+    settings = annotations.get(LATERAL_FRAME_PARAGRAPH_RENDERER_SETTINGS_KEY)
+    if settings is None:
+        settings = annotations[LATERAL_FRAME_PARAGRAPH_RENDERER_SETTINGS_KEY] = LateralFrameParagraphRendererSettings()
+        locate(settings, context)
+    return settings
+
+
+#
+# Framed text paragraph default renderer
+#
+
+class BaseFrameParagraphRenderer(BaseContentRenderer):
+    """Base frame paragraph renderer"""
+
+    i18n_context_attrs = ('title', 'body')
+    illustration_renderer = None
+
+    def update(self):
+        super(BaseFrameParagraphRenderer, self).update()
+        if self.settings.can_display_illustration():
+            illustration = IIllustration(self.context, None)
+            renderer = illustration.get_renderer(self.request)
+            if renderer is not None:
+                renderer.update()
+                self.illustration_renderer = renderer
+
+
+@adapter_config(name='default', context=(IFrameParagraph, IPyAMSLayer), provides=IContentRenderer)
+@template_config(template='templates/frame-default.pt', layer=IPyAMSLayer)
+class DefaultFrameParagraphRenderer(BaseFrameParagraphRenderer):
+    """Framed text paragraph default renderer"""
+
+    label = _("Default frame renderer")
+    weight = 1
+
+    settings_interface = IDefaultFrameParagraphRendererSettings
+
+
+@adapter_config(name='left', context=(IFrameParagraph, IPyAMSLayer), provides=IContentRenderer)
+@template_config(template='templates/frame-left.pt', layer=IPyAMSLayer)
+class LeftFrameParagraphRenderer(BaseFrameParagraphRenderer):
+    """Framed text paragraph renderer displayed on the left"""
+
+    label = _("Small frame on the left")
+    weight = 2
+
+    settings_interface = ILateralFrameParagraphRendererSettings
+
+
+@adapter_config(name='right', context=(IFrameParagraph, IPyAMSLayer), provides=IContentRenderer)
+@template_config(template='templates/frame-right.pt', layer=IPyAMSLayer)
+class RightFrameParagraphRenderer(BaseFrameParagraphRenderer):
+    """Framed text paragraph renderer displayed on the right"""
+
+    label = _("Small frame on the right")
+    weight = 3
+
+    settings_interface = ILateralFrameParagraphRendererSettings
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/header.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,42 @@
+#
+# Copyright (c) 2008-2017 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.component.paragraph.interfaces.header import IHeaderParagraph
+from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_skin.layer import IPyAMSLayer
+
+# import packages
+from pyams_content.features.renderer.zmi import BaseContentRenderer
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+
+from pyams_default_theme import _
+
+
+#
+# Header paragraph default renderer
+#
+
+@adapter_config(name='default', context=(IHeaderParagraph, IPyAMSLayer), provides=IContentRenderer)
+@template_config(template='templates/header-default.pt', layer=IPyAMSLayer)
+class HeaderParagraphDefaultRenderer(BaseContentRenderer):
+    """Header paragraph default renderer"""
+
+    label = _("Default header renderer")
+
+    i18n_context_attrs = ('header', )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/interfaces/__init__.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,19 @@
+#
+# Copyright (c) 2008-2017 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+# import standard library
+
+# import interfaces
+
+# import packages
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/interfaces/contact.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,64 @@
+#
+# Copyright (c) 2008-2017 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+
+# import packages
+from zope.interface import Interface
+from zope.schema import Bool, Choice
+from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
+
+from pyams_default_theme import _
+
+
+CONTENT_POSITIONS = (
+    {'id': 'left', 'title': _("Left")},
+    {'id': 'right', 'title': _("Right")}
+)
+
+CONTENT_POSITIONS_VOCABULARY = SimpleVocabulary([SimpleTerm(item['id'], title=item['title'])
+                                                for item in CONTENT_POSITIONS])
+
+
+class IContactParagraphDefaultRendererSettings(Interface):
+    """Contact paragraph default renderer settings interface"""
+
+    display_photo = Bool(title=_("Show photo?"),
+                         description=_("Display contact photo"),
+                         required=True,
+                         default=True)
+
+    photo_position = Choice(title=_("Photo position"),
+                            required=False,
+                            vocabulary=CONTENT_POSITIONS_VOCABULARY,
+                            default='left')
+
+    def can_display_photo(self):
+        """Check if photo can be displayed"""
+
+    display_map = Bool(title=_("Show location map?"),
+                       description=_("If 'no', location map will not be displayed"),
+                       required=True,
+                       default=True)
+
+    map_position = Choice(title=_("Map position"),
+                          required=False,
+                          vocabulary=CONTENT_POSITIONS_VOCABULARY,
+                          default='right')
+
+    def can_display_map(self):
+        """Check if location map can be displayed"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/interfaces/frame.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,70 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+
+# import packages
+from zope.interface import Interface
+from zope.schema import Bool, Choice
+from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
+
+from pyams_default_theme import _
+
+
+FRAME_POSITIONS = (
+    {'id': 'left', 'title': _("Left")},
+    {'id': 'right', 'title': _("Right")},
+    {'id': 'center', 'title': _("Center (full width)")}
+)
+
+FRAME_POSITIONS_VOCABULARY = SimpleVocabulary([SimpleTerm(item['id'], title=item['title'])
+                                               for item in FRAME_POSITIONS])
+
+
+class IFrameParagraphRendererSettings(Interface):
+    """Framed paragraph default renderer settings interface"""
+
+    display_illustration = Bool(title=_("Show illustration?"),
+                                description=_("If 'no', illustration will not be displayed"),
+                                required=True,
+                                default=True)
+
+    def can_display_illustration(self):
+        """Check if illustration can be displayed"""
+
+    display_associations = Bool(title=_("Show associations?"),
+                                description=_("If 'no', associations will not be displayed"),
+                                required=True,
+                                default=True)
+
+    def can_display_associations(self):
+        """Check if associations can be displayed"""
+
+
+class IDefaultFrameParagraphRendererSettings(IFrameParagraphRendererSettings):
+    """Framed paragraph default renderer settings interface"""
+
+
+class ILateralFrameParagraphRendererSettings(IFrameParagraphRendererSettings):
+    """Framed paragraph lateral renderer settings interface"""
+
+    relative_width = Choice(title=_("Relative width"),
+                            description=_("Relative width used by this frame, relative to it's parent, "
+                                          "given as columns count; full width counts for 12 columns"),
+                            required=True,
+                            values=list(range(1, 13)),
+                            default=4)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/interfaces/verbatim.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,62 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+
+# import packages
+from zope.interface import Interface
+from zope.schema import Bool, Choice
+from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
+
+from pyams_default_theme import _
+
+
+FRAME_POSITIONS = (
+    {'id': 'left', 'title': _("Left")},
+    {'id': 'right', 'title': _("Right")},
+    {'id': 'center', 'title': _("Center (full width)")}
+)
+
+FRAME_POSITIONS_VOCABULARY = SimpleVocabulary([SimpleTerm(item['id'], title=item['title'])
+                                               for item in FRAME_POSITIONS])
+
+
+class IVerbatimParagraphRendererSettings(Interface):
+    """Verbatim paragraph default renderer settings interface"""
+
+    display_illustration = Bool(title=_("Show illustration?"),
+                                description=_("If 'no', illustration will not be displayed"),
+                                required=True,
+                                default=True)
+
+    def can_display_illustration(self):
+        """Check if illustration can be displayed"""
+
+
+class IDefaultVerbatimParagraphRendererSettings(IVerbatimParagraphRendererSettings):
+    """Verbatim paragraph default renderer settings interface"""
+
+
+class ILateralVerbatimParagraphRendererSettings(IVerbatimParagraphRendererSettings):
+    """Verbatim paragraph lateral renderer settings interface"""
+
+    relative_width = Choice(title=_("Relative width"),
+                            description=_("Relative width used by this paragraph, relative to it's parent, "
+                                          "given as columns count; full width counts for 12 columns"),
+                            required=True,
+                            values=list(range(1, 13)),
+                            default=4)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/keypoint.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,46 @@
+#
+# Copyright (c) 2008-2017 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.component.paragraph.interfaces.keypoint import IKeypointsParagraph
+from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_skin.layer import IPyAMSLayer
+
+# import packages
+from pyams_content.features.renderer.zmi import BaseContentRenderer
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+
+from pyams_default_theme import _
+
+
+#
+# Key points paragraph default renderer
+#
+
+@adapter_config(name='default', context=(IKeypointsParagraph, IPyAMSLayer), provides=IContentRenderer)
+@template_config(template='templates/keypoints-default.pt', layer=IPyAMSLayer)
+class KeypointsParagraphDefaultRenderer(BaseContentRenderer):
+    """Key points paragraph default renderer"""
+
+    label = _("Default key points renderer")
+
+    i18n_context_attrs = ('body', )
+
+    @property
+    def keypoints(self):
+        return (self.body or '').split('\n')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/templates/contact-default.pt	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,13 @@
+<div class="bordered row contact padding-10"
+	 tal:define="settings view.settings">
+	<h3 tal:content="view.title">Contact title</h3>
+	<div tal:condition="settings.can_display_photo()"
+		 tal:attributes="class string:thumbnail photo pull-${settings.photo_position}">
+		<img tal:attributes="src extension:absolute_url(view.photo, '++thumb++w128')" />
+	</div>
+	<div tal:condition="settings.can_display_map()"
+		 tal:attributes="class string:map pull-${settings.map_position}">
+		Location map
+	</div>
+	<strong tal:content="view.name">Contact name</strong><br />
+</div><div class="clearfix"></div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/templates/frame-default.pt	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,8 @@
+<div class="bordered row frame padding-10"
+	 tal:define="settings view.settings">
+	<h3 tal:condition="view.title" tal:content="view.title">Title</h3>
+	<div tal:condition="settings.can_display_illustration()"
+		 tal:replace="structure view.illustration_renderer.render() if view.illustration_renderer is not None else None">
+	</div>
+	<p tal:replace="structure view.body">body</p>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/templates/frame-left.pt	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,8 @@
+<div class="bordered row frame padding-10 pull-left"
+	 tal:define="settings view.settings"
+	 tal:attributes="class string:${default} col-md-${settings.relative_width}">
+	<h3 tal:condition="view.title" tal:content="view.title">Title</h3>
+	<img tal:condition="settings.can_display_illustration()"
+		 tal:replace="structure view.illustration_renderer.render() if view.illustration_renderer is not None else None" />
+	<p tal:replace="structure view.body">body</p>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/templates/frame-right.pt	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,8 @@
+<div class="bordered row frame padding-10 pull-right"
+	 tal:define="settings view.settings"
+	 tal:attributes="class string:${default} col-md-${settings.relative_width}">
+	<h3 tal:condition="view.title" tal:content="view.title">Title</h3>
+	<img tal:condition="settings.can_display_illustration()"
+		 tal:replace="structure view.illustration_renderer.render() if view.illustration_renderer is not None else None" />
+	<p tal:replace="structure view.body">body</p>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/templates/header-default.pt	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,2 @@
+<div class="strong margin-bottom-10"
+	 tal:content="structure extension:html(view.header)">header</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/templates/keypoints-default.pt	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,8 @@
+<div class="bordered margin-bottom-10 padding-10"
+	 tal:define="keypoints view.keypoints"
+	 tal:condition="keypoints">
+	<ul class="inside">
+		<li tal:repeat="item keypoints"
+			tal:content="item">item</li>
+	</ul>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/templates/verbatim-default.pt	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,24 @@
+<div class="bordered row verbatim padding-20"
+	 tal:define="settings view.settings;
+				 display_illustration settings.can_display_illustration()">
+	<tal:if condition="display_illustration">
+		<div class="illustration col-md-2"
+			 tal:content="structure view.illustration_renderer.render() if view.illustration_renderer is not None else None">
+		</div>
+		<div class="author col-md-4">
+			<span tal:content="view.author">Author</span><br />
+			<span tal:content="view.charge">Charge</span>
+		</div>
+		<div class="quote col-md-5"
+			 tal:content="extension:html(view.quote)">Quote</div>
+	</tal:if>
+	<tal:if condition="not:display_illustration">
+		<div class="quote"
+			 tal:content="extension:html(view.quote)">Quote</div>
+		<div class="author">
+			<span tal:content="view.author">Author</span>
+			<tal:if condition="view.charge">&ndash;</tal:if>
+			<span tal:content="view.charge">Charge</span>
+		</div>
+	</tal:if>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/templates/verbatim-left.pt	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,17 @@
+<div class="bordered row verbatim padding-20 pull-left"
+	 tal:define="settings view.settings"
+	 tal:attributes="class string:${default} col-md-${settings.relative_width}">
+	<h3 tal:condition="view.title" tal:content="view.title">Title</h3>
+	<div tal:condition="settings.can_display_illustration()"
+		 tal:replace="structure view.illustration_renderer.render() if view.illustration_renderer is not None else None">
+	</div>
+	<div class="quote"
+		 tal:content="structure extension:html(view.quote)">Quote</div>
+	<div class="author">
+		<span tal:content="view.author">Author</span>
+		<tal:if condition="view.charge">
+			&ndash;
+			<span tal:content="view.charge">Charge</span>
+		</tal:if>
+	</div>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/templates/verbatim-right.pt	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,17 @@
+<div class="bordered row verbatim padding-20 pull-right"
+	 tal:define="settings view.settings"
+	 tal:attributes="class string:${default} col-md-${settings.relative_width}">
+	<h3 tal:condition="view.title" tal:content="view.title">Title</h3>
+	<div tal:condition="settings.can_display_illustration()"
+		 tal:replace="structure view.illustration_renderer.render() if view.illustration_renderer is not None else None">
+	</div>
+	<div class="quote"
+		 tal:content="structure extension:html(view.quote)">Quote</div>
+	<div class="author">
+		<span tal:content="view.author">Author</span>
+		<tal:if condition="view.charge">
+			&ndash;
+			<span tal:content="view.charge">Charge</span>
+		</tal:if>
+	</div>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/templates/video-default.pt	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,31 @@
+<h3 tal:condition="view.title"
+	tal:content="view.title">title</h3>
+<div tal:condition="view.body"
+	 tal:content="structure view.body">body</div>
+<div tal:condition="view.description"
+	 tal:content="structure extension:html(view.description)">Description</div>
+<div class="flowplayer"
+	 data-ams-plugins="flowplayer"
+	 data-ams-plugin-flowplayer-async="false"
+	 data-ams-callback="PyAMS_media.initPlayer"
+	 tal:attributes="data-ams-plugin-flowplayer-src extension:resource_path('pyams_media.skin:flowplayer');
+					 data-ams-plugin-flowplayer-css extension:resource_path('pyams_media.skin:functional_css');
+					 data-ams-callback-source extension:resource_path('pyams_media.skin:pyams_media');">
+	<video
+		tal:define="video view.data;
+					href extension:absolute_url(video);
+					thumbnails extension:thumbnails(video);
+					conversions extension:conversions(video);
+					size thumbnails.get_image_size();"
+		tal:attributes="poster string:${href}/++thumb++${size[0]}x${size[1]}.png">
+		<tal:loop repeat="conversion conversions.get_conversions(order=('video/webm','video/mp4','video/x-flv'))">
+			<source type="video/mp4"
+					tal:define="media_width conversions.get_conversion_width(conversion.__name__);
+								video_type extension:video_type(conversion);"
+					tal:condition="video_type"
+					tal:attributes="type video_type;
+									src extension:absolute_url(conversion);
+									media 'all and (max-width: {0}px)'.format(media_width) if media_width else None;" />
+		</tal:loop>
+	</video>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/verbatim.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,151 @@
+#
+# Copyright (c) 2008-2017 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+from persistent import Persistent
+
+# import interfaces
+from pyams_content.component.association.interfaces import IAssociationContainer
+from pyams_content.component.illustration.interfaces import IIllustration
+from pyams_content.component.paragraph.interfaces.verbatim import IVerbatimParagraph
+from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_default_theme.component.paragraph.interfaces.verbatim import IVerbatimParagraphRendererSettings, \
+    ILateralVerbatimParagraphRendererSettings, IDefaultVerbatimParagraphRendererSettings
+from pyams_i18n.interfaces import II18n
+from pyams_skin.layer import IPyAMSLayer
+from zope.annotation.interfaces import IAnnotations
+
+# import packages
+from pyams_content.features.renderer.zmi import BaseContentRenderer
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+from zope.interface import implementer
+from zope.location import locate, Location
+from zope.schema.fieldproperty import FieldProperty
+
+from pyams_default_theme import _
+
+
+#
+# Verbatim paragraph default renderer settings
+#
+
+VERBATIM_PARAGRAPH_RENDERER_SETTINGS_KEY = 'pyams_content.verbatim.renderer:default'
+
+
+@implementer(IVerbatimParagraphRendererSettings)
+class BaseVerbatimParagraphRendererSettings(Persistent, Location):
+    """Base frame text paragraph renderer settings"""
+
+    display_illustration = FieldProperty(IVerbatimParagraphRendererSettings['display_illustration'])
+
+    def can_display_illustration(self):
+        if not self.display_illustration:
+            return False
+        frame = IVerbatimParagraph(self.__parent__)
+        illustration = IIllustration(frame, None)
+        return (illustration is not None) and bool(II18n(illustration).query_attribute('data'))
+
+
+@implementer(IDefaultVerbatimParagraphRendererSettings)
+class DefaultVerbatimParagraphRendererSettings(BaseVerbatimParagraphRendererSettings):
+    """Verbatim paragraph lateral renderer settings"""
+
+
+@adapter_config(context=IVerbatimParagraph, provides=IDefaultVerbatimParagraphRendererSettings)
+def DefaultVerbatimParagraphRendererSettingsFactory(context):
+    """Frame paragraph default renderer settings factory"""
+    annotations = IAnnotations(context)
+    settings = annotations.get(VERBATIM_PARAGRAPH_RENDERER_SETTINGS_KEY)
+    if settings is None:
+        settings = annotations[VERBATIM_PARAGRAPH_RENDERER_SETTINGS_KEY] = DefaultVerbatimParagraphRendererSettings()
+        locate(settings, context)
+    return settings
+
+
+@implementer(ILateralVerbatimParagraphRendererSettings)
+class LateralVerbatimParagraphRendererSettings(BaseVerbatimParagraphRendererSettings):
+    """Verbatim paragraph lateral renderer settings"""
+
+    relative_width = FieldProperty(ILateralVerbatimParagraphRendererSettings['relative_width'])
+
+
+LATERAL_VERBATIM_PARAGRAPH_RENDERER_SETTINGS_KEY = 'pyams_content.verbatim.renderer:lateral'
+
+
+@adapter_config(context=IVerbatimParagraph, provides=ILateralVerbatimParagraphRendererSettings)
+def LateralVerbatimParagraphRendererSettingsFactory(context):
+    """Frame text paragraph lateral renderer settings factory"""
+    annotations = IAnnotations(context)
+    settings = annotations.get(LATERAL_VERBATIM_PARAGRAPH_RENDERER_SETTINGS_KEY)
+    if settings is None:
+        settings = annotations[LATERAL_VERBATIM_PARAGRAPH_RENDERER_SETTINGS_KEY] = \
+            LateralVerbatimParagraphRendererSettings()
+        locate(settings, context)
+    return settings
+
+
+#
+# Verbatim paragraph default renderer
+#
+
+class BaseVerbatimParagraphRenderer(BaseContentRenderer):
+    """Base frame paragraph renderer"""
+
+    context_attrs = ('author', )
+    i18n_context_attrs = ('title', 'quote', 'charge')
+    illustration_renderer = None
+
+    def update(self):
+        super(BaseVerbatimParagraphRenderer, self).update()
+        if self.settings.can_display_illustration():
+            illustration = IIllustration(self.context, None)
+            renderer = illustration.get_renderer(self.request)
+            if renderer is not None:
+                renderer.update()
+                self.illustration_renderer = renderer
+
+
+@adapter_config(name='default', context=(IVerbatimParagraph, IPyAMSLayer), provides=IContentRenderer)
+@template_config(template='templates/verbatim-default.pt', layer=IPyAMSLayer)
+class DefaultVerbatimParagraphRenderer(BaseVerbatimParagraphRenderer):
+    """Verbatim paragraph default renderer"""
+
+    label = _("Default verbatim renderer")
+    weight = 1
+
+    settings_interface = IDefaultVerbatimParagraphRendererSettings
+
+
+@adapter_config(name='left', context=(IVerbatimParagraph, IPyAMSLayer), provides=IContentRenderer)
+@template_config(template='templates/verbatim-left.pt', layer=IPyAMSLayer)
+class LeftVerbatimParagraphRenderer(BaseVerbatimParagraphRenderer):
+    """Verbatim paragraph renderer displayed on the left"""
+
+    label = _("Small frame on the left")
+    weight = 2
+
+    settings_interface = ILateralVerbatimParagraphRendererSettings
+
+
+@adapter_config(name='right', context=(IVerbatimParagraph, IPyAMSLayer), provides=IContentRenderer)
+@template_config(template='templates/verbatim-right.pt', layer=IPyAMSLayer)
+class RightVerbatimParagraphRenderer(BaseVerbatimParagraphRenderer):
+    """Verbatim paragraph renderer displayed on the right"""
+
+    label = _("Small frame on the right")
+    weight = 3
+
+    settings_interface = ILateralVerbatimParagraphRendererSettings
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/video.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,43 @@
+#
+# Copyright (c) 2008-2017 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_content.component.paragraph.interfaces.video import IVideoParagraph
+from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_skin.layer import IPyAMSLayer
+
+# import packages
+from pyams_content.features.renderer.zmi import BaseContentRenderer
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+
+from pyams_default_theme import _
+
+
+#
+# Video paragraph default renderer
+#
+
+@adapter_config(name='default', context=(IVideoParagraph, IPyAMSLayer), provides=IContentRenderer)
+@template_config(template='templates/video-default.pt', layer=IPyAMSLayer)
+class VideoParagraphDefaultRenderer(BaseContentRenderer):
+    """Video paragraph default renderer"""
+
+    label = _("Default video renderer")
+
+    context_attrs = ('author', 'data')
+    i18n_context_attrs = ('title', 'body', 'description')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/shared/__init__.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,19 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+# import standard library
+
+# import interfaces
+
+# import packages
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/shared/logo/__init__.py	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,41 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+# import standard library
+
+# import interfaces
+from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_content.shared.logo.interfaces import ILogosParagraph
+from pyams_skin.layer import IPyAMSLayer
+
+# import packages
+from pyams_content.features.renderer.zmi import BaseContentRenderer
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+
+from pyams_default_theme import _
+
+
+#
+# Logos paragraph default renderer
+#
+
+@adapter_config(name='default', context=(ILogosParagraph, IPyAMSLayer), provides=IContentRenderer)
+@template_config(template='templates/logos-default.pt', layer=IPyAMSLayer)
+class LogosParagraphDefaultRenderer(BaseContentRenderer):
+    """Logos paragraph default renderer"""
+
+    label = _("Default logos renderer")
+
+    i18n_context_attrs = ('title', )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/shared/logo/templates/logos-default.pt	Wed Mar 07 16:32:24 2018 +0100
@@ -0,0 +1,14 @@
+<h3 tal:content="view.title">title</h3>
+<div class="padding-10" i18n:domain="pyams_content">
+	<tal:loop repeat="logo context.get_targets()">
+		<tal:if condition="logo is not None">
+			<a tal:omit-tag="not:logo.url"
+			   tal:attributes="href logo.url" target="_blank">
+				<img class="thumbnail margin-10"
+					 tal:define="thumbnails extension:thumbnails(logo.image);
+								 thumbnail thumbnails.get_thumbnail('200x200');"
+					 tal:attributes="src extension:absolute_url(thumbnail)" />
+			</a>
+		</tal:if>
+	</tal:loop>
+</div>