Merge default dev-dc
authorDamien Correia
Tue, 13 Nov 2018 14:48:49 +0100
branchdev-dc
changeset 217 58c9992cb4dc
parent 180 e5d91569642f (current diff)
parent 216 a12caaa4299d (diff)
child 218 549c215da6ce
Merge default
--- a/.hgtags	Tue Oct 09 17:51:18 2018 +0200
+++ b/.hgtags	Tue Nov 13 14:48:49 2018 +0100
@@ -10,3 +10,6 @@
 461fa3beb4386cf6764d60d59c4854fbba01833b 0.1.9
 d02350fa13e7d76393c414edecef40cd18cbb837 0.1.10
 9264287735a6f0538eb4580567e914cfb91383f2 0.1.11
+a948e0a00f5ce1f6d79d559ade979a3397f0d722 0.1.12
+39482eb2520fa8610d376e575c10dc2bcc79abc6 0.1.13
+fe7f30ba737d6790b949bac378f8539dcb3ab62f 0.1.14
--- a/buildout.cfg	Tue Oct 09 17:51:18 2018 +0200
+++ b/buildout.cfg	Tue Nov 13 14:48:49 2018 +0100
@@ -79,4 +79,4 @@
 eggs = pyams_default_theme [test]
 
 [versions]
-pyams_default_theme = 0.1.11
+pyams_default_theme = 0.1.15
--- a/docs/HISTORY.txt	Tue Oct 09 17:51:18 2018 +0200
+++ b/docs/HISTORY.txt	Tue Nov 13 14:48:49 2018 +0100
@@ -1,6 +1,23 @@
 History
 =======
 
+0.1.14
+------
+ - updated default fonts
+ - updated renderers and CSS styles
+
+0.1.13
+------
+ - updated meta-headers
+
+0.1.12
+------
+ - added site summary portlet renderer
+ - added default associations renderer
+ - added content link illustration adapter
+ - updated pictures renderers
+ - updated default video renderer
+
 0.1.11
 ------
  - added method to pictograms paragraph renderer
--- a/setup.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/setup.py	Tue Nov 13 14:48:49 2018 +0100
@@ -22,7 +22,7 @@
 README = os.path.join(DOCS, 'README.txt')
 HISTORY = os.path.join(DOCS, 'HISTORY.txt')
 
-version = '0.1.11'
+version = '0.1.15'
 long_description = open(README).read() + '\n\n' + open(HISTORY).read()
 
 tests_require = []
--- a/src/pyams_default_theme.egg-info/PKG-INFO	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme.egg-info/PKG-INFO	Tue Nov 13 14:48:49 2018 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: pyams-default-theme
-Version: 0.1.11
+Version: 0.1.14
 Summary: PyAMS default theme
 Home-page: http://hg.ztfy.org/pyams/pyams_default_theme
 Author: Thierry Florac
@@ -11,6 +11,23 @@
         History
         =======
         
+        0.1.14
+        ------
+         - updated default fonts
+         - updated renderers and CSS styles
+        
+        0.1.13
+        ------
+         - updated meta-headers
+        
+        0.1.12
+        ------
+         - added site summary portlet renderer
+         - added default associations renderer
+         - added content link illustration adapter
+         - updated pictures renderers
+         - updated default video renderer
+        
         0.1.11
         ------
          - added method to pictograms paragraph renderer
--- a/src/pyams_default_theme.egg-info/SOURCES.txt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme.egg-info/SOURCES.txt	Tue Nov 13 14:48:49 2018 +0100
@@ -18,9 +18,11 @@
 src/pyams_default_theme.egg-info/top_level.txt
 src/pyams_default_theme/component/__init__.py
 src/pyams_default_theme/component/association/__init__.py
+src/pyams_default_theme/component/association/viewlet.py
 src/pyams_default_theme/component/association/interfaces/__init__.py
 src/pyams_default_theme/component/association/templates/association-default.pt
 src/pyams_default_theme/component/association/templates/association-remote-content.pt
+src/pyams_default_theme/component/association/templates/association-viewlet.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
@@ -117,6 +119,9 @@
 src/pyams_default_theme/shared/imagemap/templates/render.pt
 src/pyams_default_theme/shared/logo/__init__.py
 src/pyams_default_theme/shared/logo/templates/logos-default.pt
+src/pyams_default_theme/shared/site/__init__.py
+src/pyams_default_theme/shared/site/portlet/__init__.py
+src/pyams_default_theme/shared/site/portlet/templates/site-summary.pt
 src/pyams_default_theme/shared/view/__init__.py
 src/pyams_default_theme/shared/view/portlet/__init__.py
 src/pyams_default_theme/shared/view/portlet/templates/view-items-list.pt
--- a/src/pyams_default_theme/__init__.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -15,6 +15,7 @@
 
 from fanstatic import Resource, Library
 from pyams_skin import myams_js, bootstrap_css, bootstrap
+from pyams_utils.fanstatic import ExternalResource
 
 from pyramid.i18n import TranslationStringFactory
 _ = TranslationStringFactory('pyams_default_theme')
@@ -22,9 +23,12 @@
 
 library = Library('pyams_default_theme', 'resources')
 
