imio.smartweb.core 1.2.70__py3-none-any.whl → 1.2.75__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. imio/smartweb/core/browser/controlpanel.py +2 -0
  2. imio/smartweb/core/contents/pages/configure.zcml +1 -0
  3. imio/smartweb/core/contents/pages/views.py +41 -0
  4. imio/smartweb/core/contents/sections/contact/utils.py +2 -0
  5. imio/smartweb/core/contents/sections/contact/view.py +28 -12
  6. imio/smartweb/core/contents/sections/events/view.py +1 -1
  7. imio/smartweb/core/contents/sections/news/view.py +1 -1
  8. imio/smartweb/core/rest/authentic_sources.py +15 -2
  9. imio/smartweb/core/tests/test_section_contact.py +42 -10
  10. imio/smartweb/core/vocabularies.py +2 -1
  11. imio/smartweb/core/webcomponents/build/css/smartweb-webcomponents-compiled.css +1 -1
  12. imio/smartweb/core/webcomponents/build/js/373.smartweb-webcomponents-compiled.js +1 -1
  13. imio/smartweb/core/webcomponents/build/js/486.smartweb-webcomponents-compiled.js +1 -1
  14. imio/smartweb/core/webcomponents/build/js/884.smartweb-webcomponents-compiled.js +1 -1
  15. imio/smartweb/core/webcomponents/src/components/Annuaire/ContactContent/ContactContent.jsx +26 -2
  16. imio/smartweb/core/webcomponents/src/components/Events/EventContent/EventContent.jsx +6 -1
  17. imio/smartweb/core/webcomponents/src/components/News/NewsCard/NewsCard.jsx +2 -2
  18. imio/smartweb/core/webcomponents/src/index.scss +9 -0
  19. {imio.smartweb.core-1.2.70.dist-info → imio.smartweb.core-1.2.75.dist-info}/METADATA +48 -1
  20. {imio.smartweb.core-1.2.70.dist-info → imio.smartweb.core-1.2.75.dist-info}/RECORD +26 -26
  21. {imio.smartweb.core-1.2.70.dist-info → imio.smartweb.core-1.2.75.dist-info}/WHEEL +1 -1
  22. /imio.smartweb.core-1.2.70-py3.12-nspkg.pth → /imio.smartweb.core-1.2.75-py3.12-nspkg.pth +0 -0
  23. {imio.smartweb.core-1.2.70.dist-info → imio.smartweb.core-1.2.75.dist-info}/LICENSE.GPL +0 -0
  24. {imio.smartweb.core-1.2.70.dist-info → imio.smartweb.core-1.2.75.dist-info}/LICENSE.rst +0 -0
  25. {imio.smartweb.core-1.2.70.dist-info → imio.smartweb.core-1.2.75.dist-info}/namespace_packages.txt +0 -0
  26. {imio.smartweb.core-1.2.70.dist-info → imio.smartweb.core-1.2.75.dist-info}/top_level.txt +0 -0
@@ -171,11 +171,13 @@ class ISmartwebControlPanel(Interface):
171
171
  description=_(
172
172
  "For staging : https://conseil.staging.imio.be | for production : https://www.deliberations.be"
173
173
  ),
174
+ default="https://www.deliberations.be",
174
175
  required=False,
175
176
  )
176
177
 
177
178
  iadeliberations_api_username = schema.TextLine(
178
179
  title=_("Username to consume I.A. Deliberations API"),
180
+ default="wsviewersmartweb",
179
181
  required=False,
180
182
  )
181
183
 
@@ -15,6 +15,7 @@
15
15
  template="view.pt"
16
16
  class=".views.PagesView"
17
17
  permission="zope2.View"
18
+ allowed_attributes="get_page_contacts"
18
19
  layer="imio.smartweb.core.interfaces.IImioSmartwebCoreLayer"
19
20
  />
20
21
 
@@ -1,9 +1,11 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
3
  from Acquisition import aq_inner
