@fui-org/mlis-cli 0.0.0-stage → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,135 @@
1
+ ---
2
+ name: mlis
3
+ description: Dùng cho mọi việc thiết kế giao diện website MLIS (lhu.edu.vn và các site MLIS khác) qua CLI `mlis` — xem, tải về, sửa và đẩy lên các trang .ascx/.css/.js cùng hình, font trong thư mục ViewPage, dùng các control <MLIS:NewsList>, <MLIS:CatMenu>, <MLIS:LoadPage>…, đọc/sửa bài viết (tin tức) cấp nội dung động cho trang, và upload tài liệu qua CKFinder. Kích hoạt khi người dùng nói đến ViewPage, trang ascx, giao diện MLIS, PageStyle, HeaderOfSite, hoặc gọi lệnh mlis.
4
+ ---
5
+
6
+ # MLIS ViewPage với `mlis`
7
+
8
+ `mlis` là CLI làm việc với API `api/viewpage/*` của website MLIS (ASP.NET). Mọi trang giao diện nằm trong thư mục `ViewPage/` trên server:
9
+ - **.ascx, .css, .js** — sửa như văn bản, có diff.
10
+ - **Hình, font, media, pdf** (png, jpg, gif, webp, svg, ico, avif, woff/woff2/ttf/otf/eot, mp4/webm/mp3, pdf; ≤ 10 MB) — `pull`/`push`/`rm` như file code nhưng chỉ so khác/giống. Cần server có API `UploadFile`/`DownloadFile`; server cũ sẽ báo cần deploy.
11
+ - Tài liệu dùng trong bài viết (doc, xls, zip…) hoặc ảnh không thuộc giao diện → CKFinder (`mlis cdn`), lưu ở `/Data/...`.
12
+
13
+ ## Chuẩn bị (một lần)
14
+
15
+ ```bash
16
+ mlis config # xem url, workdir, token (chỉ hiện "(set)")
17
+ mlis token set # dán giá trị cookie jwtAuth (prompt ẩn, hoặc pipe qua stdin)
18
+ mlis config set url https://lhu.edu.vn
19
+ mlis config set cdn-cat <CatID> # thư mục (CatID) chứa hình/tài liệu cho CKFinder
20
+ ```
21
+
22
+ Token là cookie `jwtAuth` của tài khoản có SystemRight ≥ 3. **Không bao giờ** in, dán vào chat, ghi vào file hay truyền token làm tham số dòng lệnh; không `cat` file config. Lỗi "server rejected the token" → nhờ người dùng chạy lại `mlis token set`.
23
+
24
+ ## Quy trình chuẩn
25
+
26
+ 1. **Xem**: `mlis tree [folder] [--files]`, `mlis ls <folder>`, `mlis cat <file>`.
27
+ 2. **Tải về**: `mlis pull <folder|file>` → bản sao ở `<workdir>/<đường dẫn ViewPage>` (mặc định `C:\APP\mlis-workspaces\…`). Mỗi workspace dành cho một site. Luôn pull trước khi sửa; file local lâu ngày có thể đã cũ — chạy `mlis status <folder> --remote` để kiểm.
28
+ 3. **Sửa** file trong workspace (Edit/Write trực tiếp file local).
29
+ 4. **Kiểm**: `mlis status <folder>` (new/changed/missing) và `mlis diff <file|folder>` (so với bản đang chạy trên server). Đọc kỹ diff trước khi đẩy.
30
+ 5. **Đẩy**: `mlis push <file|folder>...` (`--dry-run` để xem trước). Trước khi ghi, bản server được lưu vào `_history/`.
31
+ 6. **Xác minh**: mở trang thật trên trình duyệt. Khách chưa đăng nhập có thể thấy HTML cũ tới **60 giây** (HtmlPageCache); CSS được nạp kèm `?version` của site nên trình duyệt có thể giữ CSS cũ — tải lại cứng (Ctrl+F5) hoặc kiểm bằng `mlis cat`.
32
+
33
+ Đường dẫn: dùng đường dẫn ViewPage như `LHUENB4/@HomePage/default.ascx` (có thể kèm tiền tố `ViewPage/`), hoặc đường dẫn local nằm trong workspace (`.\default.ascx` khi đang đứng trong thư mục đó).
34
+
35
+ ## Thiết kế một trang: dữ liệu → mã → xem nháp → đẩy
36
+
37
+ 1. **Biết trang phục vụ thư mục nào và lấy dữ liệu từ đâu** — không đoán CatID/Location:
38
+ ```bash
39
+ mlis category find "tuyển sinh" # tìm CatID theo tên (bỏ dấu cũng được)
40
+ mlis category pages LHUENB4/DefaultPageCatalog # thư mục nào đang dùng trang này (page/read)
41
+ mlis category tree 21 --depth 2 # cây con: id, kiểu, page/read, loc:Location1,2,3
42
+ mlis category ls 21 # thư mục con của 21 và Location của chúng
43
+ mlis category show 21 # mọi cấu hình + đường dẫn + tên miền chủ
44
+ ```
45
+ `<MLIS:NewsList CatID="21" Location="22">` lấy tin của **các thư mục con của 21 có Location1/2/3 = 22** (mỗi thư mục con một khối ListFormat). `Location="0"` lấy tin của chính CatID. `CatID="-100"` = thư mục đang mở, `0` = trang chủ tên miền. Danh sách thư mục lưu ở workspace; `mlis category sync` để làm mới (dùng chung API của trang quản trị thư mục `/api/admin/GetAllFolder`, `GetFolderList`).
46
+ 2. **Viết mã**: xem mẫu thật cùng loại (`mlis skill ref design-patterns`), thuộc tính control (`controls`), HTML control sinh ra để viết CSS (`rendered-html`). Xong thì **`mlis check <thư mục>`**: bắt đường dẫn `ViewPage/...` không tồn tại (Src, CssFolder, href/src, url()), thẻ MLIS thiếu `runat="server"`, dấu `{`/`}` lạc trong format (làm trang lỗi), directive `CodeFile`, ảnh thiếu/giữ chỗ `alt`, số `<h1>`, thẻ SEO trùng với nền tảng. Sửa hết `error` trước khi render/push (exit code 1 khi còn error).
47
+ 3. **Tự render trên máy và xem ảnh chụp trước khi đẩy** — không ghi gì lên server:
48
+ ```bash
49
+ mlis render shot LHUENB4/MyPage # trang thư mục; in đường dẫn ảnh desktop (1366px) + mobile (390px)
50
+ mlis render shot LHUENB4/@HomePage --full # trang chủ, chụp cả trang
51
+ mlis render shot LHUENB4/@ReadPage # trang đọc tin (bài mẫu)
52
+ mlis render shot LHUENB4/MyPage --as read # thư mục không có "home"/"read" trong tên: chọn kiểu bằng --as
53
+ mlis render shot LHUENB4/_default/Box.ascx # xem riêng một control (trang bọc + HeaderOfSite)
54
+ mlis render start LHUENB4/MyPage # chỉ chạy, in http://localhost:5080/… để mở bằng trình duyệt
55
+ mlis render stop
56
+ ```
57
+ `mlis render` chạy chính mã nền tảng (Default.aspx, App_Code, control MLIS) từ `site-src` (mặc định `C:\APP\MLIS_2025`) trên `http://localhost:<render-port>`, thư mục ViewPage trỏ thẳng vào workspace (sửa file là thấy khi chạy lại), cơ sở dữ liệu thay bằng **dữ liệu mẫu**: thư mục mẫu cho mọi `CatID`/`Location` mà trang và các file nó `LoadPage` dùng (3 thư mục con mỗi Location, mỗi cái 2 con), tin mẫu có tiêu đề/tóm tắt/nội dung tiếng Việt, hình giữ chỗ ghi kích thước (`tin-<cat>-<k> · 800×500`). Vì ViewPage chỉ là khuôn, dữ liệu thật không cần có sẵn. File tĩnh nền tảng mà bản local thiếu được lấy từ site thật (chỉ GET, lưu đệm). Lần đầu mất ~1 phút để biên dịch site, các lần sau vài giây. Lỗi biên dịch/parse của .ascx được in ra kèm file và dòng.
58
+ **Mở các ảnh PNG (Read) và tự review**: bố cục, tràn ngang, chữ tiếng Việt, khoảng cách, mobile; đối chiếu yêu cầu, sửa rồi chạy lại cho tới khi đạt. Cần Windows + .NET Framework 4 và `playwright` (Chromium).
59
+ 4. **Đẩy trang thật**: `mlis check` → `mlis diff` → `mlis push` → mở trang thật kiểm lại. Trang mới cần người dùng gán PageStyle/PageRead trong /admin (CLI không làm được).
60
+
61
+ ## Nội dung động: đặt chữ trong bài viết, không cứng trong .ascx
62
+
63
+ Chữ/hình hay đổi (giới thiệu, thông báo, số liệu, thẻ, slider, FAQ) nên lấy từ **bài viết** qua `<MLIS:NewsDetail NewsID="…" NewsFormat="{4}">` (một khối) hoặc `<MLIS:NewsList CatID="…">` (mỗi bài một khối), để người quản trị sửa trong /admin mà không đụng mã. Khi người dùng muốn **đổi nội dung** đang hiển thị: tìm CatID/NewsID trong .ascx, rồi `mlis news ls <CatID>` → `mlis news pull <NewsID>` → sửa `_news/<NewsID>.html` → `mlis news diff` → `mlis news push`. Đổi chữ thì sửa bài, không sửa .ascx. Chi tiết, định dạng file và quy tắc: `mlis skill ref news`.
64
+
65
+ ## Quy tắc thiết kế (SEO, AI search, truy cập)
66
+
67
+ - **Một `<h1>` mỗi trang.** Trang đọc tin: h1 là tiêu đề bài (`{1}` của NewsDetail sinh `<h1 class=NTitle>`) — không thêm h1 khác. Trang chủ/trang thư mục: tự đặt một h1 (tên trang/thư mục, có thể ẩn bằng CSS chuẩn `visually-hidden` nếu thiết kế không muốn hiện); tiêu đề khối dùng h2/h3 (`CatFormat="<h2>…</h2>"`).
68
+ - **Ảnh có ý nghĩa phải có `alt`** mô tả nội dung; ảnh trang trí đặt `alt=""` hoặc dùng CSS background. Không đưa chữ quan trọng vào ảnh.
69
+ - Nội dung chính nằm trong HTML khi tải trang, không sinh bằng JavaScript (crawler AI không chạy JS). Không ẩn nội dung chính trong tab/accordion chỉ mở bằng JS mà không có HTML sẵn.
70
+ - Liên kết có chữ mô tả rõ ("Thông báo tuyển sinh 2027", không "Xem tại đây"); dùng `<a href>` thật, không `onclick`.
71
+ - Không thêm `<title>`, `meta description`, `og:*`, canonical, JSON-LD Breadcrumb/NewsArticle vào ascx/HeaderOfSite — nền tảng đã tự thêm (`viewpage-structure` → "Những gì nền tảng tự làm").
72
+ - Kiểm tra mobile: Bootstrap grid (`col-12 col-md-…`), ảnh `max-width:100%`, không chiều rộng cố định.
73
+
74
+ ## An toàn
75
+
76
+ - `push` dừng với **exit 3** nếu file trên server đã bị sửa sau lần pull cuối (hoặc tồn tại mà chưa từng pull). Không tự ý thêm `--force`: chạy `mlis diff <file>`, báo người dùng, rồi chọn: giữ bản mình (`push --force`, bản server vào `_history`) hoặc lấy bản server (`pull <file> --force`, bản local vào `_history`) rồi sửa lại.
77
+ - `pull` không ghi đè file local đang sửa dở (báo `kept-local`); `--force` mới ghi đè và lưu bản local vào `_history`.
78
+ - Xoá (`mlis rm`, `mlis rmdir`, `mlis cdn rm/rmdir`) là vĩnh viễn trên server: chỉ chạy khi người dùng đồng ý rõ ràng cho đúng file đó, kèm `--confirm-delete`. Yêu cầu "sửa trang" không bao hàm quyền xoá file.
79
+ - Site đang chạy thật: thay đổi có hiệu lực ngay cho người xem. Sửa trang chủ/HeaderOfSite/_default (dùng chung toàn site) cần cẩn thận gấp đôi; luôn `mlis render shot` và xem ảnh trước khi `push` trang thật.
80
+ - Một lỗi cú pháp trong .ascx làm trang lỗi ngay (redirect `/error`). Giữ thẻ `runat="server"` đóng đúng, thuộc tính dùng nháy kép bên ngoài và nháy đơn bên trong chuỗi format (`ListFormat="<div class='row'>{1}</div>"`), hoặc dùng thẻ con `<NewsFormat>…</NewsFormat>` khi format dài/phức tạp.
81
+ - `mlis history <file>` liệt kê bản sao lưu; `mlis restore <file> [--at stamp]` đưa bản cũ về local rồi `push` để khôi phục.
82
+
83
+ ## Tạo trang mới
84
+
85
+ ```bash
86
+ mlis new LHUENB4/MyPage/Default.ascx # trang (LoadPage css + Header/Footer của _default) — chỉ tạo trong workspace
87
+ mlis new LHUENB4/MyPage/StyleMain.css # css cùng thư mục được LoadPage CssFolder nạp tự động
88
+ mlis new LHUENB4/_default/Box.ascx # control con, chèn bằng <MLIS:LoadPage Src="ViewPage/LHUENB4/_default/Box.ascx" runat="server" />
89
+ # viết mã → mlis check LHUENB4/MyPage → mlis render shot LHUENB4/MyPage → xem ảnh, sửa → lặp lại
90
+ mlis push LHUENB4/MyPage # khi đã đạt: tạo thư mục + file trên server (bản sạch, không CodeFile)
91
+ ```
92
+
93
+ `mlis new` chỉ ghi vào workspace (nội dung mẫu sạch, không có `<%@ Control CodeFile=… %>` — trang ViewPage không có code-behind); server chưa có gì cho tới `mlis push`. `--push` tạo luôn trên server — không dùng khi chưa render/review, nhất là với trang route `_<Tên>` (có URL ngay khi file tồn tại). Trang thư mục mới chỉ hiển thị khi một thư mục (category) trong /admin có PageStyle = `LHUENB4/MyPage` — việc này người dùng làm.
94
+
95
+ **Đặt tên thư mục** (chi tiết: `mlis skill ref viewpage-structure`):
96
+ - Không có `_` ở đầu → là "trang", chọn được làm PageStyle/PageRead của thư mục (chỉ cấp `<site>/<trang>` hoặc `<site>/<trang>/<con>`, tối đa 50 ký tự).
97
+ - `_` ở đầu → thư mục hỗ trợ, bị ẩn khỏi ô chọn trang: `_default` (header/footer/css dùng chung), `_assets` (HeaderOfSite.ascx, favicon.ico, default_image.png — tên cố định), `_images`/`_fonts` (tài nguyên của trang), `_bk` (sao lưu).
98
+ - `_<Tên>/default.ascx` còn là trang theo URL `/<Tên>/…` (`_Search`, `_Index`, `_RSS`, `_Event`, `_more-List`…): tạo `LHUENB4/_LienHe/default.ascx` là có `https://<site>/LienHe` mà không cần tạo thư mục trong /admin.
99
+ - `@` ở đầu (`@HomePage`, `@ReadPage`) chỉ là quy ước cho trang chính, không có xử lý riêng.
100
+
101
+ ## Hình ảnh, font và tài liệu
102
+
103
+ Hình/font của giao diện để ngay trong ViewPage, cạnh trang dùng nó:
104
+
105
+ ```bash
106
+ # chép file vào workspace rồi đẩy lên như file code
107
+ copy .\hero.webp C:\APP\mlis-workspaces\LHUENB4\MyPage\_images\hero.webp
108
+ mlis status LHUENB4/MyPage
109
+ mlis push LHUENB4/MyPage
110
+ ```
111
+
112
+ Trong css của trang dùng đường dẫn tương đối `url(_images/hero.webp)`; trong .ascx dùng `/ViewPage/LHUENB4/MyPage/_images/hero.webp`.
113
+
114
+ Tài liệu đính kèm (doc, xls, zip…) hoặc file muốn quản lý qua CKFinder:
115
+
116
+ ```bash
117
+ mlis cdn info # Images/Files: URL gốc, giới hạn, đuôi cho phép
118
+ mlis cdn mkdir banner # thư mục trong Images
119
+ mlis cdn upload .\logo.png --to banner # in {"url":"/Data/Folders/<CatID>/images/banner/logo.png"}
120
+ mlis cdn upload .\quyche.pdf --type Files # tài liệu → Files
121
+ mlis cdn list banner # liệt kê + URL
122
+ ```
123
+
124
+ Dán **URL tương đối** in ra vào .ascx/.css (`<img src="/Data/Folders/…">`, `background:url(/Data/Folders/…)`). CKFinder có thể đổi tên khi trùng hoặc thu nhỏ ảnh quá khổ — luôn dùng URL mà lệnh trả về. Chi tiết: `mlis skill ref cdn-files`.
125
+
126
+ ## Tham khảo
127
+
128
+ - `mlis skill ref controls` — thuộc tính và placeholder `{n}` của từng control MLIS (NewsList, CatMenu, CatShow, EventList, NewsDetail, …), giá trị CatID/Location/PageLink/Visibility.
129
+ - `mlis skill ref rendered-html` — HTML mà từng control sinh ra (class, cấu trúc) để viết CSS đúng.
130
+ - `mlis skill ref design-patterns` — mẫu trang thật đang chạy (trang chủ, trang thư mục, trang đọc tin, header/footer) và các quy ước CSS/JS của site.
131
+ - `mlis skill ref viewpage-structure` — cách site chọn trang (PageStyle/PageRead, route `_<Tên>`), nhiều tên miền, những gì nền tảng tự thêm, quy ước tiền tố `_`/`@`, file tĩnh.
132
+ - `mlis skill ref news` — nội dung động bằng bài viết: NewsDetail/NewsList làm khuôn, `mlis news ls|show|pull|diff|push|new`, định dạng file `_news/<NewsID>.html`, quy tắc lưu bài của server, sự kiện, xung đột.
133
+ - `mlis skill ref cdn-files` — CKFinder: CatID, mode folder/news, Images/Files.
134
+
135
+ Lệnh luôn in dữ liệu ra stdout (bảng khi ở terminal, TSV khi pipe, `--json` cho máy), ghi chú/lỗi ra stderr. Exit code: 0 ok, 1 lỗi, 2 sai cú pháp, 3 bị chặn an toàn.
@@ -0,0 +1,38 @@
1
+ # Hình ảnh và tài liệu qua CKFinder (`mlis cdn`)
2
+
3
+ Hình/font thuộc giao diện nên để ngay trong ViewPage (`mlis push`, xem viewpage-structure). CKFinder dành cho file nằm ngoài ViewPage: tài liệu đính kèm (doc, xls, zip…), ảnh dùng chung với bài viết, hoặc khi server chưa có API `UploadFile`. CLI dùng CKFinder 2.6.3 có sẵn của MLIS (`/JScript/CKFinder/core/connector/aspx/connector.aspx`) với cùng token `jwtAuth`.
4
+
5
+ ## Nơi lưu file
6
+
7
+ CKFinder của MLIS gắn với một **thư mục (CatID)** trong trang quản trị:
8
+ - `--mode folder` (mặc định): `/Data/Folders/<CatID>/images/…` và `/Data/Folders/<CatID>/files/…`
9
+ - `--mode news`: `/Data/News/<CatID>/…`
10
+
11
+ Đặt CatID mặc định một lần: `mlis config set cdn-cat <CatID>` (hoặc `--cat` cho từng lệnh). Nên dùng một thư mục riêng cho tài nguyên giao diện, hỏi người dùng CatID nào nếu chưa đặt. CLI tự "mồi" session bằng `/Admin/CKFinder.aspx?CatID=…&mode=…` trước khi gọi connector.
12
+
13
+ ## Loại tài nguyên
14
+
15
+ | type | Đuôi | Giới hạn |
16
+ |---|---|---|
17
+ | `Images` | gif, jpeg, jpg, png | theo Web.config (CKImagesMaxSize*); ảnh quá khổ bị CKFinder thu nhỏ |
18
+ | `Files` | 7z, csv, doc, docx, gif, jpg, mp3, mp4, pdf, png, ppt, pptx, rar, txt, xls, xlsx, zip, … | 4 MB |
19
+
20
+ `mlis cdn info` cho giá trị thật từ server. `upload` tự chọn `Images` cho ảnh, `Files` cho phần còn lại; mặc định các lệnh khác là `Images`, đổi bằng `--type Files`.
21
+
22
+ ## Lệnh
23
+
24
+ ```bash
25
+ mlis cdn info
26
+ mlis cdn list [folder] [--type Files] [--recursive]
27
+ mlis cdn mkdir banner
28
+ mlis cdn upload .\banner.jpg --to banner [--name banner-2026.jpg]
29
+ mlis cdn url banner/banner-2026.jpg
30
+ mlis cdn rename banner/banner-2026.jpg banner-tet.jpg
31
+ mlis cdn download banner/banner-tet.jpg -o .\banner-tet.jpg
32
+ mlis cdn rm banner/banner-tet.jpg --confirm-delete
33
+ mlis cdn rmdir banner --confirm-delete [--recursive]
34
+ ```
35
+
36
+ `upload` in `{"type","path","url"}`; dán `url` (tương đối, ví dụ `/Data/Folders/12/images/banner/banner-tet.jpg`) vào .ascx/.css. CKFinder có thể đổi tên file khi trùng (lệnh báo `# CKFinder renamed…`) — luôn dùng URL trả về.
37
+
38
+ Xoá là vĩnh viễn và có thể làm vỡ ảnh trên trang/bài viết đang dùng: chỉ xoá khi người dùng cho phép đúng file/thư mục đó.
@@ -0,0 +1,161 @@
1
+ # Control MLIS trong trang .ascx
2
+
3
+ Nguồn: `MLIS_UserControl.doc` (tài liệu gốc của MLIS), đối chiếu với thuộc tính thật trong `App_Code/PagesControl/*.vb`. Control dùng tiền tố `MLIS` (đã đăng ký trong Web.config; nhiều trang vẫn ghi thêm `<%@ Register TagPrefix="MLIS" Namespace="MLIS" %>` ở đầu, cũng được). Mọi control cần `runat="server"`. Giá trị trong ngoặc là mặc định.
4
+
5
+ ## URL của site
6
+
7
+ - Trang chủ: `https://lhu.edu.vn/`
8
+ - Trang một thư mục: `https://lhu.edu.vn/40/Gioi-thieu.html` — 40 là ID thư mục (CatID).
9
+ - Trang đọc tin: `https://lhu.edu.vn/446/3387/Bai-hat-truyen-thong.html` — 446 là thư mục (và trang ascx hiển thị tin), 3387 là ID tin (NewsID).
10
+ - Trang theo route: `/more-list/…`, `/more-news/…`, `/more-related/…`, `/search/…` → nạp `ViewPage/<site>/_<tên>/default.ascx` (xem viewpage-structure).
11
+
12
+ ## Thuộc tính chung
13
+
14
+ **CatID** — thư mục để lấy dữ liệu.
15
+ - `> 0`: đúng CategoryID đó.
16
+ - `0`: cấp lớn nhất (trang chủ, HomeID); `-1`: dưới trang chủ 1 cấp; `-X`: dưới trang chủ X cấp.
17
+ - `-100`: thư mục đang chọn (cấp nhỏ nhất); `-101`: thư mục đang chọn lên 1 cấp; `-10X`: lên X cấp.
18
+
19
+ **Location** (0) — số thứ tự vị trí: chọn các thư mục con của CatID được gán "vị trí" này trong trang quản trị; tin trong các thư mục con đó được lấy ra. `0` = lấy tin nằm ngay trong CatID (với CatMenu: lấy hết menu; với CatShow: chính thư mục đang chọn).
20
+
21
+ **Visibility** (0) — `0` luôn hiển thị; `1` ẩn khi đang đọc tin (có NewsID); `2` ẩn khi không đọc tin.
22
+
23
+ **Related** — dùng "Quan hệ thư mục": `0` cột "Tổng hợp tin" (bit 0), `1` "Cùng nội dung" (bit 1), `2` "Nội dung gần" (bit 2). NewsList/EventList mặc định `5` = không tổng hợp.
24
+
25
+ **PageLink** (0) — thư mục nằm trong link của tin:
26
+ - `0` ID thư mục chứa tin (`/201/50868/…`)
27
+ - `1` ID thư mục của chính danh sách (thư mục CatID/thư mục con theo Location đang được liệt kê — dùng khi tổng hợp tin từ thư mục khác) (`/21/50868/…`)
28
+ - `2` ID trang đang chọn (`/34/50868/…`)
29
+ - `3` ID trang chủ đang hiển thị (`/1/50868/…`)
30
+
31
+ **Skip** (0) — bỏ qua N tin đầu, lấy từ tin N+1.
32
+
33
+ **CatFormat** (`<a>{1}</a>`) — hiển thị thông tin thư mục; thẻ `<a>` tự thành link, bỏ `<a></a>` nếu không muốn link.
34
+ `{0}` Icon (class CIco) · `{1}` Tên thư mục · `{2}` Hình (class CPic) · `{3}` Mô tả · `{4}` Tên khác (Alias) · `{5}` Tài liệu HTML của thư mục · `{6}` Sub menu (thư mục con)
35
+
36
+ **NewsFormat** — định dạng một tin:
37
+ `{0}` Hình (NImg) · `{1}` Tiêu đề (NTitle) · `{2}` Ngày giờ (NTime) · `{3}` Tóm tắt (NIntro) · `{4}` Nội dung (NContent) · `{5}` Từ khoá (NKey) · `{6}` Nguồn/tác giả (NSource) · `{7}` Thời gian cập nhật (NUpdate) · `{8}` chèn class `Selected` cho bài đang xem · `{9}` Lượt xem (NCounter)
38
+
39
+ **DateTimeFormat** — chuỗi định dạng .NET (`dd/MM/yyyy HH:mm:ss`, `H:mm`, …).
40
+ **ViewMore** ("") — chữ của link "xem thêm" cuối danh sách (mở thư mục).
41
+ **HyperFormat** ("") — ưu tiên cao nhất, thay cho ListFormat/EventFormat: khuôn bao ngoài dùng được placeholder của CatFormat (`{1}` tên, `<a>` tự thành link…) và các khối `{{NewsItem}}`, `{{OtherItem}}`, `{{EventItem}}`, `{{ViewMore}}`. Ngoặc nhọn **đôi** là bắt buộc: chuỗi đi qua `String.Format` trước (một cặp ngoặc bị nuốt) rồi mới thay `{NewsItem}`… Một dấu `{` đơn lẻ không phải placeholder trong bất kỳ format nào cũng phải viết `{{` (CSS inline trong format: dùng class thay vì `style="…{…}"`).
42
+ **CssClass** — class CSS của khối.
43
+
44
+ Format có thể viết làm thuộc tính (nháy kép bên ngoài, nháy đơn bên trong) hoặc làm thẻ con khi dài:
45
+
46
+ ```aspx
47
+ <MLIS:NewsList Location="7" PageLink="0" CatFormat="" NewsNumber="2" NewsOther="0" runat="server">
48
+ <NewsFormat>
49
+ <li class='row news'><div class='row'><div class='col-md-5'>{0}</div><div class="col-md-7">{1}{3}</div></div></li>
50
+ </NewsFormat>
51
+ <HyperFormat>
52
+ <ul class="">{{NewsItem}}</ul>
53
+ </HyperFormat>
54
+ </MLIS:NewsList>
55
+ ```
56
+
57
+ ## Nạp trang / css
58
+
59
+ ### LoadPage
60
+ Chèn một trang .ascx vào vị trí đặt control và/hoặc nạp mọi file `*.css` trong một thư mục vào `<head>`.
61
+ - `Src` ("") — đường dẫn .ascx, ví dụ `ViewPage/LHUENB4/_default/Header.ascx`.
62
+ - `CssFolder` ("") — thư mục chứa các .css cần nạp, ví dụ `ViewPage/LHUENB4/@HomePage` (nạp tất cả *.css trong đó, kèm `?version`).
63
+
64
+ ```aspx
65
+ <MLIS:LoadPage CssFolder="ViewPage/LHUENB4/@HomePage" runat="server" />
66
+ <MLIS:LoadPage CssFolder="ViewPage/LHUENB4/_default" Src="ViewPage/LHUENB4/_default/Header.ascx" runat="server" />
67
+ ```
68
+
69
+ ## Menu và thư mục
70
+
71
+ ### CatMenu — các thư mục dạng menu
72
+ `CatID` (-100: lấy thư mục con của nó) · `Location` (0 = hết menu) · `CatFormat` (`<a>{1}</a>`, không áp dụng cho sub menu) · `Visibility` · `CssClass` ("CatMenu"; mục đang chọn có class `Selected`) · `ItemClass` ("" class từng nhóm) · `ShowSubName` (false: hiện tên menu cấp 2) · `ShowSubIcon` (false) · `NewsNumber` (0: lấy thêm N tin làm menu; tin xếp từ thứ tự 20 trở đi).
73
+
74
+ ```aspx
75
+ <MLIS:CatMenu ID="CatMenu1" CatID="0" Location="1" CssClass="navbar-nav navbar-left" ShowSubName="false" runat="server" />
76
+ ```
77
+
78
+ ### CatRelated — thư mục có "Quan hệ thư mục" dạng menu
79
+ `CatID` (-100) · `Related` (1) · `CatFormat` · `Visibility` · `CssClass` ("CatMenu") · `ItemClass` · `ShowSubName` · `ShowSubIcon`.
80
+
81
+ ### CatShow — thuộc tính của một thư mục
82
+ `CatID` (-100) · `Location` (0 = thư mục đang chọn) · `Visibility` · `CatFormat` (`<a>{1}</a>`) · `CssClass` ("CatShow").
83
+
84
+ ```aspx
85
+ <MLIS:CatShow ID="CatShow1" CssClass="CatShow" CatFormat="<h2>{1}</h2>" runat="server" />
86
+ <MLIS:CatShow ID="CatShow2" CatFormat="<div>{2}</div><div class='CatHTML'>{5}</div>" runat="server" />
87
+ ```
88
+
89
+ ### CatStream — chuỗi đường dẫn (breadcrumb)
90
+ `Level` (0: từ cấp N trở đi, 0 = gốc HomeID) · `PathText` (`<span>&raquo;</span>` ký hiệu phân cách) · `UseAlias` (false) · `CssClass` ("CatStream").
91
+
92
+ ### IndexAZ — chỉ mục A–Z các thư mục
93
+ `VisualPath`.
94
+
95
+ ## Danh sách tin
96
+
97
+ ### NewsList — tin dạng liệt kê có tóm tắt
98
+ `CatID` (-100) · `Location` (0) · `Related` (5) · `PageLink` (0) · `Visibility` · `Skip` · `NewsNumber` (1 tin chính) · `NewsOther` (0 tin khác, chỉ tiêu đề) · `GetFirstNews` (false: lấy tin đầu để hiện chi tiết) · `DateTimeFormat` ("dd/MM/yyyy HH:mm:ss") · `ViewMore` · `HyperFormat` (`{{NewsItem}}`, `{{OtherItem}}`, `{{ViewMore}}`).
99
+ - `ListFormat` (`<div>{0}<ul>{1}</ul><ul>{2}</ul>{3}</div>`): `{0}` CatFormat · `{1}` danh sách NewsFormat · `{2}` danh sách OtherFormat · `{3}` link ViewMore
100
+ - `CatFormat` (`<h3 class=CName><a>{1}</a></h3>`)
101
+ - `NewsFormat` (`<li class="{8}">{0}{1}{2}{3}</li>`) — placeholder như NewsFormat chung
102
+ - `OtherFormat` (`<li>{1}{2}</li>`): `{0}` hình · `{1}` tiêu đề · `{2}` ngày giờ
103
+
104
+ ```aspx
105
+ <MLIS:NewsList Location="22" PageLink="1" NewsNumber="1" NewsOther="6" Skip="0"
106
+ ListFormat="<div class='container NewsList'>{0}<div class='row'><div class='col-md-5'><ul class='row h-100'>{1}</ul></div><div class='col-md-7'><ul class='row h-100'>{2}</ul></div></div>{3}</div>"
107
+ CatFormat="<h2 class='row mb-4 mt-5 text-center'><a>{1}</a></h2>"
108
+ NewsFormat="<li class='mb-4'><div class='card shadow-sm h-100'>{0}<div class='card-body'><b>{1}</b><i>{3}</i></div></div></li>"
109
+ OtherFormat="<li class='col-md-4 mb-4'><div class='card shadow-sm h-100'>{0}<div class='card-body'>{1}</div></div></li>"
110
+ runat="server" />
111
+ ```
112
+
113
+ ### NewsRelated — tin trong các thư mục có quan hệ với thư mục đang chọn
114
+ (không hiện tin của chính thư mục đó) — thuộc tính như NewsList, thêm `Merge` (false: để riêng từng thư mục; true: trộn hết) và `Related` mặc định 0. `ListFormat` mặc định `{0}<ul>{1}</ul><ul>{2}</ul>{3}`.
115
+
116
+ ### NewsOther — tin liên quan tới bài đang đọc (chỉ hiện khi có NewsID)
117
+ - `ListFormat` (`<ul>{0}</ul><ul>{1}</ul><ul>{2}</ul>{3}`): `{0}` tin cũ hơn tin đang đọc · `{1}` tin cùng dòng sự kiện · `{2}` tin cùng từ khoá · `{3}` ViewMore
118
+ - `NewsFormat` (`<li>{0}{1}{2}</li>`): `{0}` hình · `{1}` tiêu đề · `{2}` ngày giờ
119
+ - `NewsOther` (5) · `NewsStream` (5) · `NewsKeyword` (5) · `Skip` · `PageLink` (0 và 1 như nhau: thư mục chứa tin) · `DateTimeFormat` · `ViewMore`.
120
+
121
+ ### EventList — danh sách sự kiện
122
+ `CatID` (-100) · `Location` (0) · `Related` (5) · `Skip` · `Visibility` · `EventNumber` (5) · `DateTimeFormat` ("yyyy/MM/dd HH:mm ") · `DayFormat` ("MM/dd") · `ViewMore` · `CatFormat` (`<h3 class=CName><a>{1}</a></h3>`).
123
+ - `EventFormat` (`{0}{1}{2}`): `{0}` CatFormat · `{1}` danh sách EventItemFormat · `{2}` link các sự kiện tiếp theo
124
+ - `EventItemFormat` (`{1}{2}`): `{0}` hình (NImg) · `{1}` tiêu đề (NTitle) · `{2}` thời gian (DateTimeFormat)
125
+ - `HyperFormat`: `{{EventItem}}`, `{{ViewMore}}`
126
+
127
+ ```aspx
128
+ <MLIS:EventList Location="11" Skip="2" DayFormat="''<i>d</i><u>T MM</u>" DateTimeFormat="H:mm" EventNumber="1" runat="server">
129
+ <EventItemFormat>{0}{1}{2}</EventItemFormat>
130
+ <HyperFormat>
131
+ <div class='d-md-none d-lg-block col-lg-4 mb-4'>{{EventItem}}</div>
132
+ </HyperFormat>
133
+ </MLIS:EventList>
134
+ ```
135
+
136
+ ## Chi tiết
137
+
138
+ ### NewsDetail — một tin dạng chi tiết (trang đọc tin `@ReadPage`)
139
+ `NewsID` (0: tin của URL; >0 chỉ định một tin) · `CatName` (`<h2>{0}</h2>`, `{0}` tên thư mục) · `NewsFormat` (`{1}{4}{5}{6}`, placeholder như NewsFormat chung) · `Author` ("{0}", ví dụ "Tác giả {0}", class NSource) · `HitCounter` ("{0}", ví dụ "Lượt xem {0}", class NCount) · `DateTimeFormat` ("dd/MM/yyyy HH:mm:ss").
140
+
141
+ ## Khác
142
+
143
+ ### Attr — dữ liệu hệ thống
144
+ `AttrFormat`: `{0}` số lần truy cập · `{1}` số người online · `{2}` tên miền · `{3}` HomeID · `{4}` số người đang xem HomeID.
145
+
146
+ ```aspx
147
+ <i class="fa fa-bar-chart-o"></i> <MLIS:Attr ID="Attr4" AttrFormat="{1}" runat="server" />
148
+ ```
149
+
150
+ ### Search — ô tìm kiếm
151
+ `Text` ("" tiêu đề ô) · `CssClass` ("Search") · `icon` (HTML của nút, ví dụ `<i class='fa fa-search'></i>`).
152
+
153
+ ### RSS — các kênh RSS 2.0
154
+ `UseAlias` (true: dùng tên khác; false: tên thư mục).
155
+
156
+ ## Cấu hình thư mục liên quan (trong /admin, không sửa bằng CLI)
157
+
158
+ - **Liên kết**: để trống = link tới chính thư mục; `http://…` = link ngoài; `{codien.lhu.edu.vn}` = dùng tên miền đó cho thư mục.
159
+ - **Trang chủ (Home)**: 0 = chính nó là trang chủ; 1 = thư mục cha; 2 = ông nội; …
160
+ - **Kiểu thư mục (CatType)**: 0 ẩn · 1 trang chủ · 2 thư mục · 3 thông báo · 4 sự kiện · 5 giới thiệu · 6 thư viện ảnh · 7 liên kết · 8 tài liệu HTML.
161
+ - **PageStyle**: thư mục trang trong ViewPage dùng để hiển thị thư mục đó (xem viewpage-structure).
@@ -0,0 +1,94 @@
1
+ # Mẫu thiết kế thật trên lhu.edu.vn (site `LHUVNB4`)
2
+
3
+ Rút ra từ các trang đang chạy (đọc bằng `mlis pull`, 10/2026). Site `LHUVNB4` phục vụ lhu.edu.vn và nhiều tên miền con; `LHUENB4` là bản tiếng Anh. Trước khi làm trang mới, `mlis pull` trang cùng loại gần nhất và theo đúng khung của nó.
4
+
5
+ ## Khung chung của mọi trang
6
+
7
+ ```aspx
8
+ <%@ Register TagPrefix="MLIS" Namespace="MLIS" %>
9
+ <link href="/ViewPage/LHUVNB4/<TrangNay>/StyleMain.css" rel="stylesheet" type="text/css" /> <%-- hoặc LoadPage CssFolder --%>
10
+ <MLIS:LoadPage ID="LoadPage1" CssFolder="ViewPage/LHUVNB4/_default" Src="ViewPage/LHUVNB4/_default/Header.ascx" runat="server" />
11
+ <div id="TimeLineImage"></div> <%-- chừa chỗ dưới header cố định; trang nào cũng có --%>
12
+
13
+ <div class="container"> … nội dung … </div>
14
+
15
+ <br />
16
+ <MLIS:LoadPage ID="LoadPage2" Src="ViewPage/LHUVNB4/_default/Footer.ascx" runat="server" />
17
+ ```
18
+
19
+ - `_default/Header.ascx` + `_default/StyleDefault21.css`, `StyleHeaderFooter21.css` (nạp qua `CssFolder="ViewPage/LHUVNB4/_default"`). Header có: menu "Thông tin cho" (`<MLIS:CatMenu CatID="94">`), menu chính (`<MLIS:CatMenu CatID="0" Location="1">`), ô tìm kiếm (`<MLIS:Search icon="<i class='fa fa-search'></i>">` → trang `_Search` dùng Google Custom Search), logo có `alt`, chat widget `https://cdn.lhu.edu.vn/scripts/chat-widget.min.js` (ai.lhu.edu.vn).
20
+ - `_default` còn nhiều biến thể (`Header25.ascx`, `Header-ASU.ascx`, `Footer_2026.ascx`, `Footer_CareerFair.ascx`…): trang đặc biệt chọn header/footer riêng bằng `Src`.
21
+ - HeaderOfSite (`_assets/HeaderOfSite.ascx`) đã nạp: Source Sans Pro, Font Awesome **4.2 và 6.4** (`fa fa-…` và `fa-solid fa-…`), Bootstrap + MDB (`/assets/mdb/`), owlCarousel, Google Tag Manager; và tự khởi chạy owlCarousel cho mọi phần tử `.Carousel` có từ 2 con trở lên.
22
+
23
+ ## Trang chủ (`@HomePage/default.ascx`)
24
+
25
+ - Banner: `<MLIS:CatShow ID="CatShow1" CatFormat="{5}" CssClass="Carousel" runat="server" />` — **nội dung banner là HTMLDoc của thư mục trang chủ**, nhân viên sửa trong /admin, không cần designer; class `Carousel` để owlCarousel chạy.
26
+ - Các khối tin: `NewsList` với **Location** (13, 10, 12, 7, 19…) — mỗi số là vị trí gán cho thư mục con của trang chủ. Kiểm tra số nào dùng cho thư mục nào: `mlis category ls <CatID trang chủ>`.
27
+ ```aspx
28
+ <MLIS:NewsList Related="0" Location="13" PageLink="0" CatFormat="" NewsNumber="1" NewsOther="0" runat="server">
29
+ <NewsFormat><li><div class="NewsTitle">{0}{1}</div></li></NewsFormat>
30
+ <HyperFormat><ul class="News">{{NewsItem}}</ul></HyperFormat>
31
+ </MLIS:NewsList>
32
+ <%-- cùng nguồn, bỏ qua tin đã hiện: Skip="1" --%>
33
+ ```
34
+ - Sự kiện: `<MLIS:EventList Location="11" Skip="0" DayFormat="''<i>d</i><u>T MM</u>" …>` lặp 3 lần với Skip 0/1/2 để chia cột.
35
+ - Khối liên kết cuối trang: `<MLIS:CatMenu CatID="0" Location="20" CatFormat="{6}" CssClass="CatMenu row" ItemClass="col-md col-sm-6" ShowSubName="true">` — mỗi thư mục Location 20 là một cột, `{6}` in menu con.
36
+ - Tiêu đề khối dùng `<div class="TitleGroup">` + `<div class="SubTitle">` (CSS `@HomePage/StyleMain.css`), nút `<button class="Nut">`. **Trang chủ hiện không có `<h1>`** — trang mới nên có một h1 (có thể ẩn bằng CSS) và dùng h2 cho TitleGroup.
37
+ - Hình của trang chủ nằm trong `@HomePage/_images/` (và bản cũ `@HomePage/Image/`); `_default.ascx` trong thư mục là bản lưu cũ (tên có `_` nên không ai chọn nhầm).
38
+
39
+ ## Trang thư mục
40
+
41
+ **Dải tiêu đề + breadcrumb** (DefaultPage2Column, DefaultPageCatalog):
42
+ ```aspx
43
+ <div class="HeaderSubContent"><div class="container"><div class="HeaderSub">
44
+ <MLIS:CatShow ID="CatShow1" CatFormat="<h2>{1}</h2>" runat="server" />
45
+ <MLIS:CatStream ID="CatStream01" Level="0" PathText="<span>&nbsp; &raquo; &nbsp;</span>" runat="server" />
46
+ </div></div></div>
47
+ ```
48
+ Hiện dùng `<h2>` cho tên thư mục và trang không có h1 — trang mới nên dùng `CatFormat="<h1>{1}</h1>"` (giữ class/CSS như cũ).
49
+
50
+ - **DefaultPage2Column** — mỗi thư mục con (Location 2) một thẻ card liệt kê 5 tin:
51
+ `<MLIS:NewsList Location="2" Visibility="1" NewsNumber="0" NewsOther="5" ViewMore="Xem thêm" ListFormat="<div class='col-sm-6 d-flex'><div class='card padding mb-5'><div class='text-uppercase CatName'>{0}</div><ul class='more-news'>{2}</ul><div class='view-more'><i class='fa fa-chevron-circle-right'></i>{3}</div></div></div>" OtherFormat="<li>{1}</li>"/>`
52
+ - **DefaultPageCatalog** — cột trái danh sách tiêu đề (tối đa 30) của thư mục, cột phải chi tiết tin đầu tiên: `NewsList … GetFirstNews="true" NewsFormat="<li class='{8}'>{1}</li>"` + `<MLIS:NewsDetail NewsFormat="<div class='NewsDetail'>{1}{4}</div>" CatName="" />`. `{8}` tô tin đang xem. Menu trái thu gọn trên mobile bằng `navbar-collapse`.
53
+ - **DefaultPageCat** — mô tả thư mục `{3}`, danh sách thư mục con, cột phải hình `{2}` + HTMLDoc `{5}`. ⚠ Bản đang chạy còn trỏ site cũ `ViewPage/LHUVN/...` (không tồn tại → mất CSS, header, footer). `mlis check LHUVNB4/DefaultPageCat` báo lỗi này.
54
+
55
+ ## Trang đọc tin (`@ReadPage/Default.ascx`)
56
+
57
+ ```aspx
58
+ <div class="container"><div class="row">
59
+ <div class="col-md-9">
60
+ <MLIS:NewsDetail ID="NewsDetail1" NewsFormat="<div class='NewsDetail'>{1}{4}{6}{5}</div>"
61
+ DateTimeFormat="'Lạc Hồng', dd/MM/yyyy - HH:mm:ss" HitCounter="Lượt xem {0}" runat="server" />
62
+ <MLIS:NewsOther ID="NewsOther1" PageLink="0" NewsOther="3" NewsKeyword="0" NewsStream="0" ViewMore="Tin khác"
63
+ ListFormat="<ul class='row NewsList'>{0}</ul>…<div class='view-more'>…{3}</div>" NewsFormat="<li class='col-md-4'>{0}{1}</li>" runat="server"/>
64
+ </div>
65
+ <div class="col-md-3"> <%-- cột phải: tin của thư mục cố định --%>
66
+ <MLIS:NewsList PageLink="1" CatID="118" NewsNumber="5" … NewsFormat="<li class='col-md-12'>{0}{1}</li>" runat="server"/>
67
+ <MLIS:NewsList PageLink="1" CatID="21" NewsNumber="5" … runat="server"/>
68
+ </div>
69
+ </div></div>
70
+ ```
71
+ `{1}` của NewsDetail là `<h1 class=NTitle>` — h1 duy nhất của trang. Biến thể: `@ReadPageEvent`, `@ReadPageHomePage`, `@ReadPageNNTH` (gán qua PageRead).
72
+
73
+ ## Trang nội dung tĩnh / landing
74
+
75
+ - **@LandingPage**: toàn bộ nội dung là HTMLDoc của thư mục: `<div class="container NewsDetail"><div class="NContent"><MLIS:CatShow CatFormat="{5}" runat="server" /></div></div>` — nhân viên soạn trong /admin, designer chỉ làm khung + CSS. Nên thêm h1 (`CatShow CatFormat="<h1>{1}</h1>"`) phía trên.
76
+ - **GioiThieu**: trang viết tay dài (card, ảnh trong `GioiThieu/_Images/`, ảnh 360 ở `/Data/img360/...`) kèm `NewsList Location="25"` cho danh sách bài. ⚠ Nhiều ảnh `alt="Card image cap"`/`"smaple image"` hoặc thiếu alt — trang mới phải mô tả ảnh thật.
77
+ - **Trang theo route** `_TuyenSinhDaiHoc` (URL `/TuyenSinhDaiHoc`): landing tuyển sinh tự chứa (CSS riêng, JS bundle), dùng `CatShow CatID="0" CatFormat="{4}"` (tên khác của trang chủ tên miền) và `CatFormat="{5}"`. ⚠ Bản đang chạy nạp CSS từ `ViewPage/LHUVNB4/TuyenSinhDaiHoc/` (thiếu dấu `_`) — `mlis check` báo.
78
+
79
+ ## Trang hệ thống
80
+
81
+ - `_more-List`: `NewsList PageLink="1" NewsNumber="100"` — danh sách "xem thêm" phân trang theo `Skip` trên URL.
82
+ - `_Search`: Google CSE (`cse.google.com`, `queryParameterName="q"`) + `<form runat="server">` (vì vậy trang không được cache).
83
+
84
+ ## Lớp CSS hay dùng (StyleDefault21.css / StyleMain.css)
85
+
86
+ `#TimeLineImage`, `.mobile-fluid`, `.Carousel`, `.TitleGroup`, `.SubTitle`, `.Nut`, `.News`, `.News2`, `.boxNews`, `.NewsList`, `.NewsTitleList`, `.more-news`, `.view-more`, `.HeaderSubContent`, `.HeaderSub`, `.CatName`, `.NewsDetail`, `.NContent`, `.btn-action`, `.no-hover`, cùng các class do control sinh (`NImg`, `NTitle`, `NTime`… — xem `rendered-html`). Bootstrap: `container`, `row`, `col-md-*`, `col-lg-*`, `d-flex`, `py-5`, `card`.
87
+
88
+ ## Lỗi thường gặp đã thấy trên site (tránh lặp lại)
89
+
90
+ 1. Trỏ site cũ `ViewPage/LHUVN/...` sau khi đổi tên site → mất CSS/header không báo lỗi. Luôn `mlis check` trước khi push.
91
+ 2. Chép thư mục trang nhưng quên đổi đường dẫn `ViewPage/<thư mục>` bên trong (ví dụ `TuyenSinhDaiHoc` ↔ `_TuyenSinhDaiHoc`).
92
+ 3. Alt giữ chỗ từ mẫu Bootstrap; ảnh không alt.
93
+ 4. Không có h1 / dùng h2 cho tên trang.
94
+ 5. File thừa trong thư mục trang (`Default - Copy.ascx`, `_BK…`): vô hại nhưng gây nhầm; bản lưu nên để trong `_bk` hoặc dùng `mlis history`.
@@ -0,0 +1,65 @@
1
+ # Nội dung động: bài viết (news) thay cho chữ cứng trong .ascx
2
+
3
+ Trang .ascx là **khuôn**. Chữ, hình, danh sách hay đổi (giới thiệu, thông báo, số liệu, khối "Vì sao chọn…", slider, liên hệ…) nên nằm trong **bài viết** để người quản trị sửa ngay trong /admin → Bài viết (hoặc AI sửa bằng `mlis news`), không phải sửa mã và đẩy lại ViewPage.
4
+
5
+ ## Ba cách đưa nội dung vào trang
6
+
7
+ | Cần | Viết trong .ascx | Sửa nội dung ở đâu |
8
+ |---|---|---|
9
+ | Một khối nội dung cố định (đoạn giới thiệu, bảng học phí, hướng dẫn) | `<MLIS:NewsDetail NewsID="3387" NewsFormat="{4}" CatName="" runat="server" />` — `{4}` là nội dung HTML của bài; `{1}` tiêu đề, `{0}` hình, `{3}` tóm tắt | bài 3387: `mlis news pull 3387` |
10
+ | Một nhóm khối giống nhau (thẻ, slider, lý do, đối tác, câu hỏi thường gặp) | `<MLIS:NewsList CatID="512" NewsNumber="6" NewsFormat="<div class='card'>{0}<h3>{1}</h3>{3}</div>" runat="server" />` — mỗi bài là một khối, thứ tự theo thứ tự sắp trong /admin | thêm/sửa/ẩn bài trong thư mục 512: `mlis news ls 512` |
11
+ | Mô tả/HTML gắn với chính thư mục | `<MLIS:CatShow CatFormat="{3}">` (mô tả), `{5}` (Tài liệu HTML của thư mục) | /admin → Thư mục → Tài liệu HTML (chưa có lệnh mlis) |
12
+
13
+ Placeholder đầy đủ: `mlis skill ref controls` (NewsFormat chung: `{0}` hình · `{1}` tiêu đề · `{2}` ngày · `{3}` tóm tắt · `{4}` nội dung · `{5}` từ khoá · `{6}` nguồn). Thư mục dùng làm kho nội dung cho trang thường để **không** cho hiện trong menu/RSS (cấu hình trong /admin).
14
+
15
+ Khi thiết kế trang mới có nội dung động: đề xuất với người dùng thư mục/bài sẽ chứa nội dung (CatID/NewsID phải có sẵn hoặc tạo trong /admin — CLI không tạo thư mục), viết khuôn bằng NewsList/NewsDetail, rồi đưa nội dung vào bài bằng `mlis news`. `mlis render` dùng dữ liệu mẫu nên khuôn xem được cả khi bài chưa có.
16
+
17
+ ## Tìm bài đang cấp nội dung cho một trang
18
+
19
+ 1. Đọc .ascx (`mlis pull <thư mục>`), tìm `CatID="…"`/`NewsID="…"` trong `<MLIS:NewsList|NewsDetail|NewsRelated|EventList>`. `CatID="-100"` = thư mục đang mở, `0` = trang chủ tên miền; `Location="n"` = các thư mục con có Location n (`mlis category ls <CatID>`).
20
+ 2. `mlis news ls <CatID> [--search chữ]` → NewsID, tiêu đề, từ khoá, người tạo/sửa (giống danh sách trong /admin: tài khoản chỉ có quyền đóng góp thì chỉ thấy bài của mình).
21
+ 3. Trên trang thật, URL đọc tin `/<CatID>/<NewsID>/<tên>.html` cho biết NewsID.
22
+
23
+ ## Lệnh
24
+
25
+ ```bash
26
+ mlis news ls 512 [--search "học phí"] [--from 2026/01/01] [--to 2026/12/31] [--skip 20] [--limit 50] [--trash]
27
+ mlis news show 3387 # in bài (không ghi file); --json: mọi cột
28
+ mlis news pull 3387 3388 # → <workspace>/_news/3387.html
29
+ mlis news diff 3387 # local ↔ server
30
+ mlis news push 3387 [--dry-run] # lưu; exit 3 nếu bài đã bị sửa trên server sau lần pull
31
+ mlis news new 512 --title "Học bổng 2027" # tạo _news/new-512-<giờ>.html (newsId 0); push mới tạo bài
32
+ ```
33
+
34
+ File bài: header là chú thích HTML, dưới là nội dung HTML của bài:
35
+ ```html
36
+ <!--mlis-news
37
+ newsId: 3387
38
+ catId: 512
39
+ title: Học phí năm học 2026-2027
40
+ intro: Tóm tắt một dòng
41
+ images: /Data/News/512/images/hoc-phi.jpg
42
+ keyword: học phí, tuyển sinh
43
+ source: Phòng Tài chính
44
+ # news trong thư mục Thông tin tuyển sinh ← dòng # chỉ để đọc
45
+ -->
46
+ <p>Nội dung…</p>
47
+ ```
48
+ - Giá trị nhiều dòng hoặc có khoảng trắng đầu/cuối, bắt đầu bằng `"` → viết dạng chuỗi JSON (`intro: "dòng 1\ndòng 2"`).
49
+ - **Sự kiện** (thư mục kiểu sự kiện): có `orderTime: yyyy-MM-dd HH:mm` (thời gian diễn ra), không có `intro`/`keyword`.
50
+ - **Thư viện ảnh**: chỉ đọc; sửa trong /admin.
51
+ - `catId` chỉ có ý nghĩa khi tạo bài mới; bài đã có giữ thư mục của nó (chuyển thư mục: kéo bài trong /admin → Bài viết).
52
+
53
+ ## Quy tắc của server (giống trang sửa bài trong /admin — App_Code\NewsEditor.vb)
54
+
55
+ - Tiêu đề bắt buộc, ≤ 150 ký tự, không HTML (`<` `>` bị đổi thành `&lt;` `&gt;`), khoảng trắng thừa bị gộp.
56
+ - Nội dung ≥ 30 ký tự, ≤ 50.000 (sự kiện 500.000); không nhúng ảnh base64 (`data:image`) — upload ảnh trước bằng `mlis cdn upload --mode news --cat <CatID>` rồi dùng URL `/Data/News/<CatID>/…`.
57
+ - Từ khoá được chuẩn hoá thành `a, b, c`. Sau push, file được ghi lại đúng như server lưu (thông báo "server normalised").
58
+ - Quyền: chỉ bài trong thư mục tài khoản có quyền; không có quyền → lỗi 404/403 từ server.
59
+
60
+ ## An toàn
61
+
62
+ - Bài viết hiển thị **ngay** trên site thật (sau tối đa 60 giây cache). Chỉ sửa bài người dùng yêu cầu; đọc `mlis news diff` trước khi push và báo người dùng nội dung thay đổi.
63
+ - Exit 3 = bài đã bị sửa trên web sau lần pull: chạy `mlis news diff`, hỏi người dùng giữ bản nào — `mlis news pull <id> --force` (lấy bản server, bản local vào `_history/_news/`) hoặc `mlis news push <id> --force` (ghi đè, bản server vào `_history/_news/`).
64
+ - Không có lệnh xoá/ẩn bài: việc đó làm trong /admin.
65
+ - Nội dung bài là HTML do người biên tập soạn bằng CKEditor: giữ cấu trúc đơn giản (p, h2/h3, ul/ol, table, img có alt, a), không đưa `<script>`, `<style>` hay class bố cục vào bài — bố cục thuộc về .ascx/.css.
@@ -0,0 +1,91 @@
1
+ # HTML mà các control MLIS sinh ra (để viết CSS)
2
+
3
+ Trích từ mã `App_Code/PagesControl/*.vb` và `App_Code/AppPublic.vb`. Phần trong `ListFormat`/`NewsFormat`/`CatFormat`… là của designer; phần dưới đây là phần control tự sinh và **không đổi được** bằng thuộc tính — CSS phải bám theo các class này.
4
+
5
+ ## Khối dùng chung
6
+
7
+ ### Một tin trong danh sách (NewsList, NewsRelated, NewsOther, EventList)
8
+
9
+ | Placeholder | HTML sinh ra |
10
+ |---|---|
11
+ | `{0}` hình | `<div class=NImg><a href="/{CatID}/{NewsID}/{tieu-de}.html" aria-label="Tiêu đề" style="background-image:url('/Data/News/…')"></a></div>` — ảnh là **background** của thẻ `<a>` rỗng: CSS phải cho `.NImg a` kích thước (vd. `display:block; padding-top:56%; background-size:cover; background-position:center`) |
12
+ | `{1}` tiêu đề | `<div class=NTitle><a href="/{CatID}/{NewsID}/{tieu-de}.html">Tiêu đề</a></div>` |
13
+ | `{2}` ngày giờ | `<div class=NTime>20/10/2026 09:30:00</div>` (theo `DateTimeFormat`) |
14
+ | `{3}` tóm tắt | `<div class=NIntro>…</div>` (chỉ khi có tóm tắt) |
15
+ | `{4}` nội dung | `<div class=NContent>…HTML bài viết…</div>` |
16
+ | `{8}` | chữ `Selected` khi đó là tin đang đọc (dùng trong thuộc tính class: `<li class='{8}'>`) |
17
+
18
+ `CatID` trong link: `PageLink=0` thư mục chứa tin, `1` thư mục của danh sách, `2` thư mục đang mở, `3` trang chủ.
19
+
20
+ ### Thông tin thư mục (`CatFormat`, `HyperFormat`)
21
+
22
+ | Placeholder | HTML sinh ra |
23
+ |---|---|
24
+ | `{0}` icon | `<div class=CIco><div style="background-image:url('…')"></div></div>` nếu CatIcon là ảnh; ngược lại `<div class="CIco {CatIcon}"></div>` (CatIcon là tên class, vd. `fa fa-book`) |
25
+ | `{1}` tên | chữ thuần |
26
+ | `{2}` hình | `<div class=CPic><div style="background-image:url('…')"></div></div>` |
27
+ | `{3}` mô tả, `{4}` tên khác, `{5}` HTMLDoc | chữ/HTML thuần, không bọc |
28
+ | `{6}` menu con | (chỉ CatMenu) `<ul class="CSub"><li>…</li></ul>` |
29
+
30
+ Thẻ `<a>` trong format: nếu thư mục bật "Cho liên kết" thì được gắn `href="/{CatID}/{ten}.html"` (hoặc `//ten-mien` khi Liên kết là `{ten-mien}`) và `target="_blank"` nếu bật "Mở cửa sổ mới"; nếu tắt thì **thẻ `<a>` bị xoá**, chỉ còn chữ.
31
+
32
+ ### Link "xem thêm" (`{3}` của ListFormat, `{{ViewMore}}`)
33
+
34
+ `<a href="/more-list/{CatID}/{Related}/{Skip}/{ten}.html">{ViewMore}</a>` (EventList: `/more-event/…`, NewsOther: `/more-news/…`). Rỗng khi `ViewMore=""`.
35
+
36
+ ## Từng control
37
+
38
+ **LoadPage** — không sinh thẻ bao: chèn nguyên HTML của `Src`; `CssFolder` thêm `<link rel=stylesheet href="/ViewPage/…/x.css?{version}">` vào `<head>` cho mọi file .css trong thư mục.
39
+
40
+ **CatMenu**
41
+ ```html
42
+ <ul class="{CssClass}"> <!-- mặc định CatMenu -->
43
+ <li class="Selected {ItemClass}">{CatFormat của thư mục, {6} = menu con}</li>
44
+ ...
45
+ </ul>
46
+ <!-- menu con (ShowSubName/ShowSubIcon): -->
47
+ <ul class="CSub"><li class=Selected><a href="/{id}/{ten}.html">{icon}{tên}</a></li></ul>
48
+ ```
49
+ `Selected` gắn cho mục đang mở. Mục tắt "Cho liên kết" ở menu con là `<span>{icon}{tên}</span>`.
50
+
51
+ **CatRelated** — như CatMenu.
52
+
53
+ **CatShow** — `<div class="{CssClass}">{CatFormat}</div>` (mặc định `CatShow`); với `Location>0` lặp CatFormat cho từng thư mục con trong cùng một div. Không có gì để hiện thì không sinh thẻ.
54
+
55
+ **CatStream** — `<div id="{ID}" class="{CssClass}"><a href="/{id}/{ten}.html">Cấp 1</a>{PathText}<a …>Cấp 2</a>{PathText}Thư mục đang mở</div>` (mục cuối không có link).
56
+
57
+ **NewsList / NewsRelated** — đúng `ListFormat` (hoặc `HyperFormat`) với `{0}` = CatFormat, `{1}` = các NewsFormat nối liền, `{2}` = các OtherFormat nối liền, `{3}` = link xem thêm. **Không có thẻ bao nào khác**: muốn `<ul>` thì tự viết trong ListFormat. `Location>0`: lặp nguyên ListFormat cho từng thư mục con có Location đó. Thư mục không có tin: không sinh gì.
58
+
59
+ Trong `HyperFormat`, khối danh sách viết là `{{NewsItem}}`, `{{OtherItem}}`, `{{ViewMore}}` (dấu ngoặc đôi vì chuỗi đi qua `String.Format` trước).
60
+
61
+ **NewsOther** — `ListFormat` với `{0}` tin cũ hơn, `{1}` cùng dòng sự kiện, `{2}` cùng từ khoá, `{3}` xem thêm; mỗi tin dùng `NewsFormat` nhưng chỉ có `{0}` hình, `{1}` tiêu đề, `{2}` ngày. Chỉ hiện khi đang đọc tin.
62
+
63
+ **NewsDetail** (trang đọc tin) — `CatName` rồi `NewsFormat` với:
64
+ `{0}` `<div class=NImg><img src="…" alt="Tiêu đề" class="img-responsive"/></div>` · `{1}` **`<h1 class=NTitle>Tiêu đề</h1>`** (h1 của trang đọc tin) · `{2}` `<div class=NTime>` · `{3}` `<div class=NIntro>` · `{4}` `<div class=NContent>` · `{5}` `<p class=NKey><span>từ khoá</span> #<span>…</span></p>` · `{6}` `<div class=NSource>` (Author) · `{7}` `<div class=NUpdate>` · `{9}` `<div class=NCounter>` (HitCounter).
65
+
66
+ **EventList**
67
+ ```html
68
+ <ul class=EventList>
69
+ <li><div class=EDay>{DayFormat}</div>
70
+ <ul class=EDayGroup>
71
+ <li>{EventItemFormat: {0} NImg · {1} <div class=ETitle><a …>…</a></div> · {2} <div class=ETime>…</div>}</li>
72
+ </ul>
73
+ </li>
74
+ </ul>
75
+ ```
76
+ Các sự kiện cùng ngày gom vào một `EDayGroup`.
77
+
78
+ **Search** — `<div id="{ID}" class="{CssClass}">{Text}<div class=SearchTextBox><input type="Text" id="{ID}"><span class=SearchImg>{icon}</span></div></div>`; Enter hoặc bấm icon chuyển tới `/search/?q=…` (trang `_Search`).
79
+
80
+ **IndexAZ** — `<div class=AlphabetBar><a href="{VisualPath}/A">A</a>… (chữ đang chọn có class Selected)</div><div class=ListOfCat><div><a href="/{id}/{ten}.html">Tên</a></div>…</div>`.
81
+
82
+ **RSS** — `<div class="RSS"><span class=CName><a href="https://…/rss/{id}/{ten}.html">Tên</a> - https://…/rss/{id}/{ten}.html</span>…</div>`.
83
+
84
+ **Attr** — chỉ chuỗi `AttrFormat` đã điền số, không bọc thẻ.
85
+
86
+ ## Lưu ý khi viết CSS
87
+
88
+ - Nhiều thẻ dùng **class không có nháy** trong HTML (`class=NTitle`) — vẫn là class bình thường cho CSS.
89
+ - Ảnh tin (`.NImg a`) là background: không có `<img>` để đặt `width`; tự định tỉ lệ khung.
90
+ - Không dựa vào `id` tự sinh của ASP.NET; chỉ `Search`, `CatStream` dùng đúng `ID` bạn đặt.
91
+ - Font Awesome 4.2, Bootstrap 4 (mdb) có sẵn từ HeaderOfSite; class Bootstrap viết thẳng trong các format.