+pyams_default_theme_fonts = ExternalResource(library, 'https://fonts.googleapis.com/css?family=Lato:300|Oswald',
+                                             resource_type='css')
+
 pyams_default_theme_css = Resource(library, 'css/pyams-default.css',
                                    minified='css/pyams-default.min.css',
-                                   depends=[bootstrap_css, ])
+                                   depends=[bootstrap_css, pyams_default_theme_fonts])
 
 pyams_default_theme = Resource(library, 'js/pyams-default.js',
                                minified='js/pyams-default.min.js',
--- a/src/pyams_default_theme/component/association/__init__.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/association/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,29 +12,25 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
-
-# import packages
 from persistent import Persistent
 from zope.interface import implementer
 from zope.location import Location
 from zope.schema.fieldproperty import FieldProperty
 
-# import interfaces
-from pyams_content.component.association.interfaces import IAssociationParagraph, IAssociationInfo, \
-    IAssociationContainer
+from pyams_content.component.association.interfaces import IAssociationContainer, IAssociationInfo, \
+    IAssociationParagraph
 from pyams_content.component.extfile import IExtFile
-from pyams_content.component.links.interfaces import IInternalLink, IBaseLink
-from pyams_content.component.paragraph.interfaces import IParagraphContainerTarget, IParagraphContainer
+from pyams_content.component.links.interfaces import IBaseLink, IInternalLink
+from pyams_content.component.paragraph.interfaces import IParagraphContainer, IParagraphContainerTarget
 from pyams_content.features.renderer.interfaces import IContentRenderer
-from pyams_content.features.renderer.skin import BaseContentRenderer
-from pyams_default_theme import _
 from pyams_default_theme.component.association.interfaces import IAssociationParagraphRemoteContentRendererSettings
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config, get_annotation_adapter
 
+from pyams_default_theme import _
+
 
 #
 # Associations paragraph default renderer
@@ -46,16 +42,11 @@
     """Associations paragraph default renderer"""
 
     label = _("Default associations renderer")
-    i18n_context_attrs = ('title', )
+    i18n_context_attrs = ('title',)
 
     links = None
     attachments = None
 
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.attachments = []
-        self.links = []
-
     def get_associations(self):
         yield from IAssociationContainer(self.context).get_visible_items(self.request)
 
@@ -65,12 +56,15 @@
 
     def update(self):
         super().update()
+        self.attachments = []
+        self.links = []
         for item in self.get_associations():
             if IExtFile.providedBy(item):
                 self.attachments.append(item)
             elif IBaseLink.providedBy(item):
                 self.links.append(item)
 
+
 #
 # Associations paragraph remote content renderer
 #
@@ -101,8 +95,9 @@
     """Associations container remote content renderer"""
 
     label = _("Include remote content")
+    weight = 10
 
-    i18n_context_attrs = ('title', )
+    i18n_context_attrs = ('title',)
     links = ()
 
     settings_interface = IAssociationParagraphRemoteContentRendererSettings
--- a/src/pyams_default_theme/component/association/templates/association-default.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/association/templates/association-default.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -1,4 +1,2 @@
-<i18n:var domain="pyams_default_theme">
-	<h3 tal:content="view.title">§ title</h3>
-		${structure:provider:pyams.association}
-</i18n:var>
+<h3 tal:content="view.title">§ title</h3>
+${structure:provider:pyams.associations}
--- a/src/pyams_default_theme/component/association/templates/association-viewlet.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/association/templates/association-viewlet.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -1,21 +1,34 @@
-<ul>
-	<li tal:repeat="link view.links">
-		<tal:var tal:define="link_info view.get_link_info(link);
-							 description i18n:link.description;">
-			<p>${structure:tales:html(description)}<br tal:condition="description">
-				<a tal:define="href link.get_url(request);
-							   target None if href.startswith(request.application_url) else '_blank'"
-						       href="${href}" target="${target}">${link_info.user_title}</a>
+<ul class="associations">
+	<li tal:define="prefix view.attachments_title_prefix"
+		tal:repeat="item view.attachments">
+		<tal:var define="item_info view.get_link_info(item);
+						 description i18n:item.description;
+						 data i18n:item.data">
+			<p>
+				<tal:if condition="description">
+					${structure:tales:html(description)}
+					<br />
+				</tal:if>
+				<a tal:define="href_data tales:absolute_url(data);"
+				   href="${href_data}" target="_blank">
+					${prefix} ${item_info.user_title}
+				</a>
 			</p>
 		</tal:var>
 	</li>
-	<li tal:repeat="item view.attachments">
-		<tal:var tal:define="item_info view.get_link_info(item);
-							 description i18n:item.description;
-							 data i18n:item.data">
-			<p>${structure:tales:html(description)}<br tal:condition="description">
-				<a tal:define="href_data tales:absolute_url(data);"
-						href="${href_data}">${item_info.user_title}</a>
+	<li tal:repeat="link view.links">
+		<tal:var define="link_info view.get_link_info(link);
+						 description i18n:link.description;">
+			<p>
+				<tal:if condition="description">
+					${structure:tales:html(description)}
+					<br />
+				</tal:if
+				<a tal:define="href link.get_url(request);
+							   target None if href.startswith(request.application_url) else '_blank'"
+				   href="${href}" target="${target}">
+					${link_info.user_title}
+				</a>
 			</p>
 		</tal:var>
 	</li>
--- a/src/pyams_default_theme/component/association/viewlet.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/association/viewlet.py	Tue Nov 13 14:48:49 2018 +0100
@@ -16,13 +16,14 @@
 
 from pyams_skin.layer import IPyAMSUserLayer
 from pyams_content.component.association.interfaces import IAssociationContainer, IAssociationInfo
-from pyams_content.component.extfile.interfaces import IExtFile
+from pyams_content.component.extfile.interfaces import IExtFile, IExtFileManagerInfo
 from pyams_content.component.links.interfaces import IBaseLink
+from pyams_i18n.interfaces import II18n
 from pyams_template.template import template_config
 from pyams_viewlet.viewlet import ViewContentProvider, contentprovider_config
 
 
-@contentprovider_config(name='pyams.association', layer=IPyAMSUserLayer, view=Interface)
+@contentprovider_config(name='pyams.associations', layer=IPyAMSUserLayer, view=Interface)
 @template_config(template='templates/association-viewlet.pt', layer=IPyAMSUserLayer)
 class AssociationContentProvider(ViewContentProvider):
     """Association default content provider"""
@@ -49,3 +50,9 @@
                 self.attachments.append(item)
             elif IBaseLink.providedBy(item):
                 self.links.append(item)
+
+    @property
+    def attachments_title_prefix(self):
+        manager_info = IExtFileManagerInfo(self.request.root, None)
+        if manager_info is not None:
+            return II18n(manager_info).query_attribute('default_title_prefix', request=self.request)
--- a/src/pyams_default_theme/component/gallery/__init__.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/gallery/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,15 +12,15 @@
 
 __docformat__ = 'restructuredtext'
 
-
 from pyams_content.component.gallery.interfaces import IBaseGallery
 from pyams_content.features.renderer.interfaces import IContentRenderer
-from pyams_content.features.renderer.skin import BaseContentRenderer
-from pyams_default_theme import _
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
 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"""
--- a/src/pyams_default_theme/component/gallery/templates/renderer-default.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/gallery/templates/renderer-default.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -2,33 +2,19 @@
 	<tal:loop repeat="image context.get_visible_medias()">
 		<picture tal:define="image_data i18n:image.data;
 							 image_url tales:absolute_url(image_data);
-							 timestamp tales:timestamp(image);
-							 base_width 100 / 12;">
-			<!-- lg source -->
-			<source media="(min-width: 1200px)" sizes="12vw, 25vw, 50vw, 100vw"
-					tal:attributes="srcset string:${image_url}/++thumb++lg:w128?_=${timestamp} 128w,
-												  ${image_url}/++thumb++lg:w256?_=${timestamp} 256w,
-												  ${image_url}/++thumb++lg:w512?_=${timestamp} 512w,
-												  ${image_url}/++thumb++lg:w1200?_=${timestamp} 1200w" />
+							 timestamp tales:timestamp(image);">
+			<!-- xs source -->
+			<source media="(max-width: 767px)"
+					srcset="${image_url}/++thumb++w768?_=${timestamp}" />
+			<!-- sm source -->
+			<source media="(max-width: 991px)"
+					srcset="${image_url}/++thumb++w992?_=${timestamp}" />
 			<!-- md source -->
-			<source media="(min-width: 992px)" sizes="12vw, 25vw, 50vw, 100vw"
-					tal:attributes="srcset string:${image_url}/++thumb++md:w128?_=${timestamp} 128w,
-												  ${image_url}/++thumb++md:w256?_=${timestamp} 256w,
-												  ${image_url}/++thumb++md:w512?_=${timestamp} 512w,
-												  ${image_url}/++thumb++md:w992?_=${timestamp} 992w" />
-			<!-- sm source -->
-			<source media="(min-width: 768px)" sizes="12vw, 25vw, 50vw, 100vw"
-					tal:attributes="srcset string:${image_url}/++thumb++sm:w128?_=${timestamp} 128w,
-												  ${image_url}/++thumb++sm:w256?_=${timestamp} 256w,
-												  ${image_url}/++thumb++sm:w512?_=${timestamp} 512w,
-												  ${image_url}/++thumb++sm:w768?_=${timestamp} 768w,
-												  " />
-			<!-- xs source -->
-			<source media="(max-width: 767px)" sizes="12vw, 25vw, 50vw, 100vw"
-					tal:attributes="srcset string:${image_url}/++thumb++xs:w128?_=${timestamp} 128w,
-												  ${image_url}/++thumb++xs:w256?_=${timestamp} 256w,
-												  ${image_url}/++thumb++xs:w512?_=${timestamp} 512w,
-												  ${image_url}/++thumb++xs:w768?_=${timestamp} 768w" />
+			<source media="(max-width: 1199px)"
+					srcset="${image_url}/++thumb++w1200?_=${timestamp}" />
+			<!-- lg source -->
+			<source media="(min-width: 1200px)"
+					srcset="${image_url}/++thumb++w1600?_=${timestamp}" />
 			<!-- fallback image -->
 			<img style="width: 100%;"
 				 tal:attributes="alt i18n:image.alt_title; src image_url" />
--- a/src/pyams_default_theme/component/illustration/__init__.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/illustration/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -21,13 +21,15 @@
 from pyams_content.component.illustration.interfaces import IIllustration
 from pyams_content.component.links import IInternalLink
 from pyams_content.features.renderer.interfaces import IContentRenderer
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_content.interfaces import IBaseContent
 from pyams_content.root import ISiteRoot
+from pyams_content.shared.common import ISharedContent
+from pyams_content.shared.site.interfaces import IContentLink
 from pyams_default_theme.component.illustration.interfaces import IIllustrationRenderer, IIllustrationWithZoomSettings, \
     ILLUSTRATION_AFTER_BODY, ILLUSTRATION_BEFORE_BODY
-from pyams_default_theme.interfaces import IContentHeaderIllustration, IContentNavigationIllustration, \
-    IContentBannerIllustration
+from pyams_default_theme.features.renderer import BaseContentRenderer
+from pyams_default_theme.interfaces import IContentBannerIllustration, IContentHeaderIllustration, \
+    IContentNavigationIllustration
 from pyams_skin.layer import IPyAMSLayer
 from pyams_template.template import template_config
 from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config, get_annotation_adapter
@@ -74,6 +76,22 @@
     return None
 
 
+@adapter_config(context=(ISharedContent, IPyAMSLayer), provides=IContentNavigationIllustration)
+def shared_content_illustration_factory(context, request):
+    """Shared content illustration factory"""
+    version = context.published_version
+    if version is not None:
+        return request.registry.queryMultiAdapter((version, request), IContentNavigationIllustration)
+
+
+@adapter_config(context=(IContentLink, IPyAMSLayer), provides=IContentNavigationIllustration)
+def content_link_illustration_factory(context, request):
+    """Content link illustration factory"""
+    target = context.target
+    if target is not None:
+        return request.registry.queryMultiAdapter((target, request), IContentNavigationIllustration)
+
+
 @adapter_config(name='pyams_illustration',
                 context=(Interface, Interface, Interface),
                 provides=ITALESExtension)
--- a/src/pyams_default_theme/component/illustration/templates/illustration-default.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/illustration/templates/illustration-default.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -1,36 +1,7 @@
 <div class="text-center margin-y-5">
-	<picture tal:define="image view.data;
-						 image_url tales:absolute_url(image);
-						 timestamp tales:timestamp(image);
-						 base_width 100 / 12;">
-		<!-- lg source -->
-		<source media="(min-width: 1200px)" sizes="12vw, 25vw, 50vw, 100vw"
-				tal:attributes="srcset string:${image_url}/++thumb++lg:w128?_=${timestamp} 128w,
-											  ${image_url}/++thumb++lg:w256?_=${timestamp} 256w,
-											  ${image_url}/++thumb++lg:w512?_=${timestamp} 512w,
-											  ${image_url}/++thumb++lg:w1200?_=${timestamp} 1200w" />
-		<!-- md source -->
-		<source media="(min-width: 992px)" sizes="12vw, 25vw, 50vw, 100vw"
-				tal:attributes="srcset string:${image_url}/++thumb++md:w128?_=${timestamp} 128w,
-											  ${image_url}/++thumb++md:w256?_=${timestamp} 256w,
-											  ${image_url}/++thumb++md:w512?_=${timestamp} 512w,
-											  ${image_url}/++thumb++md:w992?_=${timestamp} 992w" />
-		<!-- sm source -->
-		<source media="(min-width: 768px)" sizes="12vw, 25vw, 50vw, 100vw"
-				tal:attributes="srcset string:${image_url}/++thumb++sm:w128?_=${timestamp} 128w,
-											  ${image_url}/++thumb++sm:w256?_=${timestamp} 256w,
-											  ${image_url}/++thumb++sm:w512?_=${timestamp} 512w,
-											  ${image_url}/++thumb++sm:w768?_=${timestamp} 768w,
-											  " />
-		<!-- xs source -->
-		<source media="(max-width: 767px)" sizes="12vw, 25vw, 50vw, 100vw"
-				tal:attributes="srcset string:${image_url}/++thumb++xs:w128?_=${timestamp} 128w,
-											  ${image_url}/++thumb++xs:w256?_=${timestamp} 256w,
-											  ${image_url}/++thumb++xs:w512?_=${timestamp} 512w,
-											  ${image_url}/++thumb++xs:w768?_=${timestamp} 768w" />
-		<!-- fallback image -->
-		<img style="width: 100%;"
-			 tal:attributes="alt alt; src image_url" />
-	</picture>
+	<tal:var define="image view.data"
+			 condition="image">
+		${structure:tales:picture(image, alt=view.alt_title)}
+	</tal:var>
 	<span tal:content="view.title">legend</span>
 </div>
--- a/src/pyams_default_theme/component/illustration/templates/illustration-left.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/illustration/templates/illustration-left.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -1,13 +1,15 @@
-<div class="pull-left margin-10" tal:condition="view.data">
+<div class="pull-left col-xs-12 col-sm-6 col-md-6 col-lg-5 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 tales:thumbnails(view.data);
-				   target thumbnails.get_thumbnail('800x600');
-				   thumb thumbnails.get_thumbnail('300x200');"
+	   tal:define="image view.data;
+				   thumbnails tales:thumbnails(image);
+				   target thumbnails.get_thumbnail('800x600');"
 	   tal:attributes="href tales:absolute_url(target)">
-		<img tal:attributes="src tales:absolute_url(thumb);
-							 alt view.alt_title;" /><br />
+		<tal:if condition="image">
+			${structure:tales:picture(image, lg_width=5, md_width=6, sm_width=6, alt=view.alt_title)}
+		</tal:if>
 		<span tal:content="view.title">legend</span>
 	</a><br />
 </div>
--- a/src/pyams_default_theme/component/illustration/templates/illustration-right.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/illustration/templates/illustration-right.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -1,13 +1,15 @@
-<div class="pull-right margin-10" tal:condition="view.data">
+<div class="pull-right col-xs-12 col-sm-6 col-md-6 col-lg-5 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 tales:thumbnails(view.data);
-				   target thumbnails.get_thumbnail('800x600');
-				   thumb thumbnails.get_thumbnail('300x200');"
+	   tal:define="image view.data;
+				   thumbnails tales:thumbnails(view.data);
+				   target thumbnails.get_thumbnail('800x600');"
 	   tal:attributes="href tales:absolute_url(target)">
-		<img tal:attributes="src tales:absolute_url(thumb);
-							 alt view.alt_title" /><br />
+		<tal:if condition="image">
+			${structure:tales:picture(image, lg_width=5, md_width=6, sm_width=6, alt=view.alt_title)}
+		</tal:if>
 		<span tal:content="view.title">legend</span>
 	</a><br />
 </div>
--- a/src/pyams_default_theme/component/keynumber/__init__.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/keynumber/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,15 +12,10 @@
 
 __docformat__ = 'restructuredtext'
 
-# import standard library
-
-# import interfaces
 from pyams_content.component.keynumber.interfaces import IKeyNumberContainer, IKeyNumberParagraph
 from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 
--- a/src/pyams_default_theme/component/paragraph/audio.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/audio.py	Tue Nov 13 14:48:49 2018 +0100
@@ -13,15 +13,10 @@
 
 __docformat__ = 'restructuredtext'
 
-# import standard library
-
-# import interfaces
 from pyams_content.component.paragraph.interfaces.audio import IAudioParagraph
 from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 
--- a/src/pyams_default_theme/component/paragraph/contact.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/contact.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,23 +12,18 @@
 
 __docformat__ = 'restructuredtext'
 
+from persistent import Persistent
+from zope.interface import implementer
+from zope.location import Location
+from zope.schema.fieldproperty import FieldProperty
 
-# import standard library
-from persistent import Persistent
-
-# import interfaces
-from pyams_content.component.paragraph.interfaces.contact import have_gis, IContactParagraph
+from pyams_content.component.paragraph.interfaces.contact import IContactParagraph, have_gis
 from pyams_content.features.renderer.interfaces import IContentRenderer
 from pyams_default_theme.component.paragraph.interfaces.contact import IContactParagraphDefaultRendererSettings
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config, get_annotation_adapter
-from zope.interface import implementer
-from zope.location import locate, Location
-from zope.schema.fieldproperty import FieldProperty
 
 from pyams_default_theme import _
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/component/paragraph/container.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,50 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from pyams_content.component.paragraph import IParagraphContainer, IParagraphContainerTarget
+from pyams_content.component.paragraph.interfaces import IParagraphRenderer
+from pyams_content.features.renderer.interfaces import ISharedContentRenderer
+from pyams_default_theme.features.renderer import BaseContentRenderer
+from pyams_skin.layer import IPyAMSLayer
+from pyams_utils.adapter import adapter_config
+
+
+@adapter_config(name='paragraphs-render', context=(IParagraphContainerTarget, IPyAMSLayer),
+                provides=ISharedContentRenderer)
+class ParagraphsContainerRenderer(BaseContentRenderer):
+    """Paragraphs container renderer"""
+
+    weight = 10
+
+    def __init__(self, context, request):
+        super(ParagraphsContainerRenderer, self).__init__(context, request)
+        paragraphs = [para for para in IParagraphContainer(self.context).values()
+                      if para.visible]
+        registry = self.request.registry
+        self.renderers = [registry.queryMultiAdapter((paragraph, self.request), IParagraphRenderer)
+                          for paragraph in paragraphs]
+
+    def update(self):
+        super(ParagraphsContainerRenderer, self).update()
+        for renderer in self.renderers:
+            if renderer is not None:
+                renderer.language = self.language
+                renderer.update()
+
+    def render(self):
+        result = ''
+        for renderer in self.renderers:
+            if renderer is not None:
+                result += renderer.render()
+        return result
--- a/src/pyams_default_theme/component/paragraph/frame.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/frame.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,31 +12,25 @@
 
 __docformat__ = 'restructuredtext'
 
+from persistent import Persistent
+from zope.interface import implementer
+from zope.location import Location
+from zope.schema.fieldproperty import FieldProperty
 
-# 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_content.reference.pictograms.interfaces import IPictogramTable
-from pyams_default_theme.component.paragraph.interfaces.frame import IFrameParagraphRendererSettings, \
-    ILateralFrameParagraphRendererSettings, IDefaultFrameParagraphRendererSettings, ILeftFrameParagraphRendererSettings, \
+from pyams_default_theme.component.paragraph.interfaces.frame import IDefaultFrameParagraphRendererSettings, \
+    IFrameParagraphRendererSettings, ILateralFrameParagraphRendererSettings, ILeftFrameParagraphRendererSettings, \
     IRightFrameParagraphRendererSettings
-from pyams_i18n.interfaces import II18n
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config, get_annotation_adapter
 from pyams_utils.registry import query_utility
 from pyams_utils.zodb import volatile_property
-from zope.interface import implementer
-from zope.location import Location
-from zope.schema.fieldproperty import FieldProperty
 
 from pyams_default_theme import _
 
@@ -54,17 +48,9 @@
 class BaseFrameParagraphRendererSettings(Persistent, Location):
     """Base frame text paragraph renderer settings"""
 
-    display_illustration = FieldProperty(IFrameParagraphRendererSettings['display_illustration'])
     display_associations = FieldProperty(IFrameParagraphRendererSettings['display_associations'])
     _pictogram_name = FieldProperty(IDefaultFrameParagraphRendererSettings['pictogram_name'])
 
-    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
@@ -144,16 +130,17 @@
     """Base frame paragraph renderer"""
 
     i18n_context_attrs = ('title', 'body')
+
+    illustration = None
     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)
+        self.illustration = IIllustration(self.context)
+        if self.illustration.data:
+            renderer = self.illustration_renderer = self.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)
--- a/src/pyams_default_theme/component/paragraph/html.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/html.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,20 +12,16 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
-
-# import interfaces
 from pyams_content.component.illustration import IIllustration
-from pyams_content.component.paragraph.interfaces.html import IRawParagraph, IHTMLParagraph
+from pyams_content.component.paragraph.interfaces.html import IHTMLParagraph, IRawParagraph
 from pyams_content.features.renderer.interfaces import IContentRenderer
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
-from pyams_default_theme import _
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 
+from pyams_default_theme import _
+
 
 #
 # Raw paragraph default renderer
@@ -38,7 +34,7 @@
 
     label = _("Default raw HTML renderer")
 
-    i18n_context_attrs = ('title', 'body', )
+    i18n_context_attrs = ('title', 'body',)
 
 
 #
@@ -52,7 +48,7 @@
 
     label = _("Default rich text renderer")
 
-    i18n_context_attrs = ('title', 'body', )
+    i18n_context_attrs = ('title', 'body',)
 
     illustration = None
     illustration_renderer = None
@@ -64,8 +60,3 @@
             renderer = self.illustration_renderer = self.illustration.get_renderer(self.request)
             if renderer is not None:
                 renderer.update()
-
-    def render_illustration(self):
-        if not self.illustration_renderer:
-            return ''
-        return self.illustration_renderer.render()
--- a/src/pyams_default_theme/component/paragraph/interfaces/frame.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/interfaces/frame.py	Tue Nov 13 14:48:49 2018 +0100
@@ -34,14 +34,6 @@
 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,
--- a/src/pyams_default_theme/component/paragraph/interfaces/verbatim.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/interfaces/verbatim.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,14 +12,8 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
-
-# import interfaces
-
-# import packages
 from zope.interface import Interface
-from zope.schema import Bool, Choice
+from zope.schema import Choice
 from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
 
 from pyams_default_theme import _
@@ -34,23 +28,7 @@
                                                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):
+class ILateralVerbatimParagraphRendererSettings(Interface):
     """Verbatim paragraph lateral renderer settings interface"""
 
     relative_width = Choice(title=_("Relative width"),
--- a/src/pyams_default_theme/component/paragraph/keypoint.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/keypoint.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,16 +12,10 @@
 
 __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_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 
--- a/src/pyams_default_theme/component/paragraph/map.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/map.py	Tue Nov 13 14:48:49 2018 +0100
@@ -16,24 +16,20 @@
 from pyams_content.component.paragraph.interfaces.map import have_gis
 if have_gis:
 
-    # import standard library
+    from zope.interface import implementer
+    from zope.schema.fieldproperty import FieldProperty
 
-    # import interfaces
     from pyams_content.component.paragraph.interfaces.map import IMapParagraph
     from pyams_content.features.renderer.interfaces import IContentRenderer
     from pyams_default_theme.component.paragraph.interfaces.map import IMapParagraphDefaultRendererSettings
     from pyams_gis.interfaces.configuration import IMapConfiguration
     from pyams_gis.interfaces.utility import IMapManager
     from pyams_skin.layer import IPyAMSLayer
-
-    # import packages
-    from pyams_content.features.renderer.skin import BaseContentRenderer
+    from pyams_default_theme.features.renderer import BaseContentRenderer
     from pyams_gis.configuration import MapConfiguration
     from pyams_template.template import template_config
     from pyams_utils.adapter import adapter_config, get_annotation_adapter
     from pyams_utils.registry import get_utility
-    from zope.interface import implementer
-    from zope.schema.fieldproperty import FieldProperty
 
     from pyams_default_theme import _
 
--- a/src/pyams_default_theme/component/paragraph/milestone.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/milestone.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,19 +12,15 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
-
-# import interfaces
 from pyams_content.component.paragraph.interfaces.milestone import IMilestoneParagraph
 from pyams_content.features.renderer.interfaces import IContentRenderer
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
-from pyams_default_theme import _
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 
+from pyams_default_theme import _
+
 
 #
 # Milestone paragraph default renderer
--- a/src/pyams_default_theme/component/paragraph/pictogram.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/pictogram.py	Tue Nov 13 14:48:49 2018 +0100
@@ -14,7 +14,7 @@
 
 from pyams_content.component.paragraph.interfaces.pictogram import IPictogramContainer, IPictogramParagraph
 from pyams_content.features.renderer.interfaces import IContentRenderer
-from pyams_content.features.renderer.skin import BaseContentRenderer
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
--- a/src/pyams_default_theme/component/paragraph/templates/frame-default.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/templates/frame-default.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -1,8 +1,15 @@
 <div class="bordered 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>
+	 tal:define="settings view.settings;
+				 renderer view.illustration_renderer;
+				 position getattr(renderer, 'position', None) if renderer is not None else None;"
+	 tal:switch="position">
+	<tal:var case="'before-title'">${structure:renderer.render())}</tal:var>
+	<h3 tal:condition="view.title and (position != 'before-title')">${view.title}</h3>
+	<tal:var case="'before-body'">${structure:renderer.render()}</tal:var>
 	<p tal:replace="structure view.body">body</p>
+	<tal:var case="'after-body'">${structure:renderer.render()}</tal:var>
+	<div class="clearfix"></div>
+	<tal:if condition="settings.display_associations">
+		${structure:provider:pyams.associations}
+	</tal:if>
 </div>
--- a/src/pyams_default_theme/component/paragraph/templates/frame-left.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/templates/frame-left.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -1,8 +1,16 @@
 <div class="bordered 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" />
+	 tal:define="settings view.settings;
+				 renderer view.illustration_renderer;
+				 position getattr(renderer, 'position', None) if renderer is not None else None;"
+	 tal:attributes="class string:${default} col-md-${settings.relative_width}"
+	 tal:switch="position">
+	<tal:var case="'before-title'">${structure:renderer.render())}</tal:var>
+	<h3 tal:condition="view.title and (position != 'before-title')">${view.title}</h3>
+	<tal:var case="'before-body'">${structure:renderer.render()}</tal:var>
 	<p tal:replace="structure view.body">body</p>
+	<tal:var case="'after-body'">${structure:renderer.render()}</tal:var>
+	<div class="clearfix"></div>
+	<tal:if condition="settings.display_associations">
+		${structure:provider:pyams.associations}
+	</tal:if>
 </div>
--- a/src/pyams_default_theme/component/paragraph/templates/frame-right.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/templates/frame-right.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -1,8 +1,16 @@
 <div class="bordered 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" />
+	 tal:define="settings view.settings;
+				 renderer view.illustration_renderer;
+				 position getattr(renderer, 'position', None) if renderer is not None else None;"
+	 tal:attributes="class string:${default} col-md-${settings.relative_width}"
+	 tal:switch="position">
+	<tal:var case="'before-title'">${structure:renderer.render())}</tal:var>
+	<h3 tal:condition="view.title and (position != 'before-title')">${view.title}</h3>
+	<tal:var case="'before-body'">${structure:renderer.render()}</tal:var>
 	<p tal:replace="structure view.body">body</p>
+	<tal:var case="'after-body'">${structure:renderer.render()}</tal:var>
+	<div class="clearfix"></div>
+	<tal:if condition="settings.display_associations">
+		${structure:provider:pyams.associations}
+	</tal:if>
 </div>
--- a/src/pyams_default_theme/component/paragraph/templates/html-default.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/templates/html-default.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -1,11 +1,11 @@
 <tal:var define="renderer view.illustration_renderer;
 				 position getattr(renderer, 'position', None) if renderer is not None else None;"
 		 switch="position">
-	<tal:var case="'before-title'">${structure:view.render_illustration()}</tal:var>
+	<tal:var case="'before-title'">${structure:renderer.render()}</tal:var>
 	<h2 tal:condition="view.title and (position != 'before-title')">${view.title}</h2>
-	<tal:var case="'before-body'">${structure:view.render_illustration()}</tal:var>
+	<tal:var case="'before-body'">${structure:renderer.render()}</tal:var>
 	<div>${structure:tales:html(view.body, 'oid_to_href')}</div>
-	<tal:var case="'after-body'">${structure:view.render_illustration()}</tal:var>
+	<tal:var case="'after-body'">${structure:renderer.render()}</tal:var>
 	<div class="clearfix"></div>
-	<br>${structure:provider:pyams.association}
+	${structure:provider:pyams.associations}
 </tal:var>
--- a/src/pyams_default_theme/component/paragraph/templates/verbatim-default.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/templates/verbatim-default.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -1,9 +1,14 @@
 <div class="bordered verbatim padding-20"
 	 tal:define="settings view.settings;
-				 display_illustration settings.can_display_illustration()">
-	<tal:if condition="display_illustration">
+				 illustration tales:pyams_illustration(context);
+				 display_illustration view.can_display_illustration()">
+	<tal:if condition="display_illustration and illustration">
 		<div class="illustration col-md-2"
-			 tal:content="structure view.illustration_renderer.render() if view.illustration_renderer is not None else None">
+			 tal:define="photo i18n:illustration.data;
+						 alt i18n:illustration.alt_title;"
+			 tal:condition="photo">
+			${structure:tales:picture(photo, lg_thumb='portrait', md_thumb='portrait',
+											 sm_thumb='portrait', xs_thumb='portrait', alt=alt)}
 		</div>
 		<div class="author col-md-4">
 			<span tal:content="view.author">Author</span><br />
@@ -14,11 +19,11 @@
 	</tal:if>
 	<tal:if condition="not:display_illustration">
 		<div class="quote"
-			 tal:content="tales:html(view.quote)">Quote</div>
+			 tal:content="structure tales: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>
+			${view.author}
+			<tal:if condition="view.author and view.charge">, </tal:if>
+			${view.charge}
 		</div>
 	</tal:if>
 </div>
--- a/src/pyams_default_theme/component/paragraph/templates/verbatim-left.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/templates/verbatim-left.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -1,17 +1,31 @@
 <div class="bordered verbatim padding-20 pull-left"
-	 tal:define="settings view.settings"
+	 tal:define="settings view.settings;
+				 illustration tales:pyams_illustration(context);
+				 display_illustration view.can_display_illustration()"
 	 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 tales:html(view.quote)">Quote</div>
-	<div class="author">
-		<span tal:content="view.author">Author</span>
-		<tal:if condition="view.charge">
-			&ndash;
+	<tal:if condition="display_illustration and illustration">
+		<div class="illustration col-md-2"
+			 tal:define="photo i18n:illustration.data;
+						 alt i18n:illustration.alt_title;"
+			 tal:condition="photo">
+			${structure:tales:picture(photo, lg_thumb='portrait', md_thumb='portrait',
+											 sm_thumb='portrait', xs_thumb='portrait', alt=alt)}
+		</div>
+		<div class="author col-md-4">
+			<span tal:content="view.author">Author</span><br />
 			<span tal:content="view.charge">Charge</span>
-		</tal:if>
-	</div>
+		</div>
+		<div class="quote col-md-5"
+			 tal:content="tales:html(view.quote)">Quote</div>
+	</tal:if>
+	<tal:if condition="not:display_illustration">
+		<div class="quote"
+			 tal:content="structure tales:html(view.quote)">Quote</div>
+		<div class="author">
+			${view.author}
+			<tal:if condition="view.author and view.charge">, </tal:if>
+			${view.charge}
+		</div>
+	</tal:if>
 </div>
--- a/src/pyams_default_theme/component/paragraph/templates/verbatim-right.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/templates/verbatim-right.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -1,17 +1,31 @@
 <div class="bordered verbatim padding-20 pull-right"
-	 tal:define="settings view.settings"
+	 tal:define="settings view.settings;
+				 illustration tales:pyams_illustration(context);
+				 display_illustration view.can_display_illustration()"
 	 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 tales:html(view.quote)">Quote</div>
-	<div class="author">
-		<span tal:content="view.author">Author</span>
-		<tal:if condition="view.charge">
-			&ndash;
+	<tal:if condition="display_illustration and illustration">
+		<div class="illustration col-md-2"
+			 tal:define="photo i18n:illustration.data;
+						 alt i18n:illustration.alt_title;"
+			 tal:condition="photo">
+			${structure:tales:picture(photo, lg_thumb='portrait', md_thumb='portrait',
+											 sm_thumb='portrait', xs_thumb='portrait', alt=alt)}
+		</div>
+		<div class="author col-md-4">
+			<span tal:content="view.author">Author</span><br />
 			<span tal:content="view.charge">Charge</span>
-		</tal:if>
-	</div>
+		</div>
+		<div class="quote col-md-5"
+			 tal:content="tales:html(view.quote)">Quote</div>
+	</tal:if>
+	<tal:if condition="not:display_illustration">
+		<div class="quote"
+			 tal:content="structure tales:html(view.quote)">Quote</div>
+		<div class="author">
+			${view.author}
+			<tal:if condition="view.author and view.charge">, </tal:if>
+			${view.charge}
+		</div>
+	</tal:if>
 </div>
--- a/src/pyams_default_theme/component/paragraph/templates/video-default.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/templates/video-default.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -2,15 +2,9 @@
 	tal:content="view.title">title</h3>
 <div tal:condition="view.description"
 	 tal:content="structure tales: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:condition="view.data"
-	 tal:attributes="data-ams-plugin-flowplayer-src tales:resource_path('pyams_media.skin:flowplayer');
-					 data-ams-plugin-flowplayer-css tales:resource_path('pyams_media.skin:functional_css');
-					 data-ams-callback-source tales:resource_path('pyams_media.skin:pyams_media');">
-	<video
+<div class="video-wrapper"
+	 tal:condition="view.data">
+	<video controls
 		tal:define="video view.data;
 					href tales:absolute_url(video);
 					thumbnails tales:thumbnails(video);
--- a/src/pyams_default_theme/component/paragraph/verbatim.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/verbatim.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,62 +12,30 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
 from persistent import Persistent
 from zope.interface import implementer
 from zope.location import Location
 from zope.schema.fieldproperty import FieldProperty
 
-# import interfaces
 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
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
-from pyams_default_theme import _
-from pyams_default_theme.component.paragraph.interfaces.verbatim import IVerbatimParagraphRendererSettings, \
-    ILateralVerbatimParagraphRendererSettings, IDefaultVerbatimParagraphRendererSettings
+from pyams_default_theme.component.paragraph.interfaces.verbatim import ILateralVerbatimParagraphRendererSettings
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_i18n.interfaces import II18n
 from pyams_skin.layer import IPyAMSLayer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config, get_annotation_adapter
 
-#
-# Verbatim paragraph default renderer settings
-#
-
-VERBATIM_PARAGRAPH_RENDERER_SETTINGS_KEY = 'pyams_content.verbatim.renderer:default'
+from pyams_default_theme import _
 
 
-@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 default_verbatim_paragraph_renderer_settings_factory(context):
-    """Frame paragraph default renderer settings factory"""
-    return get_annotation_adapter(context, VERBATIM_PARAGRAPH_RENDERER_SETTINGS_KEY,
-                                  DefaultVerbatimParagraphRendererSettings)
-
+#
+# Verbatim paragraph lateral renderer settings
+#
 
 @implementer(ILateralVerbatimParagraphRendererSettings)
-class LateralVerbatimParagraphRendererSettings(BaseVerbatimParagraphRendererSettings):
+class LateralVerbatimParagraphRendererSettings(Persistent, Location):
     """Verbatim paragraph lateral renderer settings"""
 
     relative_width = FieldProperty(ILateralVerbatimParagraphRendererSettings['relative_width'])
@@ -90,9 +58,13 @@
 class BaseVerbatimParagraphRenderer(BaseContentRenderer):
     """Base frame paragraph renderer"""
 
-    context_attrs = ('author', )
+    context_attrs = ('author',)
     i18n_context_attrs = ('title', 'quote', 'charge')
-    illustration_renderer = None
+
+    def can_display_illustration(self):
+        frame = IVerbatimParagraph(self.context)
+        illustration = IIllustration(frame, None)
+        return (illustration is not None) and bool(II18n(illustration).query_attribute('data'))
 
 
 @adapter_config(name='default', context=(IVerbatimParagraph, IPyAMSLayer), provides=IContentRenderer)
@@ -103,8 +75,6 @@
     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)
--- a/src/pyams_default_theme/component/paragraph/video.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/paragraph/video.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,16 +12,10 @@
 
 __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_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 
--- a/src/pyams_default_theme/component/video/__init__.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/component/video/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,16 +12,10 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
-
-# import interfaces
 from pyams_content.component.video.interfaces import IExternalVideoParagraph, IExternalVideoRenderer
 from pyams_content.features.renderer.interfaces import IContentRenderer
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 from pyams_utils.registry import get_current_registry
--- a/src/pyams_default_theme/features/footer/__init__.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/features/footer/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,27 +12,22 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
-
-# import interfaces
-from pyams_content.component.association.interfaces import ASSOCIATION_CONTAINER_KEY
-from pyams_content.features.footer.interfaces import IFooterTarget, IFooterSettings
-from pyams_content.features.menu.interfaces import IMenuLinksContainerTarget, IMenuLinksContainer
-from pyams_default_theme.features.footer.interfaces import ISimpleFooterRendererSettings, ISimpleFooterLinksMenu
-from pyams_default_theme.layer import IPyAMSDefaultLayer
-from zope.location.interfaces import ISublocations
-
-# import packages
 from persistent import Persistent
-from pyams_content.features.menu import Menu
-from pyams_utils.adapter import get_annotation_adapter, adapter_config, ContextAdapter
-from pyams_utils.traversing import get_parent
-from pyams_viewlet.viewlet import contentprovider_config, ViewContentProvider
 from zope.interface import implementer
 from zope.location import Location
+from zope.location.interfaces import ISublocations
 from zope.schema.fieldproperty import FieldProperty
 
+from pyams_content.component.association.interfaces import ASSOCIATION_CONTAINER_KEY
+from pyams_content.features.footer.interfaces import IFooterSettings, IFooterTarget
+from pyams_content.features.menu import Menu
+from pyams_content.features.menu.interfaces import IMenuLinksContainer, IMenuLinksContainerTarget
+from pyams_default_theme.features.footer.interfaces import ISimpleFooterLinksMenu, ISimpleFooterRendererSettings
+from pyams_default_theme.layer import IPyAMSDefaultLayer
+from pyams_utils.adapter import ContextAdapter, adapter_config, get_annotation_adapter
+from pyams_utils.traversing import get_parent
+from pyams_viewlet.viewlet import ViewContentProvider, contentprovider_config
+
 
 @contentprovider_config(name='pyams.footer', layer=IPyAMSDefaultLayer)
 class FooterContentProvider(ViewContentProvider):
--- a/src/pyams_default_theme/features/footer/skin/__init__.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/features/footer/skin/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,18 +12,25 @@
 
 __docformat__ = 'restructuredtext'
 
+import logging
+logger = logging.getLogger('PyAMS (content)')
 
-# import standard library
+from pyramid.decorator import reify
+from zope.interface import implementer
 
-# import interfaces
+from pyams_cache.beaker import get_cache
+from pyams_content.features.renderer.interfaces import HIDDEN_RENDERER_NAME
+from pyams_default_theme.features.renderer import BaseContentRenderer
+from pyams_portal.interfaces import PREVIEW_MODE
+from pyams_portal.portlet import PORTLETS_CACHE_KEY, PORTLETS_CACHE_REGION, PORTLETS_CACHE_NAME
+from pyams_utils.interfaces import ICacheKeyValue
+from pyams_utils.interfaces.url import DISPLAY_CONTEXT
+from pyams_utils.traversing import get_parent
 from pyams_content.component.association.interfaces import IAssociationInfo
 from pyams_content.features.footer.interfaces import IFooterTarget, IFooterRenderer, IFooterSettings, \
     IFooterRendererSettings
 from pyams_default_theme.features.footer.interfaces import ISimpleFooterRendererSettings
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.footer.skin import BaseFooterRenderer
 from pyams_default_theme.features.footer import SimpleFooterRendererSettings
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
@@ -31,13 +38,83 @@
 from pyams_default_theme import _
 
 
-SIMPLE_FOOTER_RENDERER_NAME = 'PyAMS simple footer'
+#
+# Simple footer renderer
+#
+
+@implementer(IFooterRenderer)
+class BaseFooterRenderer(BaseContentRenderer):
+    """Base footer renderer"""
+
+    name = None
+    settings_key = None
+
+    @reify
+    def settings_target(self):
+        context = self.request.annotations.get(DISPLAY_CONTEXT)
+        if context is None:
+            context = self.context
+        return get_parent(context, IFooterTarget)
+
+    @reify
+    def settings(self):
+        if self.settings_interface is None:
+            return None
+        target = self.settings_target
+        settings = IFooterSettings(target)
+        while settings.inherit:
+            settings = IFooterSettings(settings.parent)
+        return settings.settings
+
+    @reify
+    def cache_key(self):
+        return PORTLETS_CACHE_KEY.format(portlet=self.name,
+                                         hostname=self.request.host,
+                                         context=ICacheKeyValue(self.settings_target),
+                                         lang=self.request.locale_name)
+
+    def render(self):
+        preview_mode = self.request.annotations.get(PREVIEW_MODE, False)
+        if preview_mode:
+            return super(BaseFooterRenderer, self).render()
+        else:
+            portlets_cache = get_cache(PORTLETS_CACHE_REGION, PORTLETS_CACHE_NAME)
+            cache_key = self.cache_key
+            if self.context is not self.request.context:  # display shared content
+                cache_key = '{0}::shared'.format(cache_key)
+            try:
+                result = portlets_cache.get_value(cache_key)
+                logger.debug("Retrieving footer content from cache key {0}".format(cache_key))
+            except KeyError:
+                result = super(BaseFooterRenderer, self).render()
+                portlets_cache.set_value(cache_key, result)
+                logger.debug("Storing footer content to cache key {0}".format(cache_key))
+            return result
+
+
+#
+# Hidden footer renderer
+#
+
+@adapter_config(name=HIDDEN_RENDERER_NAME, context=(IFooterTarget, IPyAMSLayer), provides=IFooterRenderer)
+class HiddenFooterRenderer(BaseFooterRenderer):
+    """Hidden footer renderer"""
+
+    name = HIDDEN_RENDERER_NAME
+    label = _("Hidden footer")
+    weight = -999
+
+    def render(self):
+        return ''
 
 
 #
 # Simple footer renderer
 #
 
+SIMPLE_FOOTER_RENDERER_NAME = 'PyAMS simple footer'
+
+
 @adapter_config(name=SIMPLE_FOOTER_RENDERER_NAME, context=(IFooterTarget, IPyAMSLayer),
                 provides=IFooterRenderer)
 @adapter_config(name=SIMPLE_FOOTER_RENDERER_NAME, context=(IFooterSettings, IPyAMSLayer),
--- a/src/pyams_default_theme/features/header/__init__.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/features/header/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,26 +12,21 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
-
-# import interfaces
-from pyams_content.features.header.interfaces import IHeaderTarget, IHeaderSettings
-from pyams_content.features.menu.interfaces import IMenuLinksContainerTarget, IMenuLinksContainer
-from pyams_default_theme.features.header.interfaces import ISimpleHeaderRendererSettings, ISimpleHeaderTabsMenu
-from pyams_default_theme.layer import IPyAMSDefaultLayer
+from persistent import Persistent
+from zope.interface import implementer
+from zope.location import Location
 from zope.location.interfaces import ISublocations
 
-# import packages
-from persistent import Persistent
 from pyams_content.component.association.interfaces import ASSOCIATION_CONTAINER_KEY
+from pyams_content.features.header.interfaces import IHeaderSettings, IHeaderTarget
 from pyams_content.features.menu import Menu
+from pyams_content.features.menu.interfaces import IMenuLinksContainer, IMenuLinksContainerTarget
+from pyams_default_theme.features.header.interfaces import ISimpleHeaderRendererSettings, ISimpleHeaderTabsMenu
+from pyams_default_theme.layer import IPyAMSDefaultLayer
 from pyams_file.property import FileProperty
-from pyams_utils.adapter import get_annotation_adapter, adapter_config, ContextAdapter
+from pyams_utils.adapter import ContextAdapter, adapter_config, get_annotation_adapter
 from pyams_utils.traversing import get_parent
-from pyams_viewlet.viewlet import contentprovider_config, ViewContentProvider
-from zope.interface import implementer
-from zope.location import Location
+from pyams_viewlet.viewlet import ViewContentProvider, contentprovider_config
 
 
 @contentprovider_config(name='pyams.header', layer=IPyAMSDefaultLayer)
--- a/src/pyams_default_theme/features/header/skin/__init__.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/features/header/skin/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,34 +12,116 @@
 
 __docformat__ = 'restructuredtext'
 
+import logging
+logger = logging.getLogger('PyAMS (content)')
 
-# import standard library
+from pyramid.decorator import reify
+from pyramid.location import lineage
+from zope.interface import implementer
 
-# import interfaces
+from pyams_cache.beaker import get_cache
 from pyams_content.component.association.interfaces import IAssociationInfo
 from pyams_content.component.links.interfaces import IInternalLink
-from pyams_content.features.header.interfaces import IHeaderTarget, IHeaderRenderer, IHeaderSettings, \
-    IHeaderRendererSettings
+from pyams_content.features.header.interfaces import IHeaderRenderer, IHeaderRendererSettings, IHeaderSettings, \
+    IHeaderTarget
+from pyams_content.features.renderer.interfaces import HIDDEN_RENDERER_NAME
+from pyams_default_theme.features.header import SimpleHeaderRendererSettings
 from pyams_default_theme.features.header.interfaces import ISimpleHeaderRendererSettings
+from pyams_default_theme.features.header.skin.interfaces import IHeaderClass
+from pyams_default_theme.features.renderer import BaseContentRenderer
+from pyams_portal.interfaces import PREVIEW_MODE
+from pyams_portal.portlet import PORTLETS_CACHE_KEY, PORTLETS_CACHE_NAME, PORTLETS_CACHE_REGION
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.header.skin import BaseHeaderRenderer
-from pyams_default_theme.features.header import SimpleHeaderRendererSettings
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
-from pyramid.location import lineage
+from pyams_utils.interfaces import ICacheKeyValue
+from pyams_utils.traversing import get_parent
 
 from pyams_default_theme import _
 
 
-SIMPLE_HEADER_RENDERER_NAME = 'PyAMS simple header'
+#
+# Base header renderer
+#
+
+@implementer(IHeaderRenderer)
+class BaseHeaderRenderer(BaseContentRenderer):
+    """Base header renderer"""
+
+    name = None
+    settings_key = None
+
+    @reify
+    def settings_target(self):
+        context = self.request.display_context
+        if context is None:
+            context = self.context
+        return get_parent(context, IHeaderTarget)
+
+    @reify
+    def settings(self):
+        if self.settings_interface is None:
+            return None
+        target = self.settings_target
+        settings = IHeaderSettings(target)
+        while settings.inherit:
+            settings = IHeaderSettings(settings.parent)
+        return settings.settings
+
+    @reify
+    def cache_key(self):
+        return PORTLETS_CACHE_KEY.format(portlet=self.name,
+                                         hostname=self.request.host,
+                                         context=ICacheKeyValue(self.settings_target),
+                                         lang=self.request.locale_name)
+
+    @property
+    def main_header_class(self):
+        request = self.request
+        return request.registry.queryMultiAdapter((request.context, request), IHeaderClass, default='')
+
+    def render(self):
+        preview_mode = self.request.annotations.get(PREVIEW_MODE, False)
+        if preview_mode:
+            return super(BaseHeaderRenderer, self).render()
+        else:
+            portlets_cache = get_cache(PORTLETS_CACHE_REGION, PORTLETS_CACHE_NAME)
+            cache_key = self.cache_key
+            if self.context is not self.request.context:  # display shared content
+                cache_key = '{0}::shared'.format(cache_key)
+            try:
+                result = portlets_cache.get_value(cache_key)
+                logger.debug("Retrieving header content from cache key {0}".format(cache_key))
+            except KeyError:
+                result = super(BaseHeaderRenderer, self).render()
+                portlets_cache.set_value(cache_key, result)
+                logger.debug("Storing header content to cache key {0}".format(cache_key))
+            return result
+
+
+#
+# Hidden header renderer
+#
+
+@adapter_config(name=HIDDEN_RENDERER_NAME, context=(IHeaderTarget, IPyAMSLayer), provides=IHeaderRenderer)
+class HiddenHeaderRenderer(BaseHeaderRenderer):
+    """Hidden header renderer"""
+
+    name = HIDDEN_RENDERER_NAME
+    label = _("Hidden header")
+    weight = -999
+
+    def render(self):
+        return ''
 
 
 #
 # Simple header renderer
 #
 
+SIMPLE_HEADER_RENDERER_NAME = 'PyAMS simple header'
+
+
 @adapter_config(name=SIMPLE_HEADER_RENDERER_NAME, context=(IHeaderTarget, IPyAMSLayer),
                 provides=IHeaderRenderer)
 @adapter_config(name=SIMPLE_HEADER_RENDERER_NAME, context=(IHeaderSettings, IPyAMSLayer),
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/features/header/skin/interfaces.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,19 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from zope.interface import Interface
+
+
+class IHeaderClass(Interface):
+    """Custom header CSS class"""
--- a/src/pyams_default_theme/features/header/skin/templates/simple-header.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/features/header/skin/templates/simple-header.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -1,16 +1,35 @@
 <div class="header header-simple"
-	 tal:define="settings view.settings"
+	 tal:define="settings view.settings;
+				 config tales:configuration(request.root);"
 	 tal:condition="settings is not None" i18n:domain="pyams_default_theme">
 	<tal:var replace="structure provider:pyams.banner" />
-	<ul class="nav nav-pills margin-bottom-20" role="tablist">
-		<tal:loop repeat="link settings.tabs.get_visible_items(request)">
-			<li role="presentation"
-				tal:attributes="class 'active' if view.is_active(link) else ''">
-				<a tal:define="info view.get_link_info(link);
-							   href link.get_url(request);"
-				   tal:condition="href"
-				   href="${href}">${info.user_title}</a>
-			</li>
-		</tal:loop>
-	</ul>
+	<nav class="navbar"
+		 data-spy="affix" data-offset-top="150">
+		<div class="container">
+			<div class="navbar-header">
+				<button type="button" class="navbar-toggle collapsed"
+						data-toggle="collapse" data-target="#navbar-collapser"
+						aria-expanded="false">
+					<span class="sr-only" i18n:translate="">Toggle navigation</span>
+					<span class="icon-bar"></span>
+					<span class="icon-bar"></span>
+					<span class="icon-bar"></span>
+				</button>
+				<a class="navbar-brand" href="#">${i18n:config.short_title}</a>
+			</div>
+			<div class="collapse navbar-collapse" id="navbar-collapser">
+				<ul class="nav navbar-nav" role="tablist">
+					<tal:loop repeat="link settings.tabs.get_visible_items(request)">
+						<li role="presentation"
+							tal:attributes="class 'active' if view.is_active(link) else ''">
+							<a tal:define="info view.get_link_info(link);
+										   href link.get_url(request);"
+							   tal:condition="href"
+							   href="${href}">${info.user_title}</a>
+						</li>
+					</tal:loop>
+				</ul>
+			</div>
+		</div>
+	</nav>
 </div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/features/renderer/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,71 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from pyramid.decorator import reify
+from zope.interface import implementer
+
+from pyams_content.features.renderer import IContentRenderer, IRenderedContent, IRendererSettings
+from pyams_content.features.renderer.interfaces import HIDDEN_RENDERER_NAME
+from pyams_i18n.interfaces import II18n
+from pyams_skin.layer import IPyAMSLayer
+from pyams_template.template import get_view_template
+from pyams_utils.adapter import ContextRequestAdapter, adapter_config
+
+from pyams_content import _
+
+
+@implementer(IContentRenderer)
+class BaseContentRenderer(ContextRequestAdapter):
+    """Base content renderer"""
+
+    label = None
+    weight = 0
+
+    settings_interface = None
+    resources = ()
+
+    language = None
+    context_attrs = ()
+    i18n_context_attrs = ()
+
+    @reify
+    def settings(self):
+        if self.settings_interface is None:
+            return None
+        return IRendererSettings(self.context)
+
+    def update(self):
+        for resource in self.resources:
+            resource.need()
+        for attr in self.context_attrs:
+            setattr(self, attr, getattr(self.context, attr, None))
+        if self.i18n_context_attrs:
+            i18n = II18n(self.context, None)
+            if i18n is not None:
+                for attr in self.i18n_context_attrs:
+                    setattr(self, attr, i18n.query_attribute(attr, lang=self.language, request=self.request))
+
+    render = get_view_template()
+
+
+@adapter_config(name=HIDDEN_RENDERER_NAME, context=(IRenderedContent, IPyAMSLayer), provides=IContentRenderer)
+class HiddenContentRenderer(BaseContentRenderer):
+    """Hidden content renderer"""
+
+    label = _("Hidden content")
+    weight = -999
+
+    @staticmethod
+    def render():
+        return ''
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/features/search/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,64 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from pyams_content.features.search.interfaces import ISearchFolder
+from pyams_default_theme.interfaces import IContentSummaryInfo
+from pyams_content.shared.site import ISiteElementNavigation
+from pyams_i18n.interfaces import II18n
+from pyams_skin.layer import IPyAMSUserLayer
+from pyams_utils.adapter import ContextRequestAdapter, adapter_config
+from pyams_utils.interfaces.url import IRelativeURL
+from pyams_utils.url import absolute_url
+from pyams_workflow.interfaces import IWorkflowPublicationInfo
+
+from pyams_content import _
+
+
+@adapter_config(context=(ISearchFolder, IPyAMSUserLayer), provides=IRelativeURL)
+class SearchFolderRelativeUrlAdapter(ContextRequestAdapter):
+    """Search folder relative URL adapter"""
+
+    def get_url(self, display_context=None, view_name=None, query=None):
+        return absolute_url(self.context, self.request, view_name, query)
+
+
+@adapter_config(context=(ISearchFolder, IPyAMSUserLayer), provides=ISiteElementNavigation)
+class SearchFolderNavigationAdapter(ContextRequestAdapter):
+    """Search folder navigation adapter"""
+
+    @property
+    def visible(self):
+        if not self.context.visible_in_list:
+            return False
+        return IWorkflowPublicationInfo(self.context).is_visible(self.request)
+
+
+@adapter_config(context=(ISearchFolder, IPyAMSUserLayer), provides=IContentSummaryInfo)
+class SearchFolderSummaryAdapter(ContextRequestAdapter):
+    """Search folder summary adapter"""
+
+    @property
+    def title(self):
+        i18n = II18n(self.context)
+        return i18n.query_attribute('navigation_title', request=self.request) or \
+               i18n.query_attribute('title', request=self.request)
+
+    @property
+    def header(self):
+        try:
+            return II18n(self.context).query_attribute('header', request=self.request)
+        except AttributeError:
+            return None
+
+    button_title = _("Consult folder")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/features/sitemap/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,99 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from datetime import datetime
+from itertools import product
+
+from hypatia.catalog import CatalogQuery
+from hypatia.interfaces import ICatalog
+from hypatia.query import Any, Eq
+from pyramid.view import view_config
+from zope.intid import IIntIds
+
+from pyams_catalog.query import CatalogResultSet
+from pyams_content.root import ISiteRoot, ISiteRootToolsConfiguration
+from pyams_content.shared.common import CONTENT_TYPES, IBaseSharedTool
+from pyams_i18n.interfaces import II18nManager
+from pyams_skin.layer import IPyAMSUserLayer
+from pyams_utils.list import unique_iter
+from pyams_utils.registry import get_all_utilities_registered_for, get_utility
+from pyams_workflow.interfaces import IWorkflow, IWorkflowPublicationInfo
+
+
+@view_config(name='robots.txt', context=ISiteRoot, request_type=IPyAMSUserLayer,
+             renderer='templates/robots.pt')
+def site_root_robots_view(request):
+    """Site root robots.txt view"""
+    request.response.content_type = 'text/plain'
+    return {
+        'tools_configuration': ISiteRootToolsConfiguration(request.root),
+        'disallow': [tool for tool in get_all_utilities_registered_for(IBaseSharedTool)
+                     if not tool.shared_content_menu]
+    }
+
+
+@view_config(name='humans.txt', context=ISiteRoot, request_type=IPyAMSUserLayer,
+             renderer='templates/humans.pt')
+def site_root_humans_view(request):
+    """Site root humans.txt view"""
+    request.response.content_type = 'text/plain'
+    return {}
+
+
+@view_config(name='sitemap.xml', context=ISiteRoot, request_type=IPyAMSUserLayer,
+             renderer='templates/root-sitemap.pt')
+class SiteRootSitemapView(object):
+    """Site root sitemap view"""
+
+    def __init__(self, request):
+        self.request = request
+
+    def __call__(self):
+        self.request.response.content_type = 'text/xml'
+        return {}
+
+    @property
+    def sources(self):
+        timestamp = datetime.utcnow().isoformat()
+        for tool in get_all_utilities_registered_for(IBaseSharedTool):
+            if not tool.shared_content_menu:
+                continue
+            publication_info = IWorkflowPublicationInfo(tool, None)
+            if (publication_info is None) or publication_info.is_visible(self.request):
+                yield timestamp, tool
+
+
+@view_config(name='sitemap.xml', context=IBaseSharedTool, request_type=IPyAMSUserLayer,
+             renderer='templates/tool-sitemap.pt')
+class SharedToolSitemapView(object):
+    """Shared tool sitemap view"""
+
+    def __init__(self, request):
+        self.request = request
+
+    def __call__(self):
+        self.request.response.content_type = 'text/xml'
+        return {}
+
+    @property
+    def contents(self):
+        context = self.request.context
+        catalog = get_utility(ICatalog)
+        intids = get_utility(IIntIds)
+        workflow = IWorkflow(context)
+        params = Eq(catalog['parents'], intids.register(context)) & \
+                 Any(catalog['content_type'], CONTENT_TYPES.keys()) & \
+                 Any(catalog['workflow_state'], workflow.published_states)
+        for version in unique_iter(CatalogResultSet(CatalogQuery(catalog).query(params))):
+            yield from product(II18nManager(version).get_languages(), (version,))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/features/sitemap/templates/humans.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,14 @@
+/* THANKS */
+Main architect and developer: Thierry Florac
+From: Paris, France
+
+PyAMS contributor: Damien Correia
+From: Paris, France
+
+/* SITE */
+Doctype: HTML5
+Standards: HTML5, CSS3, A11y
+IDE: PyCharm, The GIMP, Mercurial
+Components: JQuery, Bootstrap, Python 3, Hypatia
+Software: Pyramid, PyAMS, PostgreSQL, Elasticsearch, Redis
+Language: english, french
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/features/sitemap/templates/robots.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,7 @@
+Sitemap: <tal:var define="url tales:absolute_url(request.root)">${url}</tal:var>/sitemap.xml
+
+User-agent: *
+Disallow: /--static--/
+Disallow: /api/
+Disallow: /${tools_configuration.tables_name}/<tal:loop repeat="tool disallow">
+Disallow: /${tools_configuration.tools_name}/${tool.__name__}/</tal:loop>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/features/sitemap/templates/root-sitemap.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+	<tal:loop repeat="(ts, source) view.sources">
+		<sitemap>
+			<loc>${tales:absolute_url(source)}/sitemap.xml</loc>
+			<lastmod>${ts}</lastmod>
+		</sitemap>
+	</tal:loop>
+</sitemapindex>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/features/sitemap/templates/tool-sitemap.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+	<url tal:repeat="(lang, content) view.contents">
+		<loc tal:define="url tales:canonical_url(content)">${url}?lang=${lang}</loc>
+		<lastmod>${tales:timestamp(content, 'iso')}</lastmod>
+	</url>
+</urlset>
--- a/src/pyams_default_theme/include.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/include.py	Tue Nov 13 14:48:49 2018 +0100
@@ -26,5 +26,8 @@
     # add translations
     config.add_translation_dirs('pyams_default_theme:locales')
 
+    # add custom routes
+    config.add_route('oid_access', '/+/{oid}*view')
+
     # load registry components
     config.scan()
--- a/src/pyams_default_theme/interfaces/__init__.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/interfaces/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,7 +12,7 @@
 
 __docformat__ = 'restructuredtext'
 
-from zope.interface import Interface
+from zope.interface import Interface, Attribute
 
 
 class IContentNavigationTitle(Interface):
@@ -37,3 +37,15 @@
 
 class IContentNavigationIllustration(Interface):
     """Content navigation illustration adapter interface"""
+
+
+class IContentSummaryInfo(Interface):
+    """Content interface for site summary page"""
+
+    context = Attribute("Link to adapted context")
+
+    title = Attribute("Content's title")
+
+    header = Attribute("Header")
+
+    button_title = Attribute("Button's target")
Binary file src/pyams_default_theme/locales/fr/LC_MESSAGES/pyams_default_theme.mo has changed
--- a/src/pyams_default_theme/locales/fr/LC_MESSAGES/pyams_default_theme.po	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/locales/fr/LC_MESSAGES/pyams_default_theme.po	Tue Nov 13 14:48:49 2018 +0100
@@ -5,7 +5,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2018-09-25 11:23+0200\n"
+"POT-Creation-Date: 2018-11-07 18:22+0100\n"
 "PO-Revision-Date: 2017-06-07 12:41+0200\n"
 "Last-Translator: Thierry Florac <tflorac@ulthar.net>\n"
 "Language-Team: French\n"
@@ -20,10 +20,6 @@
 msgid "PyAMS default skin"
 msgstr "Skin par défaut (PyAMS)"
 
-#: src/pyams_default_theme/component/gallery/__init__.py:34
-msgid "Default gallery renderer"
-msgstr "Par défaut"
-
 #: src/pyams_default_theme/component/keynumber/__init__.py:53
 msgid "Default key numbers renderer"
 msgstr "Par défaut"
@@ -36,176 +32,77 @@
 msgid "Vertical list"
 msgstr "Liste verticale"
 
-#: src/pyams_default_theme/component/illustration/__init__.py:76
-msgid "Centered illustration before text"
-msgstr "Illustration centrée avant le texte"
-
-#: src/pyams_default_theme/component/illustration/__init__.py:87
-msgid "Small illustration on the left"
-msgstr "Illustration sur la gauche"
-
-#: src/pyams_default_theme/component/illustration/__init__.py:99
-msgid "Small illustration on the right"
-msgstr "Illustration sur la droite"
-
-#: src/pyams_default_theme/component/illustration/__init__.py:111
-msgid "Centered illustration after text"
-msgstr "Illustration centrée après le texte"
-
-#: src/pyams_default_theme/component/illustration/interfaces/__init__.py:41
-msgid "Zoom on click?"
-msgstr "Zoom sur clic ?"
-
-#: src/pyams_default_theme/component/illustration/interfaces/__init__.py:42
-msgid "If 'yes', a click on illustration thumbnail is required to zoom"
-msgstr ""
-"Si 'oui', un clic sur la vignette de l'illustration est nécessaire pour "
-"zoomer sur l'image"
-
-#: src/pyams_default_theme/component/paragraph/milestone.py:38
-msgid "Default milestones renderer"
-msgstr "Par défaut"
-
-#: src/pyams_default_theme/component/paragraph/keypoint.py:40
-msgid "Default key points renderer"
-msgstr "Par défaut"
-
-#: src/pyams_default_theme/component/paragraph/map.py:96
-msgid "Default map renderer"
+#: src/pyams_default_theme/component/gallery/__init__.py:34
+msgid "Default gallery renderer"
 msgstr "Par défaut"
 
+#: src/pyams_default_theme/component/video/__init__.py:41
 #: src/pyams_default_theme/component/paragraph/video.py:40
-#: src/pyams_default_theme/component/video/__init__.py:41
 msgid "Default video renderer"
 msgstr "Par défaut"
 
-#: src/pyams_default_theme/component/paragraph/pictogram.py:40
-msgid "Default pictograms renderer"
-msgstr "Par défaut"
-
-#: src/pyams_default_theme/component/paragraph/audio.py:40
-msgid "Default audio renderer"
-msgstr "Par défaut"
-
-#: src/pyams_default_theme/component/paragraph/frame.py:164
-msgid "Default frame renderer"
-msgstr "Encadré en pleine largeur (par défaut)"
-
-#: src/pyams_default_theme/component/paragraph/frame.py:175
-#: src/pyams_default_theme/component/paragraph/verbatim.py:114
-msgid "Small frame on the left"
-msgstr "Encadré placé sur la gauche"
-
-#: src/pyams_default_theme/component/paragraph/frame.py:186
-#: src/pyams_default_theme/component/paragraph/verbatim.py:125
-msgid "Small frame on the right"
-msgstr "Encadré placé sur la droite"
-
-#: src/pyams_default_theme/component/paragraph/verbatim.py:103
-msgid "Default verbatim renderer"
-msgstr "Encadré en pleine largeur (par défaut)"
-
-#: src/pyams_default_theme/component/paragraph/html.py:39
-msgid "Default raw HTML renderer"
-msgstr "Par défaut"
-
-#: src/pyams_default_theme/component/paragraph/html.py:53
-msgid "Default rich text renderer"
-msgstr "Par défaut"
-
 #: src/pyams_default_theme/component/paragraph/contact.py:85
 msgid "Default contact renderer"
 msgstr "Encadré en pleine largeur (par défaut)"
 
-#: src/pyams_default_theme/component/paragraph/zmi/map.py:62
-msgid "Don't use default map configuration"
-msgstr "Ne pas utiliser la configuration de carte par défaut"
-
-#: src/pyams_default_theme/component/paragraph/portlet/__init__.py:40
-msgid "Default paragraphs renderer"
+#: src/pyams_default_theme/component/paragraph/pictogram.py:34
+msgid "Default pictograms renderer"
 msgstr "Par défaut"
 
-#: src/pyams_default_theme/component/paragraph/portlet/__init__.py:67
-msgid "Default paragraphs navigation"
+#: src/pyams_default_theme/component/paragraph/milestone.py:38
+msgid "Default milestones renderer"
+msgstr "Par défaut"
+
+#: src/pyams_default_theme/component/paragraph/html.py:35
+msgid "Default raw HTML renderer"
+msgstr "Par défaut"
+
+#: src/pyams_default_theme/component/paragraph/html.py:49
+msgid "Default rich text renderer"
 msgstr "Par défaut"
 
-#: src/pyams_default_theme/component/paragraph/interfaces/map.py:34
-msgid "Don't use default configuration?"
-msgstr "Ne pas utiliser la configuration par défaut ?"
+#: src/pyams_default_theme/component/paragraph/verbatim.py:75
+msgid "Default verbatim renderer"
+msgstr "Encadré en pleine largeur (par défaut)"
+
+#: src/pyams_default_theme/component/paragraph/verbatim.py:84
+#: src/pyams_default_theme/component/paragraph/frame.py:163
+msgid "Small frame on the left"
+msgstr "Encadré placé sur la gauche"
+
+#: src/pyams_default_theme/component/paragraph/verbatim.py:95
+#: src/pyams_default_theme/component/paragraph/frame.py:174
+msgid "Small frame on the right"
+msgstr "Encadré placé sur la droite"
 
-#: src/pyams_default_theme/component/paragraph/interfaces/map.py:38
-msgid "Use default configuration?"
-msgstr "Utiliser la configuration par défaut ?"
+#: src/pyams_default_theme/component/paragraph/map.py:96
+msgid "Default map renderer"
+msgstr "Par défaut"
+
+#: src/pyams_default_theme/component/paragraph/keypoint.py:40
+msgid "Default key points renderer"
+msgstr "Par défaut"
 
-#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:30
-#: src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:28
+#: src/pyams_default_theme/component/paragraph/frame.py:152
+msgid "Default frame renderer"
+msgstr "Encadré en pleine largeur (par défaut)"
+
+#: src/pyams_default_theme/component/paragraph/audio.py:40
+msgid "Default audio renderer"
+msgstr "Par défaut"
+
 #: src/pyams_default_theme/component/paragraph/interfaces/contact.py:29
+#: src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:22
+#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:25
 msgid "Left"
 msgstr "Gauche"
 
-#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:31
-#: src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:29
 #: src/pyams_default_theme/component/paragraph/interfaces/contact.py:30
+#: src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:23
+#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:26
 msgid "Right"
 msgstr "Droite"
 
-#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:32
-#: src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:30
-msgid "Center (full width)"
-msgstr "Centré (pleine largeur)"
-
-#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:42
-#: src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:40
-msgid "Show illustration?"
-msgstr "Afficher l'illustration ?"
-
-#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:43
-#: src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:41
-msgid "If 'no', illustration will not be displayed"
-msgstr ""
-"Si 'non', l'illustration ne sera pas affichée même si un contenu a été fourni"
-
-#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:50
-msgid "Show associations?"
-msgstr "Afficher les liens et pièces jointes ?"
-
-#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:51
-msgid "If 'no', associations will not be displayed"
-msgstr ""
-"Si 'non', les liens et pièces jointes ne seront pas affichés même s'il "
-"existe des éléments visibles"
-
-#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:58
-msgid "Pictogram"
-msgstr "Pictogramme"
-
-#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:59
-msgid "Name of the pictogram associated with this frame paragraph"
-msgstr "Nom du pictogramme associé à cet encadré"
-
-#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:73
-#: src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:56
-msgid "Relative width"
-msgstr "Largeur relative"
-
-#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:74
-msgid ""
-"Relative width used by this frame, relative to it's parent, given as columns "
-"count; full width counts for 12 columns"
-msgstr ""
-"Largeur relative de ce bloc par rapport au bloc dans lequel il s'intègre. Cette largeur "
-"est indiquée en nombre de colonnes, la largeur totale étant de 12 "
-"colonnes."
-
-#: src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:57
-msgid ""
-"Relative width used by this paragraph, relative to it's parent, given as "
-"columns count; full width counts for 12 columns"
-msgstr ""
-"Largeur relative de ce bloc par rapport au bloc dans lequel il s'intègre. Cette largeur "
-"est indiquée en nombre de colonnes, la largeur totale étant de 12 "
-"colonnes."
-
 #: src/pyams_default_theme/component/paragraph/interfaces/contact.py:40
 msgid "Show photo?"
 msgstr "Afficher la photo ?"
@@ -234,11 +131,77 @@
 msgid "Map position"
 msgstr "Position de la carte"
 
-#: src/pyams_default_theme/component/association/__init__.py:47
+#: src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:24
+#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:27
+msgid "Center (full width)"
+msgstr "Centré (pleine largeur)"
+
+#: src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:34
+#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:60
+msgid "Relative width"
+msgstr "Largeur relative"
+
+#: src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:35
+msgid ""
+"Relative width used by this paragraph, relative to it's parent, given as "
+"columns count; full width counts for 12 columns"
+msgstr ""
+"Largeur relative de ce bloc par rapport au bloc dans lequel il s'intègre. "
+"Cette largeur est indiquée en nombre de colonnes, la largeur totale étant de "
+"12 colonnes."
+
+#: src/pyams_default_theme/component/paragraph/interfaces/map.py:34
+msgid "Don't use default configuration?"
+msgstr "Ne pas utiliser la configuration par défaut ?"
+
+#: src/pyams_default_theme/component/paragraph/interfaces/map.py:38
+msgid "Use default configuration?"
+msgstr "Utiliser la configuration par défaut ?"
+
+#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:37
+msgid "Show associations?"
+msgstr "Afficher les liens et pièces jointes ?"
+
+#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:38
+msgid "If 'no', associations will not be displayed"
+msgstr ""
+"Si 'non', les liens et pièces jointes ne seront pas affichés même s'il "
+"existe des éléments visibles"
+
+#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:45
+msgid "Pictogram"
+msgstr "Pictogramme"
+
+#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:46
+msgid "Name of the pictogram associated with this frame paragraph"
+msgstr "Nom du pictogramme associé à cet encadré"
+
+#: src/pyams_default_theme/component/paragraph/interfaces/frame.py:61
+msgid ""
+"Relative width used by this frame, relative to it's parent, given as columns "
+"count; full width counts for 12 columns"
+msgstr ""
+"Largeur relative de ce bloc par rapport au bloc dans lequel il s'intègre. "
+"Cette largeur est indiquée en nombre de colonnes, la largeur totale étant de "
+"12 colonnes."
+
+#: src/pyams_default_theme/component/paragraph/portlet/__init__.py:40
+msgid "Default paragraphs renderer"
+msgstr "Par défaut"
+
+#: src/pyams_default_theme/component/paragraph/portlet/__init__.py:67
+msgid "Default paragraphs navigation"
+msgstr "Par défaut"
+
+#: src/pyams_default_theme/component/paragraph/zmi/map.py:62
+msgid "Don't use default map configuration"
+msgstr "Ne pas utiliser la configuration de carte par défaut"
+
+#: src/pyams_default_theme/component/association/__init__.py:44
 msgid "Default associations renderer"
 msgstr "Par défaut"
 
-#: src/pyams_default_theme/component/association/__init__.py:88
+#: src/pyams_default_theme/component/association/__init__.py:97
 msgid "Include remote content"
 msgstr "Include les blocs de contenu distants"
 
@@ -280,43 +243,47 @@
 "Si 'oui', seuls les blocs de contenu désignés comme ancres seront "
 "sélectionnés"
 
-#: src/pyams_default_theme/shared/common/portlet/title.py:38
-msgid "Default title renderer"
-msgstr "Par défaut"
+#: src/pyams_default_theme/component/illustration/__init__.py:171
+msgid "Centered illustration before text"
+msgstr "Illustration centrée avant le texte"
 
-#: src/pyams_default_theme/shared/common/portlet/head.py:38
-msgid "Default header renderer"
-msgstr "Par défaut"
+#: src/pyams_default_theme/component/illustration/__init__.py:182
+msgid "Small illustration on the left"
+msgstr "Illustration sur la gauche"
 
-#: src/pyams_default_theme/shared/view/templates/render.pt:2
-msgid "View result items"
-msgstr "Aperçu du contenu de la vue"
+#: src/pyams_default_theme/component/illustration/__init__.py:194
+msgid "Small illustration on the right"
+msgstr "Illustration sur la droite"
 
-#: src/pyams_default_theme/shared/view/templates/render.pt:3
-msgid "WARNING: items displayed in this preview are out of context!!"
-msgstr ""
-"ATTENTION : la sélection des éléments affichés dans cet aperçu ne tient pas "
-"compte du contexte éventuellement paramétré dans la vue"
+#: src/pyams_default_theme/component/illustration/__init__.py:206
+msgid "Centered illustration after text"
+msgstr "Illustration centrée après le texte"
+
+#: src/pyams_default_theme/component/illustration/interfaces/__init__.py:41
+msgid "Zoom on click?"
+msgstr "Zoom sur clic ?"
 
-#: src/pyams_default_theme/shared/view/portlet/__init__.py:38
-msgid "Simple vertical view"
-msgstr "Liste verticale simple"
+#: src/pyams_default_theme/component/illustration/interfaces/__init__.py:42
+msgid "If 'yes', a click on illustration thumbnail is required to zoom"
+msgstr ""
+"Si 'oui', un clic sur la vignette de l'illustration est nécessaire pour "
+"zoomer sur l'image"
 
-#: src/pyams_default_theme/shared/imagemap/__init__.py:58
-msgid "Default imagemap renderer"
-msgstr "Par défaut"
+#: src/pyams_default_theme/features/footer/interfaces.py:31
+msgid "Copyright"
+msgstr "Copyright"
 
-#: src/pyams_default_theme/shared/imagemap/templates/render.pt:2
-msgid "Image map preview"
-msgstr "Aperçu des zones cliquables"
+#: src/pyams_default_theme/features/footer/interfaces.py:32
+msgid "Copyright mention displayed in page footer"
+msgstr "Mention affichée dans le pied de page"
 
-#: src/pyams_default_theme/shared/logo/__init__.py:39
-msgid "Default logos renderer"
-msgstr "Par défaut"
+#: src/pyams_default_theme/features/footer/zmi/__init__.py:64
+msgid "Footer links"
+msgstr "Liens du pied de page"
 
-#: src/pyams_default_theme/viewlet/logo/templates/logo.pt:5
-msgid "Back home"
-msgstr "Revenir à l'accueil"
+#: src/pyams_default_theme/features/footer/skin/__init__.py:50
+msgid "PyAMS simple footer with links"
+msgstr "PyAMS: pied de page simple avec liens"
 
 #: src/pyams_default_theme/features/menu/portlet/navigation/__init__.py:49
 msgid "Horizontal list with vertical illustrations"
@@ -350,21 +317,10 @@
 msgid "Label associated with second level options menu"
 msgstr "Libellé associé au second niveau de sélection"
 
-#: src/pyams_default_theme/features/footer/interfaces.py:31
-msgid "Copyright"
-msgstr "Copyright"
-
-#: src/pyams_default_theme/features/footer/interfaces.py:32
-msgid "Copyright mention displayed in page footer"
-msgstr "Mention affichée dans le pied de page"
-
-#: src/pyams_default_theme/features/footer/zmi/__init__.py:64
-msgid "Footer links"
-msgstr "Liens du pied de page"
-
-#: src/pyams_default_theme/features/footer/skin/__init__.py:50
-msgid "PyAMS simple footer with links"
-msgstr "PyAMS: pied de page simple avec liens"
+#: src/pyams_default_theme/features/search/__init__.py:64
+#: src/pyams_default_theme/shared/site/folder.py:54
+msgid "Consult folder"
+msgstr "Consulter la rubrique"
 
 #: src/pyams_default_theme/features/header/interfaces.py:31
 msgid "Banner image"
@@ -390,6 +346,61 @@
 msgid "PyAMS simple header with banner and tabs"
 msgstr "PyAMS: en-tête simple avec bandeau et onglets de navigation"
 
+#: src/pyams_default_theme/shared/view/portlet/__init__.py:38
+msgid "Simple vertical view"
+msgstr "Liste verticale simple"
+
+#: src/pyams_default_theme/shared/view/templates/render.pt:2
+msgid "View result items"
+msgstr "Aperçu du contenu de la vue"
+
+#: src/pyams_default_theme/shared/view/templates/render.pt:3
+msgid "WARNING: items displayed in this preview are out of context!!"
+msgstr ""
+"ATTENTION : la sélection des éléments affichés dans cet aperçu ne tient pas "
+"compte du contexte éventuellement paramétré dans la vue"
+
+#: src/pyams_default_theme/shared/common/summary.py:38
+#: src/pyams_default_theme/shared/site/link.py:62
+msgid "Consult content"
+msgstr "Accéder au contenu"
+
+#: src/pyams_default_theme/shared/common/portlet/title.py:37
+msgid "Default title renderer"
+msgstr "Par défaut"
+
+#: src/pyams_default_theme/shared/common/portlet/head.py:38
+msgid "Default header renderer"
+msgstr "Par défaut"
+
+#: src/pyams_default_theme/shared/site/portlet/__init__.py:35
+msgid "Site container summary"
+msgstr "Par défaut"
+
+#: src/pyams_default_theme/shared/imagemap/__init__.py:58
+msgid "Default imagemap renderer"
+msgstr "Par défaut"
+
+#: src/pyams_default_theme/shared/imagemap/templates/render.pt:2
+msgid "Image map preview"
+msgstr "Aperçu des zones cliquables"
+
+#: src/pyams_default_theme/shared/logo/__init__.py:39
+msgid "Default logos renderer"
+msgstr "Par défaut"
+
+#: src/pyams_default_theme/viewlet/logo/templates/logo.pt:5
+msgid "Back home"
+msgstr "Revenir à l'accueil"
+
+#~ msgid "Show illustration?"
+#~ msgstr "Afficher l'illustration ?"
+
+#~ msgid "If 'no', illustration will not be displayed"
+#~ msgstr ""
+#~ "Si 'non', l'illustration ne sera pas affichée même si un contenu a été "
+#~ "fourni"
+
 #~ msgid "Search..."
 #~ msgstr "Chercher..."
 
--- a/src/pyams_default_theme/locales/pyams_default_theme.pot	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/locales/pyams_default_theme.pot	Tue Nov 13 14:48:49 2018 +0100
@@ -6,7 +6,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2018-09-25 11:23+0200\n"
+"POT-Creation-Date: 2018-11-07 18:22+0100\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"
@@ -20,10 +20,6 @@
 msgid "PyAMS default skin"
 msgstr ""
 
-#: ./src/pyams_default_theme/component/gallery/__init__.py:34
-msgid "Default gallery renderer"
-msgstr ""
-
 #: ./src/pyams_default_theme/component/keynumber/__init__.py:53
 msgid "Default key numbers renderer"
 msgstr ""
@@ -36,165 +32,77 @@
 msgid "Vertical list"
 msgstr ""
 
-#: ./src/pyams_default_theme/component/illustration/__init__.py:76
-msgid "Centered illustration before text"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/illustration/__init__.py:87
-msgid "Small illustration on the left"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/illustration/__init__.py:99
-msgid "Small illustration on the right"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/illustration/__init__.py:111
-msgid "Centered illustration after text"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/illustration/interfaces/__init__.py:41
-msgid "Zoom on click?"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/illustration/interfaces/__init__.py:42
-msgid "If 'yes', a click on illustration thumbnail is required to zoom"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/milestone.py:38
-msgid "Default milestones renderer"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/keypoint.py:40
-msgid "Default key points renderer"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/map.py:96
-msgid "Default map renderer"
+#: ./src/pyams_default_theme/component/gallery/__init__.py:34
+msgid "Default gallery renderer"
 msgstr ""
 
+#: ./src/pyams_default_theme/component/video/__init__.py:41
 #: ./src/pyams_default_theme/component/paragraph/video.py:40
-#: ./src/pyams_default_theme/component/video/__init__.py:41
 msgid "Default video renderer"
 msgstr ""
 
-#: ./src/pyams_default_theme/component/paragraph/pictogram.py:40
-msgid "Default pictograms renderer"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/audio.py:40
-msgid "Default audio renderer"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/frame.py:164
-msgid "Default frame renderer"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/frame.py:175
-#: ./src/pyams_default_theme/component/paragraph/verbatim.py:114
-msgid "Small frame on the left"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/frame.py:186
-#: ./src/pyams_default_theme/component/paragraph/verbatim.py:125
-msgid "Small frame on the right"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/verbatim.py:103
-msgid "Default verbatim renderer"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/html.py:39
-msgid "Default raw HTML renderer"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/html.py:53
-msgid "Default rich text renderer"
-msgstr ""
-
 #: ./src/pyams_default_theme/component/paragraph/contact.py:85
 msgid "Default contact renderer"
 msgstr ""
 
-#: ./src/pyams_default_theme/component/paragraph/zmi/map.py:62
-msgid "Don't use default map configuration"
+#: ./src/pyams_default_theme/component/paragraph/pictogram.py:34
+msgid "Default pictograms renderer"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/paragraph/milestone.py:38
+msgid "Default milestones renderer"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/paragraph/html.py:35
+msgid "Default raw HTML renderer"
 msgstr ""
 
-#: ./src/pyams_default_theme/component/paragraph/portlet/__init__.py:40
-msgid "Default paragraphs renderer"
+#: ./src/pyams_default_theme/component/paragraph/html.py:49
+msgid "Default rich text renderer"
 msgstr ""
 
-#: ./src/pyams_default_theme/component/paragraph/portlet/__init__.py:67
-msgid "Default paragraphs navigation"
+#: ./src/pyams_default_theme/component/paragraph/verbatim.py:75
+msgid "Default verbatim renderer"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/paragraph/verbatim.py:84
+#: ./src/pyams_default_theme/component/paragraph/frame.py:163
+msgid "Small frame on the left"
 msgstr ""
 
-#: ./src/pyams_default_theme/component/paragraph/interfaces/map.py:34
-msgid "Don't use default configuration?"
+#: ./src/pyams_default_theme/component/paragraph/verbatim.py:95
+#: ./src/pyams_default_theme/component/paragraph/frame.py:174
+msgid "Small frame on the right"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/paragraph/map.py:96
+msgid "Default map renderer"
 msgstr ""
 
-#: ./src/pyams_default_theme/component/paragraph/interfaces/map.py:38
-msgid "Use default configuration?"
+#: ./src/pyams_default_theme/component/paragraph/keypoint.py:40
+msgid "Default key points renderer"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/paragraph/frame.py:152
+msgid "Default frame renderer"
 msgstr ""
 
-#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:30
-#: ./src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:28
+#: ./src/pyams_default_theme/component/paragraph/audio.py:40
+msgid "Default audio renderer"
+msgstr ""
+
 #: ./src/pyams_default_theme/component/paragraph/interfaces/contact.py:29
+#: ./src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:22
+#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:25
 msgid "Left"
 msgstr ""
 
-#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:31
-#: ./src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:29
 #: ./src/pyams_default_theme/component/paragraph/interfaces/contact.py:30
+#: ./src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:23
+#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:26
 msgid "Right"
 msgstr ""
 
-#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:32
-#: ./src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:30
-msgid "Center (full width)"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:42
-#: ./src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:40
-msgid "Show illustration?"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:43
-#: ./src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:41
-msgid "If 'no', illustration will not be displayed"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:50
-msgid "Show associations?"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:51
-msgid "If 'no', associations will not be displayed"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:58
-msgid "Pictogram"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:59
-msgid "Name of the pictogram associated with this frame paragraph"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:73
-#: ./src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:56
-msgid "Relative width"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:74
-msgid ""
-"Relative width used by this frame, relative to it's parent, given as columns "
-"count; full width counts for 12 columns"
-msgstr ""
-
-#: ./src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:57
-msgid ""
-"Relative width used by this paragraph, relative to it's parent, given as "
-"columns count; full width counts for 12 columns"
-msgstr ""
-
 #: ./src/pyams_default_theme/component/paragraph/interfaces/contact.py:40
 msgid "Show photo?"
 msgstr ""
@@ -219,11 +127,69 @@
 msgid "Map position"
 msgstr ""
 
-#: ./src/pyams_default_theme/component/association/__init__.py:47
+#: ./src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:24
+#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:27
+msgid "Center (full width)"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:34
+#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:60
+msgid "Relative width"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/paragraph/interfaces/verbatim.py:35
+msgid ""
+"Relative width used by this paragraph, relative to it's parent, given as "
+"columns count; full width counts for 12 columns"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/paragraph/interfaces/map.py:34
+msgid "Don't use default configuration?"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/paragraph/interfaces/map.py:38
+msgid "Use default configuration?"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:37
+msgid "Show associations?"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:38
+msgid "If 'no', associations will not be displayed"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:45
+msgid "Pictogram"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:46
+msgid "Name of the pictogram associated with this frame paragraph"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/paragraph/interfaces/frame.py:61
+msgid ""
+"Relative width used by this frame, relative to it's parent, given as columns "
+"count; full width counts for 12 columns"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/paragraph/portlet/__init__.py:40
+msgid "Default paragraphs renderer"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/paragraph/portlet/__init__.py:67
+msgid "Default paragraphs navigation"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/paragraph/zmi/map.py:62
+msgid "Don't use default map configuration"
+msgstr ""
+
+#: ./src/pyams_default_theme/component/association/__init__.py:44
 msgid "Default associations renderer"
 msgstr ""
 
-#: ./src/pyams_default_theme/component/association/__init__.py:88
+#: ./src/pyams_default_theme/component/association/__init__.py:97
 msgid "Include remote content"
 msgstr ""
 
@@ -261,40 +227,44 @@
 msgid "If 'yes', only paragraphs set as 'anchors' will be selected"
 msgstr ""
 
-#: ./src/pyams_default_theme/shared/common/portlet/title.py:38
-msgid "Default title renderer"
+#: ./src/pyams_default_theme/component/illustration/__init__.py:171
+msgid "Centered illustration before text"
 msgstr ""
 
-#: ./src/pyams_default_theme/shared/common/portlet/head.py:38
-msgid "Default header renderer"
+#: ./src/pyams_default_theme/component/illustration/__init__.py:182
+msgid "Small illustration on the left"
 msgstr ""
 
-#: ./src/pyams_default_theme/shared/view/templates/render.pt:2
-msgid "View result items"
+#: ./src/pyams_default_theme/component/illustration/__init__.py:194
+msgid "Small illustration on the right"
 msgstr ""
 
-#: ./src/pyams_default_theme/shared/view/templates/render.pt:3
-msgid "WARNING: items displayed in this preview are out of context!!"
+#: ./src/pyams_default_theme/component/illustration/__init__.py:206
+msgid "Centered illustration after text"
 msgstr ""
 
-#: ./src/pyams_default_theme/shared/view/portlet/__init__.py:38
-msgid "Simple vertical view"
+#: ./src/pyams_default_theme/component/illustration/interfaces/__init__.py:41
+msgid "Zoom on click?"
 msgstr ""
 
-#: ./src/pyams_default_theme/shared/imagemap/__init__.py:58
-msgid "Default imagemap renderer"
+#: ./src/pyams_default_theme/component/illustration/interfaces/__init__.py:42
+msgid "If 'yes', a click on illustration thumbnail is required to zoom"
 msgstr ""
 
-#: ./src/pyams_default_theme/shared/imagemap/templates/render.pt:2
-msgid "Image map preview"
+#: ./src/pyams_default_theme/features/footer/interfaces.py:31
+msgid "Copyright"
 msgstr ""
 
-#: ./src/pyams_default_theme/shared/logo/__init__.py:39
-msgid "Default logos renderer"
+#: ./src/pyams_default_theme/features/footer/interfaces.py:32
+msgid "Copyright mention displayed in page footer"
 msgstr ""
 
-#: ./src/pyams_default_theme/viewlet/logo/templates/logo.pt:5
-msgid "Back home"
+#: ./src/pyams_default_theme/features/footer/zmi/__init__.py:64
+msgid "Footer links"
+msgstr ""
+
+#: ./src/pyams_default_theme/features/footer/skin/__init__.py:50
+msgid "PyAMS simple footer with links"
 msgstr ""
 
 #: ./src/pyams_default_theme/features/menu/portlet/navigation/__init__.py:49
@@ -329,20 +299,9 @@
 msgid "Label associated with second level options menu"
 msgstr ""
 
-#: ./src/pyams_default_theme/features/footer/interfaces.py:31
-msgid "Copyright"
-msgstr ""
-
-#: ./src/pyams_default_theme/features/footer/interfaces.py:32
-msgid "Copyright mention displayed in page footer"
-msgstr ""
-
-#: ./src/pyams_default_theme/features/footer/zmi/__init__.py:64
-msgid "Footer links"
-msgstr ""
-
-#: ./src/pyams_default_theme/features/footer/skin/__init__.py:50
-msgid "PyAMS simple footer with links"
+#: ./src/pyams_default_theme/features/search/__init__.py:64
+#: ./src/pyams_default_theme/shared/site/folder.py:54
+msgid "Consult folder"
 msgstr ""
 
 #: ./src/pyams_default_theme/features/header/interfaces.py:31
@@ -368,3 +327,48 @@
 #: ./src/pyams_default_theme/features/header/skin/__init__.py:52
 msgid "PyAMS simple header with banner and tabs"
 msgstr ""
+
+#: ./src/pyams_default_theme/shared/view/portlet/__init__.py:38
+msgid "Simple vertical view"
+msgstr ""
+
+#: ./src/pyams_default_theme/shared/view/templates/render.pt:2
+msgid "View result items"
+msgstr ""
+
+#: ./src/pyams_default_theme/shared/view/templates/render.pt:3
+msgid "WARNING: items displayed in this preview are out of context!!"
+msgstr ""
+
+#: ./src/pyams_default_theme/shared/common/summary.py:38
+#: ./src/pyams_default_theme/shared/site/link.py:62
+msgid "Consult content"
+msgstr ""
+
+#: ./src/pyams_default_theme/shared/common/portlet/title.py:37
+msgid "Default title renderer"
+msgstr ""
+
+#: ./src/pyams_default_theme/shared/common/portlet/head.py:38
+msgid "Default header renderer"
+msgstr ""
+
+#: ./src/pyams_default_theme/shared/site/portlet/__init__.py:35
+msgid "Site container summary"
+msgstr ""
+
+#: ./src/pyams_default_theme/shared/imagemap/__init__.py:58
+msgid "Default imagemap renderer"
+msgstr ""
+
+#: ./src/pyams_default_theme/shared/imagemap/templates/render.pt:2
+msgid "Image map preview"
+msgstr ""
+
+#: ./src/pyams_default_theme/shared/logo/__init__.py:39
+msgid "Default logos renderer"
+msgstr ""
+
+#: ./src/pyams_default_theme/viewlet/logo/templates/logo.pt:5
+msgid "Back home"
+msgstr ""
--- a/src/pyams_default_theme/page.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/page.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,13 +12,7 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
-
-# import interfaces
 from pyams_default_theme.layer import IPyAMSDefaultLayer
-
-# import packages
 from pyams_template.template import layout_config
 
 
--- a/src/pyams_default_theme/resources/css/pyams-default.css	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/resources/css/pyams-default.css	Tue Nov 13 14:48:49 2018 +0100
@@ -1,4 +1,5 @@
 body {
+  font-family: "Advent Pro", Helvetica, Arial, sans-serif;
   margin: 0 auto;
 }
 body .header-simple .regularbanner {
@@ -15,6 +16,14 @@
   left: 10px;
   max-height: calc(80%);
 }
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+  font-family: Oswald, Helvetica, Arial, Sans-Serif;
+}
 @media only screen and (min-width: 1200px) {
   .portal-page .slot.col-lg-0 {
     display: none;
--- a/src/pyams_default_theme/resources/css/pyams-default.css.map	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/resources/css/pyams-default.css.map	Tue Nov 13 14:48:49 2018 +0100
@@ -1,1 +1,1 @@
-{"version":3,"sources":["../../../../../../../../../home/tflorac/Dropbox/src/PyAMS/pyams_default_theme/src/pyams_default_theme/resources/less/pyams-default.less"],"names":[],"mappings":"AAAA;EACC,cAAA;;AADD,IAGC,eACC;EACC,kBAAA;;AAIC,IANH,eACC,eAGC,QAEE;AAAD,IANH,eACC,eAIC,IACE;EACA,WAAA;;AAED,IATH,eACC,eAGC,QAKE;AAAD,IATH,eACC,eAIC,IAIE;EACA,kBAAA;EACA,SAAA;EACA,UAAA;EACA,YAAY,SAAZ;;AAUH,wBAA2C;EAC1C,YAFF,MAEG;IACA,aAAA;;;AAGF,wBAA0C,uBAAwB;EACjE,YAPF,MAOG;IACA,aAAA;;;AAGF,wBAA0C,uBAAuB;EAChE,YAZF,MAYG;IACA,aAAA;;;AAGF,wBAA0C;EACzC,YAjBF,MAiBG;IACA,aAAA;;;AAQH,cAAC;EACA,mBAAA;;AAED,cAAC;EACA,mBAAA","file":"pyams-default.css"}
\ No newline at end of file
+{"version":3,"sources":["../../../../../../../../../home/tflorac/Dropbox/src/PyAMS/pyams_default_theme/src/pyams_default_theme/resources/less/pyams-default.less"],"names":[],"mappings":"AACA;EACC,aAAa,0CAAb;EACA,cAAA;;AAFD,IAIC,eACC;EACC,kBAAA;;AAIC,IANH,eACC,eAGC,QAEE;AAAD,IANH,eACC,eAIC,IACE;EACA,WAAA;;AAED,IATH,eACC,eAGC,QAKE;AAAD,IATH,eACC,eAIC,IAIE;EACA,kBAAA;EACA,SAAA;EACA,UAAA;EACA,YAAY,SAAZ;;AAOL;AACA;AACA;AACA;AACA;AACA;EACC,iDAAA;;AAKC,wBAA2C;EAC1C,YAFF,MAEG;IACA,aAAA;;;AAGF,wBAA0C,uBAAwB;EACjE,YAPF,MAOG;IACA,aAAA;;;AAGF,wBAA0C,uBAAuB;EAChE,YAZF,MAYG;IACA,aAAA;;;AAGF,wBAA0C;EACzC,YAjBF,MAiBG;IACA,aAAA;;;AAQH,cAAC;EACA,mBAAA;;AAED,cAAC;EACA,mBAAA","file":"pyams-default.css"}
\ No newline at end of file
--- a/src/pyams_default_theme/resources/css/pyams-default.min.css	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/resources/css/pyams-default.min.css	Tue Nov 13 14:48:49 2018 +0100
@@ -1,1 +1,1 @@
-body{margin:0 auto}body .header-simple .regularbanner{position:relative}body .header-simple .regularbanner img.regularbanner__media,body .header-simple .regularbanner picture.regularbanner__media{width:100%}body .header-simple .regularbanner img.logo,body .header-simple .regularbanner picture.logo{position:absolute;top:10px;left:10px;max-height:calc(80%)}@media only screen and (min-width:1200px){.portal-page .slot.col-lg-0{display:none}}@media only screen and (min-width:992px) and (max-width:1199px){.portal-page .slot.col-md-0{display:none}}@media only screen and (min-width:768px) and (max-width:991px){.portal-page .slot.col-sm-0{display:none}}@media only screen and (max-width:767px){.portal-page .slot.col-xs-0{display:none}}.margin-bottom-10{margin-bottom:10px}.margin-bottom-20{margin-bottom:20px}
+body{font-family:"Advent Pro",Helvetica,Arial,sans-serif;margin:0 auto}body .header-simple .regularbanner{position:relative}body .header-simple .regularbanner img.regularbanner__media,body .header-simple .regularbanner picture.regularbanner__media{width:100%}body .header-simple .regularbanner img.logo,body .header-simple .regularbanner picture.logo{position:absolute;top:10px;left:10px;max-height:calc(80%)}h1,h2,h3,h4,h5,h6{font-family:Oswald,Helvetica,Arial,Sans-Serif}@media only screen and (min-width:1200px){.portal-page .slot.col-lg-0{display:none}}@media only screen and (min-width:992px) and (max-width:1199px){.portal-page .slot.col-md-0{display:none}}@media only screen and (min-width:768px) and (max-width:991px){.portal-page .slot.col-sm-0{display:none}}@media only screen and (max-width:767px){.portal-page .slot.col-xs-0{display:none}}.margin-bottom-10{margin-bottom:10px}.margin-bottom-20{margin-bottom:20px}
--- a/src/pyams_default_theme/resources/less/pyams-default.less	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/resources/less/pyams-default.less	Tue Nov 13 14:48:49 2018 +0100
@@ -1,4 +1,6 @@
+
 body {
+	font-family: "Advent Pro", Helvetica, Arial, sans-serif;
 	margin: 0 auto;
 
 	.header-simple {
@@ -21,8 +23,16 @@
 	}
 }
 
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+	font-family: Oswald, Helvetica, Arial, Sans-Serif;
+}
+
 .portal-page {
-
 	.slot {
 		@media only screen and (min-width: 1200px) {
 			&.col-lg-0 {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/routes.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,58 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from hypatia.catalog import CatalogQuery
+from hypatia.interfaces import ICatalog
+from hypatia.query import Any, Eq
+from pyramid.exceptions import NotFound
+from pyramid.response import Response
+from pyramid.view import view_config
+
+from pyams_catalog.query import CatalogResultSet
+from pyams_content.workflow import VISIBLE_STATES
+from pyams_sequence.interfaces import ISequentialIntIds
+from pyams_utils.registry import get_utility
+from pyams_utils.url import absolute_url, canonical_url
+from pyams_workflow.interfaces import IWorkflowVersions
+
+
+@view_config(route_name='oid_access')
+def get_oid_access(request):
+    """Get direct access to given OID
+
+    This route can be used to get a direct access to a given content,
+    just by submitting an URL like /+/{oid}, where {oid} is the "short"
+    sequence OID.
+    """
+    oid = request.matchdict.get('oid')
+    if oid:
+        view_name = request.matchdict.get('view')
+        sequence = get_utility(ISequentialIntIds)
+        hex_oid = sequence.get_full_oid(oid)
+        catalog = get_utility(ICatalog)
+        params = Eq(catalog['oid'], hex_oid)
+        if not view_name:
+            params &= Any(catalog['workflow_state'], VISIBLE_STATES)
+        results = list(CatalogResultSet(CatalogQuery(catalog).query(params)))
+        if results:
+            response = Response()
+            response.status_code = 302
+            if view_name:  # back-office access => last version
+                version = IWorkflowVersions(results[0]).get_last_versions()[0]
+                response.location = absolute_url(version, request, '/'.join(view_name))
+            else:
+                version = results[0]
+                response.location = canonical_url(version, request)
+            return response
+    raise NotFound()
--- a/src/pyams_default_theme/shared/common/__init__.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/shared/common/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -14,37 +14,36 @@
 
 from zope.interface import Interface
 
-from pyams_content.interfaces import IBaseContent
 from pyams_content.shared.common.interfaces import IWfSharedContent
-from pyams_default_theme.interfaces import IContentDate, IContentNavigationTitle, \
-    IContentTag
+from pyams_default_theme.interfaces import IContentDate, IContentTag
 from pyams_i18n.interfaces import II18n
-from pyams_skin.layer import IPyAMSLayer
+from pyams_skin.interfaces.metas import IHTMLContentMetas
+from pyams_skin.layer import IPyAMSUserLayer
+from pyams_skin.metas import ContentMeta, HTMLTagMeta
 from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
 from pyams_utils.date import SH_DATE_FORMAT, format_date
 from pyams_utils.interfaces.tales import ITALESExtension
 from pyams_workflow.interfaces import IWorkflowPublicationInfo
 
 
-@adapter_config(context=(IBaseContent, IPyAMSLayer), provides=IContentNavigationTitle)
-def shared_content_navigation_title(context, request):
-    """Default content navigation title adapter"""
-    return II18n(context).query_attribute('title', request=request)
+@adapter_config(name='title', context=(IWfSharedContent, IPyAMSUserLayer, Interface), provides=IHTMLContentMetas)
+class SharedContentTitleMetasAdapter(ContextRequestViewAdapter):
+    """Title metas adapter"""
+
+    order = 1
+
+    def get_metas(self):
+        i18n = II18n(self.context)
+        title = i18n.query_attribute('title', request=self.request)
+        yield HTMLTagMeta('title', title)
+        description = i18n.query_attribute('description', request=self.request)
+        if (not description) and self.context.handle_header:
+            description = i18n.query_attribute('header', request=self.request)
+        if description:
+            yield ContentMeta('description', description)
 
 
-@adapter_config(name='pyams_title',
-                context=(Interface, Interface, Interface),
-                provides=ITALESExtension)
-class PyAMSContentTitleTALESExtension(ContextRequestViewAdapter):
-    """PyAMS content title TALES extension"""
-
-    def render(self, context=None):
-        if context is None:
-            context = self.context
-        return self.request.registry.queryMultiAdapter((context, self.request), IContentNavigationTitle)
-
-
-@adapter_config(context=(IWfSharedContent, IPyAMSLayer), provides=IContentTag)
+@adapter_config(context=(IWfSharedContent, IPyAMSUserLayer), provides=IContentTag)
 def shared_content_tag_adapter(context, request):
     """Default shared content tag adapter"""
     translate = request.localizer.translate
@@ -63,7 +62,7 @@
         return self.request.registry.queryMultiAdapter((context, self.request), IContentTag)
 
 
-@adapter_config(context=(IWfSharedContent, IPyAMSLayer), provides=IContentDate)
+@adapter_config(context=(IWfSharedContent, IPyAMSUserLayer), provides=IContentDate)
 def shared_content_date_adapter(context, request):
     """Default shared content date adapter"""
     publication_info = IWorkflowPublicationInfo(context, None)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/shared/common/navigation.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,54 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from zope.interface import Interface
+
+from pyams_content.interfaces import IBaseContent
+from pyams_content.shared.common import ISharedContent
+from pyams_default_theme.interfaces import IContentNavigationTitle
+from pyams_content.shared.site import ISiteElementNavigation
+from pyams_i18n.interfaces import II18n
+from pyams_skin.layer import IPyAMSUserLayer
+from pyams_utils.adapter import ContextRequestAdapter, ContextRequestViewAdapter, adapter_config
+from pyams_utils.interfaces.tales import ITALESExtension
+from pyams_workflow.interfaces import IWorkflowPublicationInfo
+
+
+@adapter_config(context=(IBaseContent, IPyAMSUserLayer), provides=IContentNavigationTitle)
+def shared_content_navigation_title(context, request):
+    """Default content navigation title adapter"""
+    return II18n(context).query_attribute('title', request=request)
+
+
+@adapter_config(name='pyams_title',
+                context=(Interface, Interface, Interface),
+                provides=ITALESExtension)
+class PyAMSContentTitleTALESExtension(ContextRequestViewAdapter):
+    """PyAMS content title TALES extension"""
+
+    def render(self, context=None):
+        if context is None:
+            context = self.context
+        return self.request.registry.queryMultiAdapter((context, self.request), IContentNavigationTitle)
+
+
+@adapter_config(context=(ISharedContent, IPyAMSUserLayer), provides=ISiteElementNavigation)
+class SharedContentNavigationAdapter(ContextRequestAdapter):
+    """Shared content navigation adapter"""
+
+    @property
+    def visible(self):
+        version = self.context.published_version
+        if version is not None:
+            return IWorkflowPublicationInfo(version).is_visible(self.request)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/shared/common/oid.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,51 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from pyramid.exceptions import NotFound
+from zope.interface import Interface
+from zope.traversing.interfaces import ITraversable
+
+from pyams_sequence.interfaces import ISequentialIntIds
+from pyams_sequence.reference import get_reference_target
+from pyams_skin.layer import IPyAMSUserLayer
+from pyams_utils.adapter import ContextRequestAdapter, adapter_config
+from pyams_utils.interfaces.url import DISPLAY_CONTEXT
+from pyams_utils.registry import get_utility
+from pyams_workflow.interfaces import IWorkflow, IWorkflowVersions
+
+
+@adapter_config(name='oid', context=(Interface, IPyAMSUserLayer), provides=ITraversable)
+class OidTraverser(ContextRequestAdapter):
+    """++oid++ traverser"""
+
+    def traverse(self, name, furtherpath=None):
+        if not name:
+            raise NotFound()
+        if '::' in name:
+            oid, label = name.split('::', 1)
+        else:
+            oid = name
+        sequence = get_utility(ISequentialIntIds)
+        reference = sequence.get_full_oid(oid)
+        target = get_reference_target(reference)
+        if target is not None:
+            workflow = IWorkflow(target, None)
+            if workflow is not None:
+                versions = IWorkflowVersions(target).get_versions(workflow.published_states, sort=True)
+                if versions:
+                    target = versions[-1]
+        if target is not None:
+            self.request.annotations[DISPLAY_CONTEXT] = self.context
+            return target
+        raise NotFound()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/shared/common/opengraph.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,93 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from zope.dublincore.interfaces import IZopeDublinCore
+from zope.interface import Interface
+
+from pyams_content.component.illustration import IIllustration, IIllustrationTarget
+from pyams_content.component.theme import ITagsInfo
+from pyams_content.shared.common import IWfSharedContent
+from pyams_file.interfaces import IThumbnails
+from pyams_i18n.interfaces import II18n, II18nManager, INegotiator
+from pyams_skin.interfaces.configuration import IConfiguration
+from pyams_skin.interfaces.metas import IHTMLContentMetas
+from pyams_skin.layer import IPyAMSUserLayer
+from pyams_skin.metas import PropertyMeta
+from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
+from pyams_utils.registry import get_utility
+from pyams_utils.traversing import get_parent
+from pyams_utils.url import absolute_url, canonical_url
+from pyams_workflow.interfaces import IWorkflowPublicationInfo
+
+
+@adapter_config(name='opengraph', context=(IWfSharedContent, IPyAMSUserLayer, Interface), provides=IHTMLContentMetas)
+class OpengraphSharedContentMetasAdapter(ContextRequestViewAdapter):
+    """Opengraph shared content metas adapter"""
+
+    order = 15
+
+    def get_metas(self):
+        context = self.context
+        i18n = II18n(context)
+        request = self.request
+        negotiator = get_utility(INegotiator)
+        lang = negotiator.server_language
+        # main properties
+        yield PropertyMeta('og:title', i18n.query_attribute('title', lang=lang, request=request))
+        yield PropertyMeta('og:description', i18n.query_attribute('description', lang=lang, request=request) or '')
+        yield PropertyMeta('og:type', 'article')
+        # workflow informations
+        dc = IZopeDublinCore(context, None)
+        if (dc is not None) and dc.modified:
+            yield PropertyMeta('article:modified_time', dc.modified.isoformat())
+        pub_info = IWorkflowPublicationInfo(context, None)
+        if pub_info is not None:
+            if pub_info.first_publication_date:
+                yield PropertyMeta('article:published_time', pub_info.first_publication_date.isoformat())
+            if pub_info.publication_expiration_date:
+                yield PropertyMeta('article:expiration_time', pub_info.publication_expiration_date.isoformat())
+        # tags
+        tags = ITagsInfo(context, None)
+        if tags is not None:
+            for tag in tags.tags or ():
+                yield PropertyMeta('article:tag', tag.label)
+        # URL and site name
+        yield PropertyMeta('og:url', canonical_url(context, request))
+        configuration = IConfiguration(request.root)
+        yield PropertyMeta('og:site_name', configuration.title)
+        # illustration properties
+        registry = request.registry
+        illustration = None
+        target = context
+        while target is not None:
+            illustration = registry.queryAdapter(target, IIllustration, name='link')
+            if (illustration is None) or (not illustration.has_data()):
+                illustration = registry.queryAdapter(target, IIllustration)
+            if (illustration is not None) and illustration.has_data():
+                break
+            target = get_parent(target, IIllustrationTarget, allow_context=False)
+        if (target is not None) and (illustration is not None):
+            data = II18n(illustration).query_attribute('data', lang=lang, request=request)
+            thumbnail = IThumbnails(data).get_thumbnail('800x600')
+            yield PropertyMeta('og:image:url', absolute_url(thumbnail, self.request))
+            yield PropertyMeta('og:image:type', thumbnail.content_type)
+            image_size = thumbnail.image_size
+            yield PropertyMeta('og:image:width', image_size[0])
+            yield PropertyMeta('og:image:height', image_size[1])
+            yield PropertyMeta('og:image:alt', II18n(illustration).query_attribute('alt_title', lang=lang,
+                                                                                   request=request))
+        # locales properties
+        yield PropertyMeta('og:locale', lang)
+        for lang in II18nManager(context).languages or ():
+            yield PropertyMeta('og:locale:alternate', lang)
--- a/src/pyams_default_theme/shared/common/portlet/templates/head.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/shared/common/portlet/templates/head.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,12 @@
+<div tal:define="settings view.settings" i18n:domain="pyams_default_theme">
+	<tal:if condition="settings.display_breadcrumbs">
+		${structure:provider:pyams.breadcrumbs}
+	</tal:if>
+	<div class="page-header"
+		 tal:condition="settings.display_header">
+		<h1 tal:condition="settings.display_title">${i18n:context.title}</h1>
+		<div class="small">
+			<tal:var define="header tales:i18n(context, 'header', '')">${structure:tales:html(header)}</tal:var>
+		</div>
+	</div>
+</div>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/shared/common/summary.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,46 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from pyams_content.shared.common import ISharedContent, IWfSharedContent
+from pyams_default_theme.interfaces import IContentSummaryInfo
+from pyams_i18n.interfaces import II18n
+from pyams_skin.layer import IPyAMSUserLayer
+from pyams_utils.adapter import ContextRequestAdapter, adapter_config
+
+from pyams_content import _
+
+
+@adapter_config(context=(IWfSharedContent, IPyAMSUserLayer), provides=IContentSummaryInfo)
+class WfSharedContentSummaryInfo(ContextRequestAdapter):
+    """Shared content summary info adapter"""
+
+    @property
+    def title(self):
+        return II18n(self.context).query_attribute('title', request=self.request)
+
+    @property
+    def header(self):
+        return II18n(self.context).query_attribute('header', request=self.request)
+
+    @property
+    def button_title(self):
+        return _("Consult content")
+
+
+@adapter_config(context=(ISharedContent, IPyAMSUserLayer), provides=IContentSummaryInfo)
+def shared_content_summary_adapter(context, request):
+    """Shared content summary adapter"""
+    version = context.published_version
+    if version is not None:
+        return request.registry.queryMultiAdapter((version, request), IContentSummaryInfo)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/shared/common/url.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,43 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from pyams_content.shared.common import ISharedContent, IWfSharedContent
+from pyams_sequence.interfaces import ISequentialIdInfo
+from pyams_skin.layer import IPyAMSUserLayer
+from pyams_utils.adapter import ContextRequestAdapter, adapter_config
+from pyams_utils.interfaces.url import IRelativeURL
+from pyams_utils.url import absolute_url, relative_url
+
+
+@adapter_config(context=(IWfSharedContent, IPyAMSUserLayer), provides=IRelativeURL)
+class WfSharedContentRelativeUrlAdapter(ContextRequestAdapter):
+    """Workflow managed shared content relative URL adapter"""
+
+    def get_url(self, display_context=None, view_name=None, query=None):
+        return absolute_url(display_context, self.request,
+                            '++oid++{0}::{1}{2}{3}'.format(
+                                ISequentialIdInfo(self.context).get_base_oid().strip(),
+                                self.context.content_url,
+                                '/{}'.format(view_name) if view_name else '.html',
+                                '?{}'.format(query) if query else ''))
+
+
+@adapter_config(context=(ISharedContent, IPyAMSUserLayer), provides=IRelativeURL)
+class SharedContentRelativeURLAdapter(ContextRequestAdapter):
+    """Shared content relative URL adapter"""
+
+    def get_url(self, display_context=None, view_name=None, query=None):
+        version = self.context.published_version
+        if version is not None:
+            return relative_url(version, self.request, display_context, view_name, query)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/shared/form/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,89 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from z3c.form import button, field
+from zope.interface import Interface
+
+from pyams_content.features.renderer.interfaces import ISharedContentRenderer
+from pyams_content.shared.form import IFormFieldContainer, IFormFieldContainerTarget
+from pyams_default_theme.features.renderer import BaseContentRenderer
+from pyams_form.form import InnerAddForm
+from pyams_form.help import FormHelp
+from pyams_form.interfaces.form import IFormHelp
+from pyams_i18n.interfaces import II18n
+from pyams_skin.layer import IPyAMSLayer
+from pyams_utils.adapter import adapter_config
+
+
+class FormFieldContainerDisplayForm(InnerAddForm):
+    """Form fields container display form"""
+
+    @property
+    def legend(self):
+        return II18n(self.context).query_attribute('user_title', request=self.request)
+
+    @property
+    def fields(self):
+        fields = field.Fields(*IFormFieldContainer(self.context).get_fields())
+        if self.context.use_captcha:
+            # TODO: add captcha
+            # fields += field.Fields(Captcha(title='', description='', required=True))
+            pass
+        return fields
+
+    buttons = button.Buttons(Interface)
+
+    def updateWidgets(self, prefix=None):
+        super(FormFieldContainerDisplayForm, self).updateWidgets(prefix)
+        for widget in self.widgets.values():
+            field = IFormFieldContainer(self.context).get(widget.field.__name__)
+            if field is not None:
+                widget.placeholder = field.placeholder
+
+
+@adapter_config(context=(IFormFieldContainerTarget, IPyAMSLayer, FormFieldContainerDisplayForm),
+                provides=IFormHelp)
+class FormFieldContainerDisplayFormHelp(FormHelp):
+    """Form field container display form help adapter"""
+
+    def __new__(cls, context, request, view):
+        if not context.header:
+            return None
+        return FormHelp.__new__(cls)
+
+    @property
+    def message(self):
+        return II18n(self.context).query_attribute('header', request=self.request)
+
+    message_format = 'text'
+
+
+@adapter_config(name='form-render', context=(IFormFieldContainerTarget, IPyAMSLayer),
+                provides=ISharedContentRenderer)
+class FormFieldContainerRenderer(BaseContentRenderer):
+    """Form field container renderer"""
+
+    weight = 20
+    display_form = None
+
+    def __init__(self, context, request):
+        super(FormFieldContainerRenderer, self).__init__(context, request)
+        self.display_form = FormFieldContainerDisplayForm(context, self.request)
+
+    def update(self):
+        super(FormFieldContainerRenderer, self).update()
+        self.display_form.update()
+
+    def render(self):
+        return self.display_form.render()
--- a/src/pyams_default_theme/shared/imagemap/__init__.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/shared/imagemap/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,22 +12,16 @@
 
 __docformat__ = 'restructuredtext'
 
-
-# import standard library
-
-# import interfaces
 from pyams_content.component.association.interfaces import IAssociationInfo
 from pyams_content.features.renderer.interfaces import IContentRenderer
-from pyams_content.shared.imagemap.interfaces import IWfImageMap, IImageMapParagraph
-from pyams_skin.layer import IPyAMSLayer, IPyAMSUserLayer
-from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
-
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
+from pyams_content.shared.imagemap.interfaces import IImageMapParagraph, IWfImageMap
+from pyams_default_theme.features.renderer import BaseContentRenderer
 from pyams_default_theme.page import BasePreviewPage
 from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.layer import IPyAMSLayer, IPyAMSUserLayer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
+from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
 
 from pyams_default_theme import _
 
@@ -57,7 +51,7 @@
 
     label = _("Default imagemap renderer")
 
-    i18n_context_attrs = ('title', )
+    i18n_context_attrs = ('title',)
 
     def get_item_info(self, item):
         return IAssociationInfo(item, None)
--- a/src/pyams_default_theme/shared/imagemap/templates/imagemap-default.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/shared/imagemap/templates/imagemap-default.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -2,7 +2,7 @@
 <div class="no-padding" i18n:domain="pyams_content"
 	 data-ams-plugins="pyams_content"
 	 tal:define="imagemap context.get_target()"
-	 tal:attributes="data-ams-plugin-pyams_content-src tales:resource_path('pyams_content.skin:pyams_content')">
+	 tal:attributes="data-ams-plugin-pyams_content-src tales:resource_path('pyams_content.zmi:pyams_content')">
 	<img usemap="#imagemap_preview"
 		 tal:define="image i18n:imagemap.image"
 		 tal:attributes="src tales:absolute_url(image);
--- a/src/pyams_default_theme/shared/logo/__init__.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/shared/logo/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,15 +12,10 @@
 
 __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_default_theme.features.renderer import BaseContentRenderer
 from pyams_skin.layer import IPyAMSLayer
-
-# import packages
-from pyams_content.features.renderer.skin import BaseContentRenderer
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/shared/site/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,40 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from zope.interface import Interface
+
+from pyams_content.shared.site.interfaces import ISiteFolder, ISiteManager
+from pyams_i18n.interfaces import II18n
+from pyams_skin.interfaces.metas import IHTMLContentMetas
+from pyams_skin.layer import IPyAMSUserLayer
+from pyams_skin.metas import ContentMeta, HTMLTagMeta
+from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
+
+
+@adapter_config(name='title', context=(ISiteManager, IPyAMSUserLayer, Interface), provides=IHTMLContentMetas)
+@adapter_config(name='title', context=(ISiteFolder, IPyAMSUserLayer, Interface), provides=IHTMLContentMetas)
+class SiteContainerTitleMetasAdapter(ContextRequestViewAdapter):
+    """Site container metas adapter"""
+
+    order = 1
+
+    def get_metas(self):
+        i18n = II18n(self.context)
+        title = i18n.query_attribute('title', request=self.request)
+        yield HTMLTagMeta('title', title)
+        description = i18n.query_attribute('description', request=self.request)
+        if (not description) and hasattr(self.context, 'header'):
+            description = i18n.query_attribute('header', request=self.request)
+        if description:
+            yield ContentMeta('description', description)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/shared/site/breadcrumb.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,29 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from pyams_content.shared.site.interfaces import ISiteContainer
+from pyams_i18n.interfaces import II18n
+from pyams_skin.interfaces.viewlet import IBreadcrumbItem
+from pyams_skin.layer import IPyAMSUserLayer
+from pyams_skin.viewlet.breadcrumb import BreadcrumbItem
+from pyams_utils.adapter import adapter_config
+
+
+@adapter_config(context=(ISiteContainer, IPyAMSUserLayer), provides=IBreadcrumbItem)
+class SiteContainerBreadcumbAdapter(BreadcrumbItem):
+    """Site container breadcrumb adapter"""
+
+    @property
+    def label(self):
+        return II18n(self.context).query_attribute('short_name', request=self.request)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/shared/site/folder.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,84 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from pyramid.httpexceptions import HTTPSeeOther
+
+from pyams_content.shared.site import ISiteElementNavigation
+from pyams_content.shared.site.interfaces import FOLDER_REDIRECT_DISPLAY_MODE, ISiteFolder
+from pyams_default_theme.interfaces import IContentSummaryInfo
+from pyams_i18n.interfaces import II18n
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_portal.views import PortalContextIndexPage, PortalContextPreviewPage
+from pyams_skin.layer import IPyAMSUserLayer
+from pyams_utils.adapter import ContextRequestAdapter, adapter_config
+from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
+from pyams_utils.url import relative_url
+from pyams_workflow.interfaces import IWorkflowPublicationInfo
+
+from pyams_content import _
+
+
+@adapter_config(context=(ISiteFolder, IPyAMSUserLayer), provides=ISiteElementNavigation)
+class SiteFolderNavigationAdapter(ContextRequestAdapter):
+    """Site folder navigation adapter"""
+
+    @property
+    def visible(self):
+        if not self.context.visible_in_list:
+            return False
+        return IWorkflowPublicationInfo(self.context).is_visible(self.request)
+
+
+@adapter_config(context=(ISiteFolder, IPyAMSUserLayer), provides=IContentSummaryInfo)
+class SiteFolderSummaryAdapter(ContextRequestAdapter):
+    """Site folder summary adapter"""
+
+    @property
+    def title(self):
+        i18n = II18n(self.context)
+        return i18n.query_attribute('navigation_title', request=self.request) or \
+               i18n.query_attribute('title', request=self.request)
+
+    @property
+    def header(self):
+        try:
+            return II18n(self.context).query_attribute('header', request=self.request)
+        except AttributeError:
+            return None
+
+    button_title = _("Consult folder")
+
+
+@pagelet_config(name='', context=ISiteFolder, layer=IPyAMSUserLayer)
+class SiteFolderIndexPage(PortalContextIndexPage):
+    """Site folder index page"""
+
+    def __call__(self, **kwargs):
+        if self.context.navigation_mode == FOLDER_REDIRECT_DISPLAY_MODE:
+            target = next(self.context.get_visible_items(self.request), None)
+            if target is not None:
+                return HTTPSeeOther(relative_url(target, request=self.request))
+        return super(SiteFolderIndexPage, self).__call__(**kwargs)
+
+
+@pagelet_config(name='preview.html', context=ISiteFolder, layer=IPyAMSUserLayer, permission=VIEW_SYSTEM_PERMISSION)
+class SiteFolderPreviewPage(PortalContextPreviewPage):
+    """Site folder preview page"""
+
+    def __call__(self, **kwargs):
+        if self.context.navigation_mode == FOLDER_REDIRECT_DISPLAY_MODE:
+            target = next(self.context.get_visible_items(self.request), None)
+            if target is not None:
+                return HTTPSeeOther(relative_url(target, request=self.request, view_name='preview.html'))
+        return super(SiteFolderPreviewPage, self).__call__(**kwargs)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/shared/site/link.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,62 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from pyams_content.shared.site.interfaces import IContentLink
+from pyams_default_theme.interfaces import IContentSummaryInfo
+from pyams_i18n.interfaces import II18n
+from pyams_skin.layer import IPyAMSUserLayer
+from pyams_utils.adapter import ContextRequestAdapter, adapter_config
+from pyams_utils.interfaces.url import IRelativeURL
+
+from pyams_content import _
+
+
+@adapter_config(context=(IContentLink, IPyAMSUserLayer), provides=IRelativeURL)
+def content_link_relative_url(context, request):
+    """Content link relative URL"""
+    target = context.get_target()
+    if target is not None:
+        return request.registry.queryMultiAdapter((target, request), IRelativeURL)
+
+
+@adapter_config(context=(IContentLink, IPyAMSUserLayer), provides=IContentSummaryInfo)
+class ContentLinkSummaryAdapter(ContextRequestAdapter):
+    """Content link summary adapter"""
+
+    target_info = None
+
+    def __init__(self, context, request):
+        super(ContentLinkSummaryAdapter, self).__init__(context, request)
+        target = self.target = self.context.target
+        if target is not None:
+            self.target_info = request.registry.queryMultiAdapter((target, request), IContentSummaryInfo)
+
+    @property
+    def title(self):
+        title = II18n(self.context).query_attribute('navigation_title', request=self.request)
+        if (title is None) and (self.target_info is not None):
+            title = self.target_info.title
+        return title
+
+    @property
+    def header(self):
+        if self.target_info is not None:
+            return self.target_info.header
+
+    @property
+    def button_title(self):
+        if self.target_info is not None:
+            return self.target_info.button_title
+        else:
+            return self.request.localizer.translate(_("Consult content"))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/shared/site/portlet/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,44 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from zope.interface import Interface
+
+from pyams_content.shared.site.interfaces import ISiteContainer
+from pyams_default_theme.interfaces import IContentSummaryInfo
+from pyams_content.shared.site.portlet import ISiteContainerSummaryPortletSettings
+from pyams_portal.interfaces import IPortalContext, IPortletRenderer
+from pyams_portal.portlet import PortletRenderer
+from pyams_skin.layer import IPyAMSLayer
+from pyams_template.template import template_config
+from pyams_utils.adapter import adapter_config
+
+from pyams_default_theme import _
+
+
+@adapter_config(context=(IPortalContext, IPyAMSLayer, Interface, ISiteContainerSummaryPortletSettings),
+                provides=IPortletRenderer)
+@template_config(template='templates/site-summary.pt', layer=IPyAMSLayer)
+class SiteContainerSummaryPortletDefaultRenderer(PortletRenderer):
+    """Site container summary portlet default renderer"""
+
+    label = _("Site container summary")
+
+    @property
+    def visible_items(self):
+        container = ISiteContainer(self.context, None)
+        if container is not None:
+            registry = self.request.registry
+            yield from filter(lambda x: x is not None,
+                              [registry.queryMultiAdapter((item, self.request), IContentSummaryInfo)
+                               for item in container.get_visible_items(self.request)])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/shared/site/portlet/templates/site-summary.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,28 @@
+<div tal:define="settings view.settings;
+				 button_title i18n:settings.button_title;">
+	<div class="row summary">
+		<div tal:repeat="item view.visible_items">
+			<div class="thumbnail pull-left">
+				<tal:var define="illustration tales:pyams_illustration(item.context)"
+						 condition="illustration">
+					<tal:var define="image i18n:illustration.data;
+									 alt i18n:illustration.alt_title;"
+							 condition="image">
+						${structure:tales:picture(image, lg_thumb='pano', lg_width=3, md_thumb='pano', md_width=3, sm_thumb='pano',
+												  sm_width=4, xs_thumb='pano', xs_width=12, alt=alt, css_class='result_media')}
+					</tal:var>
+				</tal:var>
+			</div>
+			<div>
+				<h2>${item.title}</h2>
+				<div class="header">${item.header}</div>
+				<div class="action">
+					<a href="${tales:relative_url(item.context)}">
+						<span i18n:translate="">${button_title or item.button_title}</span>
+					</a>
+				</div>
+			</div>
+		<div class="clearfix"></div>
+		</div>
+	</div>
+</div>
--- a/src/pyams_default_theme/viewlet/banner/__init__.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/viewlet/banner/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -12,19 +12,17 @@
 
 __docformat__ = 'restructuredtext'
 
+from pyramid.decorator import reify
+from zope.interface import Interface
 
-# import standard library
-
-# import interfaces
 from pyams_content.features.header.interfaces import IHeaderTarget, IHeaderSettings
 from pyams_default_theme.features.header.interfaces import ISimpleHeaderRendererSettings
+from pyams_default_theme.interfaces import IContentBannerIllustration
+from pyams_i18n.interfaces import II18n
 from pyams_skin.layer import IPyAMSUserLayer
-
-# import packages
 from pyams_template.template import template_config
 from pyams_utils.traversing import get_parent
 from pyams_viewlet.viewlet import contentprovider_config, ViewContentProvider
-from zope.interface import Interface
 
 
 @contentprovider_config(name='pyams.banner', layer=IPyAMSUserLayer, view=Interface)
@@ -32,10 +30,23 @@
 class Banner(ViewContentProvider):
     """Banner content provider"""
 
-    @property
+    @reify
     def settings(self):
         target = get_parent(self.context, IHeaderTarget)
         settings = IHeaderSettings(target)
         while settings.inherit:
             settings = IHeaderSettings(settings.parent)
         return ISimpleHeaderRendererSettings(settings.settings, None)
+
+    @property
+    def banner(self):
+        settings = self.settings
+        if settings is not None:
+            banner = settings.banner
+            if banner is not None:
+                return banner
+        registry = self.request.registry
+        illustration = registry.queryMultiAdapter((self.context, self.request),
+                                                  IContentBannerIllustration)
+        if illustration is not None:
+            return II18n(illustration).query_attribute('data', request=self.request)
--- a/src/pyams_default_theme/viewlet/banner/templates/banner.pt	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/viewlet/banner/templates/banner.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -1,12 +1,14 @@
-<section class="wrapper-regularbanner" i18n:domain="onf_website"
-		 tal:define="settings view.settings"
-		 tal:condition="settings and settings.banner">
+<section class="wrapper-regularbanner" i18n:domain="onf_website">
 	<div class="regularbanner">
-		<div class="regularbanner__bg">
-			<img alt="" class="regularbanner__media"
-				 tal:define="image settings.banner"
-				 tal:attributes="src tales:absolute_url(image, '++thumb++w1320')" />
-		</div>  <!-- /.regularbanner__bg -->
+		<div class="regularbanner__bg"
+			tal:define="banner view.banner"
+			tal:condition="banner">
+			${structure:tales:picture(banner, lg_thumb='banner', md_thumb='banner',
+									  sm_thumb='banner', xs_thumb='banner', css_class='regularbanner__media')}
+			<!-- /.regularbanner__bg -->
+		</div>
 		<tal:var content="structure provider:pyams.logo" />
-	</div>  <!-- /.regularbanner -->
-</section>  <!-- /.wrapper-regularbanner -->
+		<!-- /.regularbanner -->
+	</div>
+	<!-- /.wrapper-regularbanner -->
+</section>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/viewlet/breadcrumb/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,47 @@
+#
+# Copyright (c) 2008-2018 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'
+
+from pyramid.location import lineage
+from zope.interface import Interface
+
+from pyams_skin.interfaces.viewlet import IBreadcrumbItem
+from pyams_skin.layer import IPyAMSUserLayer
+from pyams_skin.viewlet.breadcrumb import BreadcrumbsContentProvider as BaseBreadcrumbsContentProvider
+from pyams_template.template import template_config
+from pyams_utils.interfaces.url import DISPLAY_CONTEXT
+from pyams_viewlet.viewlet import contentprovider_config
+
+
+@contentprovider_config(name='pyams.breadcrumbs', layer=IPyAMSUserLayer, view=Interface)
+@template_config(template='breadcrumbs.pt', layer=IPyAMSUserLayer)
+class BreadcrumbsContentProvider(BaseBreadcrumbsContentProvider):
+    """Breadcrumbs content provider"""
+
+    @property
+    def items(self):
+        source = None
+        registry = self.request.registry
+        if source is None:
+            source = self.request.annotations.get(DISPLAY_CONTEXT)
+            if source is None:
+                source = self.request.context
+        if source is not None:
+            for context in reversed(tuple(lineage(source))):
+                item = registry.queryMultiAdapter((context, self.request, self.view), IBreadcrumbItem)
+                if item is None:
+                    item = registry.queryMultiAdapter((context, self.request), IBreadcrumbItem)
+                    if item is None:
+                        item = registry.queryAdapter(context, IBreadcrumbItem)
+                if item is not None:
+                    yield item
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_default_theme/viewlet/breadcrumb/breadcrumbs.pt	Tue Nov 13 14:48:49 2018 +0100
@@ -0,0 +1,6 @@
+<ol class="breadcrumb">
+	<li tal:repeat="item view.items">
+		<a class="${'active' if repeat.item.end else None}"
+		   href="${item.url}">${item.label}</a>
+	</li>
+</ol>
\ No newline at end of file
--- a/src/pyams_default_theme/viewlet/logo/__init__.py	Tue Oct 09 17:51:18 2018 +0200
+++ b/src/pyams_default_theme/viewlet/logo/__init__.py	Tue Nov 13 14:48:49 2018 +0100
@@ -34,7 +34,7 @@
 
     @property
     def settings(self):
-        target = get_parent(self.context, IHeaderTarget)
+        target = get_parent(self.request.display_context, IHeaderTarget)
         settings = IHeaderSettings(target)
         while settings.inherit:
             settings = IHeaderSettings(settings.parent)