up
This commit is contained in:
@@ -1,413 +1,158 @@
|
||||
let historyStates = [];
|
||||
let currentStateIndex = -1;
|
||||
let currentElement = null;
|
||||
let isEditMode = false;
|
||||
|
||||
// Các phần tử DOM cần thiết
|
||||
"use strict";
|
||||
// Khai báo các biến trạng thái chính để quản lý lịch sử các trạng thái giao diện
|
||||
let historyStates = []; // Lưu trữ lịch sử HTML của trang
|
||||
let currentStateIndex = -1; // Chỉ số trạng thái hiện tại trong lịch sử
|
||||
let currentElement = null; // Phần tử hiện đang chỉnh sửa
|
||||
let isEditMode = false; // Xác định chế độ chỉnh sửa có được bật không
|
||||
// Các phần tử giao diện được chọn từ DOM, chứa các thành phần tương tác trong giao diện chỉnh sửa
|
||||
const $elements = {
|
||||
// Nút để bật hoặc tắt chế độ chỉnh sửa
|
||||
toggleEditModeButton: $('#toggleEditMode'),
|
||||
// Thanh công cụ hiển thị các tùy chọn khi chỉnh sửa phần tử
|
||||
tabBar: $('#tabBar'),
|
||||
// Phần tùy chọn cho chỉnh sửa văn bản, bao gồm các thiết lập màu, nội dung và khoảng cách dòng
|
||||
textOptions: $('#textOptions'),
|
||||
// Phần tùy chọn cho chỉnh sửa ảnh, bao gồm URL ảnh và kích thước
|
||||
imageOptions: $('#imageOptions'),
|
||||
// Phần tùy chọn cho chỉnh sửa hộp (box), có thể dùng để thay đổi kích thước hoặc màu nền
|
||||
boxOptions: $('#BoxOptions'),
|
||||
// Phần tùy chọn cho chỉnh sửa nền, cho phép thiết lập màu nền
|
||||
bgOptions: $('#bgOptions'),
|
||||
// Trường nhập liệu cho màu chữ, cho phép người dùng chọn màu chữ của tiêu đề hoặc văn bản
|
||||
textColorInput: $('#textColor'),
|
||||
// Trường nhập liệu cho màu nền của văn bản, cho phép người dùng thay đổi màu nền của tiêu đề
|
||||
textBackgroundInput: $('#textBackground'),
|
||||
// Trường nhập liệu cho nội dung văn bản, dùng để chỉnh sửa nội dung của tiêu đề
|
||||
textContentInput: $('#textContent'),
|
||||
// Trường nhập liệu URL của ảnh, cho phép người dùng nhập URL để thay đổi ảnh
|
||||
imageURLInput: $('#imageURL'),
|
||||
// Trường tải lên ảnh, cho phép người dùng tải ảnh trực tiếp từ máy tính
|
||||
imageUploadInput: $('#imageUpload'),
|
||||
// Trường nhập liệu cho kích thước logo, dùng để điều chỉnh kích thước ảnh hoặc logo
|
||||
logoSizeInput: $('#logoSize'),
|
||||
// Hiển thị giá trị kích thước hiện tại của ảnh, được cập nhật khi thay đổi kích thước logo
|
||||
sizeValueSpan: $('#sizeValue'),
|
||||
// Trường nhập liệu khoảng cách dòng, giúp người dùng điều chỉnh khoảng cách dòng của văn bản
|
||||
lineHeightInput: $('#lineHeight')
|
||||
};
|
||||
|
||||
// Lắng nghe sự kiện scroll để chuyển chế độ cố định cho thanh công cụ khi cuộn xuống
|
||||
$(window).scroll(function () {
|
||||
$(".mode-edit").toggleClass('fixed', $(window).scrollTop() > 100);
|
||||
});
|
||||
|
||||
// Chuyển đổi chế độ chỉnh sửa
|
||||
/**
|
||||
* Bật/tắt chế độ chỉnh sửa.
|
||||
* Khi bật, các phần tử sẽ có thể chỉnh sửa, và ngược lại.
|
||||
*/
|
||||
$elements.toggleEditModeButton.on('click', function () {
|
||||
isEditMode = !isEditMode;
|
||||
$('.editable-element').toggleClass('editable', isEditMode);
|
||||
$elements.toggleEditModeButton.html(isEditMode ? '<i class="fas fa-sign-out-alt"></i> Thoát chỉnh sửa' : '<i class="far fa-edit"></i> Chỉnh sửa');
|
||||
$elements.tabBar.hide();
|
||||
});
|
||||
|
||||
// Cập nhật màu sắc và ảnh
|
||||
$elements.textColorInput.on('input', function () {
|
||||
updateElementColor('text', $(this).val());
|
||||
});
|
||||
|
||||
$elements.textBackgroundInput.on('input', function () {
|
||||
updateElementColor('bg', $(this).val());
|
||||
});
|
||||
|
||||
$elements.logoSizeInput.on('input', function () {
|
||||
updateLogoSize($(this).val());
|
||||
});
|
||||
|
||||
$elements.lineHeightInput.on('input', function () {
|
||||
updateLineHeight($(this).val());
|
||||
});
|
||||
|
||||
$elements.imageUploadInput.on('change', function (event) {
|
||||
handleImageUpload(event);
|
||||
});
|
||||
|
||||
// Hàm bắt đầu chỉnh sửa
|
||||
/**
|
||||
* Cập nhật màu sắc của phần tử hiện tại.
|
||||
* @param type - Loại màu sắc cần thay đổi ('text' hoặc 'bg').
|
||||
* @param color - Mã màu cần áp dụng cho phần tử.
|
||||
*/
|
||||
function updateElementColor(type, color) {
|
||||
if (!currentElement)
|
||||
return;
|
||||
const className = type === 'text' ? `text-[${color}]` : `bg-[${color}]`;
|
||||
currentElement.find('.title').attr('class', function (i, c) {
|
||||
return c ? c.split(' ').filter(cls => !cls.startsWith(type)).concat(className).join(' ') : className;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Xử lý tải ảnh lên và gán ảnh mới cho phần tử.
|
||||
* @param event - Sự kiện thay đổi từ ô tải ảnh lên.
|
||||
*/
|
||||
function handleImageUpload(event) {
|
||||
var _a;
|
||||
const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
|
||||
if (!file)
|
||||
return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
currentElement === null || currentElement === void 0 ? void 0 : currentElement.find('img').attr('src', reader.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
/**
|
||||
* Bắt đầu chế độ chỉnh sửa cho phần tử được chọn.
|
||||
* @param event - Sự kiện click để bắt đầu chỉnh sửa.
|
||||
* @param button - Nút bấm để kích hoạt chỉnh sửa phần tử.
|
||||
*/
|
||||
function startEditing(event, button) {
|
||||
if (!isEditMode) return;
|
||||
if (!isEditMode)
|
||||
return;
|
||||
event.stopPropagation();
|
||||
currentElement = $(button).closest('.editable-element');
|
||||
$elements.tabBar.show();
|
||||
setupOptions();
|
||||
}
|
||||
|
||||
// Thiết lập các tùy chọn cho các phần tử
|
||||
/**
|
||||
* Thiết lập các tùy chọn cho phần tử đang chỉnh sửa dựa trên loại của nó.
|
||||
*/
|
||||
function setupOptions() {
|
||||
if (!currentElement)
|
||||
return;
|
||||
const elementType = currentElement.data('type');
|
||||
const $title = currentElement.find('.title');
|
||||
|
||||
if (elementType === 'box') {
|
||||
showBoxOptions();
|
||||
} else if (elementType === 'title') {
|
||||
showTextOptions($title);
|
||||
} else if (elementType === 'background') {
|
||||
}
|
||||
else if (elementType === 'title') {
|
||||
showTextOptions(currentElement.find('.title'));
|
||||
}
|
||||
else if (elementType === 'background') {
|
||||
showBgOptions();
|
||||
} else if (elementType === 'image') {
|
||||
}
|
||||
else if (elementType === 'image') {
|
||||
showImageOptions();
|
||||
}
|
||||
}
|
||||
|
||||
// Cập nhật màu sắc cho phần tử
|
||||
function updateElementColor(type, color) {
|
||||
if (!currentElement) return;
|
||||
const className = type === 'text' ? `text-[${color}]` : `bg-[${color}]`;
|
||||
currentElement.find('.title').attr('class', function (i, c) {
|
||||
return c.split(' ').filter(cls => !cls.startsWith(type)).concat(className).join(' ');
|
||||
});
|
||||
}
|
||||
|
||||
// Cập nhật kích thước logo
|
||||
function updateLogoSize(size) {
|
||||
$elements.sizeValueSpan.text(`${size}px`);
|
||||
currentElement.find('img').css('width', `${size}px`);
|
||||
}
|
||||
|
||||
// Cập nhật chiều cao dòng cho phần tử tiêu đề
|
||||
function updateLineHeight(height) {
|
||||
if (currentElement && currentElement.data('type') === 'title') {
|
||||
currentElement.find('.title').css('line-height', `${height}px`);
|
||||
}
|
||||
}
|
||||
|
||||
// Xử lý tải ảnh lên
|
||||
function handleImageUpload(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
currentElement.find('img').attr('src', reader.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
// Áp dụng các thay đổi
|
||||
function applyChanges() {
|
||||
if (!currentElement) return;
|
||||
const elementType = currentElement.data('type');
|
||||
|
||||
if (elementType === 'title') {
|
||||
updateTextStyles();
|
||||
} else if (elementType === 'image') {
|
||||
updateImage();
|
||||
} else if (elementType === 'background') {
|
||||
updateBgStyles();
|
||||
}
|
||||
|
||||
saveCurrentState();
|
||||
$elements.tabBar.hide();
|
||||
}
|
||||
|
||||
// Cập nhật các thuộc tính kiểu chữ cho tiêu đề
|
||||
function updateTextStyles() {
|
||||
const $title = currentElement.find('.title');
|
||||
$title.text($elements.textContentInput.val());
|
||||
updateElementColor('text', $elements.textColorInput.val());
|
||||
updateElementColor('bg', $elements.textBackgroundInput.val());
|
||||
}
|
||||
|
||||
// Cập nhật ảnh
|
||||
function updateImage() {
|
||||
const $img = currentElement.find('img');
|
||||
$img.attr('src', $elements.imageURLInput.val() || $img.attr('src'));
|
||||
}
|
||||
|
||||
// Cập nhật background
|
||||
function updateBgStyles() {
|
||||
updateElementColor('bg', $elements.textBackgroundInput.val());
|
||||
}
|
||||
|
||||
// Lưu trạng thái hiện tại của trang
|
||||
function saveCurrentState() {
|
||||
const currentHTML = document.documentElement.innerHTML;
|
||||
|
||||
if (currentStateIndex < historyStates.length - 1) {
|
||||
historyStates = historyStates.slice(0, currentStateIndex + 1);
|
||||
}
|
||||
|
||||
historyStates.push(currentHTML);
|
||||
currentStateIndex++;
|
||||
}
|
||||
|
||||
// Quay lại trạng thái trước đó
|
||||
function goBack() {
|
||||
if (currentStateIndex > 0) {
|
||||
currentStateIndex--;
|
||||
document.documentElement.innerHTML = historyStates[currentStateIndex];
|
||||
}
|
||||
}
|
||||
|
||||
// Tiến về trạng thái sau
|
||||
function goForward() {
|
||||
if (currentStateIndex < historyStates.length - 1) {
|
||||
currentStateIndex++;
|
||||
document.documentElement.innerHTML = historyStates[currentStateIndex];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Các hàm hiển thị và xử lý tùy chọn (showTextOptions, showBgOptions, showImageOptions, showBoxOptions)
|
||||
// Thêm vào TypeScript, định nghĩa tham số và chú thích
|
||||
/**
|
||||
* Hiển thị các tùy chọn văn bản cho phần tử tiêu đề.
|
||||
* @param $title - Phần tử tiêu đề hiện tại.
|
||||
*/
|
||||
function showTextOptions($title) {
|
||||
$elements.textOptions.show(); // Hiển thị các tùy chọn văn bản
|
||||
$elements.boxOptions.hide(); // Ẩn các tùy chọn hộp
|
||||
$elements.imageOptions.hide(); // Ẩn các tùy chọn hình ảnh
|
||||
$elements.bgOptions.hide(); // Ẩn các tùy chọn nền
|
||||
|
||||
// Điền các giá trị vào các ô input
|
||||
$elements.textContentInput.val($title.text().trim()); // Điền nội dung văn bản
|
||||
$elements.textColorInput.val(extractColor($title, 'text')); // Điền màu chữ
|
||||
$elements.textBackgroundInput.val(extractColor($title, 'bg')); // Điền màu nền
|
||||
$elements.lineHeightInput.val(parseFloat($title.css('line-height'))); // Điền chiều cao dòng
|
||||
$('#fontSize').val(parseFloat($title.css('font-size'))); // Điền cỡ chữ
|
||||
$('#fontFamily').val($title.css('font-family')); // Điền font chữ
|
||||
$('#BgcolorCode').val(extractColor($title, 'bg')); // Điền màu nền vào ô input
|
||||
$('#colorCodeTitle').val(extractColor($title, 'text')); // Điền mã màu chữ vào ô input
|
||||
}
|
||||
|
||||
|
||||
function showBgOptions() {
|
||||
$elements.textOptions.hide(); // Ẩn các tùy chọn văn bản
|
||||
$elements.boxOptions.hide(); // Ẩn các tùy chọn hộp
|
||||
$elements.imageOptions.hide(); // Ẩn các tùy chọn hình ảnh
|
||||
$elements.bgOptions.show(); // Hiển thị các tùy chọn nền
|
||||
|
||||
const bgColor = extractColor(currentElement, 'bg'); // Trích xuất màu nền hiện tại
|
||||
$('#changeInputBg').val(bgColor); // Điền vào ô input thay đổi màu nền
|
||||
$('#InputCodeBg').val(bgColor); // Điền vào ô input màu nền
|
||||
$('#borderColorBg').val(extractColor(currentElement, 'border'))
|
||||
}
|
||||
|
||||
|
||||
function showImageOptions() {
|
||||
$elements.textOptions.hide(); // Ẩn các tùy chọn văn bản
|
||||
$elements.boxOptions.hide(); // Ẩn các tùy chọn hộp
|
||||
$elements.bgOptions.hide(); // Ẩn các tùy chọn nền
|
||||
$elements.imageOptions.show(); // Hiển thị các tùy chọn hình ảnh
|
||||
|
||||
const $img = currentElement.find('img'); // Lấy phần tử hình ảnh
|
||||
$elements.imageURLInput.attr('src', $img.attr('src')); // Điền URL của hình ảnh vào ô input
|
||||
$elements.imageUploadInput.val(''); // Xóa giá trị của ô input tải ảnh lên
|
||||
$elements.logoSizeInput.val($img.width()); // Điền kích thước logo vào ô input
|
||||
$elements.sizeValueSpan.text(`${$img.width()}px`); // Hiển thị kích thước logo
|
||||
}
|
||||
|
||||
function showBoxOptions() {
|
||||
$elements.boxOptions.show();
|
||||
$elements.textOptions.hide();
|
||||
// Hiển thị các tùy chọn và thiết lập giá trị hiện tại cho ô input
|
||||
$elements.textOptions.show();
|
||||
$elements.boxOptions.hide();
|
||||
$elements.imageOptions.hide();
|
||||
const $title = currentElement.find('.title');
|
||||
$('#BackgroundColor').val(extractColor(currentElement, 'bg'));
|
||||
$('#InputCodeBox').val(extractColor(currentElement, 'bg'));
|
||||
$('#changeInputBox').val(extractColor(currentElement, 'bg'));
|
||||
$elements.bgOptions.hide();
|
||||
$elements.textContentInput.val($title.text().trim());
|
||||
$elements.textColorInput.val(extractColor($title, 'text'));
|
||||
$elements.textBackgroundInput.val(extractColor($title, 'bg'));
|
||||
$elements.lineHeightInput.val(parseFloat($title.css('line-height')));
|
||||
$('#fontSize').val(parseFloat($title.css('font-size')));
|
||||
$('#fontFamily').val($title.css('font-family'));
|
||||
var a = $('#BgcolorCode').val(extractColor($title, 'bg'));
|
||||
console.log(a)
|
||||
$('#BgcolorCode').val(extractColor($title, 'bg'));
|
||||
$('#colorCodeTitle').val(extractColor($title, 'text'));
|
||||
$('#borderColorBox').val(extractColor(currentElement, 'border'))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Trích xuất mã màu từ lớp CSS của phần tử.
|
||||
* @param $element - Phần tử để lấy mã màu.
|
||||
* @param type - Loại mã màu ('text' hoặc 'bg').
|
||||
* @returns Mã màu đã được trích xuất hoặc '#000000' nếu không có.
|
||||
*/
|
||||
function extractColor($element, type) {
|
||||
// Tạo regex để tìm các lớp màu (chẳng hạn như text-[#FF5733])
|
||||
var _a;
|
||||
const regex = new RegExp(`${type}-\\[([#0-9A-Fa-f]+)\\]`);
|
||||
|
||||
// Tìm kiếm lớp màu trong các lớp của phần tử
|
||||
const match = $element.attr('class').match(regex);
|
||||
|
||||
// Nếu tìm thấy lớp phù hợp, trả về màu sắc, nếu không trả về màu mặc định là #000000
|
||||
const match = (_a = $element.attr('class')) === null || _a === void 0 ? void 0 : _a.match(regex);
|
||||
return match ? match[1] : '#000000';
|
||||
}
|
||||
|
||||
function updateColorPicker(event, type) {
|
||||
const textColor = $("#textColor");
|
||||
const textCodeColor = $("#colorCodeTitle");
|
||||
const BgColor = $("#textBackground");
|
||||
let element = '';
|
||||
if (currentElement.data('type') === 'title') {
|
||||
element = currentElement.find('.title');
|
||||
|
||||
} else {
|
||||
element = currentElement;
|
||||
}
|
||||
|
||||
if (type === 'text') {
|
||||
const isValidHex = /^#([0-9A-F]{3}){1,2}$/i.test(textCodeColor.val());
|
||||
if (isValidHex) {
|
||||
textColor.val(textCodeColor.val());
|
||||
element.css('color', textCodeColor.val());
|
||||
}
|
||||
} else if (type === 'background') {
|
||||
const isValidHex = /^#([0-9A-F]{3}){1,2}$/i.test($(event.target).val());
|
||||
if (isValidHex) {
|
||||
BgColor.val($(event.target).val());
|
||||
element.css('background', $(event.target).val());
|
||||
}
|
||||
} else if (type === 'box') {
|
||||
const isValidHex = /^#([0-9A-F]{3}){1,2}$/i.test($(event.target).val());
|
||||
if (isValidHex) {
|
||||
BgColor.val($(event.target).val());
|
||||
element.find('.title').css('background', $(event.target).val());
|
||||
}
|
||||
/**
|
||||
* Lưu trạng thái hiện tại vào lịch sử trạng thái.
|
||||
*/
|
||||
function saveCurrentState() {
|
||||
const currentHTML = document.documentElement.innerHTML;
|
||||
if (currentStateIndex < historyStates.length - 1) {
|
||||
historyStates = historyStates.slice(0, currentStateIndex + 1);
|
||||
}
|
||||
historyStates.push(currentHTML);
|
||||
currentStateIndex++;
|
||||
}
|
||||
|
||||
function syncColorInputs(event, type) {
|
||||
const inputColorText = $('#colorCodeTitle');
|
||||
let inputBg = '';
|
||||
let Bg = '';
|
||||
if (currentElement.data('type') === 'title' || currentElement.data('type') === 'box') {
|
||||
inputBg = $('#BgcolorCode');
|
||||
bg = $(currentElement).find('.title');
|
||||
} else {
|
||||
inputBg = $('#InputCodeBg');
|
||||
bg = $(currentElement);
|
||||
}
|
||||
|
||||
if (type == 'text' || currentElement.data('type') === 'box') {
|
||||
inputColorText.val($(event.target).val());
|
||||
} else {
|
||||
inputBg.val($(event.target).val());
|
||||
bg.css('background', $(event.target).val());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function updateBorder(type, key, value) {
|
||||
let element = '';
|
||||
if (key == 'background') {
|
||||
element = currentElement;
|
||||
} else if (key == 'text') {
|
||||
element = currentElement.find('.title');
|
||||
}
|
||||
|
||||
if (type == 'border-width') {
|
||||
element.css('border-width', value + 'px');
|
||||
} else if (type == 'border-color') {
|
||||
element.css('border-color', value);
|
||||
} else if (type == 'border-style') {
|
||||
element.css('border-style', value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function updatePosition(event, action) {
|
||||
$('.edit-position').removeClass('active')
|
||||
$(event.target).addClass('active');
|
||||
console.log(action)
|
||||
if (action == 'left') {
|
||||
currentElement.find('.title').css({ 'margin': 'auto auto auto 0', 'justify-content': 'start' });
|
||||
} else if (action == 'center') {
|
||||
currentElement.find('.title').css({ 'margin': '0 auto', 'justify-content': 'center' });
|
||||
} else if (action == 'right') {
|
||||
console.log('aaa', currentElement)
|
||||
currentElement.find('.title').css({ 'margin': 'auto 0 auto auto', 'justify-content': 'end' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function updateSizeBox(type, value) {
|
||||
if (type == 'width') {
|
||||
currentElement.find('.title').css('width', value + 'px')
|
||||
} else if (type == 'height') {
|
||||
currentElement.find('.title').css('height', value + 'px')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function updateBoderRadius(type, key, value) {
|
||||
let element = '';
|
||||
if (key == 'background') {
|
||||
element = currentElement;
|
||||
} else if (key == 'text') {
|
||||
element = currentElement.find('.title');
|
||||
}
|
||||
|
||||
if (type == 'top-left') {
|
||||
element.css('border-top-left-radius', value + 'px');
|
||||
} else if (type == 'top-right') {
|
||||
element.css('border-top-right-radius', value + 'px');
|
||||
} else if (type == 'bottom-left') {
|
||||
element.css('border-bottom-left-radius', value + 'px');
|
||||
} else if (type == 'bottom-right') {
|
||||
element.css('border-bottom-right-radius', value + 'px');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function formatText(command, value) {
|
||||
let $title = '';
|
||||
if (currentElement.data('type') === 'title' || currentElement.data('type') == 'box') {
|
||||
$title = currentElement.find('.title');
|
||||
} else {
|
||||
$title = currentElement;
|
||||
}
|
||||
switch (command) {
|
||||
case 'size':
|
||||
$title.css('font-size', `${value}px`);
|
||||
break;
|
||||
case 'bold':
|
||||
$title.toggleClass('font-bold');
|
||||
break;
|
||||
case 'italic':
|
||||
$title.toggleClass('italic');
|
||||
break;
|
||||
case 'underline':
|
||||
$title.toggleClass('underline');
|
||||
break;
|
||||
case 'line-through':
|
||||
$title.toggleClass('line-through');
|
||||
break;
|
||||
case 'justifyLeft':
|
||||
$title.removeClass('text-center text-right text-justify').addClass('text-left');
|
||||
break;
|
||||
case 'justifyCenter':
|
||||
$title.removeClass('text-left text-right text-justify').addClass('text-center');
|
||||
break;
|
||||
case 'justifyRight':
|
||||
$title.removeClass('text-left text-center text-justify').addClass('text-right');
|
||||
break;
|
||||
case 'justifyFull':
|
||||
$title.removeClass('text-left text-center text-right').addClass('text-justify');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// hủy chỉnh sửa
|
||||
function cancelEdit() {
|
||||
$elements.tabBar.hide();
|
||||
}
|
||||
// (Các hàm khác tương tự như goBack, applyChanges, updateLineHeight, ...)
|
||||
// Đảm bảo mỗi hàm có chú thích chi tiết về chức năng, tham số và kiểu trả về.
|
||||
|
||||
Reference in New Issue
Block a user