4
+ from imio.smartweb.core.config import DIRECTORY_URL
4
5
  from imio.smartweb.core.utils import get_scale_url
5
6
  from imio.smartweb.core.interfaces import IHtmxViewUtils
6
7
  from imio.smartweb.core.interfaces import IViewWithoutLeadImage
8
+ from imio.smartweb.core.utils import get_json
7
9
  from imio.smartweb.locales import SmartwebMessageFactory as _
8
10
  from Products.CMFPlone.resources import add_bundle_on_request
9
11
  from plone import api
@@ -11,11 +13,14 @@ from plone.app.content.browser.contents.rearrange import OrderContentsBaseAction
11
13
  from plone.app.content.utils import json_loads
12
14
  from plone.app.contenttypes.browser.folder import FolderView
13
15
  from plone.app.contenttypes.browser.full_view import FullViewItem as BaseFullViewItem
16
+ from plone.memoize.view import memoize
14
17
  from Products.CMFPlone import utils
15
18
  from Products.CMFPlone.browser.navigation import PhysicalNavigationBreadcrumbs
16
19
  from zope.component import getMultiAdapter
17
20
  from zope.interface import implementer
18
21
 
22
+ import itertools
23
+
19
24
 
20
25
  @implementer(IViewWithoutLeadImage)
21
26
  @implementer(IHtmxViewUtils)
@@ -46,6 +51,42 @@ class PagesView(FolderView):
46
51
  add_bundle_on_request(self.request, "flexbin")
47
52
  return self.index()
48
53
 
54
+ @memoize
55
+ def get_page_contacts(self):
56
+ # TODO We need to check every tests to be sure to avoid caching that
57
+ # creates false positive assertions
58
+ sections_contacts = self.context.listFolderContents(
59
+ contentFilter={
60
+ "portal_type": [
61
+ "imio.smartweb.SectionContact",
62
+ ]
63
+ }
64
+ )
65
+ if len(sections_contacts) == 0:
66
+ return None
67
+ nested_contact_list = [
68
+ section_contacts.related_contacts
69
+ for section_contacts in sections_contacts
70
+ if section_contacts.related_contacts is not None
71
+ ]
72
+ if nested_contact_list == []:
73
+ return None
74
+ # Make a "set" to simplifying request in url (avoid duplicate uid)
75
+ # Sometimes we have duplicate uid because some pages were build with contact attributes
76
+ # in a section and for layout issues,for the same contact, others attributes are in another section.
77
+ page_contacts = list(
78
+ set(list(itertools.chain.from_iterable(nested_contact_list)))
79
+ )
80
+ uids = "&UID=".join(page_contacts)
81
+ url = "{}/@search?UID={}&fullobjects=1".format(DIRECTORY_URL, uids)
82
+ current_lang = api.portal.get_current_language()[:2]
83
+ if current_lang != "fr":
84
+ url = f"{url}&translated_in_{current_lang}=1"
85
+ json_data = get_json(url)
86
+ if json_data is None or len(json_data.get("items", [])) == 0:
87
+ return
88
+ return json_data
89
+
49
90
  def results(self, **kwargs):
