Skip to content

Instantly share code, notes, and snippets.

@prince-neres
Last active July 22, 2025 02:21
Show Gist options
  • Select an option

  • Save prince-neres/090162aa3da4d39c3f3d09a93c74cc5d to your computer and use it in GitHub Desktop.

Select an option

Save prince-neres/090162aa3da4d39c3f3d09a93c74cc5d to your computer and use it in GitHub Desktop.
Liferay card fragment
{
"fieldSets": [
{
"fields": [
{
"name": "horizontalCard",
"label": "Card Horizonal",
"type": "checkbox",
"defaultValue": true
},
{
"name": "hideImage",
"label": "Esconder Imagem",
"type": "checkbox",
"defaultValue": false
},
{
"name": "hideCategories",
"label": "Esconder Categorias",
"type": "checkbox",
"defaultValue": false
},
{
"name": "categoryBadgeBgColor",
"label": "Cor de Fundo da Categoria",
"type": "colorPicker",
"defaultValue": "var(--color-state-info-lighten-2)"
},
{
"name": "categoryBadgeTextColor",
"label": "Cor do Texto da Categoria",
"type": "colorPicker",
"defaultValue": "var(--info)"
},
{
"name": "hideOverlineText",
"label": "Esconder Sobrelinha",
"type": "checkbox",
"defaultValue": true
},
{
"name": "hideText",
"label": "Esconder Texto",
"type": "checkbox",
"defaultValue": false
},
{
"name": "hideButton",
"label": "Esconder Botão",
"type": "checkbox",
"defaultValue": true
},
{
"name": "hideShareIcon",
"label": "Esconder Ícone de Compartilhar",
"type": "checkbox",
"defaultValue": true
},
{
"name": "hideDateText",
"label": "Esconder Texto de Data",
"type": "checkbox",
"defaultValue": false
},
{
"name": "dateTextFormat",
"label": "Formato do Texto da Data",
"type": "select",
"dataType": "string",
"typeOptions": {
"validValues": [
{
"label": "Atualizado pela ultima vez 10 Horas.",
"value": "news"
},
{
"label": "Postado em 1 de Janeiro de 2024.",
"value": "events"
},
{
"label": "01/01/2024",
"value": "default"
}
]
},
"defaultValue": "default"
},
{
"name": "titleLineClamp",
"label": "Limite de Linhas do Título",
"type": "select",
"dataType": "int",
"typeOptions": {
"validValues": [
{"value": "1"},
{"value": "2"},
{"value": "3"},
{"value": "4"},
{"value": "5"}
]
},
"defaultValue": "3"
},
{
"name": "textLineClamp",
"label": "Limite de Linhas do Texto",
"type": "select",
"dataType": "int",
"typeOptions": {
"validValues": [
{"value": "1"},
{"value": "2"},
{"value": "3"},
{"value": "4"},
{"value": "5"}
]
},
"defaultValue": "3"
}
]
}
]
}
{
"configurationPath": "configuration.json",
"jsPath": "index.js",
"htmlPath": "index.html",
"cssPath": "index.css",
"icon": "code",
"name": "Card Customizado",
"type": "component"
}
.lfr-layout-structure-item-collection .container-fluid:has(.custom-card) {
overflow: visible !important;
}
.custom-card {
border: none;
box-shadow: 0 2px 6px 0 #00000038;
border-radius: var(--border-radius-lg);
overflow: hidden;
}
.custom-card .card-img {
border-radius: 0;
object-fit: cover;
object-position: center;
}
.custom-card .card-img-left {
height: 100%;
object-fit: cover;
}
.custom-card .card-img-top {
height: 9.375rem;
}
.card-horizontal .card-body {
padding: 0.938rem 1.563rem;
}
.card-vertical .card-body {
padding: 1.563rem 0.938rem;
}
.custom-card .card-text-overline p {
font-weight: bold;
font-size: var(--text-small-caps-font-size);
margin-bottom: var(--spacer-1);
}
.custom-card .card-title a {
text-underline-offset: auto;
}
.custom-card .card-title {
line-height: 1.5rem;
}
.custom-card .card-title {
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
}
.custom-card .card-text {
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
}
.custom-card .card-text a {
color: var(--color-neutral-8) !important;
text-decoration: none !important;
}
.custom-card .card-categories {
max-height: 3.5rem;
overflow: hidden;
}
.custom-card .card-categories span {
text-wrap: nowrap;
}
.custom-card .card-btn {
color: var(--color-brand-primary-darken-5);
font-weight: bold;
border: 2px solid var(--color-brand-primary-darken-5);
border-radius: var(--border-radius);
text-decoration: none;
}
.custom-card .card-share-link {
cursor: pointer;
}
.custom-card .card-share-link svg {
fill: var(--color-neutral-4);
height: 1.5rem;
}
.custom-card .card-text,
.custom-card .card-date {
font-size: var(--text-paragraph-sm-font-size);
}
[#assign assetEntryLocalService = serviceLocator.findService("com.liferay.asset.kernel.service.AssetEntryLocalService") /]
[#assign groupLocalService = serviceLocator.findService("com.liferay.portal.kernel.service.GroupLocalService") /]
[#assign journalArticleLocalService = serviceLocator.findService("com.liferay.journal.service.JournalArticleLocalService") /]
[#assign
groupFriendlyURL = groupLocalService.getGroup(themeDisplay.getSiteGroupId()).getFriendlyURL()
objectItemReference = (request.getAttribute("INFO_ITEM_REFERENCE"))!""
entryCategories = ["Categoria 1", "Categoria 2"]
cardDateMessage = ""
cardOrientationSlug = configuration.horizontalCard?then('horizontal', 'vertical')
cardDateTextFormat = configuration.dateTextFormat
entryModifiedDate = ""
entryPublishDate = ""
title = ""
displayPageUrl = ""
/]
[#if objectItemReference != ""]
[#assign
classPK = objectItemReference.getInfoItemIdentifier().getClassPK()!0
className = objectItemReference.getClassName()!""
entry = assetEntryLocalService.getEntry(className, classPK)
entryCategories = entry.getCategories()
entryModifiedDate = entry.getModifiedDate()
entryPublishDate = entry.getPublishDate()
journalArticle = journalArticleLocalService.fetchLatestArticle(classPK)
assetRenderer = entry.getAssetRenderer()!""
displayPageUrl = "/web" + groupFriendlyURL + "/w/" + assetRenderer.getUrlTitle()
title = entry.getTitle(locale)
/]
[/#if]
<div class="custom-card card card-${cardOrientationSlug}">
[#if configuration.horizontalCard]
<div class="row no-gutters">
<div class="${configuration.hideImage?then('', 'col-4')}">
[/#if]
[#if !configuration.hideImage]
<img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAeAAAAEOCAYAAABRmsRnAAAACXBIWXMAAC4jAAAuIwF4pT92AAAK9klEQVR4Xu3VUQnAMBAFwcS/kEA9VFsLEXH7M2fgwXCw+zzvtxwBAgQIECAwKrAFeNTbGAECBAgQuAIC7BEIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAgLsBwgQIECAQCAgwAG6SQIECBAgIMB+gAABAgQIBAICHKCbJECAAAECAuwHCBAgQIBAICDAAbpJAgQIECAgwH6AAAECBAgEAgIcoJskQIAAAQIC7AcIECBAgEAgIMABukkCBAgQICDAfoAAAQIECAQCAhygmyRAgAABAj/ibTjyR8Zs7wAAAABJRU5ErkJggg=="
class="card-img ${configuration.horizontalCard?then('card-img-left', 'card-img-top')}"
data-lfr-editable-type="image"
data-lfr-editable-id="Imagem"
alt="..."
/>
[/#if]
[#if configuration.horizontalCard]
</div>
<div class="${configuration.hideImage?then('', 'col-8')}">
[/#if]
<div class="card-body">
[#if entryCategories?size > 0 && !configuration.hideCategories]
<div class="card-categories d-flex flex-wrap">
[#assign badgeBgColor = configuration.categoryBadgeBgColor /]
[#assign badgeTextColor = configuration.categoryBadgeTextColor /]
[#list 0..entryCategories?size-1 as category]
<span
class="rounded font-weight-bold p-2 mb-3 ${(category==0)?then('mr-3', '')}"
style="background-color: ${badgeBgColor}; color: ${badgeTextColor};"
>
[#if entryCategories[category]?is_hash]
${entryCategories[category].getTitle(locale)}
[#else]
${entryCategories[category]}
[/#if]
</span>
[#if category == 2]
[#break]
[/#if]
[/#list]
</div>
[/#if]
[#if !configuration.hideOverlineText]
<div class="card-text-overline">
<p
data-lfr-editable-type="text"
data-lfr-editable-id="Sobrelinha"
>
SOBRELINHA
</p>
</div>
[/#if]
<h3
class="card-title mb-3"
data-lfr-editable-type="text"
data-lfr-editable-id="Título"
style="-webkit-line-clamp: ${configuration.titleLineClamp};"
>
Título
</h3>
[#if !configuration.hideText]
<p
class="card-text"
data-lfr-editable-type="text"
data-lfr-editable-id="Texto"
style="-webkit-line-clamp: ${configuration.textLineClamp};"
>
${configuration.horizontalCard?then(
"Este é um cartão mais largo com texto de apoio abaixo como uma introdução natural para conteúdo adicional. Este conteúdo é um pouco mais longo.",
"Um texto de exemplo rápido para desenvolver o título do cartão e compor a maior parte do conteúdo do cartão."
)}
</p>
[/#if]
[#if (!configuration.hideButton || !configuration.hideShareIcon)]
<div class="card-actions w-100 d-flex justify-content-between align-items-center mb-3">
[#if !configuration.hideButton]
<a
class="card-btn btn btn-link px-3 py-2"
data-lfr-editable-type="link"
data-lfr-editable-id="Botão"
>
Botão
</a>
[/#if]
[#if !configuration.hideShareIcon]
<span
class="card-share-link"
data-share-title="${title}"
data-share-url="${displayPageUrl}"
>
[@clay["icon"] symbol="share" /]
</span>
[/#if]
</div>
[/#if]
[#if !configuration.hideDateText && entryPublishDate?is_date]
<p
class="card-date mb-0"
[#if cardDateTextFormat == "default"]
data-lfr-editable-type="date-time"
data-lfr-editable-id="Texto de Data"
[/#if]
>
[#if cardDateTextFormat == "default"]
${entryPublishDate?date?string("dd/MM/yyyy")}
[#elseif cardDateTextFormat == "news"]
${getLastUpdateTime(entryModifiedDate)}
[#elseif cardDateTextFormat == "events"]
${getPostDate(entryPublishDate)}
[/#if]
</p>
[/#if]
</div>
[#if configuration.horizontalCard]
</div>
[/#if]
</div>
[#function getPostDate previous]
[#assign
monthKey = previous?date?string("MMMM")
month = languageUtil.get(locale, monthKey?lower_case)
dateFormat = (locale == 'pt_BR')?then("d 'de ${month} de' yyyy","'${month}' d, yyyy")
formattedDate = previous?date?string(dateFormat)
/]
[#return languageUtil.format(locale, "posted-on-x", formattedDate) /]
[/#function]
[#function getLastUpdateTime previous]
[#assign current = .now
elapsed = current?long - previous?date?long
msPerMinute = 60 * 1000
msPerHour = msPerMinute * 60
msPerDay = msPerHour * 24
msPerMonth = msPerDay * 30
msPerYear = msPerDay * 365
/]
[#if elapsed < msPerMinute]
[#assign seconds = (elapsed / 1000)?string('0') /]
[#return "${languageUtil.get(locale, 'last-updated')} ${seconds} ${(seconds == '1')?then(languageUtil.get(locale, 'second'), languageUtil.get(locale, 'seconds'))}" /]
[#elseif elapsed < msPerHour]
[#assign minutes = (elapsed / msPerMinute)?string('0') /]
[#return "${languageUtil.get(locale, 'last-updated')} ${minutes} ${(minutes == '1')?then(languageUtil.get(locale, 'minute'), languageUtil.get(locale, 'minutes'))}" /]
[#elseif elapsed < msPerDay]
[#assign hours = (elapsed / msPerHour)?string('0') /]
[#return "${languageUtil.get(locale, 'last-updated')} ${hours} ${(hours == '1')?then(languageUtil.get(locale, 'hour'), languageUtil.get(locale, 'hours'))}" /]
[#elseif elapsed < msPerMonth]
[#assign days = (elapsed / msPerDay)?string('0') /]
[#return "${languageUtil.get(locale, 'last-updated')} ${days} ${(days == '1')?then(languageUtil.get(locale, 'day'), languageUtil.get(locale, 'days'))}" /]
[#elseif elapsed < msPerYear]
[#assign months = (elapsed / msPerMonth)?string('0') /]
[#return "${languageUtil.get(locale, 'last-updated')} ${months} ${(months == '1')?then(languageUtil.get(locale, 'month'), languageUtil.get(locale, 'months'))}" /]
[#else]
[#assign years = (elapsed / msPerYear)?string('0') /]
[#return "${languageUtil.get(locale, 'last-updated')} ${years} ${(years == '1')?then(languageUtil.get(locale, 'year'), languageUtil.get(locale, 'years'))}" /]
[/#if]
[/#function]
if (!configuration.hideShareIcon) {
const shareIcon = fragmentElement.querySelector(".card-share-link");
shareIcon.addEventListener("click", async () => {
const shareTitle = shareIcon.dataset.shareTitle;
const shareUrl = shareIcon.dataset.shareUrl;
if (navigator.share) {
try {
await navigator.share({
title: document.title,
text: shareTitle,
url: window.location.host + shareUrl,
});
} catch (error) {
console.error("Error sharing: ", error);
}
}
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment