feat: implement livestream post functionality and update site theme assets
This commit is contained in:
@@ -56,6 +56,8 @@ public class PostController {
|
||||
return "posts/sidebar";
|
||||
case FULL_WIDTH:
|
||||
return "posts/full-width";
|
||||
case LIVESTREAM:
|
||||
return "posts/livestream";
|
||||
case STANDARD:
|
||||
default:
|
||||
return "posts/standard";
|
||||
|
||||
+1
-1
@@ -156,7 +156,7 @@ public class ManagePostController {
|
||||
}
|
||||
postService.saveWithTags(post, tagNames);
|
||||
redirectAttributes.addFlashAttribute("successMessage", "Post updated successfully!");
|
||||
return "redirect:/manage/posts";
|
||||
return "redirect:/manage/posts/" + id + "/edit";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -63,6 +63,10 @@ public class Post extends AbstractAuditingEntity<Long> {
|
||||
@Column(name = "event_time")
|
||||
private java.time.Instant eventTime;
|
||||
|
||||
@Size(max = 255)
|
||||
@Column(name = "location", length = 255)
|
||||
private String location;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "category_id")
|
||||
private Category category;
|
||||
@@ -166,6 +170,14 @@ public class Post extends AbstractAuditingEntity<Long> {
|
||||
this.eventTime = eventTime;
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public void setLocation(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
@jakarta.persistence.Transient
|
||||
public String getEventTimeLocal() {
|
||||
if (this.eventTime != null) {
|
||||
|
||||
@@ -7,5 +7,6 @@ public enum PostLayout {
|
||||
STANDARD,
|
||||
SIDEBAR,
|
||||
FULL_WIDTH,
|
||||
EVENT
|
||||
EVENT,
|
||||
LIVESTREAM
|
||||
}
|
||||
|
||||
+109
-9
@@ -1,19 +1,20 @@
|
||||
package com.sisvietnamvn.web.service;
|
||||
|
||||
import com.sisvietnamvn.web.domain.ComponentTemplate;
|
||||
import com.sisvietnamvn.web.domain.Menu;
|
||||
import com.sisvietnamvn.web.domain.MenuItem;
|
||||
import com.sisvietnamvn.web.repository.ComponentTemplateRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.sisvietnamvn.web.domain.ComponentTemplate;
|
||||
import com.sisvietnamvn.web.domain.Menu;
|
||||
import com.sisvietnamvn.web.domain.MenuItem;
|
||||
import com.sisvietnamvn.web.repository.ComponentTemplateRepository;
|
||||
|
||||
/**
|
||||
* Service for managing Component Templates and rendering them with dynamic data.
|
||||
*
|
||||
@@ -221,6 +222,8 @@ public class ComponentTemplateService {
|
||||
itemHtml = itemHtml.replace("{{title}}", safeTitle);
|
||||
itemHtml = itemHtml.replace("{{slug}}", post.getSlug() != null ? post.getSlug() : "");
|
||||
itemHtml = itemHtml.replace("{{excerpt}}", safeExcerpt);
|
||||
itemHtml = itemHtml.replace("{{content}}", post.getContent() != null ? parseContentToHtml(post.getContent()) : "");
|
||||
itemHtml = itemHtml.replace("{{metaDescription}}", post.getMetaDescription() != null ? org.springframework.web.util.HtmlUtils.htmlEscape(post.getMetaDescription()) : "");
|
||||
itemHtml = itemHtml.replace("{{featuredImageUrl}}", post.getFeaturedImage() != null ? post.getFeaturedImage() : "");
|
||||
String dateStr = post.getCreatedDate() != null ? formatter.format(post.getCreatedDate()) : "";
|
||||
itemHtml = itemHtml.replace("{{date}}", dateStr);
|
||||
@@ -235,6 +238,13 @@ public class ComponentTemplateService {
|
||||
itemHtml = itemHtml.replace("{{eventTimeStr}}", "");
|
||||
}
|
||||
|
||||
if (post.getLocation() != null && !post.getLocation().isEmpty()) {
|
||||
String locHtml = "<div class=\"event-location\">" + org.springframework.web.util.HtmlUtils.htmlEscape(post.getLocation()) + "</div>";
|
||||
itemHtml = itemHtml.replace("{{locationHtml}}", locHtml);
|
||||
} else {
|
||||
itemHtml = itemHtml.replace("{{locationHtml}}", "");
|
||||
}
|
||||
|
||||
itemHtml = itemHtml.replace("{{index}}", String.valueOf(index));
|
||||
rendered.append(itemHtml);
|
||||
index++;
|
||||
@@ -257,6 +267,56 @@ public class ComponentTemplateService {
|
||||
public void seedDefaults() {
|
||||
seedCtaButtons();
|
||||
seedInfoDropdown();
|
||||
seedNewsGrid();
|
||||
}
|
||||
|
||||
private void seedNewsGrid() {
|
||||
Optional<ComponentTemplate> existing = templateRepository.findBySlug("news-grid");
|
||||
if (existing.isPresent()) {
|
||||
ComponentTemplate t = existing.get();
|
||||
if (t.getHtmlTemplate() == null || !t.getHtmlTemplate().contains("<style>")) {
|
||||
return; // Already clean
|
||||
}
|
||||
// Update the existing one
|
||||
t.setHtmlTemplate(getNewsGridHtml());
|
||||
templateRepository.save(t);
|
||||
LOG.info("Cleaned up hardcoded CSS in news-grid component template");
|
||||
return;
|
||||
}
|
||||
|
||||
ComponentTemplate t = new ComponentTemplate();
|
||||
t.setSlug("news-grid");
|
||||
t.setName("News Grid");
|
||||
t.setDescription("Dynamic grid layout for news.");
|
||||
t.setActive(true);
|
||||
t.setHtmlTemplate(getNewsGridHtml());
|
||||
templateRepository.save(t);
|
||||
LOG.info("Seeded default component template: news-grid");
|
||||
}
|
||||
|
||||
private String getNewsGridHtml() {
|
||||
return "{{#each news_posts}}\n" +
|
||||
"<div class=\"featured-grid__item\">\n" +
|
||||
" <article class=\"news-card news-card--featured\">\n" +
|
||||
" <div class=\"news-card__image\">\n" +
|
||||
" <div>\n" +
|
||||
" <img loading=\"lazy\" src=\"{{featuredImageUrl}}\" alt=\"{{title}}\">\n" +
|
||||
" </div>\n" +
|
||||
" </div>\n" +
|
||||
" <div class=\"news-card__content\">\n" +
|
||||
" <div class=\"featured-grid__category\">\n" +
|
||||
" <a href=\"#\">TIN TỨC</a>\n" +
|
||||
" </div>\n" +
|
||||
" <h3 class=\"news-card__title\">\n" +
|
||||
" <a href=\"/post/{{slug}}\" rel=\"bookmark\">\n" +
|
||||
" <span>{{title}}</span>\n" +
|
||||
" </a>\n" +
|
||||
" </h3>\n" +
|
||||
" <div class=\"news-card__date\">{{date}}</div>\n" +
|
||||
" </div>\n" +
|
||||
" </article>\n" +
|
||||
"</div>\n" +
|
||||
"{{/each}}";
|
||||
}
|
||||
|
||||
private void seedCtaButtons() {
|
||||
@@ -325,4 +385,44 @@ public class ComponentTemplateService {
|
||||
templateRepository.save(t);
|
||||
LOG.info("Seeded default component template: info-dropdown");
|
||||
}
|
||||
|
||||
private String parseContentToHtml(String content) {
|
||||
if (content == null || !content.trim().startsWith("{")) {
|
||||
return content;
|
||||
}
|
||||
try {
|
||||
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
java.util.Map<String, Object> data = mapper.readValue(content, new com.fasterxml.jackson.core.type.TypeReference<>() {});
|
||||
if (data.containsKey("blocks")) {
|
||||
java.util.List<java.util.Map<String, Object>> blocks = (java.util.List<java.util.Map<String, Object>>) data.get("blocks");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (java.util.Map<String, Object> block : blocks) {
|
||||
String type = (String) block.get("type");
|
||||
java.util.Map<String, Object> blockData = (java.util.Map<String, Object>) block.get("data");
|
||||
if (blockData != null) {
|
||||
if ("paragraph".equals(type)) {
|
||||
sb.append("<p>").append(blockData.get("text")).append("</p>");
|
||||
} else if ("header".equals(type)) {
|
||||
sb.append("<h").append(blockData.get("level")).append(">").append(blockData.get("text")).append("</h").append(blockData.get("level")).append(">");
|
||||
} else if ("list".equals(type)) {
|
||||
String style = (String) blockData.get("style");
|
||||
String listTag = "ordered".equals(style) ? "ol" : "ul";
|
||||
sb.append("<").append(listTag).append(">");
|
||||
java.util.List<String> items = (java.util.List<String>) blockData.get("items");
|
||||
if (items != null) {
|
||||
for (String item : items) {
|
||||
sb.append("<li>").append(item).append("</li>");
|
||||
}
|
||||
}
|
||||
sb.append("</").append(listTag).append(">");
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Fallback to raw content if parsing fails
|
||||
}
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-3
@@ -6,12 +6,16 @@
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd">
|
||||
|
||||
<changeSet id="20260714080000-1" author="antigravity">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM sis_component_template WHERE slug = 'news-grid'</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="sis_component_template">
|
||||
<column name="id" valueComputed="(select coalesce(max(id), 0) + 1 from sis_component_template)"/>
|
||||
<column name="slug" value="news-grid"/>
|
||||
<column name="name" value="News Grid"/>
|
||||
<column name="description" value="Dynamic grid layout for news."/>
|
||||
<column name="active" valueBoolean="true"/>
|
||||
<column name="created_by" value="system"/>
|
||||
<column name="html_template"><![CDATA[{{#each news_posts}}
|
||||
<div class="featured-grid__item">
|
||||
<article class="news-card news-card--featured">
|
||||
@@ -43,6 +47,7 @@
|
||||
<column name="name" value="Events List"/>
|
||||
<column name="description" value="List layout for events."/>
|
||||
<column name="active" valueBoolean="true"/>
|
||||
<column name="created_by" value="system"/>
|
||||
<column name="html_template"><![CDATA[<style>
|
||||
.campus-events-widget { background: transparent; padding: 0; font-family: inherit; }
|
||||
.campus-events-widget .events-list { list-style: none; margin: 0; padding: 0; }
|
||||
@@ -95,6 +100,8 @@
|
||||
</div>]]></column>
|
||||
<column name="created_date" valueComputed="CURRENT_TIMESTAMP"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="20260714080000-3" author="antigravity" runAlways="true">
|
||||
<update tableName="sis_component_template">
|
||||
<column name="html_template"><![CDATA[{{#each news_posts}}
|
||||
@@ -122,9 +129,6 @@
|
||||
<where>slug = 'news-grid'</where>
|
||||
</update>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd">
|
||||
|
||||
<changeSet id="20260714080000-2" author="antigravity" runAlways="true">
|
||||
<update tableName="sis_component_template">
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
|
||||
|
||||
<changeSet id="20260714112000-1" author="antigravity">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not>
|
||||
<columnExists tableName="sis_post" columnName="location"/>
|
||||
</not>
|
||||
</preConditions>
|
||||
<addColumn tableName="sis_post">
|
||||
<column name="location" type="varchar(255)">
|
||||
<constraints nullable="true" />
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
|
||||
|
||||
<changeSet id="20260714113000-1" author="antigravity" runAlways="true">
|
||||
<update tableName="sis_component_template">
|
||||
<column name="html_template"><![CDATA[<style>
|
||||
.campus-events-widget { background: transparent; padding: 0; font-family: inherit; }
|
||||
.campus-events-widget .events-list { list-style: none; margin: 0; padding: 0; }
|
||||
.campus-events-widget .teaser-item { padding: 20px 0; border-bottom: 1px solid #e5e5e5; }
|
||||
.campus-events-widget .teaser-item:last-child { border-bottom: none; }
|
||||
.campus-events-widget .event-wrapper { display: flex; gap: 20px; align-items: flex-start; }
|
||||
.campus-events-widget .event-date { display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 0.125rem; padding: 0.25rem; background: #881C1C; width: 2.875rem; height: 3.125rem; box-sizing: border-box; }
|
||||
.campus-events-widget .event-date__month { font-size: 1rem; font-weight: 800; line-height: 1.25rem; text-transform: uppercase; text-align: center; color: #fff; }
|
||||
.campus-events-widget .event-date__day { font-size: 1rem; font-weight: 800; line-height: 1.25rem; text-transform: uppercase; text-align: center; color: #fff; }
|
||||
.campus-events-widget .event-details { flex: 1; position: relative; }
|
||||
.campus-events-widget .event-title { font-size: 1rem; font-weight: 800; line-height: 1.25rem; text-transform: uppercase; color: #000; }
|
||||
.campus-events-widget .event-title a { color: inherit; text-decoration: none; }
|
||||
.campus-events-widget .event-title a:hover { text-decoration: underline; }
|
||||
.campus-events-widget .event-time { font-size: 16px; color: #000; margin-bottom: 4px; }
|
||||
.campus-events-widget .event-location { font-size: 1rem; font-weight: 400; line-height: 1.5rem; font-style: normal; color: #000; }
|
||||
@media (max-width: 768px) {
|
||||
.campus-events-widget .event-title { font-size: 0.75rem; }
|
||||
.campus-events-widget .event-time { font-size: 0.75rem; }
|
||||
.campus-events-widget .event-location { font-size: 0.75rem; }
|
||||
.campus-events-widget .event-wrapper { display: flex; flex: 1; padding-right: 10px; align-items: stretch; gap: 12px; border: 1px solid #CCC; }
|
||||
.campus-events-widget .event-date { display: flex; padding: 4px 8px; flex-direction: column; justify-content: center; align-items: center; align-self: stretch; background: #881C1C; min-width: 48px; height: auto; }
|
||||
.campus-events-widget .event-details { padding: 8px 10px 8px 0; }
|
||||
.campus-events-widget .teaser-item { flex: 0 0 279px; display: flex; border: 0px solid #CCC; padding-bottom: 0; }
|
||||
.campus-events-widget .events-list { display: flex; gap: 1rem; overflow-x: auto; padding-bottom: 1rem; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.campus-events-widget .event-wrapper { gap: 10px; }
|
||||
.campus-events-widget .event-date__day { font-size: 18px; }
|
||||
}
|
||||
</style>
|
||||
<div class="campus-events-widget">
|
||||
<div class="events-list">
|
||||
{{#each event_posts}}
|
||||
<div class="teaser-item">
|
||||
<div class="event-wrapper">
|
||||
<div class="event-date">
|
||||
<div class="event-date__month">{{eventMonth}}</div>
|
||||
<div class="event-date__day">{{eventDay}}</div>
|
||||
</div>
|
||||
<div class="event-details">
|
||||
<div class="event-title">
|
||||
<a href="/post/{{slug}}">{{title}}</a>
|
||||
</div>
|
||||
<div class="event-time">{{eventTimeStr}}</div>
|
||||
{{locationHtml}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>]]></column>
|
||||
<where>slug = 'events-list'</where>
|
||||
</update>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
@@ -37,4 +37,6 @@
|
||||
<!-- jhipster-needle-liquibase-add-constraints-changelog - JHipster will add liquibase constraints changelogs here -->
|
||||
<!-- jhipster-needle-liquibase-add-incremental-changelog - JHipster will add incremental liquibase changelogs here -->
|
||||
<include file="config/liquibase/changelog/20260714080000_seed_news_events_components.xml" relativeToChangelogFile="false"/>
|
||||
<include file="config/liquibase/changelog/20260714112000_add_location_to_post.xml" relativeToChangelogFile="false"/>
|
||||
<include file="config/liquibase/changelog/20260714113000_update_events_list.xml" relativeToChangelogFile="false"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+8
-8
File diff suppressed because one or more lines are too long
@@ -60,4 +60,465 @@
|
||||
/* Ensures it sits above the video */
|
||||
}
|
||||
|
||||
/* News Grid Tablet Layout */
|
||||
@media screen and (min-width: 769px) and (max-width: 1024px) {
|
||||
.news-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 24px;
|
||||
align-items: stretch;
|
||||
}
|
||||
.featured-grid__item {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
.featured-grid__item .news-card--featured {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
border: 1px solid #881C1C;
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
gap: 16px;
|
||||
background: #fff;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.featured-grid__item .news-card__image {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.featured-grid__item .news-card__image > div {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.featured-grid__item .news-card__image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.featured-grid__item .news-card__content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
min-width: 0;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.featured-grid__item .featured-grid__category {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.featured-grid__item .featured-grid__category a {
|
||||
display: inline-block;
|
||||
background-color: #f2f2f2;
|
||||
color: #881C1C;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.featured-grid__item .news-card__title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
margin-top: 0;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.featured-grid__item .news-card__title a {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
}
|
||||
.featured-grid__item .news-card__title a:hover {
|
||||
color: #881C1C;
|
||||
}
|
||||
.featured-grid__item .news-card__date {
|
||||
margin-top: auto;
|
||||
color: #881C1C;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
/* News Grid Desktop Layout */
|
||||
@media screen and (min-width: 1025px){
|
||||
.news-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 24px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.education-column .news-list {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.featured-grid__item {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
.featured-grid__item .news-card--featured {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
border: 1px solid #881C1C;
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
gap: 16px;
|
||||
background: #fff;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.featured-grid__item .news-card__image {
|
||||
flex: 0 0 170px;
|
||||
width: 100%;
|
||||
height: 170px;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.featured-grid__item .news-card__image > div {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.featured-grid__item .news-card__image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.featured-grid__item .news-card__content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
min-width: 0;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.featured-grid__item .featured-grid__category {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.featured-grid__item .featured-grid__category a {
|
||||
display: inline-block;
|
||||
background-color: #f2f2f2;
|
||||
color: #881C1C;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.featured-grid__item .news-card__title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
margin-top: 0;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.featured-grid__item .news-card__title a {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
}
|
||||
.featured-grid__item .news-card__title a:hover {
|
||||
color: #881C1C;
|
||||
}
|
||||
.featured-grid__item .news-card__date {
|
||||
margin-top: auto;
|
||||
color: #881C1C;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Desktop Layouts (>= 1024px)
|
||||
========================================================================== */
|
||||
@media screen and (min-width: 1025px) {
|
||||
/* Side-by-side layout for News and Events */
|
||||
.news-events-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 40px;
|
||||
align-items: stretch;
|
||||
}
|
||||
.news-events-row > .news-column {
|
||||
flex: 1.8;
|
||||
min-width: 0;
|
||||
}
|
||||
.news-events-row > .events-column {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Navigation Container & Mobile Menu
|
||||
========================================================================== */
|
||||
|
||||
/* --- Desktop (>1024px) --- */
|
||||
.navigation-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 1.5rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* Desktop: show horizontal menu, hide hamburger */
|
||||
@media (min-width: 1025px) {
|
||||
#mobile-hamburger {
|
||||
display: none !important;
|
||||
}
|
||||
#mobile-flyout-container {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
}
|
||||
.primary-navigation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
/* Desktop: hide arrow toggles and back buttons */
|
||||
.mc--main-menu .arrow-toggle,
|
||||
.mc--main-menu .button-back,
|
||||
.mc--main-menu .navigation-title {
|
||||
display: none !important;
|
||||
}
|
||||
/* Desktop: horizontal main menu */
|
||||
.m--main-menu {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.m--main-menu > .menu-item > .link-arrow-wrapper > a {
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
padding: 10px 15px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Mobile (<= 768px) --- */
|
||||
@media (max-width: 768px) {
|
||||
/* Hide ambient video on mobile to save bandwidth and use image fallback */
|
||||
.f--ambient-video,
|
||||
.video-controls {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Override Tabbed Media Content text colors since background was changed to light gray */
|
||||
.cc--tabbed-media-content .f--description p {
|
||||
color: #000000 !important;
|
||||
}
|
||||
.cc--tabbed-media-content .f--description a {
|
||||
color: var(--color-brand) !important;
|
||||
}
|
||||
|
||||
/* Style tab labels and borders to black */
|
||||
.cc--tabbed-media-content .tab-labels-inner {
|
||||
border-bottom-color: #cccccc !important; /* light gray line for the tab row */
|
||||
}
|
||||
.cc--tabbed-media-content .tab-label {
|
||||
color: #555555 !important; /* dark gray for inactive tabs */
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
.cc--tabbed-media-content .tab-label[aria-selected="true"] {
|
||||
color: #000000 !important; /* solid black for active tab */
|
||||
border-bottom: 3px solid #000000 !important; /* solid black underline for active tab */
|
||||
}
|
||||
|
||||
/* Center children inside tabbed-media-content-media-col */
|
||||
.cc--tabbed-media-content .tabbed-media-content-media-col {
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
/* --- Mobile & Tablet (<=1024px) --- */
|
||||
@media (max-width: 1024px) {
|
||||
.navigation-container {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Hamburger button */
|
||||
#mobile-hamburger {
|
||||
display: block;
|
||||
z-index: 1001;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
}
|
||||
.hamburger-box {
|
||||
width: 30px;
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
.hamburger-inner,
|
||||
.hamburger-inner::before,
|
||||
.hamburger-inner::after {
|
||||
width: 30px;
|
||||
height: 3px;
|
||||
background-color: #fff;
|
||||
border-radius: 2px;
|
||||
position: absolute;
|
||||
transition: transform 0.3s ease, opacity 0.2s ease;
|
||||
}
|
||||
.hamburger-inner {
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.hamburger-inner::before {
|
||||
content: '';
|
||||
top: -9px;
|
||||
}
|
||||
.hamburger-inner::after {
|
||||
content: '';
|
||||
bottom: -9px;
|
||||
}
|
||||
/* Hamburger X state */
|
||||
#mobile-hamburger.is-active .hamburger-inner {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
#mobile-hamburger.is-active .hamburger-inner::before {
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
#mobile-hamburger.is-active .hamburger-inner::after {
|
||||
bottom: 0;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
/* Mobile flyout container */
|
||||
#mobile-flyout-container {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
background: #fff;
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-top: 80px;
|
||||
}
|
||||
|
||||
/* Primary navigation inside flyout */
|
||||
.primary-navigation {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Main menu list - vertical */
|
||||
.m--main-menu {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.m--main-menu > .menu-item {
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
/* Link + arrow wrapper */
|
||||
.link-arrow-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
}
|
||||
.link-arrow-wrapper > a {
|
||||
flex: 1;
|
||||
padding: 15px 20px;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* Arrow toggle button */
|
||||
.arrow-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
/* border-left: 1px solid #e0e0e0; */
|
||||
/* padding: 15px 20px; */
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 50px;
|
||||
}
|
||||
.arrow-toggle svg {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.arrow-toggle.is-active svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Submenu list */
|
||||
.submenu {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.submenu .menu-item {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.submenu .menu-item a,
|
||||
.submenu .menu-item span {
|
||||
display: block;
|
||||
padding: 12px 20px;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
font-size: 15px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
.submenu .menu-item a:hover {
|
||||
background-color: #f5f5f5;
|
||||
color: #881c1c;
|
||||
}
|
||||
|
||||
/* Hide desktop-only elements on mobile */
|
||||
.desktop-only {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Show mobile-only elements on mobile */
|
||||
.mobile-only {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/* Hide quicklink buttons, utility menu, and social links on mobile flyout */
|
||||
.mc--quicklink-button-menu,
|
||||
.mc--utility-menu,
|
||||
[data-component-id="umass_base:header-socials"] {
|
||||
margin-top: 20px;
|
||||
border-top: 2px solid #881c1c;
|
||||
padding-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* On Desktop, hide mobile-only elements */
|
||||
@media (min-width: 1366) {
|
||||
.mobile-only {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('.video-controls').forEach(function (controls) {
|
||||
var playBtn = controls.querySelector('.video-play-button');
|
||||
var pauseBtn = controls.querySelector('.video-pause-button');
|
||||
|
||||
// Find the associated ambient video. In the HTML structure, the .f--ambient-video
|
||||
// container is a sibling (specifically previousElementSibling) of .video-controls.
|
||||
// If not, we fall back to searching within the closest parent.
|
||||
var videoContainer = controls.previousElementSibling;
|
||||
if (!videoContainer || !videoContainer.classList.contains('f--ambient-video')) {
|
||||
var parent = controls.closest('.image-video-container');
|
||||
if (parent) {
|
||||
videoContainer = parent.querySelector('.f--ambient-video');
|
||||
}
|
||||
}
|
||||
|
||||
if (!videoContainer) return;
|
||||
var video = videoContainer.querySelector('video');
|
||||
if (!video) return;
|
||||
|
||||
if (pauseBtn) {
|
||||
pauseBtn.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
pauseBtn.classList.add('hidden');
|
||||
if (playBtn) {
|
||||
playBtn.classList.add('active');
|
||||
playBtn.focus();
|
||||
}
|
||||
video.pause();
|
||||
});
|
||||
}
|
||||
|
||||
if (playBtn) {
|
||||
playBtn.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
playBtn.classList.remove('active');
|
||||
if (pauseBtn) {
|
||||
pauseBtn.classList.remove('hidden');
|
||||
pauseBtn.focus();
|
||||
}
|
||||
video.play();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Mobile Menu - Hamburger toggle, submenu accordion interactions.
|
||||
* Pure vanilla JS — jQuery is NOT available.
|
||||
* Only active on mobile/tablet (≤1024px via CSS).
|
||||
*/
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var hamburger = document.getElementById('mobile-hamburger');
|
||||
var flyout = document.getElementById('mobile-flyout-container');
|
||||
var header = document.querySelector('[data-component-id="umass_base:site-header"]');
|
||||
|
||||
// Helper functions for fade animation
|
||||
function fadeIn(el) {
|
||||
el.style.display = 'block';
|
||||
var anim = el.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 300, fill: 'forwards' });
|
||||
el._fadeAnim = anim;
|
||||
}
|
||||
|
||||
function fadeOut(el) {
|
||||
var anim = el.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 300, fill: 'forwards' });
|
||||
el._fadeAnim = anim;
|
||||
anim.onfinish = function() {
|
||||
if (el._fadeAnim === anim) {
|
||||
el.style.display = 'none';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// --- Hamburger toggle ---
|
||||
if (hamburger && flyout) {
|
||||
hamburger.addEventListener('click', function () {
|
||||
var isActive = hamburger.classList.toggle('is-active');
|
||||
if (isActive) {
|
||||
fadeIn(flyout);
|
||||
} else {
|
||||
fadeOut(flyout);
|
||||
// Close all open submenus when closing the flyout
|
||||
flyout.querySelectorAll('.submenu-wrapper').forEach(function (w) {
|
||||
if (w.style.display !== 'none') fadeOut(w);
|
||||
});
|
||||
flyout.querySelectorAll('.arrow-toggle.is-active').forEach(function (a) {
|
||||
a.classList.remove('is-active');
|
||||
});
|
||||
}
|
||||
if (header) header.classList.toggle('mobile-menu-active', isActive);
|
||||
});
|
||||
}
|
||||
|
||||
// --- Arrow toggle: open/close submenu (fade style) ---
|
||||
document.querySelectorAll('#mobile-flyout-container .arrow-toggle').forEach(function (btn) {
|
||||
btn.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// The submenu-wrapper is the next sibling of .link-arrow-wrapper
|
||||
var linkArrowWrapper = btn.closest('.link-arrow-wrapper');
|
||||
if (!linkArrowWrapper) return;
|
||||
var wrapper = linkArrowWrapper.nextElementSibling;
|
||||
if (!wrapper || !wrapper.classList.contains('submenu-wrapper')) return;
|
||||
|
||||
var isActive = btn.classList.toggle('is-active');
|
||||
if (isActive) {
|
||||
fadeIn(wrapper);
|
||||
} else {
|
||||
fadeOut(wrapper);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// --- Back button: close submenu ---
|
||||
document.querySelectorAll('.button-back').forEach(function (btn) {
|
||||
btn.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var wrapper = btn.closest('.submenu-wrapper');
|
||||
if (wrapper) {
|
||||
fadeOut(wrapper);
|
||||
// Also reset the arrow toggle on the parent li
|
||||
var li = wrapper.closest('li');
|
||||
if (li) {
|
||||
var toggle = li.querySelector('.link-arrow-wrapper > .arrow-toggle');
|
||||
if (toggle) {
|
||||
toggle.classList.remove('is-active');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 176 KiB |
+8
-8
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
|
After Width: | Height: | Size: 176 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 176 KiB |
@@ -19,10 +19,33 @@
|
||||
|
||||
<nav class="mc--menu mc--main-menu" th:if="${primaryMenu != null}">
|
||||
<ul class="menu m--menu m--main-menu">
|
||||
<li class="megamenu menu-item" th:each="item : ${primaryMenu.items}">
|
||||
<li th:each="item : ${primaryMenu.items}" th:if="${item.parent == null}"
|
||||
th:class="${item.children.empty} ? 'menu-item' : 'menu-item menu-item--expanded'">
|
||||
|
||||
<div class="link-arrow-wrapper">
|
||||
<a th:href="${item.url}" th:text="${item.label}">Menu Item</a>
|
||||
|
||||
<button th:if="${!item.children.empty}" type="button" class="has-submenu arrow-toggle" aria-expanded="false" aria-haspopup="true" th:attr="aria-label='Display Sub Menu for ' + ${item.label}" data-once="site-header-arrow">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 16 7" enable-background="new 0 0 16 7" xml:space="preserve" width="16" height="7">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#881c1c" d="M7.88,7L15.75,0H0s7.88,7,7.88,7Z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="submenu-wrapper" th:if="${!item.children.empty}">
|
||||
<button class="button-back">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 5 10" enable-background="new 0 0 5 10" xml:space="preserve" width="5" height="10">
|
||||
<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#881c1c" points="0,5 5,0 5,10 "></polygon>
|
||||
</svg> Back
|
||||
</button>
|
||||
<span class="navigation-title" th:text="${item.label}">Submenu Title</span>
|
||||
<ul class="submenu">
|
||||
<li class="menu-item" th:each="child : ${item.children}">
|
||||
<a th:href="${child.url}" th:text="${child.label}">Child Item</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -2242,11 +2242,17 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h3 class="section-title">
|
||||
BÀI VIẾT NỔI BẬT
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="featured-grid">
|
||||
<div class="news-events-row">
|
||||
|
||||
<div class="news-column">
|
||||
<h3 class="section-title--news">
|
||||
TIN TỨC
|
||||
</h3>
|
||||
|
||||
|
||||
<div class="featured-grid">
|
||||
<div class="featured-grid__item">
|
||||
|
||||
<article class="news-card news-card--featured">
|
||||
@@ -2405,108 +2411,8 @@
|
||||
</article>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="news-events-row">
|
||||
|
||||
<div class="news-column">
|
||||
<h3 class="section-title--news">
|
||||
TIN TỨC MỚI NHẤT
|
||||
</h3>
|
||||
|
||||
<div class="news-list">
|
||||
<div class="news-list__item">
|
||||
|
||||
<article class="news-teaser">
|
||||
|
||||
<div class="news-teaser__image">
|
||||
|
||||
<div> <img loading="lazy"
|
||||
src="https://sisvietnam.vn/wp-content/uploads/2026/06/S.I.S-Can-Tho-trao-tang-xe-dap-cho-thieu-nhi-1.jpg"
|
||||
width="4500" height="3006"
|
||||
alt="Pyrotechnics erupt over the Football Performance Center at the conclusion of the 2026 UMass Amherst Undergraduate Commencement Ceremony">
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="news-teaser__content">
|
||||
|
||||
<div class="news-list__category">
|
||||
<a href="https://sisvietnam.vn/taxonomy/term/1001" hreflang="vi">Tin Tức Bệnh
|
||||
Viện</a>
|
||||
</div>
|
||||
|
||||
<h3 class="news-teaser__title">
|
||||
<a href="https://sisvietnam.vn/news/article/tuan-le-nang-cao-nhan-thuc"
|
||||
rel="bookmark">
|
||||
<span>Bệnh Viện S.I.S Cần Thơ Tổ Chức Thành Công Tuần Lễ Nâng Cao Nhận Thức
|
||||
Về Đột Quỵ</span>
|
||||
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<div class="news-teaser__summary">
|
||||
|
||||
<div>
|
||||
<p>Ban Giám đốc Bệnh viện cùng đội ngũ chuyên gia đã tổ chức chuỗi sự kiện
|
||||
truyền thông, tư vấn sức khỏe miễn phí thu hút hàng nghìn người dân tham
|
||||
gia tìm hiểu và phòng ngừa bệnh.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</article>
|
||||
|
||||
</div>
|
||||
<div class="news-list__item">
|
||||
|
||||
<article class="news-teaser">
|
||||
|
||||
<div class="news-teaser__image">
|
||||
|
||||
<div> <img loading="lazy"
|
||||
src="https://sisvietnam.vn/wp-content/uploads/2026/04/Mo-hinh-S.I.S-gop-phan-hoan-thien-mang-luoi-dot-quy-khu-vuc-huong-den-APEC-2027-08.jpg"
|
||||
width="5392" height="3592"
|
||||
alt="Student speaker Mansi Maheshwari addresses the 2026 Master’s and Education Specialist Commencement Ceremony at the Mullins Center on May 15, 2026.">
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="news-teaser__content">
|
||||
|
||||
<div class="news-list__category">
|
||||
<a href="https://sisvietnam.vn/taxonomy/term/1001" hreflang="vi">Tin Tức Bệnh
|
||||
Viện</a>
|
||||
</div>
|
||||
|
||||
<h3 class="news-teaser__title">
|
||||
<a href="https://sisvietnam.vn/news/article/tiep-nhan-thiet-bi" rel="bookmark">
|
||||
<span>Bệnh Viện Đa Khoa Quốc Tế S.I.S Cần Thơ Tiếp Nhận Thêm Dàn Thiết Bị Y
|
||||
Tế Hiện Đại Bậc Nhất</span>
|
||||
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<div class="news-teaser__summary">
|
||||
|
||||
<div>
|
||||
<p>Nhằm nâng cao chất lượng chẩn đoán và điều trị, S.I.S Cần Thơ đã chính
|
||||
thức đưa vào hoạt động hệ thống máy DSA và MRI thế hệ mới, hỗ trợ tối đa
|
||||
cho các ca can thiệp thần kinh phức tạp.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</article>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="events-column">
|
||||
|
||||
@@ -275,6 +275,13 @@
|
||||
<input type="datetime-local" class="form-control" id="postEventTime" th:field="*{eventTimeLocal}">
|
||||
<small class="form-text text-muted">Specify the date and time for this event.</small>
|
||||
</div>
|
||||
<div class="form-group mt-3" id="eventLocationGroup" style="display: none;">
|
||||
<label for="postLocation" class="font-weight-bold text-danger">
|
||||
<i class="fas fa-map-marker-alt"></i> Location
|
||||
</label>
|
||||
<input type="text" class="form-control" id="postLocation" th:field="*{location}" placeholder="e.g. Room 101">
|
||||
<small class="form-text text-muted">Specify the location for this event.</small>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between">
|
||||
<a th:href="@{/manage/posts}" class="btn btn-secondary btn-sm">
|
||||
@@ -481,11 +488,14 @@
|
||||
// --- Event Time Layout Toggle ---
|
||||
var layoutSelect = document.getElementById('postLayout');
|
||||
var eventTimeGroup = document.getElementById('eventTimeGroup');
|
||||
var eventLocationGroup = document.getElementById('eventLocationGroup');
|
||||
function toggleEventTime() {
|
||||
if (layoutSelect.value === 'EVENT') {
|
||||
eventTimeGroup.style.display = 'block';
|
||||
if (eventLocationGroup) eventLocationGroup.style.display = 'block';
|
||||
} else {
|
||||
eventTimeGroup.style.display = 'none';
|
||||
if (eventLocationGroup) eventLocationGroup.style.display = 'none';
|
||||
}
|
||||
}
|
||||
layoutSelect.addEventListener('change', toggleEventTime);
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{fragments/layout}">
|
||||
|
||||
<head>
|
||||
<title th:text="${post.title} + ' | UMass Amherst'">Post Title | UMass Amherst</title>
|
||||
<meta name="description" th:content="${post.metaDescription ?: post.excerpt}">
|
||||
<style>
|
||||
.livestream-video-wrapper {
|
||||
position: relative;
|
||||
padding-bottom: 56.25%; /* 16:9 Aspect Ratio */
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
margin-bottom: 2rem;
|
||||
background: #000;
|
||||
}
|
||||
.livestream-video-wrapper iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
.livestream-meta {
|
||||
margin-bottom: 2rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
.livestream-title {
|
||||
font-size: 2.25rem;
|
||||
font-weight: 800;
|
||||
color: #111;
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.livestream-date {
|
||||
color: #881C1C;
|
||||
font-weight: 700;
|
||||
font-size: 0.95rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<div id="block-umass-base-content" class="block block-system block-system-main-block tc--template-container tc--article">
|
||||
<div class="t--template t--article">
|
||||
|
||||
<!-- Content Top (Video Embed or Placeholder) -->
|
||||
<div class="content-top">
|
||||
<div class="lc--layout-container lc--one-column">
|
||||
<div class="l--layout l--one-column">
|
||||
<div class="lr--layout-region lr--main">
|
||||
<div class="cc--component-container">
|
||||
<!-- YouTube Embed -->
|
||||
<div th:if="${post.metaDescription != null and !post.metaDescription.isEmpty()}" class="livestream-video-wrapper">
|
||||
<iframe th:src="${post.metaDescription}"
|
||||
title="Livestream Video"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen></iframe>
|
||||
</div>
|
||||
<!-- Fallback Hero Image if no video -->
|
||||
<div th:if="${post.metaDescription == null or post.metaDescription.isEmpty()}" class="cc--component-container cc--hero-article">
|
||||
<div class="c--component c--hero-article">
|
||||
<div class="f--field f--image" th:if="${post.featuredImage != null and !post.featuredImage.isEmpty()}">
|
||||
<img th:src="${post.featuredImage}" th:alt="${post.title}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Main (Description and Info) -->
|
||||
<div class="content-main">
|
||||
<div class="lc--layout-container lc--one-column">
|
||||
<div class="l--layout l--one-column">
|
||||
<div class="lr--layout-region lr--main">
|
||||
<div class="livestream-meta">
|
||||
<!-- If the excerpt is used for the display title, show it. Otherwise show standard post title -->
|
||||
<h1 class="livestream-title" th:text="${post.excerpt != null and !post.excerpt.isEmpty() ? post.excerpt : post.title}">Post Title</h1>
|
||||
<p class="livestream-date" th:if="${formattedDate != null}" th:text="${formattedDate}">Date</p>
|
||||
</div>
|
||||
<section class="cc--component-container cc--wysiwyg">
|
||||
<div class="c--component c--wysiwyg">
|
||||
<div class="f--field f--wysiwyg editorjs-content" id="postContentRaw" style="display:none;" th:text="${post.content}"></div>
|
||||
<div class="f--field f--wysiwyg" id="postContentParsed"></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Include Editor.js parser to render the content on the frontend -->
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
var rawData = document.getElementById("postContentRaw").textContent;
|
||||
var parsedContainer = document.getElementById("postContentParsed");
|
||||
|
||||
try {
|
||||
var data = JSON.parse(rawData);
|
||||
var html = "";
|
||||
if (data.blocks) {
|
||||
data.blocks.forEach(function(block) {
|
||||
switch(block.type) {
|
||||
case "paragraph":
|
||||
html += "<p>" + block.data.text + "</p>";
|
||||
break;
|
||||
case "header":
|
||||
html += "<h" + block.data.level + ">" + block.data.text + "</h" + block.data.level + ">";
|
||||
break;
|
||||
case "list":
|
||||
var tag = block.data.style === "ordered" ? "ol" : "ul";
|
||||
html += "<" + tag + ">";
|
||||
block.data.items.forEach(function(item) {
|
||||
html += "<li>" + item + "</li>";
|
||||
});
|
||||
html += "</" + tag + ">";
|
||||
break;
|
||||
case "image":
|
||||
html += "<figure><img src='" + block.data.file.url + "' alt='" + (block.data.caption || "") + "'><figcaption>" + (block.data.caption || "") + "</figcaption></figure>";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
parsedContainer.innerHTML = html;
|
||||
} else {
|
||||
parsedContainer.innerHTML = rawData.replace(/\n/g, "<br>");
|
||||
}
|
||||
} catch(e) {
|
||||
// Fallback to raw text if it is not JSON
|
||||
parsedContainer.innerHTML = rawData.replace(/\n/g, "<br>");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -242,8 +242,15 @@
|
||||
</svg> Back </button>
|
||||
<span class="navigation-title" th:text="${item.label}">Menu Item</span>
|
||||
|
||||
<!-- Mobile Submenu Layout (Simple List) -->
|
||||
<ul class="submenu mobile-only">
|
||||
<li class="menu-item" th:each="child : ${item.children}">
|
||||
<a th:href="${child.url}" th:text="${child.label}">Child Item</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Mega Menu Content Layout -->
|
||||
<div class="mega-menu-content">
|
||||
<div class="mega-menu-content desktop-only">
|
||||
<!-- Column 1: Info (Title and Text) -->
|
||||
<div class="mega-col-info">
|
||||
<h3 th:text="${item.label}">Về Bệnh viện</h3>
|
||||
@@ -275,115 +282,6 @@
|
||||
</th:block>
|
||||
</ul>
|
||||
</nav>
|
||||
<div data-component-id="umass_base:nav-quicklink-buttons">
|
||||
<nav class="mc--menu mc--quicklink-button-menu">
|
||||
<ul class="menu m--menu m--quicklink-button-menu">
|
||||
<li class="menu-item">
|
||||
<div class="f--field f--button">
|
||||
<a href="https://www.umass.edu/admissions/apply" class="button-primary button-context-light " aria-label="Apply" data-component-id="umass_base:button">
|
||||
<span class="button-text"> Apply </span>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<div class="f--field f--button">
|
||||
<a href="https://minutefund.uma-foundation.org/project/29554/donate" class="button-primary button-context-light ext" aria-label="Give" data-component-id="umass_base:button" data-extlink="">
|
||||
<span class="button-text"> Give </span>
|
||||
<svg focusable="false" width="1em" height="1em" class="ext" data-extlink-placement="append" aria-label="(link is external)" viewBox="0 0 80 40" role="img" aria-hidden="false">
|
||||
<title>(link is external)</title>
|
||||
<path d="M48 26c-1.1 0-2 0.9-2 2v26H10V18h26c1.1 0 2-0.9 2-2s-0.9-2-2-2H8c-1.1 0-2 0.9-2 2v40c0 1.1 0.9 2 2 2h40c1.1 0 2-0.9 2-2V28C50 26.9 49.1 26 48 26z"></path>
|
||||
<path d="M56 6H44c-1.1 0-2 0.9-2 2s0.9 2 2 2h7.2L30.6 30.6c-0.8 0.8-0.8 2 0 2.8C31 33.8 31.5 34 32 34s1-0.2 1.4-0.6L54 12.8V20c0 1.1 0.9 2 2 2s2-0.9 2-2V8C58 6.9 57.1 6 56 6z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<div class="f--field f--button">
|
||||
<a href="https://www.umass.edu/admissions/visit" class="button-primary button-context-light " aria-label="Visit" data-component-id="umass_base:button">
|
||||
<span class="button-text"> Visit </span>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<nav class="mc--menu mc--utility-menu">
|
||||
<ul class="menu m--menu m--utility-menu">
|
||||
<li class="menu-item menu-item--expanded">
|
||||
<div class="utility-button-wrapper">
|
||||
<button type="button" class="utility-button arrow-toggle information-for-toggle" aria-label="Display submenu for Information menu" aria-expanded="false" aria-haspopup="true" data-once="site-header-arrow"> Info For <svg version="1.1" class="arrow-down" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 10 5" enable-background="new 0 0 10 5" xml:space="preserve" width="10" height="5">
|
||||
<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#881c1c" points="5,5 0,0 10,0 "></polygon>
|
||||
</svg>
|
||||
<svg version="1.1" class="arrow-right" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 5 10" enable-background="new 0 0 5 10" xml:space="preserve" width="5" height="10">
|
||||
<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#881c1c" points="5,5 0,10 0,0 "></polygon>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="submenu-wrapper">
|
||||
<button class="button-back">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 5 10" enable-background="new 0 0 5 10" xml:space="preserve" width="5" height="10">
|
||||
<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#881c1c" points="0,5 5,0 5,10 "></polygon>
|
||||
</svg> Back </button>
|
||||
<span class="navigation-title">Info For</span>
|
||||
<ul class="submenu">
|
||||
<li class="menu-item">
|
||||
<a href="https://www.umass.edu/gateway/info/prospective-students" data-drupal-link-system-path="node/42831">Prospective Students</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="https://www.umass.edu/gateway/info/current-students" title="Click here for info for current students" data-drupal-link-system-path="node/61">Current Students</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="https://www.umass.edu/gateway/info/faculty-and-staff" title="Click here for info for faculty and staff" data-drupal-link-system-path="node/66">Faculty and Staff</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="https://www.umass.edu/gateway/info/parents-and-families" title="Click here for info for parents" data-drupal-link-system-path="node/81">Parents and Families</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="https://www.umassalumni.com/" title="UMass Amherst Alumni Association">Alumni</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="https://umassathletics.com/" title="UMass Amherst Athletics">Athletics</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="https://www.uma-foundation.org/" title="Giving to UMass Amherst">Giving</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="https://www.library.umass.edu/">Libraries</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav data-component-id="umass_base:header-socials">
|
||||
<ul class="social-media-links--platforms platforms ally-focus-within">
|
||||
<li>
|
||||
<a class="ext" href="https://www.facebook.com/UMassAmherst/" target="_blank" rel="noopener noreferrer">
|
||||
<svg aria-hidden="true" focusable="false" viewBox="0 0 17 17">
|
||||
<title>Find UMass Amherst on Facebook</title>
|
||||
<path fill-rule="evenodd" fill="currentColor" d="M17,8.5C17,3.81,13.19,0,8.5,0S0,3.81,0,8.5c0,3.98,2.75,7.33,6.45,8.25v-5.65h-1.75v-2.6h1.75v-1.12c0-2.89,1.31-4.23,4.15-4.23.54,0,1.47.11,1.85.21v2.35c-.2-.02-.55-.03-.98-.03-1.39,0-1.93.53-1.93,1.9v.92h2.78l-.48,2.6h-2.3v5.84c4.21-.51,7.47-4.09,7.47-8.44Z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="ext" href="https://www.instagram.com/umass/" target="_blank" rel="noopener noreferrer">
|
||||
<!--?xml version="1.0" encoding="UTF-8"?-->
|
||||
<svg aria-hidden="true" focusable="false" viewBox="0 0 17 20">
|
||||
<title>Find UMass Amherst on Instagram</title>
|
||||
<path fill-rule="evenodd" fill="currentColor" <path="" d="M8.51,5.51c-.57,0-1.14.11-1.67.34-.53.22-1.01.55-1.42.97-.82.84-1.28,1.98-1.29,3.17,0,1.19.45,2.34,1.27,3.18.82.84,1.93,1.32,3.08,1.32,1.16,0,2.27-.47,3.09-1.31.82-.84,1.28-1.98,1.29-3.17,0-1.19-.45-2.34-1.27-3.18-.82-.84-1.93-1.32-3.08-1.32ZM8.49,7.09c.37,0,.74.07,1.08.22.34.15.66.36.92.63.26.27.47.59.62.94.14.35.22.73.22,1.11,0,.38-.07.76-.21,1.12-.14.35-.35.68-.61.95-.26.27-.57.49-.92.64-.34.15-.71.22-1.08.23-.37,0-.74-.07-1.08-.22-.34-.15-.66-.36-.92-.63-.26-.27-.47-.59-.62-.94-.14-.35-.22-.73-.22-1.11,0-.38.07-.76.21-1.12.14-.35.35-.68.61-.95.26-.27.57-.49.92-.64.34-.15.71-.22,1.08-.23ZM12.03,5.32c0-.28.11-.54.3-.74.19-.2.45-.31.72-.31s.53.11.72.31c.19.2.3.46.3.74s-.11.54-.3.74c-.19.2-.45.31-.72.31s-.53-.11-.72-.31c-.19-.2-.3-.46-.3-.74ZM16.95,6.39c-.06-1.4-.38-2.64-1.37-3.67-.99-1.02-2.2-1.34-3.56-1.41-1.4-.08-5.61-.08-7.02,0-1.36.07-2.57.39-3.56,1.41C.44,3.74.13,4.98.06,6.38-.02,7.83-.02,12.16.06,13.61c.06,1.4.38,2.64,1.37,3.67,1,1.02,2.2,1.34,3.56,1.41,1.4.08,5.61.08,7.02,0,1.36-.07,2.57-.39,3.56-1.41.99-1.02,1.31-2.27,1.37-3.67.08-1.45.08-5.77,0-7.22ZM15.14,15.16c-.3.77-.87,1.36-1.62,1.66-1.12.46-3.78.35-5.01.35s-3.9.1-5.01-.35c-.74-.3-1.32-.89-1.62-1.66-.44-1.15-.34-3.89-.34-5.16s-.1-4.01.34-5.16c.3-.77.87-1.36,1.62-1.66,1.12-.46,3.78-.35,5.01-.35s3.9-.1,5.01.35c.74.3,1.32.89,1.62,1.66.44,1.15.34,3.89.34,5.16s.1,4.01-.34,5.16Z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="ext" href="https://www.snapchat.com/add/umassamherst" target="_blank" rel="noopener noreferrer">
|
||||
<svg aria-hidden="true" focusable="false" viewBox="0 0 17 17">
|
||||
<title>Add UMass Amherst on Snapchat</title>
|
||||
<path d="m12.8333333 20.3333333c-.0833333 0-.0833333 0-.1666666 0h-.0833334c-1.0833333 0-1.6666666-.3333333-2.25-.75-.41666663-.3333333-.74999997-.5833333-1.16666663-.5833333-.25 0-.41666667-.0833333-.66666667-.0833333-.33333333 0-.66666667.0833333-.91666667.0833333-.16666666 0-.33333333.0833333-.41666666.0833333-.33333334 0-.5-.25-.5-.4166666 0-.1666667-.08333334-.25-.08333334-.4166667 0-.0833333-.08333333-.25-.08333333-.3333333-1.33333333-.25-2.08333333-.5-2.25-1-.08333333 0-.08333333-.0833334-.08333333-.1666667 0-.25.16666666-.4166667.41666666-.5 2.25-.3333333 3.33333334-2.75 3.33333334-2.8333333.08333333-.25.16666666-.4166667.08333333-.5833334-.08333333-.1666666-.58333333-.3333333-.91666667-.4166666-.08333333 0-.16666666-.0833334-.25-.0833334-.83333333-.3333333-1-.75-.91666666-1.0833333.08333333-.5.83333333-.75 1.25-.5833333.33333333.1666666.58333333.1666666.75.1666666h.25c0-.0833333 0-.25 0-.3333333-.08333334-1.16666667-.25-2.66666667.16666666-3.5 1.16666667-2.66666667 3.66666667-2.83333333 4.33333337-2.83333333h.3333333c.75 0 3.1666667.16666666 4.3333333 2.83333333.4166667.91666667.3333334 2.33333333.25 3.5v.0833333.25h.1666667c.1666667 0 .4166667-.0833333.6666667-.1666666.1666666-.0833334.25-.0833334.4166666-.0833334.1666667 0 .25 0 .4166667.0833334.3333333.0833333.5833333.4166666.5833333.6666666 0 .4166667-.3333333.6666667-.9166666.9166667-.0833334 0-.1666667.0833333-.25.0833333-.3333334.0833334-.8333334.25-.9166667.5-.0833333.1666667 0 .3333334.0833333.5.0833334.25 1.0833334 2.5 3.25 2.8333334.25 0 .4166667.25.4166667.5 0 .0833333 0 .1666666-.0833333.1666666-.1666667.4166667-.9166667.75-2.25 1 0 .0833334-.0833334.25-.0833334.3333334 0 .1666666-.0833333.25-.0833333.4166666-.0833333.25-.25.4166667-.5.4166667-.0833333 0-.25 0-.4166667-.0833333-.25-.0833334-.5-.0833334-.9166666-.0833334-.1666667 0-.4166667 0-.6666667.0833334-.4166667.0833333-.75.3333333-1.1666667.5833333-.6666666.5833333-1.5.8333333-2.5.8333333" fill="currentColor" fill-rule="evenodd" transform="translate(-4 -3)"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<button id="search-drawer-trigger" aria-label="Open Search" data-once="header-search">
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
<link rel="stylesheet" media="all" th:href="@{/theme-assets/umass/css/css_09S7WcUyhLeUXabknzECn_ua5zOangtv2BF4OXAW2fM.css}">
|
||||
<link rel="stylesheet" media="all" th:href="@{/theme-assets/umass/css/css_5USufIKxGNJyomvgpy2m9ITQlTN9qcrtJQsw_06dSbo.css}">
|
||||
<link rel="stylesheet" media="all" th:href="@{/theme-assets/umass/css/css_G5ztZKRblBn54t8h9F_EK5Y3CsZrMvA2BWPMI5jJoO4.css}">
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" th:href="@{/css/custom.css}">
|
||||
<!-- Theme Customizer CSS Output -->
|
||||
<style th:utext="${themeCss}"></style>
|
||||
<!-- wp_head hook -->
|
||||
@@ -46,6 +48,8 @@
|
||||
</div>
|
||||
<!-- UMass Scripts -->
|
||||
<script th:src="@{/theme-assets/umass/js/js_aQtUyeGxehNR84AlGzGB1VfMu1Wn3lqHxvL8rocj1EU.js}"></script>
|
||||
<script th:src="@{/js/mobile-menu.js}"></script>
|
||||
<script th:src="@{/js/ambient-video.js}"></script>
|
||||
<!-- wp_footer hook -->
|
||||
<th:block th:utext="${hookManager.doActionAndReturn('wp_footer')}"></th:block>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user