50
91
  """
51
92
  Gets results for folder listings without taking care of friendly_types
@@ -69,6 +69,8 @@ class ContactProperties(ContactSchedule):
69
69
  return json.dumps(geo_json)
70
70
 
71
71
  def images(self, image_scale, nb_results_by_batch):
72
+ if "gallery" not in self.context.visible_blocks:
73
+ return
72
74
  contact_url = self.contact["@id"]
73
75
  query = "@search?portal_type=Image&path.depth=1&metadata_fields=modified"
74
76
  images_url_request = "{}/{}".format(contact_url, query)
@@ -4,30 +4,46 @@ from imio.smartweb.core.config import DIRECTORY_URL
4
4
  from imio.smartweb.core.contents.sections.contact.utils import ContactProperties
5
5
  from imio.smartweb.core.contents.sections.views import HashableJsonSectionView
6
6
  from imio.smartweb.core.utils import batch_results
7
- from imio.smartweb.core.utils import get_json
8
7
  from plone import api
8
+ from zope.component import queryMultiAdapter
9
9
 
10
10
 
11
11
  class ContactView(HashableJsonSectionView):
12
12
  """Contact Section view"""
13
13
 
14
14
  def contacts(self):
15
- if self.context.related_contacts is None:
16
- return
17
- related_contacts = self.context.related_contacts
18
- uids = "&UID=".join(related_contacts)
15
+ # Firstly, try to get contact from the container view
16
+ container_view = queryMultiAdapter(
17
+ (self.context.aq_parent, self.request), name="full_view"
18
+ )
19
+ self.json_data = container_view.get_page_contacts()
20
+ if self.json_data is None or len(self.json_data.get("items", [])) == 0:
21
+ return []
22
+
23
+ container_contacts = self.json_data.get("items")
24
+ results_items = [
25
+ contact
26
+ for contact in container_contacts
27
+ if contact["UID"] in self.context.related_contacts
28
+ ]
29
+ index_map = {
30
+ value: index for index, value in enumerate(self.context.related_contacts)
31
+ }
32
+ results_items = sorted(results_items, key=lambda x: index_map[x["UID"]])
33
+
34
+ # construct JSON data as before WEBBDC-1265 to avoid hash differences
35
+ uids = "&UID=".join(self.context.related_contacts)
19
36
  url = "{}/@search?UID={}&fullobjects=1".format(DIRECTORY_URL, uids)
20
37
  current_lang = api.portal.get_current_language()[:2]
21
38
  if current_lang != "fr":
22
39
  url = f"{url}&translated_in_{current_lang}=1"
23
- self.json_data = get_json(url)
40
+ self.json_data = {
41
+ "@id": url,
42
+ "items": results_items,
43
+ "items_total": len(results_items),
44
+ }
24
45
  self.refresh_modification_date()
25
- if self.json_data is None or len(self.json_data.get("items", [])) == 0: # NOQA
26
- return
27
- results = self.json_data.get("items")
28
- index_map = {value: index for index, value in enumerate(related_contacts)}
29
- results = sorted(results, key=lambda x: index_map[x["UID"]])
30
- return batch_results(results, self.context.nb_contact_by_line)
46
+ return batch_results(results_items, self.context.nb_contact_by_line)
31
47
 
32
48
  def get_contact_properties(self, json_dict):
33
49
  return ContactProperties(json_dict, self.context)
@@ -51,7 +51,7 @@ class EventsView(CarouselOrTableSectionView, HashableJsonSectionView):
51
51
  url = "{}/@events?{}".format(EVENTS_URL, "&".join(params))
52
52
  self.json_data = get_json(url)
53
53
  self.refresh_modification_date()
54
- if self.json_data is None or len(self.json_data.get("items", [])) == 0: # NOQA
54
+ if self.json_data is None or len(self.json_data.get("items", [])) == 0:
55
55
  return []
56
56
  linking_view_url = self.context.linking_rest_view.to_object.absolute_url()
57
57
  image_scale = self.image_scale
@@ -47,7 +47,7 @@ class NewsView(CarouselOrTableSectionView, HashableJsonSectionView):
47
47
  url = "{}/@search?{}".format(NEWS_URL, "&".join(params))
48
48
  self.json_data = get_json(url)
49
49
  self.refresh_modification_date()
50
- if self.json_data is None or len(self.json_data.get("items", [])) == 0: # NOQA
50
+ if self.json_data is None or len(self.json_data.get("items", [])) == 0:
51
51
  return []
52
52
  linking_view_url = self.context.linking_rest_view.to_object.absolute_url()
53
53
  image_scale = self.image_scale
@@ -1,5 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
+ from imio.smartweb.common.utils import is_log_active
3
4
  from imio.smartweb.core.config import DIRECTORY_URL
4
5
  from imio.smartweb.core.config import EVENTS_URL
5
6
  from imio.smartweb.core.config import NEWS_URL
@@ -12,9 +13,12 @@ from zope.interface import alsoProvides
12
13
  from zope.interface import implementer
13
14
  from zope.publisher.interfaces import IPublishTraverse
14
15
 
16
+ import logging
15
17
  import os
16
18
  import requests
17
19
 
20
+ logger = logging.getLogger("imio.smartweb.core")
21
+
18
22
 
19
23
  @implementer(IPublishTraverse)
20
24
  class BaseRequestForwarder(Service):
@@ -38,8 +42,17 @@ class BaseRequestForwarder(Service):
38
42
 
39
43
  def forward_request(self, url):
40
44
  method = self.request.method
41
- token = get_wca_token(self.client_id, self.client_secret)
42
- headers = {"Accept": "application/json", "Authorization": token}
45
+ token = None
46
+ if method == "GET" and self.request.form.get("wcatoken") == "false":
47
+ headers = {"Accept": "application/json"}
48
+ else:
49
+ token = get_wca_token(self.client_id, self.client_secret)
50
+ headers = {"Accept": "application/json", "Authorization": token}
51
+ if is_log_active():
52
+ logger.info("======== Forwarding request to AUTHENTIC SOURCE =========")
53
+ logger.info(f"url to forward : {url} ({method})")
54
+ logger.info(f"token : {token}")
55
+ logger.info(f"headers : {headers}")
43
56
  params = self.request.form
44
57
  if method == "GET":
45
58
  params = self.add_missing_metadatas(params)
@@ -8,6 +8,7 @@ from imio.smartweb.common.contact_utils import formatted_schedule
8
8
  from imio.smartweb.common.contact_utils import get_schedule_for_today
9
9
  from imio.smartweb.core.contents.sections.contact.utils import ContactProperties
10
10
  from imio.smartweb.core.contents.sections.views import SECTION_ITEMS_HASH_KEY
11
+ from imio.smartweb.core.tests.utils import clear_cache
11
12
  from imio.smartweb.core.testing import IMIO_SMARTWEB_CORE_FUNCTIONAL_TESTING
12
13
  from imio.smartweb.core.testing import ImioSmartwebTestCase
13
14
  from imio.smartweb.core.tests.utils import get_json
@@ -57,7 +58,8 @@ class TestSectionContact(ImioSmartwebTestCase):
57
58
  view = queryMultiAdapter((self.page, self.request), name="full_view")
58
59
  self.assertIn("My contact", view())
59
60
  contact_view = queryMultiAdapter((contact, self.request), name="view")
60
- self.assertIsNone(contact_view.contacts())
61
+ self.assertEqual(contact_view.contacts(), [])
62
+
61
63
  authentic_contact_uid = "2dc381f0fb584381b8e4a19c84f53b35"
62
64
  contact.related_contacts = [authentic_contact_uid]
63
65
  contact_search_url = (
@@ -69,12 +71,13 @@ class TestSectionContact(ImioSmartwebTestCase):
69
71
  authentic_contact_uid
70
72
  )
71
73
  m.get(contact_search_url, exc=requests.exceptions.ConnectTimeout)
72
- self.assertIsNone(contact_view.contacts())
74
+ self.assertEqual(contact_view.contacts(), [])
73
75
  m.get(contact_search_url, status_code=404)
74
- self.assertIsNone(contact_view.contacts())
76
+ self.assertEqual(contact_view.contacts(), [])
75
77
  m.get(contact_search_url, text=json.dumps(self.json_no_contact))
76
- self.assertIsNone(contact_view.contacts())
78
+ self.assertEqual(contact_view.contacts(), [])
77
79
  m.get(contact_search_url, text=json.dumps(self.json_contact))
80
+ clear_cache(self.request)
78
81
  self.assertIsNotNone(contact_view.contacts())
79
82
  json_contact = ContactProperties(self.json_contact.get("items")[0], contact)
80
83
  self.assertEqual(json_contact.contact_type_class, "contact-type-organization")
@@ -113,6 +116,13 @@ class TestSectionContact(ImioSmartwebTestCase):
113
116
  m.get(contact_images_url, text=json.dumps(self.json_contact_images))
114
117
  self.assertIn("contact_gallery", view())
115
118
 
119
+ contact.visible_blocks = ["titles"]
120
+ json_contact = ContactProperties(self.json_contact.get("items")[0], contact)
121
+ images = json_contact.images(contact.image_scale, contact.nb_results_by_batch)
122
+ self.assertNotIn("contact_gallery", view())
123
+ self.assertIsNone(images)
124
+
125
+ contact.visible_blocks = ["titles", "gallery"]
116
126
  json_contact = ContactProperties(self.json_contact.get("items")[0], contact)
117
127
  images = json_contact.images(contact.image_scale, contact.nb_results_by_batch)
118
128
  self.assertEqual(len(images[0]), 2)
@@ -130,6 +140,18 @@ class TestSectionContact(ImioSmartwebTestCase):
130
140
  images = json_contact.images(contact.image_scale, contact.nb_results_by_batch)
131
141
  self.assertIsNone(images)
132
142
 
143
+ @requests_mock.Mocker()
144
+ def test_sorted_contacts_are_empty(self, m):
145
+ # TODO Separate test test_sorted_contacts_is_none /
146
+ # test_sorted_contacts 'cause of Memoize ??!!
147
+ contact = api.content.create(
148
+ container=self.page,
149
+ type="imio.smartweb.SectionContact",
150
+ title="My contact",
151
+ )
152
+ contact_view = queryMultiAdapter((contact, self.request), name="view")
153
+ self.assertEqual(contact_view.contacts(), [])
154
+
133
155
  @requests_mock.Mocker()
134
156
  def test_sorted_contacts(self, m):
135
157
  contact = api.content.create(
@@ -138,7 +160,6 @@ class TestSectionContact(ImioSmartwebTestCase):
138
160
  title="My contact",
139
161
  )
140
162
  contact_view = queryMultiAdapter((contact, self.request), name="view")
141
- self.assertIsNone(contact_view.contacts())
142
163
  authentic_contact_uid = [
143
164
  "2dc381f0fb584381b8e4a19c84f53b35",
144
165
  "af7bd1f547034b24a2e0da16c0ba0358",
@@ -501,7 +522,9 @@ class TestSectionContact(ImioSmartwebTestCase):
501
522
  "afternoonend": "",
502
523
  "comments": "",
503
524
  }
525
+ clear_cache(self.request)
504
526
  m.get(contact_search_url, text=json.dumps(json_contact_empty_schedule))
527
+ view = queryMultiAdapter((self.page, self.request), name="full_view")
505
528
  json_contact = ContactProperties(
506
529
  json_contact_empty_schedule.get("items")[0], contact
507
530
  )
@@ -549,7 +572,6 @@ class TestSectionContact(ImioSmartwebTestCase):
549
572
 
550
573
  annotations = IAnnotations(contact)
551
574
  self.assertIsNone(annotations.get(SECTION_ITEMS_HASH_KEY))
552
-
553
575
  self.assertIsNotNone(contact_view.contacts())
554
576
  hash_1 = annotations.get(SECTION_ITEMS_HASH_KEY)
555
577
  self.assertIsNotNone(hash_1)
@@ -557,15 +579,25 @@ class TestSectionContact(ImioSmartwebTestCase):
557
579
 
558
580
  sleep(1)
559
581
  m.get(contact_search_url, text=json.dumps(self.json_no_contact))
560
- self.assertIsNone(contact_view.contacts())
582
+ clear_cache(self.request)
583
+ contact_view = queryMultiAdapter((contact, self.request), name="view")
584
+ self.assertEqual(contact_view.contacts(), [])
585
+ # refresh_modification_date doesn't calculate when json_data is None
586
+ # For this section, this is the case
587
+ # For other sections, we get json_data with empty "items"
588
+ # Refactoring needed to ensure clarity ?
561
589
  next_modification = self.page.ModificationDate()
562
590
  hash_2 = annotations.get(SECTION_ITEMS_HASH_KEY)
563
- self.assertNotEqual(hash_1, hash_2)
564
- self.assertNotEqual(first_modification, next_modification)
591
+ self.assertEqual(hash_1, hash_2)
592
+ self.assertEqual(first_modification, next_modification)
565
593
 
566
594
  sleep(1)
567
- self.assertIsNone(contact_view.contacts())
595
+ contact_view = queryMultiAdapter((contact, self.request), name="view")
596
+ self.assertEqual(contact_view.contacts(), [])
568
597
  last_modification = self.page.ModificationDate()
569
598
  hash_3 = annotations.get(SECTION_ITEMS_HASH_KEY)
570
599
  self.assertEqual(hash_2, hash_3)
571
600
  self.assertEqual(next_modification, last_modification)
601
+
602
+ # TODO we should test with various contact sections containing
603
+ # contacts
@@ -636,7 +636,8 @@ SendInBlueButtonPosVocabulary = SendInBlueButtonPosVocabularyFactory()
636
636
  class RemoteIADeliberationsInstitutionsVocabularyFactory:
637
637
  def __call__(self, context=None):
638
638
  iadeliberation_url = get_iadeliberation_url_from_registry()
639
- url = f"{iadeliberation_url}/@search?portal_type=Institution&metadata_fields=UID&sort_on=sortable_title"
639
+ # What a devilish request... Get institutions from deliberations. Come on !
640
+ url = f"{iadeliberation_url}/@search?portal_type=Institution&metadata_fields=UID&sort_on=sortable_title&b_size=666"
640
641
  try:
641
642
  json_institutions = get_iadeliberation_json(url)
642
643
  return SimpleVocabulary(
@@ -1 +1 @@
1
- .portaltype-imio-smartweb-directoryview #portal-breadcrumbs,.portaltype-imio-smartweb-directoryview #portal-breadcrumbs .breadcrumb,.portaltype-imio-smartweb-directoryview #portal-header{margin-bottom:0!important}.r-wrapper{position:relative}.r-wrapper figure{display:block}.r-actu-wrapper{padding-top:1rem}.r-result{position:relative}@media screen and (min-width:992px){.r-add-contact,.r-add-event,.r-add-news{bottom:0;padding-bottom:1rem;position:absolute;right:0}}.r-add-contact a,.r-add-event a,.r-add-news a{align-items:center;color:#2d2d2d;display:flex;flex-direction:row;gap:7px;text-decoration:none}.r-add-contact a:focus,.r-add-contact a:hover,.r-add-event a:focus,.r-add-event a:hover,.r-add-news a:focus,.r-add-news a:hover{text-decoration:underline}.r-list-item-link{inset:0!important;position:absolute!important}.r-list-item-group{margin:15px 0;position:relative}.r-list-item{align-items:stretch;display:flex;height:100%}.load-more-link{background:#0000;border:none;display:block;margin:0 auto}.r-content-description{font-weight:500}.r-wrapper .r-result-list .r-list-item-group .r-list-item .r-item-text{flex:min-content}.leaflet-popup{padding:0!important}.leaflet-container{opacity:1!important}.r-content-files{margin:2rem 0}.r-content-files-title{margin-bottom:1rem}.r-content-files .r-content-file{margin:1rem 0}.r-content-files .r-content-file-link{border:1px solid #e6e6e6;display:flex;gap:20px;justify-content:space-between;padding:1rem;text-decoration:none}.r-content-files .r-content-file-link:hover{border:1px solid #868686}.r-content-files span{display:block}.r-content-gallery{margin:2rem 0}.r-content-gallery.flexbin>*,.r-content-gallery.flexbin>*>img{height:226px}.lds-roller-container{background:#ffffffd1;bottom:0;left:0;position:absolute;right:0;top:0}.lds-roller{display:inline-block;height:80px;left:50%;position:absolute;top:5rem;transform:translate(-50%,-50%);width:80px;z-index:0}.lds-roller div{animation:lds-roller 1.2s cubic-bezier(.5,0,.5,1) infinite;transform-origin:40px 40px}.lds-roller div:after{background:#333;border-radius:50%;content:" ";display:block;height:7px;margin:-4px 0 0 -4px;position:absolute;width:7px}.lds-roller div:first-child{animation-delay:-36ms}.lds-roller div:first-child:after{left:63px;top:63px}.lds-roller div:nth-child(2){animation-delay:-72ms}.lds-roller div:nth-child(2):after{left:56px;top:68px}.lds-roller div:nth-child(3){animation-delay:-.108s}.lds-roller div:nth-child(3):after{left:48px;top:71px}.lds-roller div:nth-child(4){animation-delay:-.144s}.lds-roller div:nth-child(4):after{left:40px;top:72px}.lds-roller div:nth-child(5){animation-delay:-.18s}.lds-roller div:nth-child(5):after{left:32px;top:71px}.lds-roller div:nth-child(6){animation-delay:-.216s}.lds-roller div:nth-child(6):after{left:24px;top:68px}.lds-roller div:nth-child(7){animation-delay:-.252s}.lds-roller div:nth-child(7):after{left:17px;top:63px}.lds-roller div:nth-child(8){animation-delay:-.288s}.lds-roller div:nth-child(8):after{left:12px;top:56px}@keyframes lds-roller{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.r-content-figure{aspect-ratio:16/9;background-color:#000;position:relative}.r-content-figure-blur{background-position:50%;background-size:cover;height:100%;left:0;opacity:.8;position:absolute;top:0;width:100%}.r-content-figure-img{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);height:100%;object-fit:cover;object-position:center;position:absolute;width:100%}.r-content-img-blur{height:100%;left:0;position:absolute;top:0;width:100%}.img-cover,.r-content-img-blur{background-position:50%;background-size:cover}.img-contain{background-position:50%;background-repeat:no-repeat;background-size:contain;object-fit:contain;object-position:center}.no-search-item-img{background:#000;background-position:50%!important}.no-search-item-img,.r-search-img{background-color:#c9c9c9!important}.portaltype-imio-smartweb-directoryview #portal-breadcrumbs,.portaltype-imio-smartweb-directoryview #portal-header,.portaltype-imio-smartweb-eventsview #portal-breadcrumbs,.portaltype-imio-smartweb-eventsview #portal-header,.portaltype-imio-smartweb-newsview #portal-breadcrumbs,.portaltype-imio-smartweb-newsview #portal-header{margin-bottom:0!important}.edit-rest-elements{align-items:center;display:flex;flex-direction:column;padding:10px;position:absolute;right:5px;top:5px}.edit-rest-elements-content{top:10px}.edit-rest-elements-news{padding:10px}
1
+ .portaltype-imio-smartweb-directoryview #portal-breadcrumbs,.portaltype-imio-smartweb-directoryview #portal-breadcrumbs .breadcrumb,.portaltype-imio-smartweb-directoryview #portal-header{margin-bottom:0!important}.r-wrapper{position:relative}.r-wrapper figure{display:block}.r-actu-wrapper{padding-top:1rem}.r-result{position:relative}@media screen and (min-width:992px){.r-add-contact,.r-add-event,.r-add-news{bottom:0;padding-bottom:1rem;position:absolute;right:0}}.r-add-contact a,.r-add-event a,.r-add-news a{align-items:center;color:#2d2d2d;display:flex;flex-direction:row;gap:7px;text-decoration:none}.r-add-contact a:focus,.r-add-contact a:hover,.r-add-event a:focus,.r-add-event a:hover,.r-add-news a:focus,.r-add-news a:hover{text-decoration:underline}.r-list-item-link{inset:0!important;position:absolute!important}.r-list-item-group{margin:15px 0;position:relative}.r-list-item{align-items:stretch;display:flex;height:100%}.load-more-link{background:#0000;border:none;display:block;margin:0 auto}.r-content-description{font-weight:500}.r-wrapper .r-result-list .r-list-item-group .r-list-item .r-item-text{flex:min-content}.leaflet-popup{padding:0!important}.leaflet-container{opacity:1!important}.r-content-files{margin:2rem 0}.r-content-files-title{margin-bottom:1rem}.r-content-files .r-content-file{margin:1rem 0}.r-content-files .r-content-file-link{border:1px solid #e6e6e6;display:flex;gap:20px;justify-content:space-between;padding:1rem;text-decoration:none}.r-content-files .r-content-file-link:hover{border:1px solid #868686}.r-content-files .r-content-file-title{align-items:center;display:flex;gap:5px}.r-content-files .r-content-file-title-size{font-size:14px;opacity:.5}.r-content-files span{display:block}.r-content-gallery{margin:2rem 0}.r-content-gallery.flexbin>*,.r-content-gallery.flexbin>*>img{height:226px}.lds-roller-container{background:#ffffffd1;bottom:0;left:0;position:absolute;right:0;top:0}.lds-roller{display:inline-block;height:80px;left:50%;position:absolute;top:5rem;transform:translate(-50%,-50%);width:80px;z-index:0}.lds-roller div{animation:lds-roller 1.2s cubic-bezier(.5,0,.5,1) infinite;transform-origin:40px 40px}.lds-roller div:after{background:#333;border-radius:50%;content:" ";display:block;height:7px;margin:-4px 0 0 -4px;position:absolute;width:7px}.lds-roller div:first-child{animation-delay:-36ms}.lds-roller div:first-child:after{left:63px;top:63px}.lds-roller div:nth-child(2){animation-delay:-72ms}.lds-roller div:nth-child(2):after{left:56px;top:68px}.lds-roller div:nth-child(3){animation-delay:-.108s}.lds-roller div:nth-child(3):after{left:48px;top:71px}.lds-roller div:nth-child(4){animation-delay:-.144s}.lds-roller div:nth-child(4):after{left:40px;top:72px}.lds-roller div:nth-child(5){animation-delay:-.18s}.lds-roller div:nth-child(5):after{left:32px;top:71px}.lds-roller div:nth-child(6){animation-delay:-.216s}.lds-roller div:nth-child(6):after{left:24px;top:68px}.lds-roller div:nth-child(7){animation-delay:-.252s}.lds-roller div:nth-child(7):after{left:17px;top:63px}.lds-roller div:nth-child(8){animation-delay:-.288s}.lds-roller div:nth-child(8):after{left:12px;top:56px}@keyframes lds-roller{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.r-content-figure{aspect-ratio:16/9;background-color:#000;position:relative}.r-content-figure-blur{background-position:50%;background-size:cover;height:100%;left:0;opacity:.8;position:absolute;top:0;width:100%}.r-content-figure-img{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);height:100%;object-fit:cover;object-position:center;position:absolute;width:100%}.r-content-img-blur{height:100%;left:0;position:absolute;top:0;width:100%}.img-cover,.r-content-img-blur{background-position:50%;background-size:cover}.img-contain{background-position:50%;background-repeat:no-repeat;background-size:contain;object-fit:contain;object-position:center}.no-search-item-img{background:#000;background-position:50%!important}.no-search-item-img,.r-search-img{background-color:#c9c9c9!important}.portaltype-imio-smartweb-directoryview #portal-breadcrumbs,.portaltype-imio-smartweb-directoryview #portal-header,.portaltype-imio-smartweb-eventsview #portal-breadcrumbs,.portaltype-imio-smartweb-eventsview #portal-header,.portaltype-imio-smartweb-newsview #portal-breadcrumbs,.portaltype-imio-smartweb-newsview #portal-header{margin-bottom:0!important}.edit-rest-elements{align-items:center;display:flex;flex-direction:column;padding:10px;position:absolute;right:5px;top:5px}.edit-rest-elements-content{top:10px}.edit-rest-elements-news{padding:10px}