11 Commits

186 changed files with 21040 additions and 1468 deletions
+4 -2
View File
@@ -7,14 +7,16 @@
"request": "launch",
"mainClass": "com.sisvietnamvn.web.SisvietnamvnApp",
"projectName": "sisvietnamvn",
"cwd": "${workspaceFolder}/sisvietnamvn_main",
"vmArgs": "-Dspring.profiles.active=dev"
},
{
"type": "java",
"name": "Debug (Attach) - Port 5005",
"request": "attach",
"hostName": "localhost",
"port": 5005
"hostName": "127.0.0.1",
"port": 5005,
"projectName": "sisvietnamvn"
}
]
}
+3 -3
View File
@@ -1,11 +1,11 @@
{
"java.compile.nullAnalysis.mode": "automatic",
"jdk.jdkhome": "C:\\Program Files\\Eclipse Adoptium\\jdk-21.0.11.10-hotspot",
"java.jdt.ls.java.home": "C:\\Program Files\\Eclipse Adoptium\\jdk-21.0.11.10-hotspot",
"jdk.jdkhome": "/usr/lib/jvm/java-21-openjdk-amd64",
"java.jdt.ls.java.home": "/usr/lib/jvm/java-21-openjdk-amd64",
"java.configuration.runtimes": [
{
"name": "JavaSE-21",
"path": "C:\\Program Files\\Eclipse Adoptium\\jdk-21.0.11.10-hotspot",
"path": "/usr/lib/jvm/java-21-openjdk-amd64",
"default": true
}
],
+69
View File
@@ -0,0 +1,69 @@
Dưới đây là danh sách đầy đủ toàn bộ các chức năng của trang quản trị WordPress (WordPress Admin Dashboard), được chia làm hai phần chính: Giao diện người dùng (UI/UX) và Các hàm PHP/APIs dành cho Lập trình viên để phát triển/tùy biến trang admin.
PHẦN 1: CÁC CHỨC NĂNG TRÊN GISAO DIỆN ADMIN (UI MENUS)
Đây là các chức năng mặc định xuất hiện ở thanh Menu bên trái (Sidebar) của trang quản trị:
Menu chính Menu con Chức năng chi tiết
1. Dashboard (Bảng điều khiển) Home Xem tổng quan trạng thái website (Site Health, số lượng bài viết/trang, bình luận gần đây, hoạt động viết nháp nhanh).
Updates Cập nhật phiên bản WordPress Core, Themes, Plugins và các gói dịch vụ ngôn ngữ.
2. Posts (Bài viết) All Posts Quản lý toàn bộ bài viết tin tức, blog, lọc theo ngày/chuyên mục.
Add New Soạn thảo bài viết mới bằng Block Editor (Gutenberg) hoặc Classic Editor.
Categories Tạo và quản lý các Chuyên mục (thư mục phân loại bài viết).
Tags Tạo và quản lý các Thẻ (nhãn từ khóa tìm kiếm nhanh).
3. Media (Thư viện) Library Quản lý toàn bộ hình ảnh, tài liệu (PDF, Docx), video, âm thanh đã tải lên.
Add New Tải tệp tin mới lên máy chủ.
4. Pages (Trang) All Pages Quản lý các trang tĩnh (ví dụ: Trang chủ, Trang liên hệ, Giới thiệu bệnh viện, Giờ làm việc...).
Add New Tạo trang tĩnh mới.
5. Comments (Bình luận) (Không có) Quản lý bình luận của người đọc (Phê duyệt, Trả lời, Đánh dấu Spam, Xóa tạm/Xóa vĩnh viễn).
6. Appearance (Giao diện) Themes Cài đặt, kích hoạt hoặc xóa các giao diện hiển thị của website.
Customize Chỉnh sửa giao diện trực quan theo thời gian thực (Header, Footer, Colors, Font, v.v.).
Widgets Quản lý các khối nội dung ở Sidebar hoặc Footer.
Menus Thiết kế menu điều hướng chính ở đầu trang và chân trang.
Theme File Editor Chỉnh sửa trực tiếp file code của giao diện (chỉ dùng cho lập trình viên).
7. Plugins (Gói mở rộng) Installed Plugins Quản lý, kích hoạt, vô hiệu hóa hoặc xóa các Plugin đang có trên hệ thống.
Add New Tìm kiếm và cài đặt Plugin mới từ thư viện WordPress.org hoặc upload file .zip.
Plugin File Editor Chỉnh sửa mã nguồn của các Plugin trực tiếp từ admin.
8. Users (Thành viên) All Users Quản lý danh sách toàn bộ tài khoản đăng ký trên hệ thống.
Add New Tạo tài khoản người dùng mới và chỉ định vai trò (Role).
Profile Chỉnh sửa thông tin cá nhân của tài khoản đang đăng nhập (đổi pass, ngôn ngữ hiển thị).
9. Tools (Công cụ) Available Tools Các công cụ hỗ trợ chuyển đổi dữ liệu.
Import / Export Nhập/Xuất toàn bộ bài viết, trang, media sang file .xml để sao lưu hoặc chuyển host.
Site Health Kiểm tra sức khỏe hệ thống (phiên bản PHP, MySQL, các lỗi bảo mật).
Export / Erase Personal Data Xuất hoặc xóa dữ liệu cá nhân của người dùng để tuân thủ luật bảo mật (GDPR).
10. Settings (Cài đặt) General Thiết lập tên website, câu khẩu hiệu, địa chỉ email admin, múi giờ, định dạng ngày tháng.
Writing Cấu hình mặc định khi soạn thảo văn bản và gửi bài viết qua email.
Reading Thiết lập trang chủ hiển thị, số lượng bài viết tối đa trên một trang blog, chặn công cụ tìm kiếm.
Discussion Cấu hình bình luận (yêu cầu phê duyệt, chặn từ ngữ nhạy cảm).
Media Cài đặt kích thước mặc định cho ảnh khi upload (Thumbnail, Medium, Large).
Permalinks Định dạng cấu trúc đường dẫn tĩnh (URL) tối ưu cho SEO (ví dụ: /post-name/).
Privacy Thiết lập trang chính sách bảo mật cho website.
PHẦN 2: CÁC HÀM PHP & HOOKS ĐỂ PHÁT TRIỂN TRANG ADMIN (DEVELOPER API)
Nếu bạn đang lập trình tùy biến trang admin (hoặc xây dựng CMS tương tự), WordPress cung cấp bộ API để tạo và kiểm soát trang admin sau:
1. Các hàm tạo Menu & Submenu trong Admin
add_menu_page(): Thêm một Menu chính mới (cấp cao nhất) ở thanh sidebar bên trái.
add_submenu_page(): Thêm một Menu con (Submenu) vào dưới một Menu chính bất kỳ.
Các hàm phái sinh viết nhanh (Helper functions):
add_options_page(): Thêm trang con vào menu Settings (Cài đặt).
add_theme_page(): Thêm trang con vào menu Appearance (Giao diện).
add_plugins_page(): Thêm trang con vào menu Plugins.
add_users_page(): Thêm trang con vào menu Users (Thành viên).
add_dashboard_page(): Thêm trang con vào menu Dashboard (Bảng điều khiển).
add_management_page(): Thêm trang con vào menu Tools (Công cụ).
2. Các Action Hooks quan trọng trong Admin
admin_menu: Chạy khi cấu trúc menu admin đang được khởi tạo. Đây là nơi bắt buộc để gọi các hàm add_menu_page() và add_submenu_page().
admin_init: Chạy khi trang admin được tải và khởi tạo. Thường dùng để đăng ký cấu hình (Settings API), kiểm tra quyền hoặc xử lý form gửi lên.
admin_enqueue_scripts: Hook chuẩn duy nhất để tải thêm file CSS và JavaScript tùy chỉnh vào trang quản trị (tránh làm ảnh hưởng đến các plugin khác).
admin_post_{action} & admin_post_nopriv_{action}: Xử lý các yêu cầu gửi Form (POST/GET) từ trang quản trị một cách an toàn.
3. Các hàm kiểm tra Phân quyền & Trạng thái
is_admin(): Kiểm tra xem trang hiện tại người dùng đang truy cập có thuộc phân hệ quản trị (backend /wp-admin/) hay không.
current_user_can( $capability ): Kiểm tra người dùng hiện tại có đủ quyền thực hiện hành động hay không (ví dụ: current_user_can('manage_options') để kiểm tra quyền Admin).
get_current_screen(): Lấy thông tin chi tiết về trang admin hiện tại đang hiển thị (ID, Base, Post Type).
4. Settings API (Quản lý và lưu trữ thiết lập)
register_setting(): Đăng ký một tùy chọn (option) mới trong cơ sở dữ liệu để lưu trữ cấu hình.
add_settings_section(): Tạo ra một vùng chứa (section) trên trang để gom nhóm các trường cài đặt.
add_settings_field(): Thêm một trường nhập liệu (Input, Checkbox, Select) vào vùng chứa đã tạo.
settings_fields() & do_settings_sections(): Các hàm render giao diện form cấu hình tự động và tạo mã bảo mật Token (Nonce).
TIP
Ứng dụng cho dự án Spring Boot hiện tại: Nếu bạn đang chuyển đổi hoặc xây dựng mới phân hệ Quản trị (Admin Panel) cho dự án website bệnh viện S.I.S Cần Thơ, bạn có thể ánh xạ các chức năng của WordPress như trên thành các Controller trong Spring Boot (ví dụ: @Controller @RequestMapping("/admin")), chia các chức năng quản lý thành các REST API và phân quyền truy cập thông qua Spring Security (tương đương với current_user_can trong WordPress).
File diff suppressed because one or more lines are too long
@@ -0,0 +1,900 @@
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* hebrew */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2) format('woff2');
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* math */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* hebrew */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2) format('woff2');
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* math */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* hebrew */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2) format('woff2');
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* math */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtE6F15M.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWvU6F15M.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6F15M.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuk6F15M.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* hebrew */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWu06F15M.woff2) format('woff2');
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* math */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWxU6F15M.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqW106F15M.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtk6F15M.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWt06F15M.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWuU6F.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* hebrew */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2');
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* math */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* hebrew */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2');
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* math */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* hebrew */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2');
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* math */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* hebrew */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2');
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* math */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* hebrew */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2');
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* math */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 800;
font-stretch: 100%;
font-display: swap;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@@ -0,0 +1 @@
/* @license GPL-2.0-or-later https://www.drupal.org/licensing/faq */
@@ -0,0 +1,11 @@
/* @license GPL-2.0-or-later https://www.drupal.org/licensing/faq */
.progress{position:relative;}.progress__track{min-width:100px;max-width:100%;height:16px;margin-top:5px;border:1px solid;background-color:#fff;}.progress__bar{width:3%;min-width:3%;max-width:100%;height:1.5em;background-color:#000;}.progress__description,.progress__percentage{overflow:hidden;margin-top:0.2em;color:#555;font-size:0.875em;}.progress__description{float:left;}[dir="rtl"] .progress__description{float:right;}.progress__percentage{float:right;}[dir="rtl"] .progress__percentage{float:left;}.progress--small .progress__track{height:7px;}.progress--small .progress__bar{height:7px;background-size:20px 20px;}
.ajax-progress{display:inline-block;padding:1px 5px 2px 5px;}[dir="rtl"] .ajax-progress{float:right;}.ajax-progress-throbber .throbber{display:inline;padding:1px 5px 2px;background:transparent url(/themes/contrib/stable/images/core/throbber-active.gif) no-repeat 0 center;}.ajax-progress-throbber .message{display:inline;padding:1px 5px 2px;}tr .ajax-progress-throbber .throbber{margin:0 2px;}.ajax-progress-bar{width:16em;}.ajax-progress-fullscreen{position:fixed;z-index:1000;top:48.5%;left:49%;width:24px;height:24px;padding:4px;opacity:0.9;border-radius:7px;background-color:#232323;background-image:url(/themes/contrib/stable/images/core/loading-small.gif);background-repeat:no-repeat;background-position:center center;}[dir="rtl"] .ajax-progress-fullscreen{right:49%;left:auto;}
.text-align-left{text-align:left;}.text-align-right{text-align:right;}.text-align-center{text-align:center;}.text-align-justify{text-align:justify;}.align-left{float:left;}.align-right{float:right;}.align-center{display:block;margin-right:auto;margin-left:auto;}
.container-inline div,.container-inline label{display:inline;}.container-inline .details-wrapper{display:block;}
.clearfix:after{display:table;clear:both;content:"";}
.hidden{display:none;}.visually-hidden{position:absolute !important;overflow:hidden;clip:rect(1px,1px,1px,1px);width:1px;height:1px;word-wrap:normal;}.visually-hidden.focusable:active,.visually-hidden.focusable:focus{position:static !important;overflow:visible;clip:auto;width:auto;height:auto;}.invisible{visibility:hidden;}
.js .js-hide{display:none;}.js-show{display:none;}.js .js-show{display:block;}
.cc_banner-wrapper{z-index:9001;position:relative}.cc_container .cc_btn{cursor:pointer;text-align:center;font-size:0.6em;transition:font-size 200ms;line-height:1em}.cc_container .cc_message{font-size:0.6em;transition:font-size 200ms;margin:0;padding:0;line-height:1.5em}.cc_container .cc_logo{display:none;text-indent:-1000px;overflow:hidden;width:100px;height:22px;background-size:cover;background-image:url(//cdnjs.cloudflare.com/ajax/libs/cookieconsent2/1.0.10/logo.png);opacity:0.9;transition:opacity 200ms}.cc_container .cc_logo:hover,.cc_container .cc_logo:active{opacity:1}@media screen and (min-width:500px){.cc_container .cc_btn{font-size:0.8em}.cc_container .cc_message{font-size:0.8em}}@media screen and (min-width:768px){.cc_container .cc_btn{font-size:1em}.cc_container .cc_message{font-size:1em;line-height:1em}}@media screen and (min-width:992px){.cc_container .cc_message{font-size:1em}}@media print{.cc_banner-wrapper,.cc_container{display:none}}.cc_container{position:fixed;left:0;right:0;bottom:0;overflow:hidden;padding:10px}.cc_container .cc_btn{padding:8px 10px;background-color:#f1d600;cursor:pointer;transition:font-size 200ms;text-align:center;font-size:0.6em;display:block;width:33%;margin-left:10px;float:right;max-width:120px}.cc_container .cc_message{transition:font-size 200ms;font-size:0.6em;display:block}@media screen and (min-width:500px){.cc_container .cc_btn{font-size:0.8em}.cc_container .cc_message{margin-top:0.5em;font-size:0.8em}}@media screen and (min-width:768px){.cc_container{padding:15px 30px 15px}.cc_container .cc_btn{font-size:1em;padding:8px 15px}.cc_container .cc_message{font-size:1em}}@media screen and (min-width:992px){.cc_container .cc_message{font-size:1em}}.cc_container{background:#222;color:#fff;font-size:17px;font-family:"Helvetica Neue Light","HelveticaNeue-Light","Helvetica Neue",Calibri,Helvetica,Arial;box-sizing:border-box}.cc_container ::-moz-selection{background:#ff5e99;color:#fff;text-shadow:none}.cc_container .cc_btn,.cc_container .cc_btn:visited{color:#000;background-color:#f1d600;transition:background 200ms ease-in-out,color 200ms ease-in-out,box-shadow 200ms ease-in-out;-webkit-transition:background 200ms ease-in-out,color 200ms ease-in-out,box-shadow 200ms ease-in-out;border-radius:5px;-webkit-border-radius:5px}.cc_container .cc_btn:hover,.cc_container .cc_btn:active{background-color:#fff;color:#000}.cc_container a,.cc_container a:visited{text-decoration:none;color:#31a8f0;transition:200ms color}.cc_container a:hover,.cc_container a:active{color:#b2f7ff}@-webkit-keyframes slideUp{0%{-webkit-transform:translateY(66px);transform:translateY(66px)}100%{-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes slideUp{0%{-webkit-transform:translateY(66px);-ms-transform:translateY(66px);transform:translateY(66px)}100%{-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}.cc_container,.cc_message,.cc_btn{animation-duration:0.8s;-webkit-animation-duration:0.8s;-moz-animation-duration:0.8s;-o-animation-duration:0.8s;-webkit-animation-name:slideUp;animation-name:slideUp}
span.ext{width:10px;height:10px;padding-right:12px;text-decoration:none;background:url(/modules/contrib/extlink/images/extlink_s.png) 2px center no-repeat;}span.mailto{width:10px;height:10px;padding-right:12px;text-decoration:none;background:url(/modules/contrib/extlink/images/extlink_s.png) -20px center no-repeat;}span.tel{width:10px;height:10px;padding-right:12px;text-decoration:none;background:url(/modules/contrib/extlink/images/extlink_s.png) -42px center no-repeat;}svg.ext{width:14px;height:14px;fill:#727272;font-weight:900;}svg.mailto,svg.tel{width:14px;height:14px;fill:#727272;}[data-extlink-placement='prepend'],[data-extlink-placement='before']{padding-right:0.2rem;}[data-extlink-placement='append'],[data-extlink-placement='after']{padding-left:0.2rem;}svg.ext path,svg.mailto path,svg.tel path{stroke:#727272;stroke-width:3;}@media print{svg.ext,svg.mailto,svg.tel,span.ext,span.mailto,span.tel{display:none;padding:0;}}.extlink i{padding-left:0.2em;}.extlink-nobreak{white-space:nowrap;}
.paragraph--unpublished{background-color:#fff4f4;}
@@ -0,0 +1,43 @@
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
// Drupal's global gtag ID. For more info: https://developers.google.com/tag-platform/devguides/gtag-integration
gtag('set', 'developer_id.dMDhkMT', true);
(function (drupalSettings) {
if (!drupalSettings.gtag) {return;}
const config = drupalSettings.gtag;
if (config.consentMode === true) {
gtag('consent', 'default', {
ad_storage: 'denied',
analytics_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
wait_for_update: 500,
});
}
if (config.tagId.length !== 0) {
const script = document.createElement('script');
script.async = true;
script.src = `https://www.googletagmanager.com/gtag/js?id=${config.tagId}`;
script.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(script);
}
const additionalConfigInfo = config.additionalConfigInfo || [];
if (additionalConfigInfo.length === 0) {
gtag('config', config.tagId);
} else {
gtag('config', config.tagId, additionalConfigInfo);
}
const otherIds = config.otherIds || [];
otherIds.forEach((id) => gtag('config', id));
const events = config.events || [];
events.forEach((event) => gtag('event', event.name, event.data));
})(drupalSettings);
@@ -0,0 +1,31 @@
const dl = drupalSettings.gtm ? drupalSettings.gtm.settings.data_layer : 'dataLayer';
window[dl] = window[dl] || [];
(function (drupalSettings) {
if (!drupalSettings.gtm) {return;}
const config = drupalSettings.gtm;
window[dl].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
const gtmSettings = config.settings;
if (gtmSettings.include_classes === true) {
window[dl].push({
'gtm.allowlist': gtmSettings.allowlist_classes ?? [],
'gtm.blocklist': gtmSettings.blocklist_classes ?? [],
});
}
let gtm_environment = '';
if (gtmSettings.include_environment === true) {
const gtm_auth = gtmSettings.environment_token ?? '';
const gtm_preview = gtmSettings.environment_id ?? '';
gtm_environment = `&gtm_auth=${gtm_auth}&gtm_preview=${gtm_preview}&gtm_cookies_win=x`;
}
config.tagIds.forEach(function (tagId) {
const script = document.createElement('script');
script.async = true;
const dLink = dl != 'dataLayer' ? `&l=${dl}` : '';
script.src = `https://www.googletagmanager.com/gtm.js?id=${tagId}${gtm_environment}${dLink}`;
script.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(script);
});
})(drupalSettings);
@@ -0,0 +1,14 @@
/* @license GPL-2.0-or-later https://www.drupal.org/licensing/faq */
(function(window,$,Drupal){'use strict';window.DP=window.DP||window.Drupal||{};window.DP.behaviors=window.DP.behaviors||{};if(window.DP._behaviorBootstrapAttached)return;window.DP._behaviorBootstrapAttached=true;Drupal.behaviors.dpBehaviorBootstrap={attach:function(context){if(context!==document)return;$.each(window.DP.behaviors,function(key,behavior){if(behavior&&typeof behavior.attach==='function')behavior.attach(document);});}};}(window,jQuery,Drupal));;
(function(Drupal,$,once){'use strict';var initHoverIntent=function(){var menu=this;$('ul.m--main > li',menu).hoverIntent({over:function(){$(this).addClass('hover');},out:function(){$(this).removeClass('hover');},interval:80});};var initMainMenu=function(){var $menuItems=$('.cc--menu-main ul.m--menu > li');ally.style.focusWithin();$menuItems.on('click',function(){$menuItems.removeClass('ally-focus-within');if(whatInput.ask()==='touch')$(this).addClass('ally-focus-within');});};window.DP.behaviors.mainMenu={attach:function(context){$(once('mainMenu','.nav-container .cc--menu-main',context)).each(function(){initHoverIntent.apply(this);initMainMenu.apply();});}};})(Drupal,jQuery,once);;
(function(Drupal,$,once){'use strict';var $hamburger;var $mobileMenu;var $arrowToggles;var $backButtons;var $subMenus;var breakpoint=window.matchMedia('(min-width: 1200px)');var initMobileMenu=function(){$hamburger=$(this);$mobileMenu=$('.cc--menu-mobile');$arrowToggles=$mobileMenu.find('.arrow-toggle');$subMenus=$mobileMenu.find('.submenu-wrapper');$backButtons=$mobileMenu.find('.button-back');$hamburger.on('click',function(e){e.preventDefault();toggleMobileMenu(this);});$arrowToggles.on('click',function(e){e.preventDefault();var $subMenu=$(this).parents('.menu-item').find('.submenu-wrapper');$subMenu.attr('aria-hidden','false');$subMenu.show();$subMenu.removeClass('is-hidden').addClass('is-visible');$mobileMenu.addClass('is-hidden');});$backButtons.on('click',function(e){e.preventDefault();var $this=$(this);$this.parents('.submenu-wrapper').attr('aria-hidden','true');$this.parents('.submenu-wrapper').addClass('is-hidden');$this.parents('.submenu-wrapper').removeClass('is-visible');$mobileMenu.removeClass('is-hidden');});checkBreakpoint();breakpoint.addListener(checkBreakpoint);};var checkBreakpoint=function(){if(breakpoint.matches){$hamburger.removeClass('is-active');$mobileMenu.removeAttr('style');$('body').removeClass('mobile-menu-is-active');hideAllSubmenus();}};var toggleMobileMenu=function(el){$('body').toggleClass('mobile-menu-is-active');$(el).toggleClass('is-active');$mobileMenu.fadeToggle(function(){if(!$('body').hasClass('mobile-menu-is-active'))hideAllSubmenus();});};var hideAllSubmenus=function(){$subMenus.removeClass('is-visible');$mobileMenu.removeClass('is-hidden');};window.DP.behaviors.mobileMenu={attach:function(context){$(once('mobileMenu','.hamburger',context)).each(function(){initMobileMenu.apply(this);});}};}(Drupal,jQuery,once));;
(function($){'use strict';var $searchIcon;var $body=$('body');var $searchFormContainer=$('.cc--header .cc--header-search');var $searchInput=$searchFormContainer.find('input');var $closeIcon=$searchFormContainer.find('.button-close');var initSearchForm=function(){$searchIcon=$(this);$searchFormContainer.attr({'aria-hidden':'true'});$searchIcon.on('click',function(e){e.preventDefault();openSearch();});$closeIcon.on('click',function(e){e.preventDefault();closeSearch();});$searchFormContainer.on('keydown',function(ev){if(ev.keyCode===9){var inputs=$searchFormContainer.find('input, button').filter(':visible').not(':disabled');if(!ev.shiftKey){if(inputs[inputs.length-1]===ev.target){ev.preventDefault();inputs.first().focus();}}else{if(inputs[0]===ev.target){ev.preventDefault();inputs.last().focus();}}}if(ev.keyCode===27){ev.preventDefault();closeSearch();}});};var openSearch=function(){$searchFormContainer.addClass('is-open').attr('aria-hidden','false');$body.addClass('search-open');$searchInput.first().focus();};var closeSearch=function(){$searchFormContainer.removeClass('is-open').attr('aria-hidden','true');$body.removeClass('search-open');};window.DP.behaviors.searchForm={attach:function(context){$(once('searchForm','.utility-nav-container .search-button',context)).each(function(){initSearchForm.apply(this);});}};}(jQuery));;
/**
* what-input - A global utility for tracking the current input method (mouse, keyboard or touch).
* @version v5.2.3
* @link https://github.com/ten1seven/what-input
* @license MIT
*/
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("whatInput",[],t):"object"==typeof exports?exports.whatInput=t():e.whatInput=t()}(this,function(){return i={},n.m=o=[function(e,t){"use strict";e.exports=function(){if("undefined"==typeof document||"undefined"==typeof window)return{ask:function(){return"initial"},element:function(){return null},ignoreKeys:function(){},specificKeys:function(){},registerOnChange:function(){},unRegisterOnChange:function(){}};var t=document.documentElement,n=null,a="initial",u=a,o=Date.now();try{window.sessionStorage.getItem("what-input")&&(a=window.sessionStorage.getItem("what-input")),window.sessionStorage.getItem("what-intent")&&(u=window.sessionStorage.getItem("what-intent"))}catch(e){}var d=["button","input","select","textarea"],i=[],c=[16,17,18,91,93],w=[],p={keydown:"keyboard",keyup:"keyboard",mousedown:"mouse",mousemove:"mouse",MSPointerDown:"pointer",MSPointerMove:"pointer",pointerdown:"pointer",pointermove:"pointer",touchstart:"touch",touchend:"touch"},r=!1,s={x:null,y:null},f={2:"touch",3:"touch",4:"mouse"},l=!1;try{var e=Object.defineProperty({},"passive",{get:function(){l=!0}});window.addEventListener("test",null,e)}catch(e){}var h=function(){var e=!!l&&{passive:!0};window.PointerEvent?(window.addEventListener("pointerdown",m),window.addEventListener("pointermove",y)):window.MSPointerEvent?(window.addEventListener("MSPointerDown",m),window.addEventListener("MSPointerMove",y)):(window.addEventListener("mousedown",m),window.addEventListener("mousemove",y),"ontouchstart"in window&&(window.addEventListener("touchstart",m,e),window.addEventListener("touchend",m))),window.addEventListener(S(),y,e),window.addEventListener("keydown",m),window.addEventListener("keyup",m),window.addEventListener("focusin",g),window.addEventListener("focusout",E)},m=function(e){var t=e.which,n=p[e.type];"pointer"===n&&(n=L(e));var o=!w.length&&-1===c.indexOf(t),i=w.length&&-1!==w.indexOf(t),r="keyboard"===n&&t&&(o||i)||"mouse"===n||"touch"===n;if(x(n)&&(r=!1),r&&a!==n){a=n;try{window.sessionStorage.setItem("what-input",a)}catch(e){}v("input")}if(r&&u!==n){var s=document.activeElement;if(s&&s.nodeName&&(-1===d.indexOf(s.nodeName.toLowerCase())||"button"===s.nodeName.toLowerCase()&&!O(s,"form"))){u=n;try{window.sessionStorage.setItem("what-intent",u)}catch(e){}v("intent")}}},v=function(e){t.setAttribute("data-what"+e,"input"===e?a:u),b(e)},y=function(e){var t=p[e.type];if("pointer"===t&&(t=L(e)),M(e),(!r&&!x(t)||r&&"wheel"===e.type||"mousewheel"===e.type||"DOMMouseScroll"===e.type)&&u!==t){u=t;try{window.sessionStorage.setItem("what-intent",u)}catch(e){}v("intent")}},g=function(e){e.target.nodeName?(n=e.target.nodeName.toLowerCase(),t.setAttribute("data-whatelement",n),e.target.classList&&e.target.classList.length&&t.setAttribute("data-whatclasses",e.target.classList.toString().replace(" ",","))):E()},E=function(){n=null,t.removeAttribute("data-whatelement"),t.removeAttribute("data-whatclasses")},L=function(e){return"number"==typeof e.pointerType?f[e.pointerType]:"pen"===e.pointerType?"touch":e.pointerType},x=function(e){var t=Date.now(),n="mouse"===e&&"touch"===a&&t-o<200;return o=t,n},S=function(){return"onwheel"in document.createElement("div")?"wheel":void 0!==document.onmousewheel?"mousewheel":"DOMMouseScroll"},b=function(e){for(var t=0,n=i.length;t<n;t++)i[t].type===e&&i[t].fn.call(void 0,"input"===e?a:u)},M=function(e){s.x!==e.screenX||s.y!==e.screenY?(r=!1,s.x=e.screenX,s.y=e.screenY):r=!0},O=function(e,t){var n=window.Element.prototype;if(n.matches||(n.matches=n.msMatchesSelector||n.webkitMatchesSelector),n.closest)return e.closest(t);do{if(e.matches(t))return e;e=e.parentElement||e.parentNode}while(null!==e&&1===e.nodeType);return null};return"addEventListener"in window&&Array.prototype.indexOf&&(p[S()]="mouse",h(),v("input"),v("intent")),{ask:function(e){return"intent"===e?u:a},element:function(){return n},ignoreKeys:function(e){c=e},specificKeys:function(e){w=e},registerOnChange:function(e,t){i.push({fn:e,type:t||"input"})},unRegisterOnChange:function(e){var t=function(e){for(var t=0,n=i.length;t<n;t++)if(i[t].fn===e)return t}(e);!t&&0!==t||i.splice(t,1)}}}()}],n.c=i,n.p="",n(0);function n(e){if(i[e])return i[e].exports;var t=i[e]={exports:{},id:e,loaded:!1};return o[e].call(t.exports,t,t.exports,n),t.loaded=!0,t.exports}var o,i});
;
@@ -0,0 +1,2 @@
/* @license GPL-2.0-or-later https://www.drupal.org/licensing/faq */
(function(){const settingsElement=document.querySelector('head > script[type="application/json"][data-drupal-selector="drupal-settings-json"], body > script[type="application/json"][data-drupal-selector="drupal-settings-json"]');window.drupalSettings={};if(settingsElement!==null)window.drupalSettings=JSON.parse(settingsElement.textContent);})();;
@@ -0,0 +1 @@
!function(i,r){function o(){}var n,e,t,c,a,d,l,s,u,f,p,y,m,v,h,_,g,E,k,S="addtoany",b=".bycdb5qo",L=i.head;"function"==typeof[].indexOf&&L&&(r.a2a=r.a2a||{},n=r.a2a_config=r.a2a_config||{},_=(e=i.currentScript instanceof HTMLScriptElement?i.currentScript:null)&&e.src?e.src:"",t=e&&e.nonce?e.nonce:null,c=r.a2a.nonce=function(e){t&&e.setAttribute("nonce",t)},a=e&&!e.async&&!e.defer,NodeList&&NodeList.prototype.forEach&&(r.a2a.init=function(e,t){void 0===t&&(t=n);var a=":not([data-a2a-url]):not(.a2a_target)";i.querySelectorAll(".a2a_dd"+a+",.a2a_kit"+a).forEach(function(e){e.matches(".a2a_kit .a2a_dd");e.a2a_index||null!==e.getAttribute("data-a2a-url")||e.matches(".a2a_kit .a2a_dd")||(e.dataset.a2aUrl=t.linkurl||"",t.linkname&&(e.dataset.a2aTitle=t.linkname))}),delete n.linkurl,delete n.linkname},r.a2a_init=r.a2a.init,a)&&r.a2a.init("page",{linkurl:n.linkurl,linkname:n.linkname}),r.a2a.page||(r.a2a.page=!0,d=[],["init_all","svg_css"].forEach(function(a){r.a2a[a]=function(){for(var e=[],t=0;t<arguments.length;t++)e[t]=arguments[t];d.push([a,e])}}),s=(l=n.static_server)?l+"/":"https://static.addtoany.com/menu/",k=_&&-1!==_.split("/")[2].indexOf(S),u=(k=(u=!l&&k?_:s).match(/^[^?#]+\//))?k[0]:u,null!=(_=r.trustedTypes)&&_.createPolicy&&(f=r.a2a.ttp=trustedTypes.createPolicy("addtoany",{createHTML:function(e){return e},createScript:function(e){return e},createScriptURL:function(e){return e}})),p=function(e,t,a){void 0===e&&(e=u+"eso"+b+".js"),void 0===t&&(t=!1);var n=i.createElement((a=void 0===a?!1:a)?"link":"script");a?(a="preload",n.href=e,n.rel=t?"module"+a:a,t||(n.as="script")):(n.src=f?f.createScriptURL(e):e,t&&(n.type="module",n.onerror=function(){return p()})),c(n),L.appendChild(n)},y=function(){var e=u+(l?"":"modules/");p(e+"core"+b+".js",!0);var t=i.createElement("link").relList.supports("modulepreload");n.overlays&&n.overlays.length&&t&&p(e+"overlays"+b+".js",!0,!0),y=o},v="a2a_sm_ifr",h=function(){var e,t,a,n;t="a2a_menu_container",e=i.getElementById(t),r.a2a.main=m=e||i.createElement("div"),m.id!=t&&(i.getElementById(S)||(m.id=S),m.style.position="static",i.body.insertBefore(m,null)),r.addEventListener("message",function(e){var t=e.origin;t&&".addtoany.com"!==t.substr(-13)||"object"==typeof(t=e.data)&&t.a2a&&(t.h1&&(r.a2a.h1=!0),"function"==typeof(e=r.a2a.userServices)?e(t.user_services):r.a2a.userServices=t.user_services,i.getElementById(v).style.display="none")}),e=i.createElement("iframe"),t=i.createElement("div"),a=e.style,n=t.style,e.id=v,a.width=a.height=n.width=n.height="1px",a.top=a.left=a.border="0",a.position=n.position="absolute",a.zIndex=n.zIndex="100000",e.title="AddToAny Utility Frame",e.setAttribute("aria-hidden","true"),e.src="https://static.addtoany.com/menu/sm.25.html#type=core&event=load",n.top="0",n.visibility="hidden",m.insertBefore(t,null),t.insertBefore(e,null),h=o},i.body&&h(),"function"==typeof(k="".matchAll)&&k.toString().includes("[native code]")?("loading"!==i.readyState&&y(),_=s+(l?"":"svg/"),i.querySelector('.a2a_dd:empty,.a2a_kit [class*="a2a_button_"]:empty')&&r.a2a.h1&&p(_+"icons.39.svg.js",!1,!0)):p(),g=function(e){var t=r.a2a.core;"function"!=typeof t||e?e?e():r.a2a.core=function(e){return g(e)}:t()},k=function(){E||(E=!0,y(),h(),g(),d.forEach(function(e){var t;(t=r.a2a)[e[0]].apply(t,e[1])}))},!(a&&L.contains(e)||!i.body)||"loading"!==i.readyState?k():(i.addEventListener("readystatechange",k),i.addEventListener("DOMContentLoaded",k))))}(document,window);
@@ -0,0 +1,4 @@
<!DOCTYPE html>
<!-- saved from url=(0064)https://static.addtoany.com/menu/sm.25.html#type=core&event=load -->
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>A2A</title><meta name="robots" content="noindex"><script>
!function(e){var o=document.cookie,t=0<o.length;var n,a={feed:[],page:[]};t&&["feed","page"].forEach(function(e){var t=e+"_services",n=o.indexOf(t+"=");-1!=n&&(-1==(t=o.indexOf(";",n=n+t.length+1))&&(t=o.length),n=decodeURIComponent(o.substring(n,t)),a[e]=n.split(","))}),n={a2a:!0,h1:(t=!1,"function"==typeof performance.getEntriesByType&&(n=(n=performance.getEntriesByType("navigation")[0])&&n.nextHopProtocol?n.nextHopProtocol:"",t=/^http\/1/.test(n)),t),user_services:{feed:a.feed,page:a.page}},e.postMessage&&e.parent.postMessage(n,"*")}(window);</script></head><body style="background-color:transparent"></body></html>
+13
View File
@@ -0,0 +1,13 @@
{
"web": {
"client_id": "416910503373-de8dpds39j5klabamn7e9lqcku29okeh.apps.googleusercontent.com",
"project_id": "main-website-500908",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "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",
"redirect_uris": [
"http://localhost:8080/login/oauth2/code/google"
]
}
}
@@ -0,0 +1,172 @@
<!DOCTYPE html>
<!-- saved from url=(0130)https://sisvietnam.com/module/account/login?ReturnUrl=%2fModule%2fBusinessWorkflow%2fDetail%2f5a5b94ce-08bb-4b84-b16d-abb9a30273b1 -->
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Đăng nhập/Login</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta content="width=device-width, initial-scale=1" name="viewport">
<meta content="" name="description">
<meta content="" name="author">
<!-- BEGIN GLOBAL MANDATORY STYLES -->
<link href="./Đăng nhập_Login_files/css" rel="stylesheet" type="text/css">
<link href="./Đăng nhập_Login_files/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href="./Đăng nhập_Login_files/simple-line-icons.min.css" rel="stylesheet" type="text/css">
<link href="./Đăng nhập_Login_files/bootstrap.min.css" rel="stylesheet" type="text/css">
<link href="./Đăng nhập_Login_files/bootstrap-switch.min.css" rel="stylesheet" type="text/css">
<!-- END GLOBAL MANDATORY STYLES -->
<!-- BEGIN PAGE LEVEL PLUGINS -->
<link href="./Đăng nhập_Login_files/select2.min.css" rel="stylesheet" type="text/css">
<link href="./Đăng nhập_Login_files/select2-bootstrap.min.css" rel="stylesheet" type="text/css">
<!-- END PAGE LEVEL PLUGINS -->
<!-- BEGIN THEME GLOBAL STYLES -->
<link href="./Đăng nhập_Login_files/components.min.css" rel="stylesheet" id="style_components" type="text/css">
<link href="./Đăng nhập_Login_files/plugins.min.css" rel="stylesheet" type="text/css">
<!-- END THEME GLOBAL STYLES -->
<!-- BEGIN PAGE LEVEL STYLES -->
<link href="./Đăng nhập_Login_files/login-dl.css" rel="stylesheet" type="text/css">
<!-- END PAGE LEVEL STYLES -->
<!-- BEGIN THEME LAYOUT STYLES -->
<!-- END THEME LAYOUT STYLES -->
<link rel="shortcut icon" href="https://sisvietnam.com/Module/Content/metronic/img/favicon.ico">
<link href="./Đăng nhập_Login_files/customer-style.css" rel="stylesheet" type="text/css">
</head>
<body class="login">
<div class="user-login-5">
<div class="row bs-reset">
<div class="col-md-8 col-sm-6 hidden-xs bs-reset mt-login-5-bsfix">
<div class="login-bg" style="background: none; position: relative; z-index: 0;">
<div class="backstretch" style="left: 0px; top: 0px; overflow: hidden; margin: 0px; padding: 0px; height: 961px; width: 1254px; z-index: -999998; position: absolute;"><img src="./Đăng nhập_Login_files/bg2.jpg" style="position: absolute; margin: 0px; padding: 0px; border-width: medium; border-style: none; border-color: currentcolor; border-image: initial; width: 1385.05px; height: 961px; max-height: none; max-width: none; z-index: -999999; left: -65.5237px; top: 0px;"></div></div>
</div>
<div class="col-md-4 col-sm-6 login-container bs-reset mt-login-5-bsfix">
<div class="login-content">
<div id="jsquickmessage" style="display:none;"></div>
<img class="login-logo" src="./Đăng nhập_Login_files/logo.png" style="width:260px;">
<form action="https://sisvietnam.com/Module/Account/Login" class="form fv-plugins-bootstrap fv-plugins-framework" id="kt_login_signin_form" method="post"><input id="client_id" name="client_id" type="hidden" value=""><input id="redirect_uri" name="redirect_uri" type="hidden" value=""><input id="response_type" name="response_type" type="hidden" value=""><input id="scope" name="scope" type="hidden" value=""><input data-val="true" data-val-required="The IsOauth field is required." id="IsOauth" name="IsOauth" type="hidden" value="False"><input id="ReturnUrl" name="ReturnUrl" type="hidden" value="/Module/BusinessWorkflow/Detail/5a5b94ce-08bb-4b84-b16d-abb9a30273b1"> <!--begin::Title-->
<!--begin::Form group-->
<div class="form-group fv-plugins-icon-container">
<input autocomplete="username" class="form-control form-control-solid h-auto py-6 px-6 rounded-lg" data-val="true" data-val-required="Vui lòng nhập tên đăng nhập" id="UserName" name="UserName" placeholder="Tên đăng nhập/Login Name" type="text" value="">
<div class="fv-plugins-message-container"></div>
</div>
<!--end::Form group-->
<!--begin::Form group-->
<div class="form-group fv-plugins-icon-container">
<input autocomplete="password" class="form-control form-control-solid h-auto py-6 px-6 rounded-lg" data-val="true" data-val-required="Vui lòng nhập mật khẩu" id="Password" name="Password" placeholder="Mật khẩu/Password" type="password" value="">
<div class="fv-plugins-message-container"></div>
</div>
<div class="form-group fv-plugins-icon-container">
<div class="rem-password">
<label class="rememberme mt-checkbox mt-checkbox-outline font-white">
<input data-val="true" data-val-required="The RememberMe field is required." id="RememberMe" name="RememberMe" type="checkbox" value="true"><input name="RememberMe" type="hidden" value="false"> Ghi nhớ/Remember
<span></span>
</label>
</div>
</div>
<!--end::Form group-->
<!--begin::Action-->
<div class="pb-lg-0 pb-5">
<button type="submit" onclick="ll.click();" id="kt_login_signin_submit1" class="btn blue">Đăng nhập/Login</button>
</div>
<div class="form-group fv-plugins-icon-container">
<div class="pull-right">
<label class="rememberme mt-checkbox mt-checkbox-outline goback">
<a href="https://sisvietnam.com/Module/Account/ForgotPassword" class="btn">
Quên mật khẩu/Forgot Password ?
</a>
</label>
</div>
</div>
<!--end::Action-->
<input type="hidden"><div></div>
</form>
</div>
<div class="login-footer">
</div>
</div>
</div>
</div>
<script src="./Đăng nhập_Login_files/jquery.min.js" type="text/javascript"></script>
<script src="./Đăng nhập_Login_files/jquery.cookie.js" type="text/javascript"></script>
<script src="./Đăng nhập_Login_files/bootstrap.min.js" type="text/javascript"></script>
<script src="./Đăng nhập_Login_files/js.cookie.min.js" type="text/javascript"></script>
<script src="./Đăng nhập_Login_files/jquery.slimscroll.min.js" type="text/javascript"></script>
<script src="./Đăng nhập_Login_files/jquery.blockui.min.js" type="text/javascript"></script>
<script src="./Đăng nhập_Login_files/bootstrap-switch.min.js" type="text/javascript"></script>
<script src="./Đăng nhập_Login_files/jquery.validate.min.js" type="text/javascript"></script>
<script src="./Đăng nhập_Login_files/additional-methods.min.js" type="text/javascript"></script>
<script src="./Đăng nhập_Login_files/select2.full.min.js" type="text/javascript"></script>
<script src="./Đăng nhập_Login_files/jquery.backstretch.min.js" type="text/javascript"></script>
<script src="./Đăng nhập_Login_files/login-5.js" type="text/javascript"></script>
<script src="./Đăng nhập_Login_files/app.js" type="text/javascript"></script>
<script type="text/javascript">
var keyLower = "qwertyuiopasdfghjklzxcvbnm";
var keyUpper = "QWERTYUIOPASDFGHJKLZXCVBNM";
var ll = {
t1: "", t2: "", t3: "",
ii: function (t1, t2, t3) {
ll.t1 = t1; ll.t2 = t2; ll.t3 = t3; var r3 = $.cookie("r3"); var r1 = $.cookie("r1"); var r2 = $.cookie("r2"); if (r3 == "1") { $('#' + t3).attr('checked', 'checked'); $('#' + t1).val(ll.ld(r1)); $('#' + t2).val(ll.ld(r2)); }
},
click: function () {
if ($('#' + ll.t3).prop('checked')) {
$.cookie("r1", ll.le($('#' + ll.t1).val()), { expires: 365 });
$.cookie("r2", ll.le($('#' + ll.t2).val()), { expires: 365 });
$.cookie("r3", "1", { expires: 365 });
} else { $.cookie("r1", "", { expires: 365 }); $.cookie("r2", "", { expires: 365 }); $.cookie("r3", "0", { expires: 365 }); }
},
le: function (t) {
var cText = "";
var lowerAlphabet = "abcdefghijklmnopqrstuvwxyz";
var upperAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
var index = null;
var ch;
for (var i = 0; i < t.length; i++) {
ch = t.charAt(i);
//checks character case whether lowercase or uppercase
var isUpper = ch == ch.toUpperCase();
isUpper ? index = upperAlphabet.indexOf(ch) : index = lowerAlphabet.indexOf(ch);
// if it's not a letter, then add it as it is
if (index == -1) {
cText = cText + ch;
}
else {
// find the corresponding letter in key and add
isUpper ? cText = cText + keyUpper.charAt(index) : cText = cText + keyLower.charAt(index);
}
}
return cText;
},
ld: function (t) {
var dText = "";
var lowerAlphabet = "abcdefghijklmnopqrstuvwxyz";
var upperAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
var index = null;
var ch;
for (var i = 0; i < t.length; i++) {
ch = t.charAt(i);
//checks character case whether lowercase or uppercase
var isUpper = ch == ch.toUpperCase();
isUpper ? index = keyUpper.indexOf(ch) : index = keyLower.indexOf(ch);
// if it's not a letter, then add it as it is
if (index == -1) {
dText = dText + ch;
}
else {
// find the corresponding letter in key and add
isUpper ? dText = dText + upperAlphabet.charAt(index) : dText = dText + lowerAlphabet.charAt(index);
}
}
return dText;
}
}
ll.ii('UserName', 'Password', 'RememberMe');
</script>
</body></html>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,300 @@
function ShowMessage(message) {
if (message != null) {
var jsMessage = $('#jsquickmessage');
var toInject = "<div class=\"Metronic-alerts alert alert-success fade in\"><button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-hidden=\"true\"></button>" + message + "</div>";
jsMessage.html(toInject);
jsMessage.show();
$('div.alert').delay(2200).fadeOut();
}
}
function ShowSuccessNotification() {
$('span.ajaxsuccessshow').fadeIn().delay('800').fadeOut();
}
var showProgress = true;
//$(document).ajaxStart(function () {
// if (showProgress) {
// $("#AjaxProgress").show();
// }
//});
//$(document).ajaxStop(function () {
// $("#AjaxProgress").hide();
// showProgress = true;
//});
$(function () {
$('div.alert').delay(5000).fadeOut();
ChangeLanguage();
var app_base = '/';
//Language manage Start
// Find the encompassing element
function getParentElement(element, elementType) {
var target = element.parent();
var found = false;
while (target != undefined && !found) {
var tagName = target.get(0).tagName;
if (tagName == undefined) {
return undefined;
}
if (tagName.toLowerCase() != elementType) {
target = target.parent();
} else {
found = true;
}
}
return target;
}
function tableContext(element) {
this.Cell = getParentElement(element, "td");
this.Row = getParentElement(element, "tr");
}
// Handler for click on the resource edit button
$('span.editresource').click(function (e) {
e.preventDefault();
var tableInfo = new tableContext($(this));
tableInfo.Cell.find(".saveresource").show();
tableInfo.Row.find(".resourcevalueedit").show();
tableInfo.Row.find(".resourcevaluedisplay").hide();
tableInfo.Cell.find(".editresource").hide();
});
// Handler for click on the resource save button
$('span.saveresource').click(function (e) {
e.preventDefault();
var tableInfo = new tableContext($(this));
var inputfield = tableInfo.Row.find(".resourcevalueedit textarea");
var displayfield = tableInfo.Row.find(".resourcevaluedisplay");
// Ajax call setup
var languageid = inputfield.data('languageid');
var resourcekey = inputfield.data('resourcekey');
var oldvalue = inputfield.data('oldvalue');
var newvalue = inputfield.val();
// Don't allow a null/empty string value, and don't update if nothing changed
if ((newvalue != null && newvalue != "") && (newvalue != oldvalue)) {
// Make a view model instance
var ajaxEditLanguageValueViewModel = new Object();
ajaxEditLanguageValueViewModel.LanguageId = languageid;
ajaxEditLanguageValueViewModel.ResourceKey = resourcekey;
ajaxEditLanguageValueViewModel.NewValue = newvalue;
// Ajax call to post the view model to the controller
var strung = JSON.stringify(ajaxEditLanguageValueViewModel);
$.ajax({
url: app_base + 'ManageLanguage/UpdateResourceValue',
type: 'POST',
data: strung,
contentType: 'application/json; charset=utf-8',
error: function (xhr, ajaxOptions, thrownError) {
ShowUserMessage("Error: " + xhr.status + " " + thrownError);
}
});
displayfield.text(newvalue);
}
tableInfo.Cell.find(".editresource").show();
tableInfo.Row.find(".resourcevaluedisplay").show();
tableInfo.Cell.find(".saveresource").hide();
tableInfo.Row.find(".resourcevalueedit").hide();
});
// Handler for click on the resource key edit button
$('span.editresourcekey').click(function (e) {
e.preventDefault();
var tableInfo = new tableContext($(this));
tableInfo.Cell.find(".saveresourcekey").show();
tableInfo.Row.find(".resourcekeyvalueedit").show();
tableInfo.Row.find(".resourcekeyvaluedisplay").hide();
tableInfo.Cell.find(".editresourcekey").hide();
});
// Handler for click on the resource key save button
$('span.saveresourcekey').click(function (e) {
e.preventDefault();
var tableInfo = new tableContext($(this));
var inputfield = tableInfo.Row.find(".resourcekeyvalueedit input");
var displayfield = tableInfo.Row.find(".resourcekeyvaluedisplay");
// Ajax call setup
var resourcekeyid = inputfield.data('resourcekeyid');
var oldvalue = inputfield.data('oldvalue');
var newvalue = inputfield.val();
// Don't allow a null/empty string value, and don't update if nothing changed
if ((newvalue != null && newvalue != "") && (newvalue != oldvalue)) {
// Make a view model instance
var ajaxEditLanguageKeyViewModel = new Object();
ajaxEditLanguageKeyViewModel.ResourceKeyId = resourcekeyid;
ajaxEditLanguageKeyViewModel.NewName = newvalue;
// Ajax call to post the view model to the controller
var strung = JSON.stringify(ajaxEditLanguageKeyViewModel);
$.ajax({
url: app_base + 'ManageLanguage/UpdateResourceKey',
type: 'POST',
data: strung,
contentType: 'application/json; charset=utf-8',
error: function (xhr, ajaxOptions, thrownError) {
ShowUserMessage("Error: " + xhr.status + " " + thrownError);
}
});
displayfield.text(newvalue);
}
tableInfo.Cell.find(".editresourcekey").show();
tableInfo.Row.find(".resourcekeyvaluedisplay").show();
tableInfo.Cell.find(".saveresourcekey").hide();
tableInfo.Row.find(".resourcekeyvalueedit").hide();
});
$('.btn-file :file').on('fileselect', function (event, numFiles, label) {
var input = $(this).parents('.input-group').find(':text'),
log = numFiles > 1 ? numFiles + ' files selected' : label;
if (input.length) {
input.val(log);
} else {
if (log) alert(log);
}
});
});
$(document).on('change', '.btn-file :file', function () {
var input = $(this),
numFiles = input.get(0).files ? input.get(0).files.length : 1,
label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
input.trigger('fileselect', [numFiles, label]);
});
var isFirstLoad = true;
function ImportLanguage() {
$("#ImportForm").submit();
}
function Import_Complete() {
// Check to see if this is the first load of the iFrame
if (isFirstLoad == true) {
isFirstLoad = false;
return;
}
// Reset the import form so the file won't get uploaded again
document.getElementById("ImportForm").reset();
// Grab the content of the textarea we named jsonResult in the hidden iframe
var importResults = $.parseJSON($("#UploadTarget").contents().find("#jsonResult")[0].innerHTML);
var displayImportResults = "";
// Redirect as there are no errors
if (!importResults.HasErrors && !importResults.HasWarnings) {
displayImportResults += "<div>The import was successful.</div>";
}
if (importResults.HasErrors) {
displayImportResults += "<div>The import had the following errors:</div>";
displayImportResults += formatImportResults(importResults.Errors);
}
if (importResults.HasWarnings) {
displayImportResults += "<div>The import had the following warnings:</div>";
displayImportResults += formatImportResults(importResults.Warnings);
}
$("#ImportResults").html(displayImportResults);
}
function formatImportResults(jsonErrorsWarnings) {
var results = "";
var errorsWarnings = jQuery.parseJSON(jsonErrorsWarnings);
for (var i = 0; i < errorsWarnings.length; i++) {
results += "<div>" + errorsWarnings[i] + "</div>";
}
return results;
}
//Language manage End
function ChangeLanguage() {
var languageSelect = $(".languageselector select");
if (languageSelect.length > 0) {
languageSelect.change(function () {
var langVal = this.value;
$.ajax({
url: "/Language/ChangeLanguage",
type: "POST",
cache: false,
data: { lang: langVal },
success: function (data) {
location.reload();
},
error: function (xhr, ajaxOptions, thrownError) {
ShowUserMessage("Error: " + xhr.status + " " + thrownError);
}
});
});
}
}
function decodeHtml(html) {
var txt = document.createElement("textarea");
txt.innerHTML = html;
return txt.value;
}
function AjaxPost(url, data, successFunction, errorFunction) {
$.ajax({
type: "POST",
url: url,
data: data,
success: function (returnData, request) {
if (typeof (successFunction) == 'function') {
successFunction(returnData, url, data);
}
},
error: errorFunction ? errorFunction : DefaultErrorFunction
});
}
function AjaxGet(url, data, successFunction, errorFunction) {
$.ajax({
type: "GET",
url: url,
data: JSON.stringify(data),
success: function (returnData, request) {
if (typeof (successFunction) == 'function') {
successFunction(returnData, url, data);
}
},
error: errorFunction ? errorFunction : DefaultErrorFunction
});
}
function DefaultErrorFunction(err) {
alert(err.responseText);
console.log(err);
}
function ShowMessageControl(control, message) {
$(control).notify(message, { position: "bottom", className: "error" });
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 MiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,360 @@
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* hebrew */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2');
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* math */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* hebrew */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2');
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* math */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* hebrew */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2');
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* math */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* hebrew */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2');
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* math */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@@ -0,0 +1,420 @@
@media (min-width: 1400px){
.container, .container-sm, .container-md, .container-lg, .container-xl, .container-xxl {
max-width: 1500px!important;
}
}
.dropdown-user.lv-lang-box{
display:none;
}
.page-header.navbar .page-logo .logo-default, .brand-logo img {
max-height: 45px !important;
margin-top: 1px !important;
}
.top-menu .navbar-nav > li.dropdown-user .dropdown-toggle > img {
border-radius: 10px;
width: 20px;
}
.lv-notification-box .badge {
background-color: #FFD5D8;
color: #A85A5C;
border: 1px #EFDDDC solid;
}
.top-menu .link-chat, .classic-menu-dropdown.active, .header-menu .menu-nav > .menu-item:first-child {
display: none !important;
}
.active-sub {
background-color: #f5f5f9 !important;
}
.active-sub span {
color: #e7505a !important;
}
.input-icon-search {
display: unset;
right: 0px;
top: 6px;
cursor: pointer;
}
#kt_aside_menu{
padding-bottom: 50px;
overflow-y: auto!important;
}
.gutter-b{
margin-bottom: 9px;
}
.page-content {
margin-top: 55px;
}
@media (min-width: 992px){
.header-menu .menu-nav {
max-width: 700px;
}
}
.dashboard-stat .details .number{
font-size: 20px!important;
}
:root {
/*lacviet text color*/
/*lacviet button background, text color*/
/*lacviet button background hover, text color hover */
/*lacviet button background, text color third*/
/*lacviet border left search advance*/
/*lacviet bg button light*/
--primary-color-lv: #E74C3C;
--primary-button-color-lv: #5b5b5b;
--second-color-lv: #5b5b5b;
--third-color-lv: #C11200;
--border-color-lv: #8d8a8a;
--light-bg-lv: #E1F0FF;
/* --primary-color-lv: #B92A85;
--second-color-lv: #891B72;
--third-color-lv: #B31E8D;
--border-color-lv: #bd82a8;
--light-bg-lv: #f2f2f2;*/
}
.user-login-5 {
background: linear-gradient(#fff, #ccc)!important;
}
.goback a {
color: #696969 !important;
}
.login .mt-checkbox.mt-checkbox-outline>span{
border: 1px #696969 solid;
}
.rem-password .rememberme.font-white,
.user-login-5 .login-container > .login-content > h1,
.user-login-5 .login-container > .login-content p
{
color:#696969!important;
}
.table-scrollable .btn.btn-primary{
background-color: #337ab7!important;
border-color: #2e6da4!important;
}
/*module*/
.page-header.navbar .page-logo .logo-default, .contract-header #kt_brand img {
max-height: 45px !important;
height: 45px;
margin-top: 0px !important;
}
/*.have-chat .link-chat, .happy-brithday{
display: none;
}*/
.medium .modal-dialog .btn-success {
color: #fff !important;
}
#create-document-button.floating-button, .cd-stretchy-nav .stretchy-nav-bg, .login .btn.blue:not(.btn-outline),
.login .btn.blue, .login .btn.btn-success, .input-search-single .btn.green:not(.btn-outline)
{
background-color: var(--primary-color-lv) !important;
}
.login .btn.blue:not(.btn-outline),
.login .btn.blue, .login .btn.btn-success,
.user-login-5 .login-container > .login-content > .login-form .form-control,
.nav.nav-tabs.nav-tabs-line .nav-link:hover:not(.disabled), .nav.nav-tabs.nav-tabs-line .nav-link.active, .nav.nav-tabs.nav-tabs-line .show > .nav-link
{
border-color: var(--primary-color-lv) !important;
}
.page-header.navbar .hor-menu .navbar-nav > li.active > a, .page-header.navbar .hor-menu .navbar-nav > li.active > a, .page-header.navbar .hor-menu .navbar-nav > li.current > a, .page-header.navbar .hor-menu .navbar-nav > li.current > a,
.e-btn.e-info, .e-css.e-btn.e-info, .e-footer-content .e-btn.e-info, .e-upload .e-file-select-wrap .e-btn, .btn.blue:not(.btn-outline),
.add-menu, .edit-menu,
ul#navigation-left-filter-tree > li .title i,
#advanced-search-table-header th div,
.btn-success,
.lv-mainTop .pull-right > a,
.lv-mainTop .fa-ellipsis-v,
.lv-mainTop .pull-right > a, .lv-mainTop .pull-left > a, #downloadAllFiles,
#Schedules thead th {
background-color: var(--primary-button-color-lv) !important;
}
.fc-unthemed .fc-toolbar .fc-button.fc-button-active
{
background-color: var(--primary-button-color-lv) !important;
border-color: var(--primary-button-color-lv) !important;
}
.input-search-single .btn.green:not(.btn-outline){
border: 1px var(--primary-color-lv) solid !important;
}
.input-search-single .btn.green:not(.btn-outline) {
border-left: 1px var(--third-color-lv) solid !important;
}
.e-btn.e-info, .e-css.e-btn.e-info, .e-footer-content .e-btn.e-info, .e-upload .e-file-select-wrap .e-btn, .btn.blue:not(.btn-outline),
.add-menu, .edit-menu,
ul#navigation-left-filter-tree > li .title i,
.btn-success {
border-color: var(--primary-button-color-lv) !important;
}
.page-content-wrapper .e-btn.e-info:hover,
.page-content-wrapper .e-css.e-btn.e-info:hover,
.page-content-wrapper .e-footer-content .e-btn.e-info:hover,
.page-content-wrapper .e-upload .e-file-select-wrap .e-btn:hover .btn.blue:not(.btn-outline):hover,
.btn-success:hover,
.trinhky .e-btn.e-info:hover, .trinhky .e-css.e-btn.e-info:hover, .trinhky .e-footer-content .e-btn.e-info:hover,
.trinhky .e-upload .e-file-select-wrap .e-btn:hover .btn.blue:not(.btn-outline):hover,
.btn.green:not(.btn-outline):focus, .btn.green:not(.btn-outline):hover,
.fc-unthemed .fc-toolbar .fc-button.fc-button-active:hover {
background-color: var(--second-color-lv) !important;
border-color: var(--second-color-lv) !important;
}
.page-header.navbar .hor-menu .navbar-nav > li.active > a:hover {
background-color: var(--second-color-lv) !important;
}
.page-sidebar .page-sidebar-menu a.nav-toggle + .sub-menu > li > a:hover, .page-sidebar .page-sidebar-menu a.selected,
.page-sidebar .page-sidebar-menu .sub-menu > li > a.selected > i,
.page-sidebar .page-sidebar-menu a.nav-toggle + .sub-menu > li.selected > a,
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a.delete-document, .dropdown-menu > li > a.delete-document i,
.lv-search #SearchMainDocumentIcon,
#MainDocumentDetailContent .document-title,
.todo-tasklist-item-text,
.btn.btn-light-primary, .title-private-folder,
.nav .nav-link:hover:not(.disabled)
{
color: var(--primary-color-lv) !important;
}
#document-form .lv-btn-se .step-user, #document-form .lv-btn-se .step-user:focus {
background-color: transparent !important;
}
.active-sub span, .header .header-menu .menu-nav > .menu-item > .menu-link.active-sub .menu-text, #content-meetings .text-muted,
#content-meetings .card-header-meeting .btn.btn-light {
color: var(--primary-color-lv) !important;
}
/*Feature*/
.have-chat .link-chat {
display: none;
}
.btn.btn-primary, .lv-res-lang .search-box .input-search-single .btn-search-text-single:hover
{
background-color: var(--primary-button-color-lv) !important;
border-color: var(--primary-button-color-lv) !important;
}
.scrolltop,
#kt_header_menu .menu-link.menu-toggle1,
.page-header.navbar .hor-menu .navbar-nav > li.active > a, .page-header.navbar .hor-menu .navbar-nav > li.active > a, .page-header.navbar .hor-menu .navbar-nav > li.current > a, .page-header.navbar .hor-menu .navbar-nav > li.current > a,
.lv-res-lang .btn.green:not(.btn-outline), .search-box #searchStr + span > .btn-search,
.modal-content .split-container .portlet.light > .portlet-title,
.nav.nav-pills .show > .nav-link, .nav.nav-pills .nav-link.active ,
.sticky .btn.btn-danger,
.btn.btn-primary:hover:not(.btn-text):not(:disabled):not(.disabled),.btn.btn-primary:hover
{
background-color: var(--primary-color-lv) !important;
}
.sticky .btn.btn-danger,
.btn.btn-primary:hover:not(.btn-text):not(:disabled):not(.disabled), .btn.btn-primary:hover{
border-color: var(--primary-color-lv) !important;
}
.btn.btn-light-primary, .card-header-meeting .btn.btn-light,
.card.card-custom.card-more > .card-header .card-title .card-label {
background-color: var(--light-bg-lv) !important;
}
.card.card-custom.card-more > .card-header .card-title .card-label:after {
border-left-color: var(--light-bg-lv) !important;
}
.page-header.navbar .hor-menu .navbar-nav > li.active > a:hover {
background-color: var(--third-color-lv) !important;
}
.btn.blue:not(.btn-outline).active, .btn.blue:not(.btn-outline):active,
.open > .btn.blue:not(.btn-outline).dropdown-toggle,
.content-user .btn-info,.btn-info,
.doc-group-title a.btn, .doc-group-title button.btn, .content-user .add-role {
background-color: var(--primary-button-color-lv) !important;
border-color: var(--primary-button-color-lv) !important;
}
.layout-menu #kt_aside_menu_wrapper, .layout-menu .aside-menu.menu-system, .layout-menu .aside-menu{
background-color: var(--primary-button-color-lv) !important;
}
.menu-system .jstree-node.jstree-open ul, .menu-system .jstree-node{
border-color: #818181;
}
.btn.blue:not(.btn-outline):hover,
#create-document-button.floating-button:hover,
.sticky .btn.btn-danger:hover,
.btn.blue:not(.btn-outline).active.focus, .btn.blue:not(.btn-outline).active:focus, .btn.blue:not(.btn-outline).active:hover, .btn.blue:not(.btn-outline):active.focus, .btn.blue:not(.btn-outline):active:focus, .btn.blue:not(.btn-outline):active:hover, .open > .btn.blue:not(.btn-outline).dropdown-toggle.focus, .open > .btn.blue:not(.btn-outline).dropdown-toggle:focus, .open > .btn.blue:not(.btn-outline).dropdown-toggle:hover,
.btn.btn-primary:focus:not(.btn-text), .btn.btn-primary.focus:not(.btn-text),
.btn.btn-outline-primary:hover:not(.btn-text):not(:disabled):not(.disabled), .btn.btn-outline-primary:focus:not(.btn-text), .btn.btn-outline-primary.focus:not(.btn-text),
.content-user .btn-info:hover,.btn-info:hover,
.doc-group-title a.btn:hover, .content-user .add-role:hover,
.doc-group-title a.btn:focus, .content-user .add-role:focus,
.btn.btn-light-primary:hover,
.fc-unthemed .fc-toolbar .pull-right .fc-button:focus, .fc-unthemed .fc-toolbar .pull-right .fc-button:active, .fc-unthemed .fc-toolbar .pull-right .fc-button.fc-button-active
{
background-color: var(--primary-color-lv) !important;
border-color: var(--primary-color-lv) !important;
}
.btn.btn-default:hover:not(.btn-text):not(:disabled):not(.disabled), .btn.btn-default:focus:not(.btn-text), .btn.btn-default.focus:not(.btn-text),
.form-control-static.attachment:hover, .form-control-static.attachment:hover .fa,
#content-meetings table.scheduler-bg-table th,
#content-meetings table.resource-table th, #content-meetings table.scheduler-table th,
#content-meetings table.resource-table td, #content-meetings table.scheduler-bg-table td,
#content-meetings table.scheduler-table td,
#content-meetings .header3-text > div > div,
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled),
.card-more .card-header .nav .nav-link:hover, .card-more .card-header .nav .show > .nav-link i, .card-more .card-header .nav .nav-link:hover:not(.disabled) i{
color: var(--primary-color-lv) !important;
}
.btn.btn-light-primary:hover, .card-more .card-header .nav .nav-link.active:hover,
#content-meetings .card-header-meeting .btn.btn-light:hover,
.more-btn .btn, .more-btn .btn:focus, .more-btn .btn:active
{
color: #fff !important;
}
.doc-group-title{
color: var(--primary-button-color-lv)!important;
}
.aside-menu .menu-nav > .menu-item .menu-submenu .menu-item:not(.menu-item-parent):not(.menu-item-open):not(.menu-item-here):not(.menu-item-active):hover > .menu-heading .menu-text,
a.text-hover-primary:hover, .text-hover-primary:hover,
.header-fixed .top-menu .navbar-nav > li.dropdown .dropdown-menu li a:hover {
color: var(--third-color-lv) !important;
}
.aside-menu .menu-nav > .menu-item .menu-submenu .menu-item:not(.menu-item-parent):not(.menu-item-open):not(.menu-item-here):not(.menu-item-active):hover > .menu-link .menu-text,
.aside-left a.menu-link:hover .menu-icon i, .aside-left .menu-item-open .menu-icon i, .aside-left .menu-icon i, .span-total {
color: var(--primary-color-lv) !important;
}
/*.card-birthday, .card-birthday + div, .sp-my-task + div {
display: none;
}*/
.sp-my-task + div {
display: none;
}
.lv-contact-detail .font-blue, .navi .navi-item .navi-link:hover,
.navi .navi-item .navi-link:hover .navi-icon i,
.navi .navi-item .navi-link:hover .navi-text,
#content-workspace .detail-dl > a button i {
color: var(--third-color-lv) !important;
}
.btn.btn-outline-primary {
color: var(--primary-color-lv);
background-color: transparent;
border-color: var(--primary-color-lv);
}
.btn.btn-hover-light-primary:hover:not(.btn-text):not(:disabled):not(.disabled), .btn.btn-hover-light-primary:focus:not(.btn-text), .btn.btn-hover-light-primary.focus:not(.btn-text) {
color: var(--primary-color-lv);
background-color: #fdf4fb;
border-color: var(--primary-color-lv);
}
.btn.btn-hover-light-primary:hover:not(.btn-text):not(:disabled):not(.disabled) i, .btn.btn-hover-light-primary:focus:not(.btn-text) i, .btn.btn-hover-light-primary.focus:not(.btn-text) i, .btn.btn-light-primary i,
.nav .nav-link.active {
color: var(--primary-color-lv);
}
.card-more-all .card.card-custom.card-more > .card-header .card-title .card-label, .aside-menu .menu-nav > .menu-item > .menu-link .menu-icon {
color: var(--primary-color-lv) !important;
}
.lv-res-lang .btn.green:not(.btn-outline), #txt-um-keyword:focus, .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled), .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):first-child {
border-color: var(--border-color-lv) !important;
}
.lv-res-lang .search-box .input-search-single .btn-search-text-single, .lv-res-lang .search-box #searchStr + span > .btn-search {
border-right-color: var(--third-color-lv) !important;
}
#MainCtrl h3.card-title, .tree_collapse_all i{
color: var(--primary-button-color-lv)!important;
}
.brand-logo img {
max-height: 38px;
}
#Schedules button.new, #Schedules .btn-success.btn-send-mail {
background-color: transparent !important;
}
.medical-event-title{
line-height: 18px;
max-height: 54px;
overflow: hidden;
}
/*message***/
.lv-box-message.e-dialog .e-dlg-header-content {
background-color: var(--primary-color-lv) !important;
}
.lv-box-message.e-dialog .e-dlg-header {
color: #fff !important;
}
.lv-box-message.e-dialog .e-btn .e-btn-icon.e-icon-dlg-close {
color: #fff !important;
}
.lv-box-message .e-btn.e-flat.e-primary, .lv-box-message .e-css.e-btn.e-flat.e-primary {
color: var(--primary-color-lv) !important;
border-radius: 5px !important;
}
.lv-box-message .e-btn.e-flat.e-primary:focus, .lv-box-message .e-css.e-btn.e-flat.e-primary:focus {
background-color: #f2f2f2 !important;
}
.lv-box-message.e-dialog .e-btn.e-dlg-closeicon-btn:hover, .lv-box-message .e-dialog .e-btn.e-dlg-closeicon-btn:focus, .lv-box-message .e-dialog .e-btn.e-dlg-closeicon-btn:active {
background-color: transparent !important;
}
/*message*/
.card-messages .card-body .scroll-pull > div > div{
overflow:unset!important;
}
.card-messages .card-footer {
border-top: none;
}
.card-messages textarea {
overflow:hidden;
}
.card-messages .thumbs-container{
margin-top: 0px;
}
.card-messages div[class*="react-scroll-to-bottom-"] {
overflow-x: hidden;
}
.card-messages .align-items-start, .card-messages .align-items-end {
margin-left: 5px;
margin-right: 5px;
}
.card-messages .chat-message .message-text {
white-space: pre-wrap;
}
.card-messages .align-items-start .bg-light-success {
background-color: #F2F6F9 !important;
}
.card-messages .align-items-end .bg-light-success {
background-color: #DBF1FF !important;
}
.layout-task #hand-over-task,.layout-task #MainCtrl #kt_todo_list h3.card-title .dropdown{
display:none;
}
.layout-task .menuLeft .menu-icon i.fa-folder:before{
color: var(--color-i-menu) !important;
}
.layout-task .detail-tabs.nav-tabs.nav-tabs-line .nav-item{
margin-left: 0px;
}
.layout-task .nav.nav-tabs.nav-tabs-line .nav-item:first-child .nav-link {
margin-left: 10px;
margin-right: 5px;
}
.layout-task .nav.nav-tabs.nav-tabs-line .nav-item .nav-link {
margin-left: 5px;
}
/*ntvy*/
#open-support {
display: none;
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
/*! Backstretch - v2.0.4 - 2013-06-19
* http://srobbin.com/jquery-plugins/backstretch/
* Copyright (c) 2013 Scott Robbin; Licensed MIT */
(function(a,d,p){a.fn.backstretch=function(c,b){(c===p||0===c.length)&&a.error("No images were supplied for Backstretch");0===a(d).scrollTop()&&d.scrollTo(0,0);return this.each(function(){var d=a(this),g=d.data("backstretch");if(g){if("string"==typeof c&&"function"==typeof g[c]){g[c](b);return}b=a.extend(g.options,b);g.destroy(!0)}g=new q(this,c,b);d.data("backstretch",g)})};a.backstretch=function(c,b){return a("body").backstretch(c,b).data("backstretch")};a.expr[":"].backstretch=function(c){return a(c).data("backstretch")!==p};a.fn.backstretch.defaults={centeredX:!0,centeredY:!0,duration:5E3,fade:0};var r={left:0,top:0,overflow:"hidden",margin:0,padding:0,height:"100%",width:"100%",zIndex:-999999},s={position:"absolute",display:"none",margin:0,padding:0,border:"none",width:"auto",height:"auto",maxHeight:"none",maxWidth:"none",zIndex:-999999},q=function(c,b,e){this.options=a.extend({},a.fn.backstretch.defaults,e||{});this.images=a.isArray(b)?b:[b];a.each(this.images,function(){a("<img />")[0].src=this});this.isBody=c===document.body;this.$container=a(c);this.$root=this.isBody?l?a(d):a(document):this.$container;c=this.$container.children(".backstretch").first();this.$wrap=c.length?c:a('<div class="backstretch"></div>').css(r).appendTo(this.$container);this.isBody||(c=this.$container.css("position"),b=this.$container.css("zIndex"),this.$container.css({position:"static"===c?"relative":c,zIndex:"auto"===b?0:b,background:"none"}),this.$wrap.css({zIndex:-999998}));this.$wrap.css({position:this.isBody&&l?"fixed":"absolute"});this.index=0;this.show(this.index);a(d).on("resize.backstretch",a.proxy(this.resize,this)).on("orientationchange.backstretch",a.proxy(function(){this.isBody&&0===d.pageYOffset&&(d.scrollTo(0,1),this.resize())},this))};q.prototype={resize:function(){try{var a={left:0,top:0},b=this.isBody?this.$root.width():this.$root.innerWidth(),e=b,g=this.isBody?d.innerHeight?d.innerHeight:this.$root.height():this.$root.innerHeight(),j=e/this.$img.data("ratio"),f;j>=g?(f=(j-g)/2,this.options.centeredY&&(a.top="-"+f+"px")):(j=g,e=j*this.$img.data("ratio"),f=(e-b)/2,this.options.centeredX&&(a.left="-"+f+"px"));this.$wrap.css({width:b,height:g}).find("img:not(.deleteable)").css({width:e,height:j}).css(a)}catch(h){}return this},show:function(c){if(!(Math.abs(c)>this.images.length-1)){var b=this,e=b.$wrap.find("img").addClass("deleteable"),d={relatedTarget:b.$container[0]};b.$container.trigger(a.Event("backstretch.before",d),[b,c]);this.index=c;clearInterval(b.interval);b.$img=a("<img />").css(s).bind("load",function(f){var h=this.width||a(f.target).width();f=this.height||a(f.target).height();a(this).data("ratio",h/f);a(this).fadeIn(b.options.speed||b.options.fade,function(){e.remove();b.paused||b.cycle();a(["after","show"]).each(function(){b.$container.trigger(a.Event("backstretch."+this,d),[b,c])})});b.resize()}).appendTo(b.$wrap);b.$img.attr("src",b.images[c]);return b}},next:function(){return this.show(this.index<this.images.length-1?this.index+1:0)},prev:function(){return this.show(0===this.index?this.images.length-1:this.index-1)},pause:function(){this.paused=!0;return this},resume:function(){this.paused=!1;this.next();return this},cycle:function(){1<this.images.length&&(clearInterval(this.interval),this.interval=setInterval(a.proxy(function(){this.paused||this.next()},this),this.options.duration));return this},destroy:function(c){a(d).off("resize.backstretch orientationchange.backstretch");clearInterval(this.interval);c||this.$wrap.remove();this.$container.removeData("backstretch")}};var l,f=navigator.userAgent,m=navigator.platform,e=f.match(/AppleWebKit\/([0-9]+)/),e=!!e&&e[1],h=f.match(/Fennec\/([0-9]+)/),h=!!h&&h[1],n=f.match(/Opera Mobi\/([0-9]+)/),t=!!n&&n[1],k=f.match(/MSIE ([0-9]+)/),k=!!k&&k[1];l=!((-1<m.indexOf("iPhone")||-1<m.indexOf("iPad")||-1<m.indexOf("iPod"))&&e&&534>e||d.operamini&&"[object OperaMini]"==={}.toString.call(d.operamini)||n&&7458>t||-1<f.indexOf("Android")&&e&&533>e||h&&6>h||"palmGetResource"in d&&e&&534>e||-1<f.indexOf("MeeGo")&&-1<f.indexOf("NokiaBrowser/8.5.0")||k&&6>=k)})(jQuery,window);
File diff suppressed because one or more lines are too long
@@ -0,0 +1,117 @@
/*!
* jQuery Cookie Plugin v1.4.1
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2013 Klaus Hartl
* Released under the MIT license
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// CommonJS
factory(require('jquery'));
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {
var pluses = /\+/g;
function encode(s) {
return config.raw ? s : encodeURIComponent(s);
}
function decode(s) {
return config.raw ? s : decodeURIComponent(s);
}
function stringifyCookieValue(value) {
return encode(config.json ? JSON.stringify(value) : String(value));
}
function parseCookieValue(s) {
if (s.indexOf('"') === 0) {
// This is a quoted cookie as according to RFC2068, unescape...
s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}
try {
// Replace server-side written pluses with spaces.
// If we can't decode the cookie, ignore it, it's unusable.
// If we can't parse the cookie, ignore it, it's unusable.
s = decodeURIComponent(s.replace(pluses, ' '));
return config.json ? JSON.parse(s) : s;
} catch(e) {}
}
function read(s, converter) {
var value = config.raw ? s : parseCookieValue(s);
return $.isFunction(converter) ? converter(value) : value;
}
var config = $.cookie = function (key, value, options) {
// Write
if (value !== undefined && !$.isFunction(value)) {
options = $.extend({}, config.defaults, options);
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setTime(+t + days * 864e+5);
}
return (document.cookie = [
encode(key), '=', stringifyCookieValue(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// Read
var result = key ? undefined : {};
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling $.cookie().
var cookies = document.cookie ? document.cookie.split('; ') : [];
for (var i = 0, l = cookies.length; i < l; i++) {
var parts = cookies[i].split('=');
var name = decode(parts.shift());
var cookie = parts.join('=');
if (key && key === name) {
// If second argument (value) is a function it's a converter...
result = read(cookie, value);
break;
}
// Prevent storing a cookie that we couldn't decode.
if (!key && (cookie = read(cookie)) !== undefined) {
result[name] = cookie;
}
}
return result;
};
config.defaults = {};
$.removeCookie = function (key, options) {
if ($.cookie(key) === undefined) {
return false;
}
// Must not alter options, thus extending a fresh object...
$.cookie(key, '', $.extend({}, options, { expires: -1 }));
return !$.cookie(key);
};
}));
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
/*! Copyright (c) 2011 Piotr Rochala (http://rocha.la)
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
*
* Improved by keenthemes for Metronic Theme
* Version: 1.3.2
*
*/
!function(e){jQuery.fn.extend({slimScroll:function(i){var o={width:"auto",height:"250px",size:"7px",color:"#000",position:"right",distance:"1px",start:"top",opacity:.4,alwaysVisible:!1,disableFadeOut:!1,railVisible:!1,railColor:"#333",railOpacity:.2,railDraggable:!0,railClass:"slimScrollRail",barClass:"slimScrollBar",wrapperClass:"slimScrollDiv",allowPageScroll:!1,wheelStep:20,touchScrollStep:200,borderRadius:"7px",railBorderRadius:"7px",animate:!0},a=e.extend(o,i);return this.each(function(){function o(t){if(u){var t=t||window.event,i=0;t.wheelDelta&&(i=-t.wheelDelta/120),t.detail&&(i=t.detail/3);var o=t.target||t.srcTarget||t.srcElement;e(o).closest("."+a.wrapperClass).is(S.parent())&&r(i,!0),t.preventDefault&&!y&&t.preventDefault(),y||(t.returnValue=!1)}}function r(e,t,i){y=!1;var o=e,r=S.outerHeight()-M.outerHeight();if(t&&(o=parseInt(M.css("top"))+e*parseInt(a.wheelStep)/100*M.outerHeight(),o=Math.min(Math.max(o,0),r),o=e>0?Math.ceil(o):Math.floor(o),M.css({top:o+"px"})),v=parseInt(M.css("top"))/(S.outerHeight()-M.outerHeight()),o=v*(S[0].scrollHeight-S.outerHeight()),i){o=e;var s=o/S[0].scrollHeight*S.outerHeight();s=Math.min(Math.max(s,0),r),M.css({top:s+"px"})}"scrollTo"in a&&a.animate?S.animate({scrollTop:o}):S.scrollTop(o),S.trigger("slimscrolling",~~o),l(),c()}function s(){window.addEventListener?(this.addEventListener("DOMMouseScroll",o,!1),this.addEventListener("mousewheel",o,!1)):document.attachEvent("onmousewheel",o)}function n(){f=Math.max(S.outerHeight()/S[0].scrollHeight*S.outerHeight(),m),M.css({height:f+"px"});var e=f==S.outerHeight()?"none":"block";M.css({display:e})}function l(){if(n(),clearTimeout(p),v==~~v){if(y=a.allowPageScroll,b!=v){var e=0==~~v?"top":"bottom";S.trigger("slimscroll",e)}}else y=!1;return b=v,f>=S.outerHeight()?void(y=!0):(M.stop(!0,!0).fadeIn("fast"),void(a.railVisible&&H.stop(!0,!0).fadeIn("fast")))}function c(){a.alwaysVisible||(p=setTimeout(function(){a.disableFadeOut&&u||h||d||(M.fadeOut("slow"),H.fadeOut("slow"))},1e3))}var u,h,d,p,g,f,v,b,w="<div></div>",m=30,y=!1,S=e(this);if("ontouchstart"in window&&window.navigator.msPointerEnabled&&S.css("-ms-touch-action","none"),S.parent().hasClass(a.wrapperClass)){var E=S.scrollTop();if(M=S.parent().find("."+a.barClass),H=S.parent().find("."+a.railClass),n(),e.isPlainObject(i)){if("height"in i&&"auto"==i.height){S.parent().css("height","auto"),S.css("height","auto");var x=S.parent().parent().height();S.parent().css("height",x),S.css("height",x)}if("scrollTo"in i)E=parseInt(a.scrollTo);else if("scrollBy"in i)E+=parseInt(a.scrollBy);else if("destroy"in i)return M.remove(),H.remove(),void S.unwrap();r(E,!1,!0)}}else{a.height="auto"==i.height?S.parent().height():i.height;var C=e(w).addClass(a.wrapperClass).css({position:"relative",overflow:"hidden",width:a.width,height:a.height});S.css({overflow:"hidden",width:a.width,height:a.height});var H=e(w).addClass(a.railClass).css({width:a.size,height:"100%",position:"absolute",top:0,display:a.alwaysVisible&&a.railVisible?"block":"none","border-radius":a.railBorderRadius,background:a.railColor,opacity:a.railOpacity,zIndex:90}),M=e(w).addClass(a.barClass).css({background:a.color,width:a.size,position:"absolute",top:0,opacity:a.opacity,display:a.alwaysVisible?"block":"none","border-radius":a.borderRadius,BorderRadius:a.borderRadius,MozBorderRadius:a.borderRadius,WebkitBorderRadius:a.borderRadius,zIndex:99}),D="right"==a.position?{right:a.distance}:{left:a.distance};H.css(D),M.css(D),S.wrap(C),S.parent().append(M),S.parent().append(H),a.railDraggable&&M.bind("mousedown",function(i){var o=e(document);return d=!0,t=parseFloat(M.css("top")),pageY=i.pageY,o.bind("mousemove.slimscroll",function(e){currTop=t+e.pageY-pageY,M.css("top",currTop),r(0,M.position().top,!1)}),o.bind("mouseup.slimscroll",function(){d=!1,c(),o.unbind(".slimscroll")}),!1}).bind("selectstart.slimscroll",function(e){return e.stopPropagation(),e.preventDefault(),!1}),"ontouchstart"in window&&window.navigator.msPointerEnabled&&(S.bind("MSPointerDown",function(e){g=e.originalEvent.pageY}),S.bind("MSPointerMove",function(e){e.originalEvent.preventDefault();var t=(g-e.originalEvent.pageY)/a.touchScrollStep;r(t,!0),g=e.originalEvent.pageY})),H.hover(function(){l()},function(){c()}),M.hover(function(){h=!0},function(){h=!1}),S.hover(function(){u=!0,l(),c()},function(){u=!1,c()}),S.bind("touchstart",function(e){e.originalEvent.touches.length&&(g=e.originalEvent.touches[0].pageY)}),S.bind("touchmove",function(e){if(y||e.originalEvent.preventDefault(),e.originalEvent.touches.length){var t=(g-e.originalEvent.touches[0].pageY)/a.touchScrollStep;r(t,!0),g=e.originalEvent.touches[0].pageY}}),n(),"bottom"===a.start?(M.css({top:S.outerHeight()-M.outerHeight()}),r(0,!0)):"top"!==a.start&&(r(e(a.start).position().top,null,!0),a.alwaysVisible||M.hide()),s()}}),this}}),jQuery.fn.extend({slimscroll:jQuery.fn.slimScroll})}(jQuery);
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
/*!
* JavaScript Cookie v2.0.4
* https://github.com/js-cookie/js-cookie
*
* Copyright 2006, 2015 Klaus Hartl & Fagner Brack
* Released under the MIT license
*/
!function(e){if("function"==typeof define&&define.amd)define(e);else if("object"==typeof exports)module.exports=e();else{var n=window.Cookies,t=window.Cookies=e();t.noConflict=function(){return window.Cookies=n,t}}}(function(){function e(){for(var e=0,n={};e<arguments.length;e++){var t=arguments[e];for(var o in t)n[o]=t[o]}return n}function n(t){function o(n,r,i){var c;if(arguments.length>1){if(i=e({path:"/"},o.defaults,i),"number"==typeof i.expires){var s=new Date;s.setMilliseconds(s.getMilliseconds()+864e5*i.expires),i.expires=s}try{c=JSON.stringify(r),/^[\{\[]/.test(c)&&(r=c)}catch(a){}return r=encodeURIComponent(String(r)),r=r.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),n=encodeURIComponent(String(n)),n=n.replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent),n=n.replace(/[\(\)]/g,escape),document.cookie=[n,"=",r,i.expires&&"; expires="+i.expires.toUTCString(),i.path&&"; path="+i.path,i.domain&&"; domain="+i.domain,i.secure?"; secure":""].join("")}n||(c={});for(var p=document.cookie?document.cookie.split("; "):[],u=/(%[0-9A-Z]{2})+/g,d=0;d<p.length;d++){var f=p[d].split("="),l=f[0].replace(u,decodeURIComponent),m=f.slice(1).join("=");'"'===m.charAt(0)&&(m=m.slice(1,-1));try{if(m=t&&t(m,l)||m.replace(u,decodeURIComponent),this.json)try{m=JSON.parse(m)}catch(a){}if(n===l){c=m;break}n||(c[l]=m)}catch(a){}}return c}return o.get=o.set=o,o.getJSON=function(){return o.apply({json:!0},[].slice.call(arguments))},o.defaults={},o.remove=function(n,t){o(n,"",e(t,{expires:-1}))},o.withConverter=n,o}return n()});
@@ -0,0 +1,50 @@
var Login = function() {
var handleLogin = function() {
$('.login-form input').keypress(function(e) {
if (e.which == 13) {
if ($('.login-form').validate().form()) {
$('.login-form').submit(); //form validation success, call ajax form submit
}
return false;
}
});
$('.forget-form input').keypress(function(e) {
if (e.which == 13) {
if ($('.forget-form').validate().form()) {
$('.forget-form').submit();
}
return false;
}
});
}
return {
//main function to initiate the module
init: function() {
handleLogin();
// init background slide images
$('.login-bg').backstretch([
"../Content/theme/assets/pages/img/login/bg1.jpg",
"../Content/theme/assets/pages/img/login/bg2.jpg",
"../Content/theme/assets/pages/img/login/bg3.jpg"
], {
fade: 1000,
duration: 8000
}
);
}
};
}();
jQuery(document).ready(function() {
Login.init();
});
@@ -0,0 +1,456 @@
/* Cubic Bezier Transition */
.user-login-5 {
min-height: 100vh;
background: linear-gradient(#006fc7, #7dc2f8);
}
.user-login-5 .bs-reset {
margin: 0;
padding: 0;
}
.user-login-5 .forgot-password.text-right {
text-align: right;
float: right;
margin-top: 30px;
}
.user-login-5 .forgot-password.text-right a {
text-decoration: underline;
color: #fff !important;
}
.user-login-5 .forgot-password.text-right a:hover {
color: #ffff99 !important;
text-decoration: underline !important;
}
.user-login-5 .login-bg {
background-position: center;
background-size: cover;
background-repeat: no-repeat;
min-height: 100vh;
}
.backstretch img {
opacity: 0.8;
}
.user-login-5 .login-logo {
position: relative;
text-align: center;
margin-top: 0%;
margin-bottom: 8%;
}
.user-login-5 .login-logo.login-6 {
top: 80px;
left: 80px;
}
.user-login-5 .login-container {
position: relative;
text-align: center;
min-height: 100vh;
}
.user-login-5 .login-container .rem-password {
text-align: left;
}
.user-login-5 .login-container > .login-content,
.user-login-5 .login-container .login-social,
.user-login-5 .login-container .login-copyright {
padding: 0 80px;
}
.user-login-5 .login-container > .login-content {
margin-top: 10% !important;
}
.user-login-5 .login-container > .login-content > h1 {
font-size: 30px;
font-weight: 300;
color: #fff;
}
.user-login-5 .login-container > .login-content p {
color: #fff;
font-size: 15px;
line-height: 22px;
}
.user-login-5 .login-container > .login-content > .login-form {
margin-top: 40px;
color: #fff;
font-size: 15px;
}
.user-login-5 .login-container > .login-content > .login-form .form-control {
width: 100%;
padding: 0 40px 0 40px;
height: 45px;
line-height: 45px;
border: none;
border-bottom: 1px solid;
border-radius: 30px !important;
border-color: #3093e2;
color: #696969 !important;
font-size: 17px;
margin-bottom: 0px;
}
input {
background-color: rgb(255, 255, 255) !important;
}
.user-login-5 .login-container > .login-content > .login-form .form-control:focus {
outline: 0;
}
.user-login-5 .login-container > .login-content > .login-form .forgot-password,
.user-login-5 .login-container > .login-content > .login-form .login-button {
display: inline-block;
}
.user-login-5 .login-container > .login-content > .login-form .rem-password {
margin-bottom: 20px;
}
.user-login-5 .login-container > .login-content > .login-form .rem-password > p {
margin: 0;
}
.user-login-5 .login-container > .login-content > .login-form .rem-password > .rem-checkbox {
border-color: #a4aab2;
}
.user-login-5 .login-container > .login-content > .login-form .forgot-password {
margin-right: 1em;
}
.user-login-5 .login-container > .login-content > .login-form .forgot-password > a {
color: #a4aab2;
}
.user-login-5 .login-container > .login-content > .login-form .forgot-password > a:hover {
color: #337ab7;
text-decoration: none;
}
.user-login-5 .login-container > .login-content > .login-form .forgot-password > a:focus {
color: #a4aab2;
text-decoration: none;
}
.input-icon i {
padding: 4px 0 0 6px;
}
.mt-checkbox-outline {
float: left;
margin-bottom: 20px;
}
.dl-lblError {
padding-bottom: 10px;
display: block;
}
.user-login-5 .login-container > .login-footer {
position: absolute;
bottom: 0;
width: 100%;
padding-bottom: 10px;
}
.user-login-5 .login-container > .login-footer .login-social {
padding-right: 0;
}
.user-login-5 .login-container > .login-footer .login-social li {
display: inline-block;
list-style: none;
margin-right: 1em;
}
.user-login-5 .login-container > .login-footer .login-social a {
color: #a9b5be;
font-size: 18px;
}
.user-login-5 .login-container > .login-footer .login-social a:hover {
color: #337ab7;
text-decoration: none;
}
.user-login-5 .login-container > .login-footer .login-social a:focus {
color: #a9b5be;
}
.user-login-5 .login-container > .login-footer .login-copyright {
padding-left: 0;
margin-top: 6px;
}
.user-login-5 .login-container > .login-footer .login-copyright > p {
margin: 0;
font-size: 13px;
color: #a9b5be;
}
.user-login-5 .alert {
margin-top: -60px;
}
.user-login-5 .form-group.has-error {
border-bottom: 2px solid #ed6b75 !important;
}
.user-login-5 .form-group.valid {
border-bottom: 1px solid #a0a9b4 !important;
}
@media (max-width: 1365px) {
.user-login-5 .login-logo.login-6 {
top: 40px;
left: 40px;
}
.user-login-5 .login-container > .login-content,
.user-login-5 .login-container .login-social,
.user-login-5 .login-container .login-copyright {
padding: 0 30px;
}
.user-login-5 .login-container .login-social {
padding-right: 0;
}
.user-login-5 .login-container .login-copyright {
padding-left: 0;
}
}
@media (max-width: 767px) {
.user-login-5 {
/*min-height: 50vh;*/
}
.user-login-5 .login-bg {
min-height: 50vh;
}
.user-login-5 .login-logo.login-6 {
position: relative;
margin: 0 0 40px 0;
}
.user-login-5 .login-container {
min-height: 50vh;
}
.user-login-5 .login-container > .login-content {
margin-top: 10px;
}
.user-login-5 .login-container > .login-content .login-form {
margin-top: 40px;
}
.user-login-5 .login-container .rem-password {
margin-bottom: 1em;
}
.user-login-5 .login-container > .login-footer {
position: relative;
margin-top: 40px;
padding-bottom: 0;
}
.user-login-5 .login-container > .login-footer .login-social li {
margin-right: 0.5em;
}
.user-login-5 .alert {
margin-top: -20px;
}
}
@media (max-width: 640px) {
.user-login-5 .login-container > .login-content .text-right {
}
}
/*** login QLVB ***/
.btn.blue, .btn.btn-success {
height: 45px;
line-height: 45px !important;
padding: 0;
text-align: center;
width: 100%;
border-radius: 30px !important;
font-size: 16px;
background-color: #0573cb !important;
border-color: #0573cb !important;
}
.btn.blue.btn-forgot-pass {
width: 50%;
}
.btn.btn-outline.yellow-crusta {
line-height: 30px !important;
border: none !important;
}
.btn.blue:hover, .btn.btn-success:hover {
background-color: #0573cb !important;
border-color: #0573cb !important;
}
.user-login-5 h3.font-green {
display: none;
}
.goback {
margin-top: 20px;
}
.goback a {
text-decoration: underline;
color: #fff !important;
}
.goback a {
text-decoration: underline !important;
color: #ffff99 !important;
}
.validation-summary-errors ul, .validation-summary-errors li {
list-style-type: none;
padding: 0px;
}
.validation-summary-errors {
color: #ec4220 !important;
}
.alert.alert-success {
position: absolute;
right: 20px;
bottom: 0;
margin-top: 0;
}
.login-content .form-control {
width: 100%;
padding: 0 20px;
height: 45px;
line-height: 45px;
border: none;
border-bottom: 1px solid #ccc;
border-radius: 30px !important;
border-top: 1px solid #ccc;
background-color: #fff !important;
color: #696969 !important;
font-size: 17px;
margin-bottom: 25px;
overflow: hidden;
}
h1.titleChangePass {
margin-bottom: 30px;
}
@media (max-height: 767px) {
.user-login-5 .login-container > .login-content {
margin-top: 10px;
}
}
/*change pass*/
.change-pass .form-group {
text-align: left;
}
.change-pass .form-group label {
text-align: left;
color: #fff;
}
.login-content .change-pass .form-control {
border-radius: 0px 6px 6px 0px !important;
margin-bottom: 0px !important;
}
.login-content .change-pass span.input-group-addon {
padding: 0 16px !important;
}
.change-pass .btn.blue {
width: 47% !important;
float: right !important;
margin-left: 3%;
padding: 0 12px;
overflow: hidden;
}
#lblAccountInfo {
color: #EC141B;
font-size: 20px;
padding-top: 10px;
padding: 10px 0px;
display: block;
}
.input-group {
position: relative;
display: table;
border-collapse: separate;
}
.input-group-addon {
padding: 6px 12px;
font-size: 14px;
font-weight: 400;
line-height: 1;
color: #555;
text-align: center;
background-color: #eee;
border: 1px solid #ccc;
border-radius: 4px 0 0 4px;
height: 45px;
line-height: 43px;
}
@media (min-width: 1030px) {
.login-ees {
margin-left: 5px;
margin-bottom: 28px;
}
}
@media (max-width: 767px) {
#login-slide-show {
display: none;
}
.login-content {
margin-top: 10% !important;
}
}
@media (max-width: 1029px) {
.login-ees {
float: left;
margin-bottom: 15px;
clear: both;
}
}
.login-ees i {
font-size: 22px;
padding-right: 10px;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+6
View File
@@ -151,3 +151,9 @@ Desktop.ini
######################
coverage/
.nyc_output/
######################
# Database
######################
src/main/docker/oracle-data/
+91
View File
@@ -312,3 +312,94 @@ spring:
username: sisvietnam
password: sisvietnam
```
## Admin Pages & Block Editor Management
We have implemented a WordPress-like static pages management feature at `/manage/pages`. This includes an interactive block-based editor powered by **Editor.js**.
### 1. How to Run and Access
1. Start your Oracle database or ensure it is accessible.
2. Run the application in development mode:
```powershell
./gradlew bootRun
```
3. Open your browser and navigate to the Page Management dashboard: [http://localhost:8080/manage/pages](http://localhost:8080/manage/pages)
4. Navigate to the Posts Management dashboards:
- **All Posts**: [http://localhost:8080/manage/posts](http://localhost:8080/manage/posts)
- **Categories**: [http://localhost:8080/manage/categories](http://localhost:8080/manage/categories)
- **Tags**: [http://localhost:8080/manage/tags](http://localhost:8080/manage/tags)
### 2. Form Fields & Features
When creating or editing a page (`/manage/pages/new` or `/manage/pages/{id}/edit`):
- **Title**: Name of the page (e.g. `About Us`, `Giới thiệu`).
- **Slug**: The URL path. If left blank, it is automatically generated from the Title, supporting clean Vietnamese character conversions (e.g. `Bệnh viện S.I.S Cần Thơ` -> `benh-vien-s-i-s-can-tho`).
- **Status**: Status of the page (`DRAFT`, `PUBLISHED`, `ARCHIVED`).
- **Display Order**: Controls sorting of page links (lower numbers appear first).
- **Meta Description**: A short summary used for SEO metadata in `<meta name="description">`.
- **Page Content (Block Editor)**: Interactive wysiwyg editor utilizing Editor.js.
### 3. Using the Block Editor
- **Add a Block**: Press `Enter` to create a new paragraph, or click the `+` icon (or press `Tab` on an empty line) to pick a block type:
- **Heading**: Formatted HTML headers (H2, H3, H4).
- **List**: Bulleted or numbered lists (supports nested lists via `Tab`).
- **Quote**: Stylized blockquotes with a caption field.
- **Table**: Simple tables with columns and rows.
- **Code**: Formatted code block.
- **Warning**: Notice or alert box with title & message.
- **Delimiter**: Horizontal dividers.
- **Inline Styling**: Highlight any text in a paragraph to apply **Bold**, *Italic*, Links, Underlines, `Inline Code`, or Highlighter.
- **Reorder & Actions**: Hover over any block and use the handle menu (`⋮⋮`) to drag-and-drop, move up/down, convert to another block type, or delete.
### 4. How the Plugin System Works (Extensibility)
If you want to create new custom block types (e.g., custom slider, doctor list, video embed) in the future:
1. Create your plugin script file inside `src/main/resources/static/js/manage/editor-plugins/` (e.g. `video-embed-plugin.js`).
2. Implement your custom block class or wrap an existing Editor.js tool.
3. Register your tool globally by adding it to `window.SISEditorPlugins`:
```javascript
window.SISEditorPlugins = window.SISEditorPlugins || {};
window.SISEditorPlugins['videoEmbed'] = {
class: VideoEmbedToolClass,
inlineToolbar: true,
config: {
placeholder: 'Enter video URL...'
}
};
```
4. In `src/main/resources/templates/manage/pages/form.html`, load the script in the `<section layout:fragment="scripts">` tag **before** the main configuration script:
```html
<script th:src="@{/js/manage/editor-plugins/video-embed-plugin.js}"></script>
```
5. The initialization logic in [editor-config.js](file:///c:/Users/laptop01/Documents/sisvietnamvn_01/sisvietnamvn_main/src/main/resources/static/js/manage/editor-config.js) will automatically scan `window.SISEditorPlugins` and register them.
## User Management & Profiles
We have implemented a comprehensive User Management system in the Admin Dashboard, located under the **Users** menu. This allows administrators to manage all registered accounts on the platform.
### 1. How to Run and Access
1. Start your Oracle database or ensure it is accessible.
2. Run the application in development mode:
```powershell
./gradlew bootRun
```
3. Log in with an Administrator account (e.g., `admin`).
4. In the left sidebar, locate the **Users** menu to access the user management tools:
- **All Users**: `http://localhost:8080/manage/users`
- **Add New**: `http://localhost:8080/manage/users/new`
### 2. Managing Users (Admin Only)
- **All Users List**: Displays a table of all registered users with their `Login`, `Email`, `Status` (Activated/Deactivated), `Language`, `Profiles` (Roles such as `ROLE_ADMIN`, `ROLE_USER`), and `Created Date`.
- **Add New User**:
- Fill in the required fields: `Login`, `Email`.
- Optionally set `First Name`, `Last Name`, and `Language`.
- Assign Roles by checking the corresponding profile boxes.
- Set the user as **Activated** (if they are ready to log in).
- *Note: New users are created with a randomly generated password and a reset key. They must use the "Forgot your password?" flow to set their initial password before they can log in.*
- **Edit User**: Click the blue Edit icon next to any user in the list to modify their email, name, language, active status, or roles. Note that the `Login` cannot be changed after creation.
- **Delete User**: Click the red Trash icon to permanently remove a user. A confirmation modal will prevent accidental deletions.
### 3. User Profiles (All Authenticated Users)
Every logged-in user (regardless of role) can manage their personal profile by clicking their username in the top right corner and selecting **Profile**, or navigating to `http://localhost:8080/manage/profile`.
The Profile page contains two sections:
- **Personal Information**: Users can update their Email, First Name, Last Name, and Display Language.
- **Change Password**: Users can securely change their password by providing their current password, a new password, and confirming the new password.
+7 -15
View File
@@ -1,20 +1,12 @@
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
public class TestOracleJDBC {
@SuppressWarnings("deprecation")
public static void main(String[] args) {
String jdbcUrl = "jdbc:oracle:thin:@192.168.1.15:1521/sisvietnam";
String username = "sisvietnam";
String password = "sisvietnam";
try {
Connection connection = DriverManager.getConnection(jdbcUrl, username, password);
System.out.println("Connection successful!");
connection.close();
} catch (SQLException e) {
System.err.println("Connection failed!");
e.printStackTrace();
}
MessageDigestPasswordEncoder encoder = new MessageDigestPasswordEncoder("SHA-256");
String hash = encoder.encode("admin");
System.out.println("Encoded hash for admin: " + hash);
boolean matches = encoder.matches("admin", "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918");
System.out.println("Does 'admin' match my hash? " + matches);
}
}
+35
View File
@@ -0,0 +1,35 @@
import os
import re
directory = '/home/x79/Documents/sisvietnamvn_01/sisvietnamvn_main/src/main/resources/templates/manage'
layout = '/home/x79/Documents/sisvietnamvn_01/sisvietnamvn_main/src/main/resources/templates/fragments/manage-layout.html'
csrf_token = '\n <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />'
def process_file(filepath):
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# Check if csrf token is already present
if '_csrf.parameterName' in content:
print(f"Skipping {filepath} - already has CSRF token")
return
# Regex to find <form ... method="post" ...>
# It finds the end of the <form ...> tag that has method="post"
pattern = r'(<form[^>]*?method=["\']post["\'][^>]*?>)'
# We want to replace the match with the match + csrf_token
new_content = re.sub(pattern, r'\1' + csrf_token, content, flags=re.IGNORECASE)
if new_content != content:
with open(filepath, 'w', encoding='utf-8') as f:
f.write(new_content)
print(f"Updated {filepath}")
for root, _, files in os.walk(directory):
for file in files:
if file.endswith('.html'):
process_file(os.path.join(root, file))
process_file(layout)
+2 -1
View File
@@ -50,12 +50,12 @@ idea {
// }
dependencies {
implementation "dev.samstevens.totp:totp-spring-boot-starter:1.7.1"
implementation libs.jhipster.framework
implementation libs.mapstruct
implementation libs.springdoc.openapi.starter.webmvc.api
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.3"
implementation "com.oracle.database.jdbc:ojdbc8"
implementation "org.springframework.boot:spring-boot-h2console"
implementation "org.springframework.boot:spring-boot-jackson2"
implementation "org.springframework.boot:spring-boot-starter"
implementation "org.springframework.boot:spring-boot-starter-actuator"
@@ -66,6 +66,7 @@ dependencies {
implementation "org.springframework.boot:spring-boot-starter-liquibase"
implementation "org.springframework.boot:spring-boot-starter-mail"
implementation "org.springframework.boot:spring-boot-starter-oauth2-resource-server"
implementation "org.springframework.boot:spring-boot-starter-oauth2-client"
implementation "org.springframework.boot:spring-boot-starter-security"
implementation "org.springframework.boot:spring-boot-starter-thymeleaf"
implementation "nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect"
+49
View File
@@ -0,0 +1,49 @@
# This configuration combines the app and database services for easy execution at the project root
name: sisvietnamvn
services:
oracle:
image: container-registry.oracle.com/database/free:latest
restart: always
ports:
- "1521:1521"
environment:
- ORACLE_PWD=Oracle123!
- ORACLE_PASSWORD=Oracle123!
- ORACLE_PDB=sisvietnam
- ORACLE_DATABASE=sisvietnam
- APP_USER=sisvietnam
- APP_USER_PASSWORD=sisvietnam
volumes:
- oracle_data:/opt/oracle/oradata
networks:
- sisvietnamvn-net
app:
image: git.victorphan.net/basketballcantho/sisvietnamvn:latest
depends_on:
oracle:
condition: service_healthy
environment:
- _JAVA_OPTIONS=-Xmx512m -Xms256m
- SPRING_PROFILES_ACTIVE=prod,api-docs,secret-samples
- MANAGEMENT_PROMETHEUS_METRICS_EXPORT_ENABLED=true
- SPRING_DATASOURCE_URL=jdbc:oracle:thin:@oracle:1521/FREEPDB1
- SPRING_DATASOURCE_USERNAME=SYSTEM
- SPRING_DATASOURCE_PASSWORD=Oracle123!
- SPRING_LIQUIBASE_URL=jdbc:oracle:thin:@oracle:1521/FREEPDB1
ports:
- "8080:8080"
healthcheck:
test: [ 'CMD-SHELL', 'bash -c ''exec 3<>/dev/tcp/127.0.0.1/8080; printf "GET /management/health HTTP/1.0\r\nHost: localhost\r\n\r\n" >&3; grep -q "\"status\":\"UP\"" <&3''' ]
interval: 5s
timeout: 5s
retries: 40
networks:
- sisvietnamvn-net
networks:
sisvietnamvn-net:
driver: bridge
volumes:
oracle_data:
+20
View File
@@ -0,0 +1,20 @@
import java.sql.*;
public class get_users {
public static void main(String[] args) {
String url = "jdbc:oracle:thin:@localhost:1521/FREEPDB1";
String user = "sisvietnam";
String password = "sisvietnam";
try (Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT id, login, email FROM jhi_user")) {
while (rs.next()) {
System.out.println("ID: " + rs.getLong("id") + ", Login: " + rs.getString("login") + ", Email: " + rs.getString("email"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Vendored Regular → Executable
View File
+5 -5
View File
@@ -2,15 +2,15 @@
name: sisvietnamvn
services:
app:
image: sisvietnamvn
image: git.victorphan.net/basketballcantho/sisvietnamvn:latest
environment:
- _JAVA_OPTIONS=-Xmx512m -Xms256m
- SPRING_PROFILES_ACTIVE=prod,api-docs,secret-samples
- MANAGEMENT_PROMETHEUS_METRICS_EXPORT_ENABLED=true
- SPRING_DATASOURCE_URL=jdbc:oracle:thin:@oracle:1521:sisvietnamvn
- SPRING_DATASOURCE_USERNAME=system
- SPRING_DATASOURCE_PASSWORD=oracle
- SPRING_LIQUIBASE_URL=jdbc:oracle:thin:@oracle:1521:sisvietnamvn
- SPRING_DATASOURCE_URL=jdbc:oracle:thin:@oracle:1521/sisvietnam
- SPRING_DATASOURCE_USERNAME=SYSTEM
- SPRING_DATASOURCE_PASSWORD=Oracle123!
- SPRING_LIQUIBASE_URL=jdbc:oracle:thin:@oracle:1521/sisvietnam
# If you want to expose these ports outside your dev PC,
# remove the "127.0.0.1:" prefix
ports:
+6 -5
View File
@@ -1,16 +1,17 @@
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
name: sisvietnam-oracle
name: sisvietnamvn
services:
sisvietnam-oracle:
oracle:
image: container-registry.oracle.com/database/free:latest
restart: always
network_mode: "host"
ports:
- "1521:1521"
environment:
# Password for the SYS, SYSTEM, and PDBADMIN administrative accounts (official image uses ORACLE_PWD)
# Password for the SYS, SYSTEM, and PDBADMIN administrative accounts
- ORACLE_PWD=Oracle123!
- ORACLE_PASSWORD=Oracle123!
- ORACLE_PDB=sisvietnam
# Name of the pluggable database (official image default is FREEPDB1)
# Name of the pluggable database
- ORACLE_DATABASE=sisvietnam
# Note: The official Oracle Free image does not automatically create custom app users/databases.
# If needed, create the 'sisvietnam' user manually or use the SYSTEM user to connect.
@@ -14,13 +14,12 @@ import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.h2console.autoconfigure.H2ConsoleAutoConfiguration;
import org.springframework.boot.liquibase.autoconfigure.LiquibaseProperties;
import org.springframework.core.env.Environment;
import tech.jhipster.config.DefaultProfileUtil;
import tech.jhipster.config.JHipsterConstants;
@SpringBootApplication(exclude = { H2ConsoleAutoConfiguration.class })
@SpringBootApplication
@EnableConfigurationProperties({ LiquibaseProperties.class, ApplicationProperties.class })
public class SisvietnamvnApp {
@@ -0,0 +1,44 @@
package com.sisvietnamvn.web.config;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import java.util.LinkedHashMap;
import java.util.Map;
public class CustomOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
private final OAuth2AuthorizationRequestResolver defaultResolver;
public CustomOAuth2AuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository, String authorizationRequestBaseUri) {
this.defaultResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, authorizationRequestBaseUri);
}
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
OAuth2AuthorizationRequest req = defaultResolver.resolve(request);
return customizeAuthorizationRequest(req);
}
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) {
OAuth2AuthorizationRequest req = defaultResolver.resolve(request, clientRegistrationId);
return customizeAuthorizationRequest(req);
}
private OAuth2AuthorizationRequest customizeAuthorizationRequest(OAuth2AuthorizationRequest req) {
if (req == null) {
return null;
}
Map<String, Object> additionalParameters = new LinkedHashMap<>(req.getAdditionalParameters());
additionalParameters.put("prompt", "select_account");
return OAuth2AuthorizationRequest.from(req)
.additionalParameters(additionalParameters)
.build();
}
}
@@ -5,7 +5,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.h2console.autoconfigure.H2ConsoleProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@@ -14,13 +13,11 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import tech.jhipster.config.JHipsterConstants;
import tech.jhipster.config.h2.H2ConfigurationHelper;
@Configuration
@EnableJpaRepositories({ "com.sisvietnamvn.web.repository" })
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
@EnableTransactionManagement
@EnableConfigurationProperties(H2ConsoleProperties.class)
public class DatabaseConfiguration {
private static final Logger LOG = LoggerFactory.getLogger(DatabaseConfiguration.class);
@@ -30,33 +27,4 @@ public class DatabaseConfiguration {
public DatabaseConfiguration(Environment env) {
this.env = env;
}
/**
* Open the TCP port for the H2 database, so it is available remotely.
*
* @return the H2 database TCP server.
* @throws SQLException if the server failed to start.
*/
@Bean(initMethod = "start", destroyMethod = "stop")
@Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)
@ConditionalOnProperty(prefix = "spring.h2.console", name = "enabled", havingValue = "true")
public Object h2TCPServer() throws SQLException {
String port = getValidPortForH2();
LOG.debug("H2 database is available on port {}", port);
return H2ConfigurationHelper.createServer(port);
}
private String getValidPortForH2() {
var port = Integer.parseInt(env.getProperty("server.port"));
if (port < 10000) {
port = 10000 + port;
} else {
if (port < 63536) {
port = port + 2000;
} else {
port = port - 2000;
}
}
return String.valueOf(port);
}
}
@@ -0,0 +1,29 @@
package com.sisvietnamvn.web.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.nio.file.Path;
import java.nio.file.Paths;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
exposeDirectory("uploads", registry);
}
private void exposeDirectory(String dirName, ResourceHandlerRegistry registry) {
Path uploadDir = Paths.get(dirName);
String uploadPath = uploadDir.toFile().getAbsolutePath();
if (dirName.startsWith("../")) {
dirName = dirName.replace("../", "");
}
registry.addResourceHandler("/" + dirName + "/**")
.addResourceLocations("file:/" + uploadPath + "/");
}
}
@@ -0,0 +1,40 @@
package com.sisvietnamvn.web.config;
import com.sisvietnamvn.web.domain.Authority;
import com.sisvietnamvn.web.repository.AuthorityRepository;
import com.sisvietnamvn.web.security.AuthoritiesConstants;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class RoleSeeder implements CommandLineRunner {
private final AuthorityRepository authorityRepository;
public RoleSeeder(AuthorityRepository authorityRepository) {
this.authorityRepository = authorityRepository;
}
@Override
public void run(String... args) {
List<String> roles = Arrays.asList(
AuthoritiesConstants.EDITOR,
AuthoritiesConstants.AUTHOR,
AuthoritiesConstants.CONTRIBUTOR,
AuthoritiesConstants.SUBSCRIBER,
AuthoritiesConstants.PRE_AUTH_2FA
);
for (String roleName : roles) {
if (authorityRepository.findById(roleName).isEmpty()) {
Authority authority = new Authority();
authority.setName(roleName);
authorityRepository.save(authority);
System.out.println("Seeded role: " + roleName);
}
}
}
}
@@ -3,6 +3,7 @@ package com.sisvietnamvn.web.config;
import static org.springframework.security.config.Customizer.withDefaults;
import com.sisvietnamvn.web.security.*;
import com.sisvietnamvn.web.security.oauth2.CustomOidcUserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@@ -11,7 +12,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
@@ -19,6 +20,10 @@ import org.springframework.security.web.SecurityFilterChain;
import tech.jhipster.config.JHipsterConstants;
import tech.jhipster.config.JHipsterProperties;
import com.sisvietnamvn.web.security.CustomAuthenticationSuccessHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
@Configuration
@EnableMethodSecurity(securedEnabled = true)
public class SecurityConfiguration {
@@ -26,28 +31,35 @@ public class SecurityConfiguration {
private final Environment env;
private final JHipsterProperties jHipsterProperties;
private final CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
private final ClientRegistrationRepository clientRegistrationRepository;
public SecurityConfiguration(Environment env, JHipsterProperties jHipsterProperties) {
public SecurityConfiguration(Environment env, JHipsterProperties jHipsterProperties, CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler, ClientRegistrationRepository clientRegistrationRepository) {
this.env = env;
this.jHipsterProperties = jHipsterProperties;
this.customAuthenticationSuccessHandler = customAuthenticationSuccessHandler;
this.clientRegistrationRepository = clientRegistrationRepository;
}
@Bean
@SuppressWarnings("deprecation")
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
return new MessageDigestPasswordEncoder("SHA-256");
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
public SecurityFilterChain filterChain(HttpSecurity http, CustomOidcUserService customOidcUserService) {
http.cors(withDefaults())
.csrf(csrf -> csrf.disable())
.csrf(withDefaults())
.authorizeHttpRequests(authz ->
// prettier-ignore
authz
.requestMatchers(HttpMethod.GET, "/", "/about", "/flex-finish", "/tin-tuc", "/tin-tuc/**", "/lien-he",
"/manage/**", "/css/**", "/images/**", "/js/**", "/UMass*/**", "/Undergraduate*/**",
"/favicon.ico", "/favicons/**", "/flex-finish/**", "/contact-us/**")
"/manage/login", "/css/**", "/images/**", "/js/**", "/vendor/**", "/fonts/**", "/login-assets/**", "/UMass*/**", "/Undergraduate*/**",
"/favicon.ico", "/favicons/**", "/flex-finish/**", "/contact-us/**", "/uploads/**", "/api/manage/snippets/**", "/page/**", "/news/article/**", "/post/**", "/error",
"/about-us", "/specialty", "/doctor", "/service", "/health-library", "/news-and-events", "/patient-support", "/medical-expert", "/umcers", "/bidding", "/contact-us")
.permitAll()
.requestMatchers(HttpMethod.POST, "/api/manage/media/upload").permitAll()
.requestMatchers(HttpMethod.GET, "/swagger-ui/**", "/v3/api-docs/**").permitAll()
.requestMatchers(HttpMethod.POST, "/api/authenticate").permitAll()
.requestMatchers(HttpMethod.GET, "/api/authenticate").permitAll()
@@ -61,11 +73,30 @@ public class SecurityConfiguration {
.requestMatchers("/management/health/**").permitAll()
.requestMatchers("/management/info").permitAll()
.requestMatchers("/management/prometheus").permitAll()
.requestMatchers("/2fa/**").hasAuthority(AuthoritiesConstants.PRE_AUTH_2FA)
.requestMatchers("/manage/**").hasAnyAuthority(AuthoritiesConstants.ADMIN, AuthoritiesConstants.EDITOR, AuthoritiesConstants.AUTHOR, AuthoritiesConstants.CONTRIBUTOR, AuthoritiesConstants.SUBSCRIBER, AuthoritiesConstants.USER)
.requestMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(exceptions -> exceptions
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
.accessDeniedHandler(new BearerTokenAccessDeniedHandler()))
.formLogin(form -> form
.loginPage("/manage/login")
.successHandler(customAuthenticationSuccessHandler)
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/manage/logout")
.logoutSuccessUrl("/manage/login?logout")
.permitAll()
)
.oauth2Login(oauth2 -> oauth2
.loginPage("/manage/login")
.successHandler(customAuthenticationSuccessHandler)
.authorizationEndpoint(authorization -> authorization
.authorizationRequestResolver(new CustomOAuth2AuthorizationRequestResolver(clientRegistrationRepository, OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI))
)
.userInfoEndpoint(userInfo -> userInfo
.oidcUserService(customOidcUserService)
)
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
.oauth2ResourceServer(oauth2 -> oauth2.jwt(withDefaults()));
if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) {
http.authorizeHttpRequests(authz -> authz.requestMatchers("/h2-console/**").permitAll())
@@ -13,6 +13,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import com.sisvietnamvn.web.hook.HookManager;
import tech.jhipster.config.JHipsterConstants;
import tech.jhipster.config.JHipsterProperties;
import tech.jhipster.config.h2.H2ConfigurationHelper;
@@ -26,12 +27,13 @@ public class WebConfigurer implements ServletContextInitializer {
private static final Logger LOG = LoggerFactory.getLogger(WebConfigurer.class);
private final Environment env;
private final JHipsterProperties jHipsterProperties;
private final HookManager hookManager;
public WebConfigurer(Environment env, JHipsterProperties jHipsterProperties) {
public WebConfigurer(Environment env, JHipsterProperties jHipsterProperties, HookManager hookManager) {
this.env = env;
this.jHipsterProperties = jHipsterProperties;
this.hookManager = hookManager;
}
@Override
@@ -44,6 +46,7 @@ public class WebConfigurer implements ServletContextInitializer {
initH2Console(servletContext);
}
LOG.info("Web application fully configured");
hookManager.doAction("init");
}
@Bean
@@ -6,10 +6,7 @@ import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "index";
}
// Trang Flex Finish (MỚI THÊM)
@GetMapping("/flex-finish")
@@ -20,17 +17,17 @@ public class HomeController {
// Trang Giới thiệu Bệnh viện
@GetMapping("/about")
public String about() {
return "about";
return "redirect:/about-us";
}
// Trang Tin tức
@GetMapping("/tin-tuc")
public String tintuc() {
return "tin-tuc";
return "redirect:/news-and-events";
}
@GetMapping("/lien-he")
public String lienhe() {
return "lien-he";
return "redirect:/contact-us";
}
}
@@ -1,16 +0,0 @@
package com.sisvietnamvn.web.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/manage")
public class ManageController {
@GetMapping({"", "/"})
public String index() {
return "manage/index";
}
}
@@ -0,0 +1,115 @@
package com.sisvietnamvn.web.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sisvietnamvn.web.domain.Page;
import com.sisvietnamvn.web.security.AuthoritiesConstants;
import com.sisvietnamvn.web.security.SecurityUtils;
import com.sisvietnamvn.web.service.PageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.server.ResponseStatusException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* Controller for rendering public-facing pages dynamically.
*/
@Controller
public class PageController {
private static final Logger LOG = LoggerFactory.getLogger(PageController.class);
private final PageService pageService;
private final ObjectMapper objectMapper;
public PageController(PageService pageService, ObjectMapper objectMapper) {
this.pageService = pageService;
this.objectMapper = objectMapper;
}
/**
* GET /page/{slug} : Render a page dynamically by slug.
*/
@GetMapping("/page/{slug}")
public String getPage(@PathVariable("slug") String slug, Model model) {
LOG.debug("REST request to get public Page : {}", slug);
return renderPage(pageService.findBySlug(slug), model);
}
@GetMapping("/")
public String getHome(Model model) {
return renderPage(pageService.findByPageType(com.sisvietnamvn.web.domain.PageType.HOME), model);
}
@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()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Page not found");
}
Page page = pageOpt.get();
// Security Check: If Draft, only Admins can view
if ("DRAFT".equals(page.getStatus()) && !SecurityUtils.hasCurrentUserThisAuthority(AuthoritiesConstants.ADMIN)) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Page not found");
}
// Parse Editor.js content JSON to extract blocks
List<Map<String, Object>> blocks = Collections.emptyList();
if (page.getContent() != null && !page.getContent().trim().isEmpty()) {
try {
Map<String, Object> editorData = objectMapper.readValue(page.getContent(), new TypeReference<>() {});
if (editorData.containsKey("blocks")) {
blocks = (List<Map<String, Object>>) editorData.get("blocks");
}
} catch (JsonProcessingException e) {
LOG.error("Failed to parse Editor.js JSON for page ID: {}", page.getId(), e);
}
}
model.addAttribute("page", page);
model.addAttribute("blocks", blocks);
return "page";
}
}
@@ -0,0 +1,54 @@
package com.sisvietnamvn.web.controller;
import com.sisvietnamvn.web.domain.PageStatus;
import com.sisvietnamvn.web.domain.Post;
import com.sisvietnamvn.web.repository.PostRepository;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.server.ResponseStatusException;
@Controller
@RequestMapping("/post")
public class PostController {
private final PostRepository postRepository;
public PostController(PostRepository postRepository) {
this.postRepository = postRepository;
}
@GetMapping("/{slug}")
public String viewPost(@PathVariable String slug, Model model) {
Post post = postRepository.findBySlug(slug)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
// Only allow viewing published posts (unless admin, but for now just check published)
if (post.getStatus() != PageStatus.PUBLISHED) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
}
model.addAttribute("post", post);
// Safely format the Instant to avoid Thymeleaf parsing errors
if (post.getCreatedDate() != null) {
String formattedDate = java.time.format.DateTimeFormatter.ofPattern("MMMM dd, yyyy")
.withZone(java.time.ZoneId.systemDefault())
.format(post.getCreatedDate());
model.addAttribute("formattedDate", formattedDate);
}
switch (post.getLayout()) {
case SIDEBAR:
return "posts/sidebar";
case FULL_WIDTH:
return "posts/full-width";
case STANDARD:
default:
return "posts/standard";
}
}
}
@@ -0,0 +1,176 @@
package com.sisvietnamvn.web.controller;
import com.sisvietnamvn.web.domain.Authority;
import com.sisvietnamvn.web.domain.User;
import com.sisvietnamvn.web.repository.UserRepository;
import com.sisvietnamvn.web.security.AuthoritiesConstants;
import com.sisvietnamvn.web.security.DomainUserDetailsService;
import dev.samstevens.totp.code.CodeGenerator;
import dev.samstevens.totp.code.CodeVerifier;
import dev.samstevens.totp.code.DefaultCodeGenerator;
import dev.samstevens.totp.code.DefaultCodeVerifier;
import dev.samstevens.totp.exceptions.QrGenerationException;
import dev.samstevens.totp.qr.QrData;
import dev.samstevens.totp.qr.QrGenerator;
import dev.samstevens.totp.qr.ZxingPngQrGenerator;
import dev.samstevens.totp.secret.DefaultSecretGenerator;
import dev.samstevens.totp.secret.SecretGenerator;
import dev.samstevens.totp.time.SystemTimeProvider;
import dev.samstevens.totp.time.TimeProvider;
import dev.samstevens.totp.util.Utils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Controller
@RequestMapping("/2fa")
public class TwoFactorController {
private final UserRepository userRepository;
public TwoFactorController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@GetMapping
public String show2FaForm(Authentication authentication) {
if (authentication == null || !authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals(AuthoritiesConstants.PRE_AUTH_2FA))) {
return "redirect:/manage/login";
}
String username = authentication.getName();
Optional<User> userOpt = userRepository.findOneByLogin(username);
if (userOpt.isPresent()) {
User user = userOpt.get();
if (!user.isUsing2FA()) {
return "redirect:/2fa/setup";
}
}
return "manage/2fa/verify";
}
@PostMapping
public String verify2Fa(@RequestParam("code") String code, Authentication authentication, RedirectAttributes redirectAttributes) {
String username = authentication.getName();
Optional<User> userOpt = userRepository.findOneWithAuthoritiesByLogin(username);
if (userOpt.isEmpty()) {
return "redirect:/manage/login";
}
User user = userOpt.get();
if (verifyCode(user.getTotpSecret(), code)) {
upgradeSecurityContext(user, authentication);
return "redirect:/manage";
} else {
redirectAttributes.addFlashAttribute("errorMessage", "Invalid 2FA code. Please try again.");
return "redirect:/2fa";
}
}
@GetMapping("/setup")
public String showSetupForm(Authentication authentication, Model model) {
String username = authentication.getName();
Optional<User> userOpt = userRepository.findOneByLogin(username);
if (userOpt.isEmpty()) {
return "redirect:/manage/login";
}
User user = userOpt.get();
if (user.isUsing2FA()) {
return "redirect:/2fa";
}
if (user.getTotpSecret() == null) {
SecretGenerator secretGenerator = new DefaultSecretGenerator();
user.setTotpSecret(secretGenerator.generate());
userRepository.save(user);
}
try {
QrData data = new QrData.Builder()
.label(user.getEmail() != null ? user.getEmail() : user.getLogin())
.secret(user.getTotpSecret())
.issuer("SIS Vietnam")
.algorithm(dev.samstevens.totp.code.HashingAlgorithm.SHA1)
.digits(6)
.period(30)
.build();
QrGenerator generator = new ZxingPngQrGenerator();
byte[] imageData = generator.generate(data);
String mimeType = generator.getImageMimeType();
String dataUri = Utils.getDataUriForImage(imageData, mimeType);
model.addAttribute("qrCode", dataUri);
model.addAttribute("secret", user.getTotpSecret());
} catch (QrGenerationException e) {
model.addAttribute("errorMessage", "Failed to generate QR Code.");
}
return "manage/2fa/setup";
}
@PostMapping("/setup")
public String verifySetup(@RequestParam("code") String code, Authentication authentication, RedirectAttributes redirectAttributes) {
String username = authentication.getName();
Optional<User> userOpt = userRepository.findOneWithAuthoritiesByLogin(username);
if (userOpt.isEmpty()) {
return "redirect:/manage/login";
}
User user = userOpt.get();
if (verifyCode(user.getTotpSecret(), code)) {
user.setUsing2FA(true);
userRepository.save(user);
upgradeSecurityContext(user, authentication);
return "redirect:/manage";
} else {
redirectAttributes.addFlashAttribute("errorMessage", "Invalid 2FA code. Please try again.");
return "redirect:/2fa/setup";
}
}
private boolean verifyCode(String secret, String code) {
TimeProvider timeProvider = new SystemTimeProvider();
CodeGenerator codeGenerator = new DefaultCodeGenerator();
CodeVerifier verifier = new DefaultCodeVerifier(codeGenerator, timeProvider);
return verifier.isValidCode(secret, code);
}
private void upgradeSecurityContext(User user, Authentication originalAuthentication) {
List<GrantedAuthority> authorities = user.getAuthorities().stream()
.map(Authority::getName)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
DomainUserDetailsService.UserWithId userWithId = new DomainUserDetailsService.UserWithId(
user.getLogin(),
user.getPassword(),
authorities,
user.getId()
);
UsernamePasswordAuthenticationToken newAuth = new UsernamePasswordAuthenticationToken(
userWithId, originalAuthentication.getCredentials(), authorities);
SecurityContextHolder.getContext().setAuthentication(newAuth);
}
}
@@ -0,0 +1,144 @@
package com.sisvietnamvn.web.controller.manage;
import com.sisvietnamvn.web.domain.Category;
import com.sisvietnamvn.web.hook.HookManager;
import com.sisvietnamvn.web.service.CategoryService;
import jakarta.validation.Valid;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.security.access.prepost.PreAuthorize;
import com.sisvietnamvn.web.security.AuthoritiesConstants;
/**
* Controller for managing post Categories in the admin panel.
* Uses a WordPress-style split layout: form on the left, table on the right.
*/
@Controller
@RequestMapping("/manage/posts/categories")
@PreAuthorize("hasAnyAuthority(\"" + AuthoritiesConstants.ADMIN + "\", \"" + AuthoritiesConstants.EDITOR + "\")")
public class ManageCategoryController {
private static final Logger LOG = LoggerFactory.getLogger(ManageCategoryController.class);
private final CategoryService categoryService;
private final HookManager hookManager;
public ManageCategoryController(CategoryService categoryService, HookManager hookManager) {
this.categoryService = categoryService;
this.hookManager = hookManager;
}
@ModelAttribute
public void adminInit() {
hookManager.doAction("admin_init");
}
/**
* GET /manage/posts/categories — List all categories with inline create form.
*/
@GetMapping
public String listCategories(Model model) {
LOG.debug("Request to list all categories");
populateModel(model, new Category(), true);
return "manage/posts/categories";
}
/**
* POST /manage/posts/categories — Save a new category.
*/
@PostMapping
public String createCategory(@Valid @ModelAttribute("category") Category category,
BindingResult bindingResult,
@RequestParam(value = "parentId", required = false) Long parentId,
Model model,
RedirectAttributes redirectAttributes) {
LOG.debug("Request to create Category : {}", category);
if (bindingResult.hasErrors()) {
populateModel(model, category, true);
return "manage/posts/categories";
}
if (parentId != null) {
categoryService.findById(parentId).ifPresent(category::setParent);
}
categoryService.save(category);
redirectAttributes.addFlashAttribute("successMessage", "Category created successfully!");
return "redirect:/manage/posts/categories";
}
/**
* GET /manage/posts/categories/{id}/edit — Show the edit form for a category.
*/
@GetMapping("/{id}/edit")
public String showEditForm(@PathVariable Long id, Model model, RedirectAttributes redirectAttributes) {
LOG.debug("Request to show edit form for Category : {}", id);
Optional<Category> categoryOptional = categoryService.findById(id);
if (categoryOptional.isEmpty()) {
redirectAttributes.addFlashAttribute("errorMessage", "Category not found.");
return "redirect:/manage/posts/categories";
}
populateModel(model, categoryOptional.get(), false);
return "manage/posts/categories";
}
/**
* POST /manage/posts/categories/{id} — Update an existing category.
*/
@PostMapping("/{id}")
public String updateCategory(@PathVariable Long id,
@Valid @ModelAttribute("category") Category category,
BindingResult bindingResult,
@RequestParam(value = "parentId", required = false) Long parentId,
Model model,
RedirectAttributes redirectAttributes) {
LOG.debug("Request to update Category : {}", id);
if (bindingResult.hasErrors()) {
populateModel(model, category, false);
return "manage/posts/categories";
}
category.setId(id);
if (parentId != null && !parentId.equals(id)) {
categoryService.findById(parentId).ifPresent(category::setParent);
} else {
category.setParent(null);
}
categoryService.save(category);
redirectAttributes.addFlashAttribute("successMessage", "Category updated successfully!");
return "redirect:/manage/posts/categories";
}
/**
* POST /manage/posts/categories/{id}/delete — Delete a category.
*/
@PostMapping("/{id}/delete")
public String deleteCategory(@PathVariable Long id, RedirectAttributes redirectAttributes) {
LOG.debug("Request to delete Category : {}", id);
try {
categoryService.delete(id);
redirectAttributes.addFlashAttribute("successMessage", "Category deleted successfully!");
} catch (IllegalStateException e) {
redirectAttributes.addFlashAttribute("errorMessage", e.getMessage());
}
return "redirect:/manage/posts/categories";
}
/**
* Populate common model attributes for the categories view.
*/
private void populateModel(Model model, Category category, boolean isNew) {
model.addAttribute("category", category);
model.addAttribute("isNew", isNew);
model.addAttribute("allCategories", categoryService.findAll());
// Build a map of category ID -> post count for the table
java.util.Map<Long, Long> postCounts = new java.util.HashMap<>();
for (Category c : categoryService.findAll()) {
postCounts.put(c.getId(), categoryService.countPosts(c.getId()));
}
model.addAttribute("postCounts", postCounts);
}
}
@@ -0,0 +1,22 @@
package com.sisvietnamvn.web.controller.manage;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.security.access.prepost.PreAuthorize;
import com.sisvietnamvn.web.security.AuthoritiesConstants;
/**
* Controller for the admin dashboard index page.
* Serves the main "/manage" landing page.
*/
@Controller
@RequestMapping("/manage")
@PreAuthorize("hasAnyAuthority(\"" + AuthoritiesConstants.ADMIN + "\", \"" + AuthoritiesConstants.EDITOR + "\", \"" + AuthoritiesConstants.AUTHOR + "\", \"" + AuthoritiesConstants.CONTRIBUTOR + "\", \"" + AuthoritiesConstants.SUBSCRIBER + "\", \"" + AuthoritiesConstants.USER + "\")")
public class ManageDashboardController {
@GetMapping({"", "/"})
public String index() {
return "manage/index";
}
}
@@ -0,0 +1,18 @@
package com.sisvietnamvn.web.controller.manage;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Controller for the admin login page.
*/
@Controller
@RequestMapping("/manage/login")
public class ManageLoginController {
@GetMapping
public String login() {
return "manage/login";
}
}
@@ -0,0 +1,145 @@
package com.sisvietnamvn.web.controller.manage;
import com.sisvietnamvn.web.domain.Page;
import com.sisvietnamvn.web.domain.PageStatus;
import com.sisvietnamvn.web.hook.HookManager;
import com.sisvietnamvn.web.service.PageService;
import jakarta.validation.Valid;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.security.access.prepost.PreAuthorize;
import com.sisvietnamvn.web.security.AuthoritiesConstants;
/**
* Controller for managing CMS Pages in the admin panel.
* Provides CRUD operations for static pages.
*/
@Controller
@RequestMapping("/manage/pages")
@PreAuthorize("hasAnyAuthority(\"" + AuthoritiesConstants.ADMIN + "\", \"" + AuthoritiesConstants.EDITOR + "\")")
public class ManagePageController {
private static final Logger LOG = LoggerFactory.getLogger(ManagePageController.class);
private final PageService pageService;
private final HookManager hookManager;
public ManagePageController(PageService pageService, HookManager hookManager) {
this.pageService = pageService;
this.hookManager = hookManager;
}
/**
* Triggered before any handler method in this controller.
*/
@ModelAttribute
public void adminInit() {
hookManager.doAction("admin_init");
}
/**
* GET /manage/pages — List all pages.
*/
@GetMapping
public String listPages(Model model) {
LOG.debug("Request to list all pages");
model.addAttribute("pages", pageService.findAll());
return "manage/pages/list";
}
/**
* GET /manage/pages/new — Show the "Add New Page" form.
*/
@GetMapping("/new")
public String showCreateForm(Model model) {
LOG.debug("Request to show create page form");
Page page = new Page();
page.setStatus(PageStatus.DRAFT);
page.setDisplayOrder(0);
model.addAttribute("page", page);
model.addAttribute("statuses", PageStatus.values());
model.addAttribute("pageTypes", com.sisvietnamvn.web.domain.PageType.values());
model.addAttribute("layouts", com.sisvietnamvn.web.domain.PageLayout.values());
model.addAttribute("isNew", true);
return "manage/pages/form";
}
/**
* POST /manage/pages — Save a new page.
*/
@PostMapping
public String createPage(@Valid @ModelAttribute("page") Page page,
BindingResult bindingResult,
Model model,
RedirectAttributes redirectAttributes) {
LOG.debug("Request to create Page : {}", page);
if (bindingResult.hasErrors()) {
model.addAttribute("statuses", PageStatus.values());
model.addAttribute("pageTypes", com.sisvietnamvn.web.domain.PageType.values());
model.addAttribute("layouts", com.sisvietnamvn.web.domain.PageLayout.values());
model.addAttribute("isNew", true);
return "manage/pages/form";
}
pageService.save(page);
redirectAttributes.addFlashAttribute("successMessage", "Page created successfully!");
return "redirect:/manage/pages";
}
/**
* GET /manage/pages/{id}/edit — Show the "Edit Page" form.
*/
@GetMapping("/{id}/edit")
public String showEditForm(@PathVariable Long id, Model model, RedirectAttributes redirectAttributes) {
LOG.debug("Request to show edit form for Page : {}", id);
Optional<Page> pageOptional = pageService.findById(id);
if (pageOptional.isEmpty()) {
redirectAttributes.addFlashAttribute("errorMessage", "Page not found.");
return "redirect:/manage/pages";
}
model.addAttribute("page", pageOptional.get());
model.addAttribute("statuses", PageStatus.values());
model.addAttribute("pageTypes", com.sisvietnamvn.web.domain.PageType.values());
model.addAttribute("layouts", com.sisvietnamvn.web.domain.PageLayout.values());
model.addAttribute("isNew", false);
return "manage/pages/form";
}
/**
* POST /manage/pages/{id} — Update an existing page.
*/
@PostMapping("/{id}")
public String updatePage(@PathVariable Long id,
@Valid @ModelAttribute("page") Page page,
BindingResult bindingResult,
Model model,
RedirectAttributes redirectAttributes) {
LOG.debug("Request to update Page : {}", id);
if (bindingResult.hasErrors()) {
model.addAttribute("statuses", PageStatus.values());
model.addAttribute("pageTypes", com.sisvietnamvn.web.domain.PageType.values());
model.addAttribute("layouts", com.sisvietnamvn.web.domain.PageLayout.values());
model.addAttribute("isNew", false);
return "manage/pages/form";
}
page.setId(id);
pageService.save(page);
redirectAttributes.addFlashAttribute("successMessage", "Page updated successfully!");
return "redirect:/manage/pages";
}
/**
* POST /manage/pages/{id}/delete — Delete a page.
*/
@PostMapping("/{id}/delete")
public String deletePage(@PathVariable Long id, RedirectAttributes redirectAttributes) {
LOG.debug("Request to delete Page : {}", id);
pageService.delete(id);
redirectAttributes.addFlashAttribute("successMessage", "Page deleted successfully!");
return "redirect:/manage/pages";
}
}
@@ -0,0 +1,185 @@
package com.sisvietnamvn.web.controller.manage;
import com.sisvietnamvn.web.domain.PageStatus;
import com.sisvietnamvn.web.domain.Post;
import com.sisvietnamvn.web.domain.PostLayout;
import com.sisvietnamvn.web.domain.Tag;
import com.sisvietnamvn.web.hook.HookManager;
import com.sisvietnamvn.web.service.CategoryService;
import com.sisvietnamvn.web.service.PostService;
import com.sisvietnamvn.web.service.TagService;
import jakarta.validation.Valid;
import java.util.Optional;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.security.access.prepost.PreAuthorize;
import com.sisvietnamvn.web.security.AuthoritiesConstants;
/**
* Controller for managing blog Posts in the admin panel.
* Provides CRUD operations with category and tag management.
*/
@Controller
@RequestMapping("/manage/posts")
@PreAuthorize("hasAnyAuthority(\"" + AuthoritiesConstants.ADMIN + "\", \"" + AuthoritiesConstants.EDITOR + "\", \"" + AuthoritiesConstants.AUTHOR + "\", \"" + AuthoritiesConstants.CONTRIBUTOR + "\")")
public class ManagePostController {
private static final Logger LOG = LoggerFactory.getLogger(ManagePostController.class);
private final PostService postService;
private final CategoryService categoryService;
private final TagService tagService;
private final HookManager hookManager;
public ManagePostController(PostService postService,
CategoryService categoryService,
TagService tagService,
HookManager hookManager) {
this.postService = postService;
this.categoryService = categoryService;
this.tagService = tagService;
this.hookManager = hookManager;
}
@ModelAttribute
public void adminInit() {
hookManager.doAction("admin_init");
}
/**
* GET /manage/posts — List all posts with optional filters.
*/
@GetMapping
public String listPosts(@RequestParam(value = "categoryId", required = false) Long categoryId,
@RequestParam(value = "status", required = false) String statusStr,
Model model) {
LOG.debug("Request to list all posts (categoryId={}, status={})", categoryId, statusStr);
PageStatus status = null;
if (statusStr != null && !statusStr.isBlank()) {
try {
status = PageStatus.valueOf(statusStr);
} catch (IllegalArgumentException e) {
LOG.warn("Invalid status filter: {}", statusStr);
}
}
model.addAttribute("posts", postService.findFiltered(categoryId, status));
model.addAttribute("categories", categoryService.findAll());
model.addAttribute("statuses", PageStatus.values());
model.addAttribute("selectedCategoryId", categoryId);
model.addAttribute("selectedStatus", statusStr);
return "manage/posts/list";
}
/**
* GET /manage/posts/new — Show the "Add New Post" form.
*/
@GetMapping("/new")
public String showCreateForm(Model model) {
LOG.debug("Request to show create post form");
Post post = new Post();
post.setStatus(PageStatus.DRAFT);
populateFormModel(model, post, "", true);
return "manage/posts/form";
}
/**
* POST /manage/posts/create — Save a new post.
*/
@PostMapping("/create")
public String createPost(@Valid @ModelAttribute("post") Post post,
BindingResult bindingResult,
@RequestParam(value = "categoryId", required = false) Long categoryId,
@RequestParam(value = "tagNames", required = false, defaultValue = "") String tagNames,
Model model,
RedirectAttributes redirectAttributes) {
LOG.debug("Request to create Post : {}", post);
if (bindingResult.hasErrors()) {
populateFormModel(model, post, tagNames, true);
return "manage/posts/form";
}
if (categoryId != null) {
categoryService.findById(categoryId).ifPresent(post::setCategory);
}
postService.saveWithTags(post, tagNames);
redirectAttributes.addFlashAttribute("successMessage", "Post created successfully!");
return "redirect:/manage/posts";
}
/**
* GET /manage/posts/{id}/edit — Show the "Edit Post" form.
*/
@GetMapping("/{id}/edit")
public String showEditForm(@PathVariable Long id, Model model, RedirectAttributes redirectAttributes) {
LOG.debug("Request to show edit form for Post : {}", id);
Optional<Post> postOptional = postService.findById(id);
if (postOptional.isEmpty()) {
redirectAttributes.addFlashAttribute("errorMessage", "Post not found.");
return "redirect:/manage/posts";
}
Post post = postOptional.get();
String tagNames = post.getTags().stream()
.map(Tag::getName)
.collect(Collectors.joining(", "));
populateFormModel(model, post, tagNames, false);
return "manage/posts/form";
}
/**
* POST /manage/posts/{id} — Update an existing post.
*/
@PostMapping("/{id}")
public String updatePost(@PathVariable Long id,
@Valid @ModelAttribute("post") Post post,
BindingResult bindingResult,
@RequestParam(value = "categoryId", required = false) Long categoryId,
@RequestParam(value = "tagNames", required = false, defaultValue = "") String tagNames,
Model model,
RedirectAttributes redirectAttributes) {
LOG.debug("Request to update Post : {}", id);
if (bindingResult.hasErrors()) {
populateFormModel(model, post, tagNames, false);
return "manage/posts/form";
}
post.setId(id);
if (categoryId != null) {
categoryService.findById(categoryId).ifPresent(post::setCategory);
} else {
post.setCategory(null);
}
postService.saveWithTags(post, tagNames);
redirectAttributes.addFlashAttribute("successMessage", "Post updated successfully!");
return "redirect:/manage/posts";
}
/**
* POST /manage/posts/{id}/delete — Delete a post.
*/
@PostMapping("/{id}/delete")
public String deletePost(@PathVariable Long id, RedirectAttributes redirectAttributes) {
LOG.debug("Request to delete Post : {}", id);
postService.delete(id);
redirectAttributes.addFlashAttribute("successMessage", "Post deleted successfully!");
return "redirect:/manage/posts";
}
/**
* Populate common model attributes for the post form.
*/
private void populateFormModel(Model model, Post post, String tagNames, boolean isNew) {
model.addAttribute("post", post);
model.addAttribute("tagNames", tagNames);
model.addAttribute("isNew", isNew);
model.addAttribute("categories", categoryService.findAll());
model.addAttribute("statuses", PageStatus.values());
model.addAttribute("layouts", PostLayout.values());
model.addAttribute("allTags", tagService.findAll());
}
}
@@ -0,0 +1,111 @@
package com.sisvietnamvn.web.controller.manage;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.sisvietnamvn.web.domain.User;
import com.sisvietnamvn.web.hook.HookManager;
import com.sisvietnamvn.web.service.UserService;
import com.sisvietnamvn.web.service.dto.AdminUserDTO;
import jakarta.validation.Valid;
/**
* Controller for managing the logged-in User's Profile in the admin panel.
*/
@Controller
@RequestMapping("/manage/profile")
@PreAuthorize("isAuthenticated()")
public class ManageProfileController {
private static final Logger LOG = LoggerFactory.getLogger(ManageProfileController.class);
private final UserService userService;
private final HookManager hookManager;
public ManageProfileController(UserService userService, HookManager hookManager) {
this.userService = userService;
this.hookManager = hookManager;
}
@ModelAttribute
public void adminInit() {
hookManager.doAction("admin_init");
}
/**
* GET /manage/profile : view the current user's profile form.
*/
@GetMapping
public String showProfileForm(Model model, RedirectAttributes redirectAttributes) {
LOG.debug("Request to show profile form");
Optional<User> userOptional = userService.getUserWithAuthorities();
if (userOptional.isEmpty()) {
redirectAttributes.addFlashAttribute("errorMessage", "User session not found.");
return "redirect:/manage";
}
model.addAttribute("userDTO", new AdminUserDTO(userOptional.get()));
return "manage/users/profile";
}
/**
* POST /manage/profile/update : update basic information for the current user.
*/
@PostMapping("/update")
public String updateProfile(@Valid @ModelAttribute("userDTO") AdminUserDTO userDTO,
BindingResult bindingResult,
Model model,
RedirectAttributes redirectAttributes) {
LOG.debug("Request to update current user Profile : {}", userDTO);
if (bindingResult.hasErrors()) {
return "manage/users/profile";
}
try {
userService.updateUser(userDTO.getFirstName(), userDTO.getLastName(), userDTO.getEmail(), userDTO.getLangKey(), userDTO.getImageUrl());
redirectAttributes.addFlashAttribute("successMessage", "Profile updated successfully!");
} catch (Exception e) {
redirectAttributes.addFlashAttribute("errorMessage", "Error updating profile: " + e.getMessage());
}
return "redirect:/manage/profile";
}
/**
* POST /manage/profile/password : change the current user's password.
*/
@PostMapping("/password")
public String changePassword(@RequestParam("currentPassword") String currentPassword,
@RequestParam("newPassword") String newPassword,
@RequestParam("confirmPassword") String confirmPassword,
RedirectAttributes redirectAttributes) {
LOG.debug("Request to change password for current user");
if (!newPassword.equals(confirmPassword)) {
redirectAttributes.addFlashAttribute("errorPasswordMessage", "New password and confirm password do not match!");
return "redirect:/manage/profile";
}
try {
userService.changePassword(currentPassword, newPassword);
redirectAttributes.addFlashAttribute("successPasswordMessage", "Password changed successfully!");
} catch (Exception e) {
redirectAttributes.addFlashAttribute("errorPasswordMessage", "Incorrect current password or error changing password.");
}
return "redirect:/manage/profile";
}
}
@@ -0,0 +1,130 @@
package com.sisvietnamvn.web.controller.manage;
import com.sisvietnamvn.web.domain.Tag;
import com.sisvietnamvn.web.hook.HookManager;
import com.sisvietnamvn.web.service.TagService;
import jakarta.validation.Valid;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.security.access.prepost.PreAuthorize;
import com.sisvietnamvn.web.security.AuthoritiesConstants;
/**
* Controller for managing post Tags in the admin panel.
* Uses a WordPress-style split layout: form on the left, table on the right.
*/
@Controller
@RequestMapping("/manage/posts/tags")
@PreAuthorize("hasAnyAuthority(\"" + AuthoritiesConstants.ADMIN + "\", \"" + AuthoritiesConstants.EDITOR + "\", \"" + AuthoritiesConstants.AUTHOR + "\")")
public class ManageTagController {
private static final Logger LOG = LoggerFactory.getLogger(ManageTagController.class);
private final TagService tagService;
private final HookManager hookManager;
public ManageTagController(TagService tagService, HookManager hookManager) {
this.tagService = tagService;
this.hookManager = hookManager;
}
@ModelAttribute
public void adminInit() {
hookManager.doAction("admin_init");
}
/**
* GET /manage/posts/tags — List all tags with inline create form.
*/
@GetMapping
public String listTags(Model model) {
LOG.debug("Request to list all tags");
populateModel(model, new Tag(), true);
return "manage/posts/tags";
}
/**
* POST /manage/posts/tags — Save a new tag.
*/
@PostMapping
public String createTag(@Valid @ModelAttribute("tag") Tag tag,
BindingResult bindingResult,
Model model,
RedirectAttributes redirectAttributes) {
LOG.debug("Request to create Tag : {}", tag);
if (bindingResult.hasErrors()) {
populateModel(model, tag, true);
return "manage/posts/tags";
}
tagService.save(tag);
redirectAttributes.addFlashAttribute("successMessage", "Tag created successfully!");
return "redirect:/manage/posts/tags";
}
/**
* GET /manage/posts/tags/{id}/edit — Show the edit form for a tag.
*/
@GetMapping("/{id}/edit")
public String showEditForm(@PathVariable Long id, Model model, RedirectAttributes redirectAttributes) {
LOG.debug("Request to show edit form for Tag : {}", id);
Optional<Tag> tagOptional = tagService.findById(id);
if (tagOptional.isEmpty()) {
redirectAttributes.addFlashAttribute("errorMessage", "Tag not found.");
return "redirect:/manage/posts/tags";
}
populateModel(model, tagOptional.get(), false);
return "manage/posts/tags";
}
/**
* POST /manage/posts/tags/{id} — Update an existing tag.
*/
@PostMapping("/{id}")
public String updateTag(@PathVariable Long id,
@Valid @ModelAttribute("tag") Tag tag,
BindingResult bindingResult,
Model model,
RedirectAttributes redirectAttributes) {
LOG.debug("Request to update Tag : {}", id);
if (bindingResult.hasErrors()) {
populateModel(model, tag, false);
return "manage/posts/tags";
}
tag.setId(id);
tagService.save(tag);
redirectAttributes.addFlashAttribute("successMessage", "Tag updated successfully!");
return "redirect:/manage/posts/tags";
}
/**
* POST /manage/posts/tags/{id}/delete — Delete a tag.
*/
@PostMapping("/{id}/delete")
public String deleteTag(@PathVariable Long id, RedirectAttributes redirectAttributes) {
LOG.debug("Request to delete Tag : {}", id);
tagService.delete(id);
redirectAttributes.addFlashAttribute("successMessage", "Tag deleted successfully!");
return "redirect:/manage/posts/tags";
}
/**
* Populate common model attributes for the tags view.
*/
private void populateModel(Model model, Tag tag, boolean isNew) {
model.addAttribute("tag", tag);
model.addAttribute("isNew", isNew);
model.addAttribute("allTags", tagService.findAll());
// Build a map of tag ID -> post count for the table
java.util.Map<Long, Long> postCounts = new java.util.HashMap<>();
for (Tag t : tagService.findAll()) {
postCounts.put(t.getId(), tagService.countPosts(t.getId()));
}
model.addAttribute("postCounts", postCounts);
}
}
@@ -0,0 +1,179 @@
package com.sisvietnamvn.web.controller.manage;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.sisvietnamvn.web.config.Constants;
import com.sisvietnamvn.web.domain.User;
import com.sisvietnamvn.web.hook.HookManager;
import com.sisvietnamvn.web.security.AuthoritiesConstants;
import com.sisvietnamvn.web.service.UserService;
import com.sisvietnamvn.web.service.dto.AdminUserDTO;
import jakarta.validation.Valid;
/**
* Controller for managing Users in the admin panel.
*/
@Controller
@RequestMapping("/manage/users")
@PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")")
public class ManageUserController {
private static final Logger LOG = LoggerFactory.getLogger(ManageUserController.class);
private final UserService userService;
private final HookManager hookManager;
public ManageUserController(UserService userService, HookManager hookManager) {
this.userService = userService;
this.hookManager = hookManager;
}
@ModelAttribute
public void adminInit() {
hookManager.doAction("admin_init");
}
/**
* GET /manage/users : get all users.
*/
@GetMapping
public String listUsers(@PageableDefault(size = 20, sort = "login") Pageable pageable, Model model) {
LOG.debug("REST request to get all User for an admin");
Page<AdminUserDTO> page = userService.getAllManagedUsers(pageable);
model.addAttribute("users", page.getContent());
model.addAttribute("page", page);
return "manage/users/list";
}
/**
* GET /manage/users/new : show create form.
*/
@GetMapping("/new")
public String showCreateForm(Model model) {
LOG.debug("Request to show create user form");
AdminUserDTO userDTO = new AdminUserDTO();
userDTO.setLangKey(Constants.DEFAULT_LANGUAGE);
model.addAttribute("userDTO", userDTO);
model.addAttribute("authorities", userService.getAuthorities());
model.addAttribute("isNew", true);
return "manage/users/form";
}
/**
* POST /manage/users : create a new user.
*/
@PostMapping
public String createUser(@Valid @ModelAttribute("userDTO") AdminUserDTO userDTO,
BindingResult bindingResult,
Model model,
RedirectAttributes redirectAttributes) {
LOG.debug("Request to create User : {}", userDTO);
if (bindingResult.hasErrors()) {
model.addAttribute("authorities", userService.getAuthorities());
model.addAttribute("isNew", true);
return "manage/users/form";
}
if (userDTO.getId() != null) {
redirectAttributes.addFlashAttribute("errorMessage", "A new user cannot already have an ID");
return "redirect:/manage/users/new";
}
// Ensure email login constraint (optional depending on jhipster config, but good practice)
if (userDTO.getLogin() != null) {
userDTO.setLogin(userDTO.getLogin().toLowerCase());
}
try {
User newUser = userService.createUser(userDTO);
redirectAttributes.addFlashAttribute("successMessage", "User created successfully! Login: " + newUser.getLogin());
} catch (Exception e) {
redirectAttributes.addFlashAttribute("errorMessage", "Error creating user: " + e.getMessage());
return "redirect:/manage/users/new";
}
return "redirect:/manage/users";
}
/**
* GET /manage/users/{login}/edit : show edit form.
*/
@GetMapping("/{login}/edit")
public String showEditForm(@PathVariable String login, Model model, RedirectAttributes redirectAttributes) {
LOG.debug("Request to show edit form for User : {}", login);
Optional<User> userOptional = userService.getUserWithAuthoritiesByLogin(login);
if (userOptional.isEmpty()) {
redirectAttributes.addFlashAttribute("errorMessage", "User not found.");
return "redirect:/manage/users";
}
model.addAttribute("userDTO", new AdminUserDTO(userOptional.get()));
model.addAttribute("authorities", userService.getAuthorities());
model.addAttribute("isNew", false);
return "manage/users/form";
}
/**
* POST /manage/users/{login} : update a user.
*/
@PostMapping("/{login}")
public String updateUser(@PathVariable String login,
@Valid @ModelAttribute("userDTO") AdminUserDTO userDTO,
BindingResult bindingResult,
Model model,
RedirectAttributes redirectAttributes) {
LOG.debug("Request to update User : {}", userDTO);
if (bindingResult.hasErrors()) {
model.addAttribute("authorities", userService.getAuthorities());
model.addAttribute("isNew", false);
return "manage/users/form";
}
Optional<User> existingUser = userService.getUserWithAuthoritiesByLogin(login);
if (existingUser.isEmpty()) {
redirectAttributes.addFlashAttribute("errorMessage", "User not found.");
return "redirect:/manage/users";
}
userDTO.setId(existingUser.get().getId());
try {
userService.updateUser(userDTO);
redirectAttributes.addFlashAttribute("successMessage", "User updated successfully!");
} catch (Exception e) {
redirectAttributes.addFlashAttribute("errorMessage", "Error updating user: " + e.getMessage());
return "redirect:/manage/users/" + login + "/edit";
}
return "redirect:/manage/users";
}
/**
* POST /manage/users/{login}/delete : delete a user.
*/
@PostMapping("/{login}/delete")
public String deleteUser(@PathVariable String login, RedirectAttributes redirectAttributes) {
LOG.debug("Request to delete User : {}", login);
userService.deleteUser(login);
redirectAttributes.addFlashAttribute("successMessage", "User deleted successfully!");
return "redirect:/manage/users";
}
}
@@ -0,0 +1,82 @@
package com.sisvietnamvn.web.controller.manage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.springframework.security.access.prepost.PreAuthorize;
import com.sisvietnamvn.web.security.AuthoritiesConstants;
@RestController
@RequestMapping("/api/manage/media")
@PreAuthorize("hasAnyAuthority(\"" + AuthoritiesConstants.ADMIN + "\", \"" + AuthoritiesConstants.EDITOR + "\", \"" + AuthoritiesConstants.AUTHOR + "\", \"" + AuthoritiesConstants.CONTRIBUTOR + "\")")
public class MediaController {
private final Logger log = LoggerFactory.getLogger(MediaController.class);
// Save files in an 'uploads' directory at the project root
private final String UPLOAD_DIR = "uploads/";
@PostMapping("/upload")
public ResponseEntity<Map<String, Object>> uploadFile(@RequestParam("file") MultipartFile file) {
log.debug("REST request to upload file: {}", file.getOriginalFilename());
Map<String, Object> response = new HashMap<>();
if (file == null || file.isEmpty()) {
response.put("success", 0);
return ResponseEntity.badRequest().body(response);
}
try {
// Ensure the upload directory exists
Path uploadPath = Paths.get(UPLOAD_DIR);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
// Generate a unique file name to avoid conflicts
String originalFilename = StringUtils.cleanPath(file.getOriginalFilename());
String extension = "";
int dotIndex = originalFilename.lastIndexOf('.');
if (dotIndex > 0) {
extension = originalFilename.substring(dotIndex);
}
String newFilename = UUID.randomUUID().toString() + extension;
// Save the file
Path filePath = uploadPath.resolve(newFilename);
Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
// Construct the Editor.js expected response
Map<String, Object> fileData = new HashMap<>();
fileData.put("url", "/uploads/" + newFilename);
fileData.put("name", originalFilename);
fileData.put("size", file.getSize());
if (extension.startsWith(".")) {
fileData.put("extension", extension.substring(1));
}
response.put("success", 1);
response.put("file", fileData);
return ResponseEntity.ok(response);
} catch (IOException e) {
log.error("Failed to store uploaded file", e);
response.put("success", 0);
return ResponseEntity.internalServerError().body(response);
}
}
}
@@ -0,0 +1,59 @@
package com.sisvietnamvn.web.controller.manage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.springframework.security.access.prepost.PreAuthorize;
import com.sisvietnamvn.web.security.AuthoritiesConstants;
/**
* REST controller for fetching predefined HTML snippets to be previewed in Editor.js.
*/
@RestController
@RequestMapping("/api/manage/snippets")
@PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")")
public class SnippetController {
private static final Logger LOG = LoggerFactory.getLogger(SnippetController.class);
private final ResourceLoader resourceLoader;
public SnippetController(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
/**
* GET /api/manage/snippets/{id} : get the raw HTML of a snippet
*/
@GetMapping(value = "/{id}", produces = MediaType.TEXT_HTML_VALUE)
public ResponseEntity<String> getSnippet(@PathVariable("id") String id) {
LOG.debug("REST request to get Snippet : {}", id);
// Prevent path traversal attacks
if (id == null || id.contains("..") || id.contains("/")) {
return ResponseEntity.badRequest().body("Invalid Snippet ID");
}
String path = "classpath:templates/snippets/" + id + ".html";
Resource resource = resourceLoader.getResource(path);
if (!resource.exists() || !resource.isReadable()) {
LOG.warn("Snippet not found at path: {}", path);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("<div style=\"padding: 20px; border: 1px dashed red; color: red;\">Snippet ID <strong>" + id + "</strong> not found.</div>");
}
try {
String content = new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
return ResponseEntity.ok().body(content);
} catch (IOException e) {
LOG.error("Error reading snippet file: {}", path, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error reading snippet file");
}
}
}
@@ -74,4 +74,12 @@ public abstract class AbstractAuditingEntity<T> implements Serializable {
public void setLastModifiedDate(Instant lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
public java.util.Date getCreatedDateAsDate() {
return createdDate != null ? java.util.Date.from(createdDate) : null;
}
public java.util.Date getLastModifiedDateAsDate() {
return lastModifiedDate != null ? java.util.Date.from(lastModifiedDate) : null;
}
}
@@ -0,0 +1,110 @@
package com.sisvietnamvn.web.domain;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.io.Serial;
import java.util.Objects;
/**
* A Category entity for classifying blog posts.
* Supports hierarchical structure via self-referencing parent relationship.
* Maps to the "sis_category" database table.
*/
@Entity
@Table(name = "sis_category")
public class Category extends AbstractAuditingEntity<Long> {
@Serial
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
@SequenceGenerator(name = "sequenceGenerator")
private Long id;
@NotNull
@Size(max = 255)
@Column(name = "name", length = 255, nullable = false)
private String name;
@NotNull
@Size(max = 255)
@Column(name = "slug", length = 255, nullable = false, unique = true)
private String slug;
@Size(max = 1000)
@Column(name = "description", length = 1000)
private String description;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private Category parent;
// --- Getters and Setters ---
@Override
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSlug() {
return slug;
}
public void setSlug(String slug) {
this.slug = slug;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Category getParent() {
return parent;
}
public void setParent(Category parent) {
this.parent = parent;
}
// --- equals, hashCode, toString ---
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Category category)) return false;
return id != null && id.equals(category.id);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public String toString() {
return "Category{" +
"id=" + getId() +
", name='" + getName() + "'" +
", slug='" + getSlug() + "'" +
", description='" + getDescription() + "'" +
"}";
}
}
@@ -0,0 +1,163 @@
package com.sisvietnamvn.web.domain;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.io.Serial;
import java.util.Objects;
/**
* A CMS Page entity representing a static page (e.g. About Us, Contact, Working Hours).
* Maps to the "sis_page" database table.
*/
@Entity
@Table(name = "sis_page")
public class Page extends AbstractAuditingEntity<Long> {
@Serial
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
@SequenceGenerator(name = "sequenceGenerator")
private Long id;
@NotNull
@Size(max = 255)
@Column(name = "title", length = 255, nullable = false)
private String title;
@NotNull
@Size(max = 255)
@Column(name = "slug", length = 255, nullable = false, unique = true)
private String slug;
@Lob
@Column(name = "content")
private String content;
@Size(max = 500)
@Column(name = "meta_description", length = 500)
private String metaDescription;
@NotNull
@Enumerated(EnumType.STRING)
@Column(name = "status", length = 20, nullable = false)
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")
private Integer displayOrder = 0;
@NotNull
@Enumerated(EnumType.STRING)
@Column(name = "layout", length = 20, nullable = false)
private PageLayout layout = PageLayout.STANDARD;
// --- Getters and Setters ---
@Override
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getSlug() {
return slug;
}
public void setSlug(String slug) {
this.slug = slug;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getMetaDescription() {
return metaDescription;
}
public void setMetaDescription(String metaDescription) {
this.metaDescription = metaDescription;
}
public PageStatus getStatus() {
return status;
}
public void setStatus(PageStatus status) {
this.status = status;
}
public PageType getPageType() {
return pageType;
}
public void setPageType(PageType pageType) {
this.pageType = pageType;
}
public Integer getDisplayOrder() {
return displayOrder;
}
public void setDisplayOrder(Integer displayOrder) {
this.displayOrder = displayOrder;
}
public PageLayout getLayout() {
return layout;
}
public void setLayout(PageLayout layout) {
this.layout = layout;
}
// --- equals, hashCode, toString ---
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Page page)) return false;
return id != null && id.equals(page.id);
}
@Override
public int hashCode() {
// Use a constant value to ensure consistency across entity state transitions.
// See: https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
return getClass().hashCode();
}
@Override
public String toString() {
return "Page{" +
"id=" + getId() +
", title='" + getTitle() + "'" +
", slug='" + getSlug() + "'" +
", pageType='" + getPageType() + "'" +
", status='" + getStatus() + "'" +
", displayOrder=" + getDisplayOrder() +
"}";
}
}
@@ -0,0 +1,10 @@
package com.sisvietnamvn.web.domain;
/**
* Enumeration for Page Layout options.
*/
public enum PageLayout {
STANDARD,
SIDEBAR,
FULL_WIDTH
}
@@ -0,0 +1,10 @@
package com.sisvietnamvn.web.domain;
/**
* Enumeration representing the publication status of a CMS page.
*/
public enum PageStatus {
DRAFT,
PUBLISHED,
ARCHIVED
}
@@ -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
}
@@ -0,0 +1,189 @@
package com.sisvietnamvn.web.domain;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.io.Serial;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
* A Post entity representing a blog/news article.
* Maps to the "sis_post" database table.
*/
@Entity
@Table(name = "sis_post")
public class Post extends AbstractAuditingEntity<Long> {
@Serial
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
@SequenceGenerator(name = "sequenceGenerator")
private Long id;
@NotNull
@Size(max = 255)
@Column(name = "title", length = 255, nullable = false)
private String title;
@NotNull
@Size(max = 255)
@Column(name = "slug", length = 255, nullable = false, unique = true)
private String slug;
@Lob
@Column(name = "content")
private String content;
@Size(max = 1000)
@Column(name = "excerpt", length = 1000)
private String excerpt;
@Size(max = 500)
@Column(name = "featured_image", length = 500)
private String featuredImage;
@NotNull
@Enumerated(EnumType.STRING)
@Column(name = "status", length = 20, nullable = false)
private PageStatus status = PageStatus.DRAFT;
@Size(max = 500)
@Column(name = "meta_description", length = 500)
private String metaDescription;
@NotNull
@Enumerated(EnumType.STRING)
@Column(name = "layout", length = 20, nullable = false)
private PostLayout layout = PostLayout.STANDARD;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
private Category category;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "sis_post_tag",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "tag_id")
)
private Set<Tag> tags = new HashSet<>();
// --- Getters and Setters ---
@Override
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getSlug() {
return slug;
}
public void setSlug(String slug) {
this.slug = slug;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getExcerpt() {
return excerpt;
}
public void setExcerpt(String excerpt) {
this.excerpt = excerpt;
}
public String getFeaturedImage() {
return featuredImage;
}
public void setFeaturedImage(String featuredImage) {
this.featuredImage = featuredImage;
}
public PageStatus getStatus() {
return status;
}
public void setStatus(PageStatus status) {
this.status = status;
}
public String getMetaDescription() {
return metaDescription;
}
public void setMetaDescription(String metaDescription) {
this.metaDescription = metaDescription;
}
public PostLayout getLayout() {
return layout;
}
public void setLayout(PostLayout layout) {
this.layout = layout;
}
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public Set<Tag> getTags() {
return tags;
}
public void setTags(Set<Tag> tags) {
this.tags = tags;
}
// --- equals, hashCode, toString ---
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Post post)) return false;
return id != null && id.equals(post.id);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public String toString() {
return "Post{" +
"id=" + getId() +
", title='" + getTitle() + "'" +
", slug='" + getSlug() + "'" +
", status='" + getStatus() + "'" +
"}";
}
}
@@ -0,0 +1,10 @@
package com.sisvietnamvn.web.domain;
/**
* Enumeration for Post Layout options.
*/
public enum PostLayout {
STANDARD,
SIDEBAR,
FULL_WIDTH
}
@@ -0,0 +1,84 @@
package com.sisvietnamvn.web.domain;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.io.Serial;
import java.util.Objects;
/**
* A Tag entity for labeling blog posts with keywords.
* Maps to the "sis_tag" database table.
*/
@Entity
@Table(name = "sis_tag")
public class Tag extends AbstractAuditingEntity<Long> {
@Serial
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
@SequenceGenerator(name = "sequenceGenerator")
private Long id;
@NotNull
@Size(max = 255)
@Column(name = "name", length = 255, nullable = false, unique = true)
private String name;
@NotNull
@Size(max = 255)
@Column(name = "slug", length = 255, nullable = false, unique = true)
private String slug;
// --- Getters and Setters ---
@Override
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSlug() {
return slug;
}
public void setSlug(String slug) {
this.slug = slug;
}
// --- equals, hashCode, toString ---
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Tag tag)) return false;
return id != null && id.equals(tag.id);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public String toString() {
return "Tag{" +
"id=" + getId() +
", name='" + getName() + "'" +
", slug='" + getSlug() + "'" +
"}";
}
}
@@ -43,8 +43,8 @@ public class User extends AbstractAuditingEntity<Long> implements Serializable {
@JsonIgnore
@NotNull
@Size(min = 60, max = 60)
@Column(name = "password_hash", length = 60, nullable = false)
@Size(min = 60, max = 100)
@Column(name = "password_hash", length = 100, nullable = false)
private String password;
@Size(max = 50)
@@ -85,6 +85,12 @@ public class User extends AbstractAuditingEntity<Long> implements Serializable {
@Column(name = "reset_date")
private Instant resetDate = null;
@Column(name = "using_2fa", nullable = false)
private boolean using2FA = false;
@Column(name = "totp_secret")
private String totpSecret;
@JsonIgnore
@ManyToMany
@JoinTable(
@@ -185,6 +191,22 @@ public class User extends AbstractAuditingEntity<Long> implements Serializable {
this.resetDate = resetDate;
}
public boolean isUsing2FA() {
return using2FA;
}
public void setUsing2FA(boolean using2FA) {
this.using2FA = using2FA;
}
public String getTotpSecret() {
return totpSecret;
}
public void setTotpSecret(String totpSecret) {
this.totpSecret = totpSecret;
}
public String getLangKey() {
return langKey;
}
@@ -0,0 +1,111 @@
package com.sisvietnamvn.web.hook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A central manager for WordPress-style Actions and Filters.
*/
@Service
public class HookManager {
private static final Logger LOG = LoggerFactory.getLogger(HookManager.class);
private final Map<String, List<ActionRegistration>> actions = new HashMap<>();
private final Map<String, List<FilterRegistration>> filters = new HashMap<>();
// --- Actions ---
public void addAction(String hookName, ActionCallback callback, int priority) {
actions.computeIfAbsent(hookName, k -> new ArrayList<>())
.add(new ActionRegistration(callback, priority));
actions.get(hookName).sort(Comparator.comparingInt(a -> a.priority));
LOG.debug("Added action to hook '{}' with priority {}", hookName, priority);
}
public void addAction(String hookName, ActionCallback callback) {
addAction(hookName, callback, 10);
}
public void doAction(String hookName, Object... args) {
if (actions.containsKey(hookName)) {
LOG.debug("Executing action hook '{}'", hookName);
for (ActionRegistration reg : actions.get(hookName)) {
try {
reg.callback.execute(args);
} catch (Exception e) {
LOG.error("Error executing action hook '{}'", hookName, e);
}
}
}
}
// --- Filters ---
public <T> void addFilter(String hookName, FilterCallback<T> callback, int priority) {
filters.computeIfAbsent(hookName, k -> new ArrayList<>())
.add(new FilterRegistration(callback, priority));
filters.get(hookName).sort(Comparator.comparingInt(a -> a.priority));
LOG.debug("Added filter to hook '{}' with priority {}", hookName, priority);
}
public <T> void addFilter(String hookName, FilterCallback<T> callback) {
addFilter(hookName, callback, 10);
}
@SuppressWarnings("unchecked")
public <T> T applyFilters(String hookName, T value, Object... args) {
if (filters.containsKey(hookName)) {
LOG.debug("Applying filter hook '{}'", hookName);
for (FilterRegistration reg : filters.get(hookName)) {
try {
value = (T) ((FilterCallback<Object>) reg.callback).filter(value, args);
} catch (Exception e) {
LOG.error("Error applying filter hook '{}'", hookName, e);
}
}
}
return value;
}
// --- Interfaces ---
@FunctionalInterface
public interface ActionCallback {
void execute(Object... args);
}
@FunctionalInterface
public interface FilterCallback<T> {
T filter(T value, Object... args);
}
// --- Internal Registration Records ---
private static class ActionRegistration {
final ActionCallback callback;
final int priority;
ActionRegistration(ActionCallback callback, int priority) {
this.callback = callback;
this.priority = priority;
}
}
private static class FilterRegistration {
final FilterCallback<?> callback;
final int priority;
FilterRegistration(FilterCallback<?> callback, int priority) {
this.callback = callback;
this.priority = priority;
}
}
}

Some files were not shown because too many files have changed in this diff Show More