bổ sung thêm các chức năng còn thiếu của appearance
This commit is contained in:
+57
@@ -0,0 +1,57 @@
|
||||
<?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="20260702184000-1" author="system">
|
||||
<createTable tableName="sis_menu">
|
||||
<column name="id" type="bigint">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="name" type="varchar(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="location" type="varchar(100)"/>
|
||||
<column name="created_by" type="varchar(50)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="created_date" type="timestamp"/>
|
||||
<column name="last_modified_by" type="varchar(50)"/>
|
||||
<column name="last_modified_date" type="timestamp"/>
|
||||
</createTable>
|
||||
|
||||
<createTable tableName="sis_menu_item">
|
||||
<column name="id" type="bigint">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="menu_id" type="bigint">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="label" type="varchar(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="url" type="varchar(500)"/>
|
||||
<column name="parent_id" type="bigint"/>
|
||||
<column name="display_order" type="integer"/>
|
||||
<column name="created_by" type="varchar(50)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="created_date" type="timestamp"/>
|
||||
<column name="last_modified_by" type="varchar(50)"/>
|
||||
<column name="last_modified_date" type="timestamp"/>
|
||||
</createTable>
|
||||
|
||||
<addForeignKeyConstraint baseColumnNames="menu_id"
|
||||
baseTableName="sis_menu_item"
|
||||
constraintName="fk_menu_item_menu_id"
|
||||
referencedColumnNames="id"
|
||||
referencedTableName="sis_menu"/>
|
||||
|
||||
<addForeignKeyConstraint baseColumnNames="parent_id"
|
||||
baseTableName="sis_menu_item"
|
||||
constraintName="fk_menu_item_parent_id"
|
||||
referencedColumnNames="id"
|
||||
referencedTableName="sis_menu_item"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -29,6 +29,7 @@
|
||||
<include file="config/liquibase/changelog/20260702143000_add_media_entity.xml" relativeToChangelogFile="false"/>
|
||||
<include file="config/liquibase/changelog/20260702144700_add_plugin_entity.xml" relativeToChangelogFile="false"/>
|
||||
<include file="config/liquibase/changelog/20260702153000_add_setting_entity.xml" relativeToChangelogFile="false"/>
|
||||
<include file="config/liquibase/changelog/20260702184000_add_menu_entity.xml" relativeToChangelogFile="false"/>
|
||||
<!-- 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-incremental-changelog - JHipster will add incremental liquibase changelogs here -->
|
||||
|
||||
@@ -124,6 +124,10 @@
|
||||
<div class="bg-white py-2 collapse-inner rounded">
|
||||
<h6 class="collapse-header">Design Management:</h6>
|
||||
<a class="collapse-item" th:href="@{/manage/themes}">Themes</a>
|
||||
<a class="collapse-item" th:href="@{/manage/theme-editor}">Theme File Editor</a>
|
||||
<a class="collapse-item" th:href="@{/manage/customize}">Customize</a>
|
||||
<a class="collapse-item" th:href="@{/manage/menus}">Menus</a>
|
||||
<a class="collapse-item" th:href="@{/manage/widgets}">Widgets</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Theme Customizer</title>
|
||||
<link href="/vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="/css/sb-admin-2.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body, html { height: 100%; overflow: hidden; margin: 0; }
|
||||
.customizer-container { display: flex; height: 100vh; }
|
||||
.customizer-sidebar { width: 350px; background: #fff; box-shadow: 2px 0 5px rgba(0,0,0,0.1); z-index: 10; display: flex; flex-direction: column; }
|
||||
.customizer-header { background: #343a40; color: #fff; padding: 15px; display: flex; justify-content: space-between; align-items: center; }
|
||||
.customizer-body { flex: 1; overflow-y: auto; padding: 20px; }
|
||||
.customizer-preview { flex: 1; background: #f8f9fc; }
|
||||
.customizer-preview iframe { width: 100%; height: 100%; border: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="customizer-container">
|
||||
<!-- Sidebar Settings -->
|
||||
<div class="customizer-sidebar">
|
||||
<div class="customizer-header">
|
||||
<a th:href="@{/manage/themes}" class="text-white text-decoration-none"><i class="fas fa-times"></i> Close</a>
|
||||
<h5 class="m-0">Customizer</h5>
|
||||
</div>
|
||||
<div class="customizer-body">
|
||||
<div th:if="${successMessage}" class="alert alert-success" th:text="${successMessage}"></div>
|
||||
|
||||
<form th:action="@{/manage/customize/save}" method="post">
|
||||
<div class="form-group">
|
||||
<label for="primaryColor">Primary Theme Color</label>
|
||||
<input type="color" class="form-control" id="primaryColor" name="primaryColor" th:value="${primaryColor}" style="height: 40px;">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="fontFamily">Base Font Family</label>
|
||||
<select class="form-control" id="fontFamily" name="fontFamily">
|
||||
<option value="sans-serif" th:selected="${fontFamily == 'sans-serif'}">Sans-Serif (Arial, Helvetica)</option>
|
||||
<option value="serif" th:selected="${fontFamily == 'serif'}">Serif (Times New Roman)</option>
|
||||
<option value="monospace" th:selected="${fontFamily == 'monospace'}">Monospace (Courier)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="customCss">Custom CSS</label>
|
||||
<textarea class="form-control" id="customCss" name="customCss" rows="10" th:text="${customCss}" style="font-family: monospace;"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-block">Publish Changes</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Live Preview -->
|
||||
<div class="customizer-preview">
|
||||
<iframe src="/" id="previewFrame" name="previewFrame"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{fragments/manage-layout}">
|
||||
<head>
|
||||
<title>Edit Menu Items</title>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0 text-gray-800" th:text="'Editing Menu: ' + ${menu.name}">Edit Menu</h1>
|
||||
<a th:href="@{/manage/menus}" class="d-none d-sm-inline-block btn btn-sm btn-secondary shadow-sm"><i class="fas fa-arrow-left fa-sm text-white-50"></i> Back to Menus</a>
|
||||
</div>
|
||||
|
||||
<!-- Success Message -->
|
||||
<div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<span th:text="${successMessage}"></span>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Add Item Form -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Add Custom Link</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form th:action="@{/manage/menus/{id}/items/add(id=${menu.id})}" method="post">
|
||||
<div class="form-group">
|
||||
<label for="label">Link Text</label>
|
||||
<input type="text" class="form-control" id="label" name="label" placeholder="e.g. Home" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="url">URL</label>
|
||||
<input type="text" class="form-control" id="url" name="url" placeholder="e.g. /" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block">Add to Menu</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Menu Items List -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Menu Structure</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center" th:each="item : ${menu.items}">
|
||||
<div>
|
||||
<strong th:text="${item.label}">Link Text</strong><br>
|
||||
<small class="text-muted" th:text="${item.url}">URL</small>
|
||||
</div>
|
||||
<!-- Future enhancement: reorder buttons and delete button -->
|
||||
<span class="badge badge-primary badge-pill" th:text="'Order: ' + ${item.displayOrder}">0</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div th:if="${menu.items.empty}" class="alert alert-info mt-3">
|
||||
No items in this menu yet.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,85 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{fragments/manage-layout}">
|
||||
<head>
|
||||
<title>Manage Menus</title>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0 text-gray-800">Menus</h1>
|
||||
</div>
|
||||
|
||||
<!-- Success Message -->
|
||||
<div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<span th:text="${successMessage}"></span>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Create New Menu</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form th:action="@{/manage/menus/create}" method="post">
|
||||
<div class="form-group">
|
||||
<label for="name">Menu Name</label>
|
||||
<input type="text" class="form-control" id="name" name="name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="location">Location</label>
|
||||
<select class="form-control" id="location" name="location">
|
||||
<option value="">-- No Location Assigned --</option>
|
||||
<option value="PRIMARY">Primary Navigation</option>
|
||||
<option value="FOOTER">Footer Navigation</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block">Create Menu</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Existing Menus</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Location</th>
|
||||
<th>Items Count</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="menu : ${menus}">
|
||||
<td th:text="${menu.name}">Menu Name</td>
|
||||
<td th:text="${menu.location ?: 'Unassigned'}">Location</td>
|
||||
<td th:text="${menu.items.size()}">0</td>
|
||||
<td>
|
||||
<a th:href="@{/manage/menus/{id}/edit(id=${menu.id})}" class="btn btn-sm btn-info">Edit Items</a>
|
||||
<form th:action="@{/manage/menus/{id}/delete(id=${menu.id})}" method="post" style="display:inline;">
|
||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to delete this menu?');">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr th:if="${menus.empty}">
|
||||
<td colspan="4" class="text-center">No menus found.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{fragments/manage-layout}">
|
||||
<head>
|
||||
<title>Theme File Editor</title>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0 text-gray-800">Theme File Editor</h1>
|
||||
</div>
|
||||
|
||||
<div th:if="${successMessage}" class="alert alert-success" th:text="${successMessage}"></div>
|
||||
<div th:if="${errorMessage}" class="alert alert-danger" th:text="${errorMessage}"></div>
|
||||
|
||||
<div class="row">
|
||||
<!-- File Tree Sidebar -->
|
||||
<div class="col-lg-3">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Theme Files</h6>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="list-group list-group-flush" style="max-height: 600px; overflow-y: auto;">
|
||||
<a th:each="f : ${files}"
|
||||
th:href="@{/manage/theme-editor(file=${f})}"
|
||||
class="list-group-item list-group-item-action"
|
||||
th:classappend="${selectedFile == f} ? 'active' : ''"
|
||||
style="font-size: 0.9em; padding: 10px 15px;">
|
||||
<i class="fas fa-file-code fa-sm mr-2" th:classappend="${selectedFile == f} ? 'text-white' : 'text-gray-500'"></i>
|
||||
<span th:text="${f}">file.html</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Code Editor -->
|
||||
<div class="col-lg-9">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
|
||||
<h6 class="m-0 font-weight-bold text-primary" th:text="${selectedFile != null ? 'Editing: ' + selectedFile : 'Select a file to edit'}">Editor</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div th:if="${selectedFile == null}" class="text-center py-5 text-muted">
|
||||
<i class="fas fa-code fa-3x mb-3"></i>
|
||||
<p>Select a file from the sidebar to start editing.</p>
|
||||
</div>
|
||||
|
||||
<form th:if="${selectedFile != null}" th:action="@{/manage/theme-editor/save}" method="post">
|
||||
<input type="hidden" name="selectedFile" th:value="${selectedFile}">
|
||||
<div class="form-group">
|
||||
<textarea class="form-control" name="fileContent" rows="25" th:text="${fileContent}" style="font-family: monospace; font-size: 14px; background-color: #272822; color: #f8f8f2;"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><i class="fas fa-save mr-2"></i> Update File</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -27,33 +27,53 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Theme Upload Form -->
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Upload New Theme</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form th:action="@{/manage/themes/upload}" method="post" enctype="multipart/form-data" class="form-inline">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
<div class="form-group mb-2">
|
||||
<input type="file" class="form-control-file" id="file" name="file" accept=".zip" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mb-2 ml-3">Upload Theme (.zip)</button>
|
||||
</form>
|
||||
<small class="form-text text-muted">The zip file should contain the theme folder structure (e.g., layout.html, header.html, footer.html).</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dynamic Layout Cards -->
|
||||
<div class="col-lg-4 mb-4" th:each="theme : ${availableThemes}">
|
||||
<div class="card shadow mb-4 h-100" th:classappend="${activeTheme == theme} ? 'border-left-primary' : ''">
|
||||
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
|
||||
<h6 class="m-0 font-weight-bold text-primary" th:text="${#strings.capitalizeWords(#strings.replace(theme, '-', ' '))} + ' Layout'">Theme Name</h6>
|
||||
<span th:if="${activeTheme == theme}" class="badge badge-success">Active</span>
|
||||
</div>
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="bg-light p-5 text-center mb-3 border rounded">
|
||||
<i class="fas fa-file-code fa-4x text-gray-400"></i>
|
||||
</div>
|
||||
<p class="card-text flex-grow-1">A custom layout file loaded from the templates/themes directory.</p>
|
||||
<!-- Available Themes Grid -->
|
||||
<div class="row">
|
||||
<div class="col-lg-4 col-md-6 mb-4" th:each="theme : ${availableThemes}">
|
||||
<div class="card h-100 shadow-sm border-0" th:classappend="${activeTheme == theme} ? 'border-primary' : ''">
|
||||
<div class="card-body text-center d-flex flex-column">
|
||||
<i class="fas fa-palette fa-3x mb-3" th:classappend="${activeTheme == theme} ? 'text-primary' : 'text-secondary'"></i>
|
||||
<h5 class="card-title text-uppercase font-weight-bold" th:text="${theme}">Theme Name</h5>
|
||||
|
||||
<div class="mt-auto d-flex justify-content-center">
|
||||
<!-- Active Badge -->
|
||||
<span th:if="${activeTheme == theme}" class="badge badge-success px-3 py-2">Active</span>
|
||||
|
||||
<form th:if="${activeTheme != theme}" th:action="@{/manage/themes/activate}" method="post" class="mt-auto">
|
||||
<!-- Activate Button -->
|
||||
<form th:if="${activeTheme != theme}" th:action="@{/manage/themes/activate}" method="post" class="mr-2">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
<input type="hidden" name="themeKey" th:value="${theme}" />
|
||||
<button type="submit" class="btn btn-primary btn-block">Activate</button>
|
||||
<button type="submit" class="btn btn-outline-primary btn-sm">Activate</button>
|
||||
</form>
|
||||
|
||||
<!-- Delete Button -->
|
||||
<form th:if="${activeTheme != theme}" th:action="@{/manage/themes/delete}" method="post">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
<input type="hidden" name="themeKey" th:value="${theme}" />
|
||||
<button type="submit" class="btn btn-outline-danger btn-sm" onclick="return confirm('Delete this theme completely?');">Delete</button>
|
||||
</form>
|
||||
<button th:if="${activeTheme == theme}" class="btn btn-secondary btn-block mt-auto" disabled>Customize</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{fragments/manage-layout}">
|
||||
<head>
|
||||
<title>Manage Widgets</title>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0 text-gray-800">Widgets</h1>
|
||||
</div>
|
||||
|
||||
<!-- Success Message -->
|
||||
<div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<span th:text="${successMessage}"></span>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Add Widget Form -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Add New Widget</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form th:action="@{/manage/widgets/add}" method="post">
|
||||
<div class="form-group">
|
||||
<label for="area">Widget Area</label>
|
||||
<select class="form-control" id="area" name="area">
|
||||
<option value="sidebar">Sidebar</option>
|
||||
<option value="footer">Footer</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="type">Widget Type</label>
|
||||
<select class="form-control" id="type" name="type">
|
||||
<option value="TEXT">Text</option>
|
||||
<option value="HTML">Custom HTML</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="title">Title</label>
|
||||
<input type="text" class="form-control" id="title" name="title" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="content">Content</label>
|
||||
<textarea class="form-control" id="content" name="content" rows="4"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block">Add Widget</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar Widgets -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Sidebar Widgets</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="list-group">
|
||||
<div class="list-group-item" th:each="widget : ${sidebarWidgets}">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1" th:text="${widget.title}">Title</h5>
|
||||
<form th:action="@{/manage/widgets/delete}" method="post">
|
||||
<input type="hidden" name="area" value="sidebar">
|
||||
<input type="hidden" name="id" th:value="${widget.id}">
|
||||
<button type="submit" class="btn btn-sm btn-danger"><i class="fas fa-trash"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
<small class="text-muted" th:text="${widget.type}">Type</small>
|
||||
<p class="mb-1" th:text="${widget.content}">Content...</p>
|
||||
</div>
|
||||
<div th:if="${sidebarWidgets.empty}" class="text-center text-muted p-3">
|
||||
No widgets in Sidebar.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Widgets -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Footer Widgets</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="list-group">
|
||||
<div class="list-group-item" th:each="widget : ${footerWidgets}">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1" th:text="${widget.title}">Title</h5>
|
||||
<form th:action="@{/manage/widgets/delete}" method="post">
|
||||
<input type="hidden" name="area" value="footer">
|
||||
<input type="hidden" name="id" th:value="${widget.id}">
|
||||
<button type="submit" class="btn btn-sm btn-danger"><i class="fas fa-trash"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
<small class="text-muted" th:text="${widget.type}">Type</small>
|
||||
<p class="mb-1" th:text="${widget.content}">Content...</p>
|
||||
</div>
|
||||
<div th:if="${footerWidgets.empty}" class="text-center text-muted p-3">
|
||||
No widgets in Footer.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,6 +5,18 @@
|
||||
<title th:text="${page.title}">Page Title</title>
|
||||
<!-- Add Meta Description for SEO -->
|
||||
<meta name="description" th:if="${page.metaDescription != null}" th:content="${page.metaDescription}" />
|
||||
|
||||
<!-- Customizer CSS overrides -->
|
||||
<style th:inline="css">
|
||||
body {
|
||||
font-family: /*[[${themeModFontFamily}]]*/ sans-serif !important;
|
||||
}
|
||||
/* Inject Customizer Primary Color dynamically */
|
||||
a, .text-primary {
|
||||
color: /*[[${themeModPrimaryColor}]]*/ #007bff !important;
|
||||
}
|
||||
/*[[${themeModCustomCss}]]*/
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -4,8 +4,26 @@
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<footer th:fragment="footer" style="background-color: #e9ecef; padding: 20px; text-align: center; margin-top: 40px;">
|
||||
<p>© 2026 Default Theme Footer. All rights reserved.</p>
|
||||
<footer th:fragment="footer" style="background-color: #343a40; color: white; padding: 20px; text-align: center;">
|
||||
<p>© 2026 SIS Vietnam. All Rights Reserved.</p>
|
||||
<div th:if="${footerMenu != null}">
|
||||
<span th:each="item, iterStat : ${footerMenu.items}">
|
||||
<a th:href="${item.url}" th:text="${item.label}" style="color: #17a2b8;">Link</a>
|
||||
<span th:if="${!iterStat.last}"> | </span>
|
||||
</span>
|
||||
</div>
|
||||
<div th:if="${footerMenu == null}">
|
||||
<a href="/privacy-policy" style="color: #17a2b8;">Privacy Policy</a> |
|
||||
<a href="/terms" style="color: #17a2b8;">Terms of Service</a>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 30px; display: flex; justify-content: space-around; text-align: left; border-top: 1px solid #555; padding-top: 20px;">
|
||||
<div th:each="widget : ${footerWidgets}" style="flex: 1; margin: 0 15px;">
|
||||
<h5 th:text="${widget.title}" style="color: #ccc;">Widget Title</h5>
|
||||
<div th:if="${widget.type == 'HTML'}" th:utext="${widget.content}" style="font-size: 0.9em; color: #aaa;"></div>
|
||||
<div th:if="${widget.type == 'TEXT'}" th:text="${widget.content}" style="font-size: 0.9em; color: #aaa;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,7 +6,13 @@
|
||||
<body>
|
||||
<header th:fragment="header" style="background-color: #e9ecef; padding: 20px; text-align: center;">
|
||||
<h2>Default Theme Header</h2>
|
||||
<nav>
|
||||
<nav th:if="${primaryMenu != null}">
|
||||
<span th:each="item, iterStat : ${primaryMenu.items}">
|
||||
<a th:href="${item.url}" th:text="${item.label}">Link</a>
|
||||
<span th:if="${!iterStat.last}"> | </span>
|
||||
</span>
|
||||
</nav>
|
||||
<nav th:if="${primaryMenu == null}">
|
||||
<a href="/">Home</a> |
|
||||
<a href="/about">About Us</a> |
|
||||
<a href="/tin-tuc">News</a> |
|
||||
|
||||
@@ -8,8 +8,19 @@
|
||||
<!-- Replace with Header Fragment -->
|
||||
<header th:replace="~{themes/__${activeTheme}__/header :: header}"></header>
|
||||
|
||||
<div style="padding: 20px; min-height: 400px;">
|
||||
<div layout:fragment="content"></div>
|
||||
<div style="display: flex; min-height: 400px; padding: 20px;">
|
||||
<div style="flex: 3; padding-right: 20px;" layout:fragment="content"></div>
|
||||
<aside style="flex: 1; background-color: #f4f4f4; padding: 15px; border-radius: 5px;">
|
||||
<h4>Sidebar</h4>
|
||||
<div th:each="widget : ${sidebarWidgets}" style="margin-bottom: 20px;">
|
||||
<h5 th:text="${widget.title}" style="border-bottom: 1px solid #ddd; padding-bottom: 5px;">Widget Title</h5>
|
||||
<div th:if="${widget.type == 'HTML'}" th:utext="${widget.content}"></div>
|
||||
<div th:if="${widget.type == 'TEXT'}" th:text="${widget.content}"></div>
|
||||
</div>
|
||||
<div th:if="${sidebarWidgets == null || sidebarWidgets.empty}">
|
||||
<p>No widgets added to sidebar.</p>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<!-- Replace with Footer Fragment -->
|
||||
|
||||
@@ -4,9 +4,23 @@
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<footer th:fragment="footer" style="background-color: #111; color: #888; padding: 40px 20px; text-align: center; margin-top: 50px;">
|
||||
<h4 style="color: #fff; margin-bottom: 10px;">Modern Theme Design</h4>
|
||||
<p style="font-size: 14px;">© 2026. Built with Spring Boot and Thymeleaf.</p>
|
||||
<footer th:fragment="footer" style="background-color: #000; color: #aaa; padding: 40px 20px; text-align: center; border-top: 2px solid #222;">
|
||||
<p style="margin: 0; font-size: 0.9em;">© 2026 MODERN INC. Crafted with care.</p>
|
||||
<div style="margin-top: 15px;" th:if="${footerMenu != null}">
|
||||
<a th:each="item : ${footerMenu.items}" th:href="${item.url}" th:text="${item.label}" style="color: #007bff; text-decoration: none; margin: 0 10px; font-weight: bold; text-transform: uppercase;">LINK</a>
|
||||
</div>
|
||||
<div style="margin-top: 15px;" th:if="${footerMenu == null}">
|
||||
<a href="/privacy-policy" style="color: #007bff; text-decoration: none; margin: 0 10px; font-weight: bold;">PRIVACY</a>
|
||||
<a href="/terms" style="color: #007bff; text-decoration: none; margin: 0 10px; font-weight: bold;">TERMS</a>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 40px; display: flex; justify-content: center; text-align: left; max-width: 1000px; margin-left: auto; margin-right: auto; padding-top: 30px; border-top: 1px solid #333;">
|
||||
<div th:each="widget : ${footerWidgets}" style="flex: 1; margin: 0 20px; min-width: 200px;">
|
||||
<h4 th:text="${widget.title}" style="color: #fff; font-size: 1.1em; margin-bottom: 15px; text-transform: uppercase; letter-spacing: 1px;">Widget Title</h4>
|
||||
<div th:if="${widget.type == 'HTML'}" th:utext="${widget.content}" style="font-size: 0.9em; color: #888; line-height: 1.6;"></div>
|
||||
<div th:if="${widget.type == 'TEXT'}" th:text="${widget.content}" style="font-size: 0.9em; color: #888; line-height: 1.6;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,7 +6,12 @@
|
||||
<body>
|
||||
<header th:fragment="header" style="background-color: #000; color: #fff; padding: 25px; display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #007bff;">
|
||||
<h2 style="margin: 0; font-weight: 300; letter-spacing: 2px;">MODERN<span style="color: #007bff; font-weight: bold;">THEME</span></h2>
|
||||
<nav>
|
||||
|
||||
<nav th:if="${primaryMenu != null}">
|
||||
<a th:each="item : ${primaryMenu.items}" th:href="${item.url}" th:text="${item.label}" style="color: #fff; text-decoration: none; margin-left: 20px; font-weight: bold; text-transform: uppercase;">LINK</a>
|
||||
</nav>
|
||||
|
||||
<nav th:if="${primaryMenu == null}">
|
||||
<a href="/" style="color: #fff; text-decoration: none; margin-left: 20px; font-weight: bold;">HOME</a>
|
||||
<a href="/about" style="color: #fff; text-decoration: none; margin-left: 20px;">ABOUT</a>
|
||||
<a href="/tin-tuc" style="color: #fff; text-decoration: none; margin-left: 20px;">NEWS</a>
|
||||
|
||||
@@ -8,7 +8,21 @@
|
||||
<!-- Replace with Header Fragment -->
|
||||
<header th:replace="~{themes/__${activeTheme}__/header :: header}"></header>
|
||||
|
||||
<div style="padding: 20px; min-height: 500px;" layout:fragment="content"></div>
|
||||
<div style="display: flex; max-width: 1200px; margin: 0 auto; padding: 40px 20px; min-height: 500px;">
|
||||
<div style="flex: 3; padding-right: 40px;" layout:fragment="content"></div>
|
||||
|
||||
<aside style="flex: 1; border-left: 1px solid #ddd; padding-left: 20px;">
|
||||
<h3 style="font-size: 1.2em; border-bottom: 2px solid #007bff; padding-bottom: 10px; margin-bottom: 20px;">SIDEBAR</h3>
|
||||
<div th:each="widget : ${sidebarWidgets}" style="margin-bottom: 30px;">
|
||||
<h4 th:text="${widget.title}" style="font-size: 1.1em; color: #333; margin-bottom: 10px;">Widget Title</h4>
|
||||
<div th:if="${widget.type == 'HTML'}" th:utext="${widget.content}" style="font-size: 0.95em; color: #666; line-height: 1.5;"></div>
|
||||
<div th:if="${widget.type == 'TEXT'}" th:text="${widget.content}" style="font-size: 0.95em; color: #666; line-height: 1.5;"></div>
|
||||
</div>
|
||||
<div th:if="${sidebarWidgets == null || sidebarWidgets.empty}">
|
||||
<p style="color: #999; font-style: italic;">No widgets found.</p>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<!-- Replace with Footer Fragment -->
|
||||
<footer th:replace="~{themes/__${activeTheme}__/footer :: footer}"></footer>
|
||||
|
||||
Reference in New Issue
Block a user