Hoan thanh chuc nang Page
This commit is contained in:
Vendored
+1
@@ -7,6 +7,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"mainClass": "com.sisvietnamvn.web.SisvietnamvnApp",
|
"mainClass": "com.sisvietnamvn.web.SisvietnamvnApp",
|
||||||
"projectName": "sisvietnamvn",
|
"projectName": "sisvietnamvn",
|
||||||
|
"cwd": "${workspaceFolder}/sisvietnamvn_main",
|
||||||
"vmArgs": "-Dspring.profiles.active=dev"
|
"vmArgs": "-Dspring.profiles.active=dev"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -151,3 +151,9 @@ Desktop.ini
|
|||||||
######################
|
######################
|
||||||
coverage/
|
coverage/
|
||||||
.nyc_output/
|
.nyc_output/
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Database
|
||||||
|
######################
|
||||||
|
src/main/docker/oracle-data/
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -46,7 +46,8 @@ public class SecurityConfiguration {
|
|||||||
authz
|
authz
|
||||||
.requestMatchers(HttpMethod.GET, "/", "/about", "/flex-finish", "/tin-tuc", "/tin-tuc/**", "/lien-he",
|
.requestMatchers(HttpMethod.GET, "/", "/about", "/flex-finish", "/tin-tuc", "/tin-tuc/**", "/lien-he",
|
||||||
"/manage/**", "/css/**", "/images/**", "/js/**", "/UMass*/**", "/Undergraduate*/**",
|
"/manage/**", "/css/**", "/images/**", "/js/**", "/UMass*/**", "/Undergraduate*/**",
|
||||||
"/favicon.ico", "/favicons/**", "/flex-finish/**", "/contact-us/**", "/uploads/**", "/api/manage/snippets/**", "/page/**")
|
"/favicon.ico", "/favicons/**", "/flex-finish/**", "/contact-us/**", "/uploads/**", "/api/manage/snippets/**", "/page/**",
|
||||||
|
"/about-us", "/specialty", "/doctor", "/service", "/health-library", "/news-and-events", "/patient-support", "/medical-expert", "/umcers", "/bidding", "/contact-us")
|
||||||
.permitAll()
|
.permitAll()
|
||||||
.requestMatchers(HttpMethod.POST, "/manage/**", "/api/manage/media/upload").permitAll()
|
.requestMatchers(HttpMethod.POST, "/manage/**", "/api/manage/media/upload").permitAll()
|
||||||
.requestMatchers(HttpMethod.GET, "/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
.requestMatchers(HttpMethod.GET, "/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ import org.springframework.web.bind.annotation.GetMapping;
|
|||||||
@Controller
|
@Controller
|
||||||
public class HomeController {
|
public class HomeController {
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/")
|
@GetMapping("/")
|
||||||
public String home() {
|
public String index() {
|
||||||
return "index";
|
return "index";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,17 +21,17 @@ public class HomeController {
|
|||||||
// Trang Giới thiệu Bệnh viện
|
// Trang Giới thiệu Bệnh viện
|
||||||
@GetMapping("/about")
|
@GetMapping("/about")
|
||||||
public String about() {
|
public String about() {
|
||||||
return "about";
|
return "redirect:/about-us";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trang Tin tức
|
// Trang Tin tức
|
||||||
@GetMapping("/tin-tuc")
|
@GetMapping("/tin-tuc")
|
||||||
public String tintuc() {
|
public String tintuc() {
|
||||||
return "tin-tuc";
|
return "redirect:/news-and-events";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/lien-he")
|
@GetMapping("/lien-he")
|
||||||
public String lienhe() {
|
public String lienhe() {
|
||||||
return "lien-he";
|
return "redirect:/contact-us";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-8
@@ -43,15 +43,48 @@ public class PageController {
|
|||||||
@GetMapping("/page/{slug}")
|
@GetMapping("/page/{slug}")
|
||||||
public String getPage(@PathVariable("slug") String slug, Model model) {
|
public String getPage(@PathVariable("slug") String slug, Model model) {
|
||||||
LOG.debug("REST request to get public Page : {}", slug);
|
LOG.debug("REST request to get public Page : {}", slug);
|
||||||
|
return renderPage(pageService.findBySlug(slug), model);
|
||||||
Optional<Page> pageOpt = pageService.findBySlug(slug);
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/about-us")
|
||||||
|
public String getAboutUs(Model model) { return renderPage(pageService.findByPageType(com.sisvietnamvn.web.domain.PageType.ABOUT_US), model); }
|
||||||
|
|
||||||
|
@GetMapping("/specialty")
|
||||||
|
public String getSpecialty(Model model) { return renderPage(pageService.findByPageType(com.sisvietnamvn.web.domain.PageType.SPECIALTY), model); }
|
||||||
|
|
||||||
|
@GetMapping("/doctor")
|
||||||
|
public String getDoctor(Model model) { return renderPage(pageService.findByPageType(com.sisvietnamvn.web.domain.PageType.DOCTOR), model); }
|
||||||
|
|
||||||
|
@GetMapping("/service")
|
||||||
|
public String getService(Model model) { return renderPage(pageService.findByPageType(com.sisvietnamvn.web.domain.PageType.SERVICE), model); }
|
||||||
|
|
||||||
|
@GetMapping("/health-library")
|
||||||
|
public String getHealthLibrary(Model model) { return renderPage(pageService.findByPageType(com.sisvietnamvn.web.domain.PageType.HEALTH_LIBRARY), model); }
|
||||||
|
|
||||||
|
@GetMapping("/news-and-events")
|
||||||
|
public String getNewsAndEvents(Model model) { return renderPage(pageService.findByPageType(com.sisvietnamvn.web.domain.PageType.NEWS_AND_EVENTS), model); }
|
||||||
|
|
||||||
|
@GetMapping("/patient-support")
|
||||||
|
public String getPatientSupport(Model model) { return renderPage(pageService.findByPageType(com.sisvietnamvn.web.domain.PageType.PATIENT_SUPPORT), model); }
|
||||||
|
|
||||||
|
@GetMapping("/medical-expert")
|
||||||
|
public String getMedicalExpert(Model model) { return renderPage(pageService.findByPageType(com.sisvietnamvn.web.domain.PageType.MEDICAL_EXPERT), model); }
|
||||||
|
|
||||||
|
@GetMapping("/umcers")
|
||||||
|
public String getUmcers(Model model) { return renderPage(pageService.findByPageType(com.sisvietnamvn.web.domain.PageType.UMCERS), model); }
|
||||||
|
|
||||||
|
@GetMapping("/bidding")
|
||||||
|
public String getBidding(Model model) { return renderPage(pageService.findByPageType(com.sisvietnamvn.web.domain.PageType.BIDDING), model); }
|
||||||
|
|
||||||
|
@GetMapping("/contact-us")
|
||||||
|
public String getContactUs(Model model) { return renderPage(pageService.findByPageType(com.sisvietnamvn.web.domain.PageType.CONTACT_US), model); }
|
||||||
|
|
||||||
|
private String renderPage(Optional<Page> pageOpt, Model model) {
|
||||||
if (pageOpt.isEmpty()) {
|
if (pageOpt.isEmpty()) {
|
||||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Page not found");
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Page not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
Page page = pageOpt.get();
|
Page page = pageOpt.get();
|
||||||
|
|
||||||
// Security Check: If Draft, only Admins can view
|
// Security Check: If Draft, only Admins can view
|
||||||
if ("DRAFT".equals(page.getStatus()) && !SecurityUtils.hasCurrentUserThisAuthority(AuthoritiesConstants.ADMIN)) {
|
if ("DRAFT".equals(page.getStatus()) && !SecurityUtils.hasCurrentUserThisAuthority(AuthoritiesConstants.ADMIN)) {
|
||||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Page not found");
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Page not found");
|
||||||
@@ -61,19 +94,17 @@ public class PageController {
|
|||||||
List<Map<String, Object>> blocks = Collections.emptyList();
|
List<Map<String, Object>> blocks = Collections.emptyList();
|
||||||
if (page.getContent() != null && !page.getContent().trim().isEmpty()) {
|
if (page.getContent() != null && !page.getContent().trim().isEmpty()) {
|
||||||
try {
|
try {
|
||||||
// Editor.js JSON has a "blocks" array at the root level
|
|
||||||
Map<String, Object> editorData = objectMapper.readValue(page.getContent(), new TypeReference<>() {});
|
Map<String, Object> editorData = objectMapper.readValue(page.getContent(), new TypeReference<>() {});
|
||||||
if (editorData.containsKey("blocks")) {
|
if (editorData.containsKey("blocks")) {
|
||||||
blocks = (List<Map<String, Object>>) editorData.get("blocks");
|
blocks = (List<Map<String, Object>>) editorData.get("blocks");
|
||||||
}
|
}
|
||||||
} catch (JsonProcessingException e) {
|
} catch (JsonProcessingException e) {
|
||||||
LOG.error("Failed to parse Editor.js JSON for page slug: {}", slug, e);
|
LOG.error("Failed to parse Editor.js JSON for page ID: {}", page.getId(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
model.addAttribute("page", page);
|
model.addAttribute("page", page);
|
||||||
model.addAttribute("blocks", blocks);
|
model.addAttribute("blocks", blocks);
|
||||||
|
|
||||||
return "page";
|
return "page";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -61,6 +61,7 @@ public class ManagePageController {
|
|||||||
page.setDisplayOrder(0);
|
page.setDisplayOrder(0);
|
||||||
model.addAttribute("page", page);
|
model.addAttribute("page", page);
|
||||||
model.addAttribute("statuses", PageStatus.values());
|
model.addAttribute("statuses", PageStatus.values());
|
||||||
|
model.addAttribute("pageTypes", com.sisvietnamvn.web.domain.PageType.values());
|
||||||
model.addAttribute("isNew", true);
|
model.addAttribute("isNew", true);
|
||||||
return "manage/pages/form";
|
return "manage/pages/form";
|
||||||
}
|
}
|
||||||
@@ -76,6 +77,7 @@ public class ManagePageController {
|
|||||||
LOG.debug("Request to create Page : {}", page);
|
LOG.debug("Request to create Page : {}", page);
|
||||||
if (bindingResult.hasErrors()) {
|
if (bindingResult.hasErrors()) {
|
||||||
model.addAttribute("statuses", PageStatus.values());
|
model.addAttribute("statuses", PageStatus.values());
|
||||||
|
model.addAttribute("pageTypes", com.sisvietnamvn.web.domain.PageType.values());
|
||||||
model.addAttribute("isNew", true);
|
model.addAttribute("isNew", true);
|
||||||
return "manage/pages/form";
|
return "manage/pages/form";
|
||||||
}
|
}
|
||||||
@@ -97,6 +99,7 @@ public class ManagePageController {
|
|||||||
}
|
}
|
||||||
model.addAttribute("page", pageOptional.get());
|
model.addAttribute("page", pageOptional.get());
|
||||||
model.addAttribute("statuses", PageStatus.values());
|
model.addAttribute("statuses", PageStatus.values());
|
||||||
|
model.addAttribute("pageTypes", com.sisvietnamvn.web.domain.PageType.values());
|
||||||
model.addAttribute("isNew", false);
|
model.addAttribute("isNew", false);
|
||||||
return "manage/pages/form";
|
return "manage/pages/form";
|
||||||
}
|
}
|
||||||
@@ -113,6 +116,7 @@ public class ManagePageController {
|
|||||||
LOG.debug("Request to update Page : {}", id);
|
LOG.debug("Request to update Page : {}", id);
|
||||||
if (bindingResult.hasErrors()) {
|
if (bindingResult.hasErrors()) {
|
||||||
model.addAttribute("statuses", PageStatus.values());
|
model.addAttribute("statuses", PageStatus.values());
|
||||||
|
model.addAttribute("pageTypes", com.sisvietnamvn.web.domain.PageType.values());
|
||||||
model.addAttribute("isNew", false);
|
model.addAttribute("isNew", false);
|
||||||
return "manage/pages/form";
|
return "manage/pages/form";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ public class Page extends AbstractAuditingEntity<Long> {
|
|||||||
@Column(name = "status", length = 20, nullable = false)
|
@Column(name = "status", length = 20, nullable = false)
|
||||||
private PageStatus status = PageStatus.DRAFT;
|
private PageStatus status = PageStatus.DRAFT;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "page_type", length = 50, nullable = false)
|
||||||
|
private PageType pageType = PageType.CUSTOM;
|
||||||
|
|
||||||
@Column(name = "display_order")
|
@Column(name = "display_order")
|
||||||
private Integer displayOrder = 0;
|
private Integer displayOrder = 0;
|
||||||
|
|
||||||
@@ -99,6 +104,14 @@ public class Page extends AbstractAuditingEntity<Long> {
|
|||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PageType getPageType() {
|
||||||
|
return pageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPageType(PageType pageType) {
|
||||||
|
this.pageType = pageType;
|
||||||
|
}
|
||||||
|
|
||||||
public Integer getDisplayOrder() {
|
public Integer getDisplayOrder() {
|
||||||
return displayOrder;
|
return displayOrder;
|
||||||
}
|
}
|
||||||
@@ -129,6 +142,7 @@ public class Page extends AbstractAuditingEntity<Long> {
|
|||||||
"id=" + getId() +
|
"id=" + getId() +
|
||||||
", title='" + getTitle() + "'" +
|
", title='" + getTitle() + "'" +
|
||||||
", slug='" + getSlug() + "'" +
|
", slug='" + getSlug() + "'" +
|
||||||
|
", pageType='" + getPageType() + "'" +
|
||||||
", status='" + getStatus() + "'" +
|
", status='" + getStatus() + "'" +
|
||||||
", displayOrder=" + getDisplayOrder() +
|
", displayOrder=" + getDisplayOrder() +
|
||||||
"}";
|
"}";
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.sisvietnamvn.web.domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The PageType enumeration.
|
||||||
|
* Specifies the template or system location that a CMS page belongs to.
|
||||||
|
*/
|
||||||
|
public enum PageType {
|
||||||
|
HOME,
|
||||||
|
ABOUT_US,
|
||||||
|
SPECIALTY,
|
||||||
|
DOCTOR,
|
||||||
|
SERVICE,
|
||||||
|
HEALTH_LIBRARY,
|
||||||
|
NEWS_AND_EVENTS,
|
||||||
|
PATIENT_SUPPORT,
|
||||||
|
MEDICAL_EXPERT,
|
||||||
|
UMCERS,
|
||||||
|
BIDDING,
|
||||||
|
CONTACT_US,
|
||||||
|
CUSTOM
|
||||||
|
}
|
||||||
@@ -23,6 +23,11 @@ public interface PageRepository extends JpaRepository<Page, Long> {
|
|||||||
*/
|
*/
|
||||||
Optional<Page> findBySlug(String slug);
|
Optional<Page> findBySlug(String slug);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a page by its predefined CMS Page Type.
|
||||||
|
*/
|
||||||
|
Optional<Page> findByPageType(com.sisvietnamvn.web.domain.PageType pageType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all pages with a given publication status.
|
* Find all pages with a given publication status.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -61,6 +61,15 @@ public class PageService {
|
|||||||
return pageRepository.findBySlug(slug);
|
return pageRepository.findBySlug(slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single page by its predefined CMS Page Type.
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<Page> findByPageType(com.sisvietnamvn.web.domain.PageType pageType) {
|
||||||
|
LOG.debug("Request to get Page by type : {}", pageType);
|
||||||
|
return pageRepository.findByPageType(pageType);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all pages with a specific status.
|
* Find all pages with a specific status.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
<?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="20260625120000-1" author="antigravity">
|
||||||
|
<addColumn tableName="sis_page">
|
||||||
|
<column name="page_type" type="varchar(50)" defaultValue="CUSTOM">
|
||||||
|
<constraints nullable="false" />
|
||||||
|
</column>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
<include file="config/liquibase/changelog/00000000000000_initial_schema.xml" relativeToChangelogFile="false"/>
|
<include file="config/liquibase/changelog/00000000000000_initial_schema.xml" relativeToChangelogFile="false"/>
|
||||||
<include file="config/liquibase/changelog/20260625100000_add_page_entity.xml" relativeToChangelogFile="false"/>
|
<include file="config/liquibase/changelog/20260625100000_add_page_entity.xml" relativeToChangelogFile="false"/>
|
||||||
|
<include file="config/liquibase/changelog/20260625120000_add_page_type.xml" relativeToChangelogFile="false"/>
|
||||||
<!-- jhipster-needle-liquibase-add-changelog - JHipster will add liquibase changelogs here -->
|
<!-- jhipster-needle-liquibase-add-changelog - JHipster will add liquibase changelogs here -->
|
||||||
<!-- jhipster-needle-liquibase-add-constraints-changelog - JHipster will add liquibase constraints changelogs here -->
|
<!-- 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 -->
|
<!-- jhipster-needle-liquibase-add-incremental-changelog - JHipster will add incremental liquibase changelogs here -->
|
||||||
|
|||||||
@@ -21,13 +21,9 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<header th:replace="~{fragments/header :: #l--main-header}"></header>
|
|
||||||
|
|
||||||
<div layout:fragment="content">
|
<div layout:fragment="content">
|
||||||
<!-- Nội dung của các trang con sẽ được chèn vào đây -->
|
<!-- All elements will be defined in the editor page -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer th:replace="~{fragments/footer :: footer}"></footer>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -117,8 +117,19 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<!-- Page Type -->
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="pageType" class="font-weight-bold">Page Type <span
|
||||||
|
class="text-danger">*</span></label>
|
||||||
|
<select class="form-control" id="pageType" th:field="*{pageType}">
|
||||||
|
<option th:each="pt : ${pageTypes}" th:value="${pt}" th:text="${pt}"></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="pageStatus" class="font-weight-bold">Status <span
|
<label for="pageStatus" class="font-weight-bold">Status <span
|
||||||
class="text-danger">*</span></label>
|
class="text-danger">*</span></label>
|
||||||
@@ -129,7 +140,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Display Order -->
|
<!-- Display Order -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="pageDisplayOrder" class="font-weight-bold">Display Order</label>
|
<label for="pageDisplayOrder" class="font-weight-bold">Display Order</label>
|
||||||
<input type="number" class="form-control" id="pageDisplayOrder"
|
<input type="number" class="form-control" id="pageDisplayOrder"
|
||||||
|
|||||||
@@ -8,102 +8,79 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<!--
|
<!-- Render Editor.js Blocks -->
|
||||||
Because the user wants absolute freedom to design the page using snippets,
|
|
||||||
we will render the blocks directly into the content fragment with NO constraining containers.
|
|
||||||
If they want a container, they can use a standard snippet.
|
|
||||||
-->
|
|
||||||
<div layout:fragment="content">
|
<div layout:fragment="content">
|
||||||
|
|
||||||
<!-- Render Editor.js Blocks -->
|
|
||||||
<th:block th:each="block : ${blocks}">
|
<th:block th:each="block : ${blocks}">
|
||||||
|
|
||||||
<!-- 1. HTML Snippet Block (Custom) -->
|
<!-- 1. HTML Snippet Block (Custom) -->
|
||||||
<th:block th:if="${block.type == 'snippet'}">
|
<th:block th:if="${block.type == 'snippet'}">
|
||||||
<div th:insert="~{'snippets/' + ${block.data.id}}"></div>
|
<div th:insert="~{'snippets/' + ${block.data.id}}" th:remove="tag"></div>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
<!-- 2. Header Block -->
|
||||||
|
<th:block th:if="${block.type == 'header'}">
|
||||||
|
<th:block th:switch="${block.data.level}">
|
||||||
|
<h1 th:case="1" th:utext="${block.data.text}"></h1>
|
||||||
|
<h2 th:case="2" th:utext="${block.data.text}"></h2>
|
||||||
|
<h3 th:case="3" th:utext="${block.data.text}"></h3>
|
||||||
|
<h4 th:case="4" th:utext="${block.data.text}"></h4>
|
||||||
|
<h5 th:case="5" th:utext="${block.data.text}"></h5>
|
||||||
|
<h6 th:case="6" th:utext="${block.data.text}"></h6>
|
||||||
|
<h2 th:case="*" th:utext="${block.data.text}"></h2>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
<!-- 2. Header Block -->
|
|
||||||
<th:block th:if="${block.type == 'header'}">
|
|
||||||
<!-- Editor.js header block has level (1-6) and text -->
|
|
||||||
<div class="container my-3">
|
|
||||||
<th:block th:switch="${block.data.level}">
|
|
||||||
<h1 th:case="1" th:utext="${block.data.text}"></h1>
|
|
||||||
<h2 th:case="2" th:utext="${block.data.text}"></h2>
|
|
||||||
<h3 th:case="3" th:utext="${block.data.text}"></h3>
|
|
||||||
<h4 th:case="4" th:utext="${block.data.text}"></h4>
|
|
||||||
<h5 th:case="5" th:utext="${block.data.text}"></h5>
|
|
||||||
<h6 th:case="6" th:utext="${block.data.text}"></h6>
|
|
||||||
<h2 th:case="*" th:utext="${block.data.text}"></h2>
|
|
||||||
</th:block>
|
|
||||||
</div>
|
|
||||||
</th:block>
|
|
||||||
|
|
||||||
<!-- 3. Paragraph Block -->
|
|
||||||
<th:block th:if="${block.type == 'paragraph'}">
|
|
||||||
<div class="container">
|
|
||||||
<p th:utext="${block.data.text}"></p>
|
|
||||||
</div>
|
|
||||||
</th:block>
|
|
||||||
|
|
||||||
<!-- 4. List Block -->
|
|
||||||
<th:block th:if="${block.type == 'list'}">
|
|
||||||
<div class="container">
|
|
||||||
<ul th:if="${block.data.style == 'unordered'}">
|
|
||||||
<li th:each="item : ${block.data.items}" th:utext="${item}"></li>
|
|
||||||
</ul>
|
|
||||||
<ol th:if="${block.data.style == 'ordered'}">
|
|
||||||
<li th:each="item : ${block.data.items}" th:utext="${item}"></li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
</th:block>
|
|
||||||
|
|
||||||
<!-- 5. Image Block -->
|
|
||||||
<th:block th:if="${block.type == 'image'}">
|
|
||||||
<div class="container text-center my-4">
|
|
||||||
<img th:src="${block.data.file.url}" class="img-fluid" th:alt="${block.data.caption}" />
|
|
||||||
<p class="text-muted small mt-1" th:if="${block.data.caption}" th:utext="${block.data.caption}"></p>
|
|
||||||
</div>
|
|
||||||
</th:block>
|
|
||||||
|
|
||||||
<!-- 6. Quote Block -->
|
|
||||||
<th:block th:if="${block.type == 'quote'}">
|
|
||||||
<div class="container my-4">
|
|
||||||
<blockquote class="blockquote">
|
|
||||||
<p class="mb-0" th:utext="${block.data.text}"></p>
|
|
||||||
<footer class="blockquote-footer" th:if="${block.data.caption}" th:utext="${block.data.caption}"></footer>
|
|
||||||
</blockquote>
|
|
||||||
</div>
|
|
||||||
</th:block>
|
|
||||||
|
|
||||||
<!-- 7. Delimiter Block -->
|
|
||||||
<th:block th:if="${block.type == 'delimiter'}">
|
|
||||||
<div class="container text-center my-4">
|
|
||||||
<span style="font-size: 24px; letter-spacing: 10px; color: #ccc;">***</span>
|
|
||||||
</div>
|
|
||||||
</th:block>
|
|
||||||
|
|
||||||
<!-- 8. Table Block -->
|
|
||||||
<th:block th:if="${block.type == 'table'}">
|
|
||||||
<div class="container my-4">
|
|
||||||
<table class="table table-bordered">
|
|
||||||
<tbody>
|
|
||||||
<tr th:each="row, rowStat : ${block.data.content}">
|
|
||||||
<!-- If withHeadings is true, make first row <th> -->
|
|
||||||
<th:block th:if="${block.data.withHeadings == true and rowStat.index == 0}">
|
|
||||||
<th th:each="cell : ${row}" th:utext="${cell}"></th>
|
|
||||||
</th:block>
|
|
||||||
<th:block th:unless="${block.data.withHeadings == true and rowStat.index == 0}">
|
|
||||||
<td th:each="cell : ${row}" th:utext="${cell}"></td>
|
|
||||||
</th:block>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</th:block>
|
|
||||||
|
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
|
<!-- 3. Paragraph Block -->
|
||||||
|
<th:block th:if="${block.type == 'paragraph'}">
|
||||||
|
<p th:utext="${block.data.text}"></p>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
<!-- 4. List Block -->
|
||||||
|
<th:block th:if="${block.type == 'list'}">
|
||||||
|
<ul th:if="${block.data.style == 'unordered'}">
|
||||||
|
<li th:each="item : ${block.data.items}" th:utext="${item}"></li>
|
||||||
|
</ul>
|
||||||
|
<ol th:if="${block.data.style == 'ordered'}">
|
||||||
|
<li th:each="item : ${block.data.items}" th:utext="${item}"></li>
|
||||||
|
</ol>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
<!-- 5. Image Block -->
|
||||||
|
<th:block th:if="${block.type == 'image'}">
|
||||||
|
<img th:src="${block.data.file.url}" th:alt="${block.data.caption}" />
|
||||||
|
<p th:if="${block.data.caption}" th:utext="${block.data.caption}"></p>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
<!-- 6. Quote Block -->
|
||||||
|
<th:block th:if="${block.type == 'quote'}">
|
||||||
|
<blockquote>
|
||||||
|
<p th:utext="${block.data.text}"></p>
|
||||||
|
<footer th:if="${block.data.caption}" th:utext="${block.data.caption}"></footer>
|
||||||
|
</blockquote>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
<!-- 7. Delimiter Block -->
|
||||||
|
<th:block th:if="${block.type == 'delimiter'}">
|
||||||
|
<hr />
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
<!-- 8. Table Block -->
|
||||||
|
<th:block th:if="${block.type == 'table'}">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr th:each="row, rowStat : ${block.data.content}">
|
||||||
|
<th:block th:if="${block.data.withHeadings == true and rowStat.index == 0}">
|
||||||
|
<th th:each="cell : ${row}" th:utext="${cell}"></th>
|
||||||
|
</th:block>
|
||||||
|
<th:block th:unless="${block.data.withHeadings == true and rowStat.index == 0}">
|
||||||
|
<td th:each="cell : ${row}" th:utext="${cell}"></td>
|
||||||
|
</th:block>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
</th:block>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user