- Acesso à informação
Divisor de acesso à informação
- Transparência São Paulo
Divisor de transparência São Paulo
- Legislação
Legislação
- Ouvidoria
Ouvidoria
- 156
O que você procura?
Um erro ocorreu enquanto processava o modelo.
The following has evaluated to null or missing:
==> imageDescription [in template "20095#20121#NOTICIA-EM-DESTAQUE" at line 112, column 37]
----
Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
----
----
FTL stack trace ("~" means nesting-related):
- Failed at: ${imageDescription} [in template "20095#20121#NOTICIA-EM-DESTAQUE" at line 112, column 35]
----
1<#assign prefix = 'psp' />
2<#assign articleId = .vars['reserved-article-id'].data />
3<#assign title = title???then(title.getData(), .vars["reserved-article-title"].data) />
4<#assign categories = restClient.get('/headless-delivery/v1.0/sites/' + groupId + '/structured-contents/by-key/' + articleId + '?fields=taxonomyCategoryBriefs')![] />
5
6<#-- Suporta tanto videoLink (novo) quanto youtubeLink (retrocompatibilidade) -->
7<#assign videoUrl = '' />
8<#if videoLink?? && videoLink.getData()?has_content>
9 <#assign videoUrl = videoLink.getData() />
10<#elseif youtubeLink?? && youtubeLink.getData()?has_content>
11 <#assign videoUrl = youtubeLink.getData() />
12</#if>
13
14<#assign isVideo = videoUrl?has_content />
15
16<#assign imageUrl = (image?? && image.getData()?has_content)?then(image.getData(), '') />
17<#if imageUrl?has_content>
18 <#assign imageFileEntryId = image.getAttribute("fileEntryId") />
19 <#assign imageDescription = image.getAttribute('alt')?has_content?then(image.getAttribute('alt'), 'Imagem de notícia') />
20</#if>
21
22<style>
23.psp-highlighted-new--capital p.psp-highlighted-new__title {
24 font-size: 1rem;
25 line-height: 1rem;
26 font-weight: var(--font-weight-semi-bold);
27 color: var(--white);
28}
29
30@media (min-width: 768px) {
31 .psp-highlighted-new--capital p.psp-highlighted-new__title {
32 font-size: 1.575rem;
33 letter-spacing: -1px;
34 font-weight: bold;
35 line-height: 1.575rem;
36 margin-top: 0;
37 }
38}
39
40.psp-highlighted-new--capital .psp-image-container--highlighted-new {
41 border-radius: 10px !important;
42 position: relative;
43 overflow: hidden;
44}
45
46.psp-highlighted-new--video {
47 border-radius: 10px;
48 display: flex;
49 align-items: center;
50 height: 85px;
51}
52
53.psp-highlighted-new--capital .psp-highlighted-new__text-outer-container {
54 display: flex;
55 justify-content: center;
56 align-items: center;
57}
58
59.psp-highlighted-new--capital .psp-highlighted-new__text-inner-container {
60 max-height: 75px;
61 min-width: 100%;
62}
63
64@media (min-width: 768px) {
65 html.contrast .psp-highlighted-new--capital p.psp-highlighted-new__title {
66 text-shadow: none;
67 }
68}
69
70.psp-scroll-capital__container {
71 overflow-y: auto;
72}
73.psp-scroll-capital__container::-webkit-scrollbar {
74 width: 5px;
75}
76.psp-scroll-capital__container::-webkit-scrollbar-thumb {
77 background: #EC6608;
78 border-radius: 0.25rem;
79}
80.psp-scroll-capital__container--light {
81 overflow-y: auto;
82}
83.psp-scroll-capital__container--light::-webkit-scrollbar {
84 width: 5px;
85}
86.psp-scroll-capital__container--light::-webkit-scrollbar-thumb {
87 background: #fff;
88 border-radius: 0.25rem;
89}
90</style>
91
92<#-- ============================================ -->
93<#-- SEÇÃO DE NOTÍCIAS (NÃO-VÍDEO) -->
94<#-- ============================================ -->
95<#if !isVideo>
96
97 <#assign description = (description?? && description.getData()?has_content)?then(description.getData(), '') >
98
99 <#macro categoriesMarkup prefix categories>
100 <#list categories as key, value>
101 <#list value as category>
102 <span class="${prefix}-badge text-2 font-weight-semi-bold font-base">
103 ${category.taxonomyCategoryName}
104 </span>
105 </#list>
106 </#list>
107 </#macro>
108
109 <a class="${prefix}-highlighted-new ${prefix}-highlighted-new--capital ${prefix}-highlighted-new--${articleId}" href="${friendlyURL}" draggable="false">
110 <#-- ID Adicionado no container da imagem para injeção via JS -->
111 <div class="${prefix}-image-container ${prefix}-image-container--highlighted-new" id="psp-image-container-${articleId}">
112 <span class="sr-only">${imageDescription}</span>
113
114 <#if imageUrl?has_content>
115 <img class="${prefix}-image-container__image" src="${imageUrl}" data-fileentryid="${imageFileEntryId}" alt="${imageDescription}" loading="lazy" draggable="false" />
116 </#if>
117 </div>
118
119 <#assign isTextAndDescriptionHidden = (hideTitleAndDescription?? && hideTitleAndDescription.getData()?has_content)?then(getterUtil.getBoolean(hideTitleAndDescription.getData()), false) >
120
121 <#if !isTextAndDescriptionHidden>
122 <div class="${prefix}-highlighted-new__text-outer-container p-2 mt-2" style="background: var(--primary);">
123
124 <div class="${prefix}-badge__container">
125 <@categoriesMarkup prefix categories />
126 </div>
127
128 <div class="${prefix}-highlighted-new__text-inner-container pl-1 pl-md-2 ${prefix}-scroll-capital__container--light">
129 <p class="${prefix}-highlighted-new__title mb-0 font-base line-clamp-2">
130 <#if altTitle?? && altTitle.getData()?has_content>
131 ${altTitle.getData()}
132 <#else>
133 ${title}
134 </#if>
135 </p>
136 </div>
137 </div>
138 </#if>
139 </a>
140
141 <#-- SCRIPT DO SLIDER ANTES/DEPOIS PARA O CARD -->
142 <script>
143 (function() {
144 const articleId = '${articleId}';
145 const groupId = themeDisplay.getSiteGroupId();
146 const container = document.getElementById('psp-image-container-' + articleId);
147
148 if (!container) return;
149
150 fetch('/o/headless-delivery/v1.0/sites/' + groupId + '/structured-contents/by-key/' + articleId)
151 .then(response => response.json())
152 .then(data => {
153 if (data.relatedContents && data.relatedContents.length > 0) {
154 const imagePromises = data.relatedContents.map(content =>
155 fetch('/o/headless-delivery/v1.0/documents/' + content.id).then(res => res.json())
156 );
157
158 Promise.all(imagePromises).then(documents => {
159 let imgAntes = '';
160 let imgDepois = '';
161
162 documents.forEach(doc => {
163 if (doc.contentUrl) {
164 const title = (doc.title || '').toLowerCase();
165 const url = doc.contentUrl.replace(/(\?|&)download=true/g, '$1download=false');
166
167 if (title.includes('slider-antes')) imgAntes = url;
168 if (title.includes('slider-depois')) imgDepois = url;
169 }
170 });
171
172 if (imgAntes && imgDepois) {
173 // Substitui a imagem estática pela estrutura do Slider, bloqueando comportamentos nativos de drag
174 container.innerHTML = `
175 <div class="cx-slider-wrapper" style="width: 100%; height: 100%; position: absolute; inset: 0;" draggable="false" ondragstart="return false;">
176 <style>
177 .cx-slider-container-${articleId} { --pos: 50%; position: relative; width: 100%; height: 100%; overflow: hidden; display: flex; user-select: none; -webkit-user-drag: none; }
178 .cx-img-panel-${articleId} { position: absolute; inset: 0; width: 100%; height: 100%; }
179 .cx-img-panel-${articleId} img { width: 100%; height: 100%; object-fit: cover; pointer-events: none; display: block; -webkit-user-drag: none; }
180 .cx-img-before-${articleId} { clip-path: polygon(0 0, var(--pos) 0, var(--pos) 100%, 0 100%); z-index: 2; }
181 .cx-img-after-${articleId} { clip-path: polygon(var(--pos) 0, 100% 0, 100% 100%, var(--pos) 100%); z-index: 1; }
182 .cx-label-${articleId} { position: absolute; top: 10px; padding: 4px 10px; background: rgba(0,0,0,0.6); color: #fff; border-radius: 12px; font-family: sans-serif; font-size: 11px; font-weight: bold; pointer-events: none; z-index: 5; }
183 .cx-label-before-${articleId} { left: 10px; }
184 .cx-label-after-${articleId} { right: 10px; }
185 .cx-slider-${articleId} { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; z-index: 10; cursor: ew-resize; margin: 0; }
186 .cx-handle-${articleId} { position: absolute; top: 0; bottom: 0; left: var(--pos); width: 3px; background: #fff; transform: translateX(-50%); pointer-events: none; z-index: 8; box-shadow: 0 0 5px rgba(0,0,0,0.5); }
187 .cx-handle-btn-${articleId} { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 30px; height: 30px; background: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 5px rgba(0,0,0,0.3); }
188 .cx-handle-btn-${articleId} svg { width: 18px; height: 18px; color: #333; }
189 </style>
190 <div class="cx-slider-container-${articleId}" style="--pos: 50%;">
191 <div class="cx-img-panel-${articleId} cx-img-after-${articleId}">
192 <img src="` + imgDepois + `" alt="Depois" draggable="false" />
193 <span class="cx-label-${articleId} cx-label-after-${articleId}">Depois</span>
194 </div>
195 <div class="cx-img-panel-${articleId} cx-img-before-${articleId}">
196 <img src="` + imgAntes + `" alt="Antes" draggable="false" />
197 <span class="cx-label-${articleId} cx-label-before-${articleId}">Antes</span>
198 </div>
199 <input type="range" min="0" max="100" value="50" class="cx-slider-${articleId}" oninput="this.parentElement.style.setProperty('--pos', this.value + '%')" draggable="false" ondragstart="return false;">
200 <div class="cx-handle-${articleId}">
201 <div class="cx-handle-btn-${articleId}">
202 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
203 <path d="M10 16l-4-4 4-4M14 8l4 4-4 4" />
204 </svg>
205 </div>
206 </div>
207 </div>
208 </div>
209 `;
210
211 // O pulo do gato: Interceptando e parando a propagação de TODOS os eventos do slider
212 // Isso impede que clicar e arrastar a barra acione a tag <a> (Link) ou arraste a imagem
213 const rangeInput = container.querySelector('.cx-slider-' + articleId);
214 if(rangeInput) {
215 const stopEvents = ['click', 'mousedown', 'mousemove', 'mouseup', 'touchstart', 'touchmove', 'touchend', 'dragstart'];
216
217 stopEvents.forEach(evt => {
218 rangeInput.addEventListener(evt, function(e) {
219 e.stopPropagation();
220 // Se for clique ou tentativa de drag, evita o comportamento padrão do navegador
221 if (evt === 'dragstart' || evt === 'click') {
222 e.preventDefault();
223 }
224 }, { passive: false });
225 });
226 }
227 }
228 });
229 }
230 })
231 .catch(err => console.error("Erro ao carregar slider antes/depois no card:", err));
232 })();
233 </script>
234
235<#-- ============================================ -->
236<#-- SEÇÃO DE VÍDEOS (UNIVERSAL COM AUTO-DETECÇÃO) -->
237<#-- ============================================ -->
238<#else>
239
240 <#-- ========================================== -->
241 <#-- FUNÇÃO PARA ADICIONAR PARÂMETROS NA URL -->
242 <#-- ========================================== -->
243 <#function addUrlParameter url paramName paramValue>
244 <#if url?contains("?")>
245 <#-- URL já tem parâmetros, adiciona com & -->
246 <#if url?contains(paramName + "=")>
247 <#-- Parâmetro já existe, não adiciona duplicado -->
248 <#return url />
249 <#else>
250 <#return url + "&" + paramName + "=" + paramValue />
251 </#if>
252 <#else>
253 <#-- URL não tem parâmetros, adiciona com ? -->
254 <#return url + "?" + paramName + "=" + paramValue />
255 </#if>
256 </#function>
257
258 <#-- ========================================== -->
259 <#-- PROCESSAMENTO DA URL COM PARÂMETROS -->
260 <#-- ========================================== -->
261 <#assign processedUrl = videoUrl />
262
263 <#-- Detecta e processa YouTube -->
264 <#if videoUrl?contains("youtube.com") || videoUrl?contains("youtu.be") || videoUrl?contains("youtube-nocookie.com")>
265 <#assign processedUrl = addUrlParameter(processedUrl, "autoplay", "1") />
266 <#assign processedUrl = addUrlParameter(processedUrl, "mute", "0") />
267 <#assign processedUrl = addUrlParameter(processedUrl, "rel", "0") />
268 <#assign processedUrl = addUrlParameter(processedUrl, "controls", "0") />
269 <#assign processedUrl = addUrlParameter(processedUrl, "modestbranding", "1") />
270
271 <#-- Detecta e processa Vimeo -->
272 <#elseif videoUrl?contains("vimeo.com")>
273 <#assign processedUrl = addUrlParameter(processedUrl, "autoplay", "1") />
274 <#assign processedUrl = addUrlParameter(processedUrl, "title", "0") />
275 <#assign processedUrl = addUrlParameter(processedUrl, "byline", "0") />
276 <#assign processedUrl = addUrlParameter(processedUrl, "portrait", "0") />
277
278 <#-- Detecta e processa Dailymotion -->
279 <#elseif videoUrl?contains("dailymotion.com")>
280 <#assign processedUrl = addUrlParameter(processedUrl, "autoplay", "1") />
281 <#assign processedUrl = addUrlParameter(processedUrl, "ui-start-screen-info", "0") />
282 <#assign processedUrl = addUrlParameter(processedUrl, "sharing-enable", "0") />
283
284 <#-- Detecta e processa Wistia -->
285 <#elseif videoUrl?contains("wistia.com") || videoUrl?contains("wistia.net")>
286 <#assign processedUrl = addUrlParameter(processedUrl, "autoPlay", "1") />
287 <#assign processedUrl = addUrlParameter(processedUrl, "controlsVisibleOnLoad", "true") />
288
289 <#-- Detecta e processa Facebook Video -->
290 <#elseif videoUrl?contains("facebook.com")>
291 <#assign processedUrl = addUrlParameter(processedUrl, "autoplay", "1") />
292 <#assign processedUrl = addUrlParameter(processedUrl, "show_text", "0") />
293
294 <#-- Detecta e processa Twitch -->
295 <#elseif videoUrl?contains("twitch.tv")>
296 <#assign processedUrl = addUrlParameter(processedUrl, "autoplay", "true") />
297 <#assign processedUrl = addUrlParameter(processedUrl, "parent", "localhost") />
298
299 <#-- Para outras plataformas, tenta adicionar autoplay genérico -->
300 <#else>
301 <#assign processedUrl = addUrlParameter(processedUrl, "autoplay", "1") />
302 </#if>
303
304 <#-- ========================================== -->
305 <#-- RENDERIZAÇÃO DO VÍDEO -->
306 <#-- ========================================== -->
307
308 <div class="${prefix}-highlighted-new ${prefix}-highlighted-new--capital ${prefix}-highlighted-new--${articleId}">
309 <div class="${prefix}-image-container ${prefix}-image-container--highlighted-new">
310
311 <#-- Exibe imagem customizada se existir, senão exibe um placeholder -->
312 <#if imageUrl?has_content>
313 <img class="${prefix}-image-container__image" src="${imageUrl}" data-fileentryid="${imageFileEntryId}" alt="${imageDescription}" loading="lazy" style="border-radius: 10px" />
314 <#else>
315 <div class="${prefix}-image-container__image" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; min-height: 200px; display: flex; align-items: center; justify-content: center;">
316 <svg width="80" height="80" viewBox="0 0 24 24" fill="white" opacity="0.8">
317 <path d="M8 5v14l11-7z"/>
318 </svg>
319 </div>
320 </#if>
321
322 <div class="${prefix}-image-container__content justify-content-end">
323 <#-- Botão de Play -->
324 <a title="Executar vídeo" id="video-play-button-${randomNamespace}" href="#" onclick="event.preventDefault(); playVideo${randomNamespace}()" class="position-absolute top-0 left-0 w-100 h-100 d-flex align-items-center justify-content-center z-index-1" style="background-color: rgba(0,0,0,0.35); border-radius:10px">
325 <span class="sr-only">Executar vídeo</span>
326 <svg class="d-none d-md-block" fill="#FFFFFF" height="160" width="160" version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60">
327 <g>
328 <path d="M45.563,29.174l-22-15c-0.307-0.208-0.703-0.231-1.031-0.058C22.205,14.289,22,14.629,22,15v30 c0,0.371,0.205,0.711,0.533,0.884C22.679,45.962,22.84,46,23,46c0.197,0,0.394-0.059,0.563-0.174l22-15 C45.836,30.64,46,30.331,46,30S45.836,29.36,45.563,29.174z M24,43.107V16.893L43.225,30L24,43.107z"></path>
329 <path d="M30,0C13.458,0,0,13.458,0,30s13.458,30,30,30s30-13.458,30-30S46.542,0,30,0z M30,58C14.561,58,2,45.439,2,30 S14.561,2,30,2s28,12.561,28,28S45.439,58,30,58z"></path>
330 </g>
331 </svg>
332 <svg class="d-md-none" fill="#FFFFFF" height="60" width="60" version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60">
333 <g>
334 <path d="M45.563,29.174l-22-15c-0.307-0.208-0.703-0.231-1.031-0.058C22.205,14.289,22,14.629,22,15v30 c0,0.371,0.205,0.711,0.533,0.884C22.679,45.962,22.84,46,23,46c0.197,0,0.394-0.059,0.563-0.174l22-15 C45.836,30.64,46,30.331,46,30S45.836,29.36,45.563,29.174z M24,43.107V16.893L43.225,30L24,43.107z"></path>
335 <path d="M30,0C13.458,0,0,13.458,0,30s13.458,30,30,30s30-13.458,30-30S46.542,0,30,0z M30,58C14.561,58,2,45.439,2,30 S14.561,2,30,2s28,12.561,28,28S45.439,58,30,58z"></path>
336 </g>
337 </svg>
338 </a>
339
340 <#-- Iframe universal com URL processada -->
341 <iframe id="iframe-video-${randomNamespace}"
342 class="d-none border-none w-100 h-100"
343 data-src="${processedUrl}"
344 title="Vídeo - ${title}"
345 frameborder="0"
346 allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
347 referrerpolicy="strict-origin-when-cross-origin"
348 allowfullscreen>
349 </iframe>
350 </div>
351 </div>
352
353 <#assign isTextHidden = (hideTitle?? && hideTitle.getData()?has_content)?then(getterUtil.getBoolean(hideTitle.getData()), false) >
354 <#assign linkDirecionamento = (linkDirecionamentoVideo?? && linkDirecionamentoVideo.getData()?has_content)?then(linkDirecionamentoVideo.getData(), '') >
355 <#assign hasLink = linkDirecionamento?has_content >
356
357 <#if !isTextHidden>
358 <div id="video-title-${randomNamespace}" class="${prefix}-highlighted-new--video p-2 mt-2 z-index-1" style="background: var(--primary);">
359 <div class="pl-1 pl-md-2">
360 <#if hasLink>
361 <a href="${linkDirecionamento}" class="text-decoration-none" target="_blank" rel="noopener noreferrer">
362 <p class="${prefix}-highlighted-new__title mb-0 font-base line-clamp-2">
363 <#if altTitle?? && altTitle.getData()?has_content>
364 ${altTitle.getData()}
365 <#else>
366 ${title}
367 </#if>
368 </p>
369 </a>
370 <#else>
371 <p class="${prefix}-highlighted-new__title mb-0 font-base line-clamp-2">
372 <#if altTitle?? && altTitle.getData()?has_content>
373 ${altTitle.getData()}
374 <#else>
375 ${title}
376 </#if>
377 </p>
378 </#if>
379 </div>
380 </div>
381 </#if>
382 </div>
383
384 <#-- Scripts de controle de vídeo -->
385 <script>
386 function playVideo${randomNamespace}() {
387 let swiper = document.querySelector(".psp-news-highlighter > .swiper").swiper;
388 let $iframe = document.getElementById('iframe-video-${randomNamespace}');
389 let $title = document.getElementById('video-title-${randomNamespace}');
390 let $playButton = document.getElementById('video-play-button-${randomNamespace}');
391
392 // Exibe o iframe e carrega a URL do vídeo
393 $iframe.classList.remove('d-none');
394 $iframe.setAttribute('src', $iframe.getAttribute('data-src'));
395
396 // Oculta o botão de play
397 $playButton.classList.add('d-none');
398 $playButton.classList.remove('d-flex');
399
400 // Para o autoplay do carrossel
401 if (swiper && swiper.autoplay) {
402 swiper.autoplay.stop();
403 swiper.on('slideChange', stopVideo${randomNamespace});
404 }
405 }
406
407 function stopVideo${randomNamespace}() {
408 let swiper = document.querySelector(".psp-news-highlighter > .swiper").swiper;
409 let $iframe = document.getElementById('iframe-video-${randomNamespace}');
410 let $title = document.getElementById('video-title-${randomNamespace}');
411 let $playButton = document.getElementById('video-play-button-${randomNamespace}');
412
413 // Oculta o iframe e remove a source (para o vídeo)
414 $iframe.classList.add('d-none');
415 $iframe.setAttribute('src', '');
416
417 // Mostra o botão de play novamente
418 $playButton.classList.remove('d-none');
419 $playButton.classList.add('d-flex');
420
421 // Reinicia o autoplay do carrossel
422 if (swiper && swiper.autoplay) {
423 swiper.autoplay.start();
424 swiper.on('slideChange', function() {});
425 }
426 }
427 </script>
428
429</#if>
Agenda do prefeito
Sexta-feira, 23 de Outubro de 2026
Não há eventos hoje
swipe_up Deslize para ver todos os eventos
geosampa
HAND TALK
Clique neste componente para ter acesso as configurações do plugin Hand Talk
Nó: liferay-6bf6474f9c-2k8z8:8080: