trang/thudao/core-v31.html · Bản trước khi chuyển sang kho (tải từ GitHub/jsDelivr)

<div id="studentWorkPage">

<!-- =========================================================
     ADMIN CHẤM BÀI / STUDENT WORK v18.5.1F

     CORE:
     student-work.js @ v18.5.1

     KIẾN TRÚC:
     ---------------------------------------------------------
     - Chấm điểm / nhận xét
     - Xóa bài
     - Quà GVCN
     - Linh thú
     - Phân trang
     - Ưu tiên bài
     - Đồng bộ
     - ADMIN
     - Reward Core Canonical Assets
     - Tài sản / Linh Thạch dùng StudentRewardSystem chung

     MODULE MÔ TẢ:
     ---------------------------------------------------------
     1. Lấy Mô tả bài tập trực tiếp từ:
        NopBaiLuyenTap
        Form Responses 1
        gid = 1837470623

     2. Cấu trúc:
        A = Dấu thời gian
        B = Họ và tên
        C = Mã học viên
        D = Tổ
        E = Khóa
        F = Tải bài tập lên
        G = Mô tả bài tập

     3. Ghép mô tả với bài theo:
        - Google Drive File ID
        - URL bài
        - Mã học viên + thời gian dự phòng

     4. Hiển thị mô tả ngay dưới tên học viên

     5. Tìm kiếm theo:
        - Họ và tên
        - Mã học viên
        - Mô tả bài tập

     6. Icon bút:
        String.fromCodePoint(0x270E)
========================================================= -->


<!-- =========================================================
     CSS v18.5.1
========================================================= -->

<link
    rel="stylesheet"
    href="https://cdn.jsdelivr.net/gh/vnthudao-source/ocd4-assets@v18.5.1/student-work.css"
/>


<!-- =========================================================
     CSS MODULE MÔ TẢ
========================================================= -->

<style>

/* =========================================================
   MÔ TẢ BÀI TẬP
========================================================= */

.tp18-work-description{
    display:flex;
    align-items:flex-start;
    gap:6px;

    width:fit-content;
    max-width:100%;

    box-sizing:border-box;

    margin:5px 0 9px;

    padding:5px 9px;

    border:1px solid #eadfd3;
    border-radius:8px;

    background:#f8f3ed;

    color:#765344;

    font-size:12px;
    font-weight:600;
    line-height:1.45;

    overflow-wrap:anywhere;
}


/* =========================================================
   ICON
========================================================= */

.tp18-work-description-icon{
    flex:0 0 auto;

    display:inline-flex;
    align-items:center;
    justify-content:center;

    min-width:14px;

    color:#a16e45;

    font-size:13px;
    line-height:1.35;
}


/* =========================================================
   TEXT
========================================================= */

.tp18-work-description-text{
    min-width:0;

    color:#765344;

    white-space:normal;
    overflow-wrap:anywhere;
}


/* =========================================================
   MOBILE
========================================================= */

@media (max-width:600px){

    .tp18-work-description{
        gap:4px;

        margin-top:4px;
        margin-bottom:7px;

        padding:4px 7px;

        border-radius:7px;

        font-size:11px;
        line-height:1.4;
    }

    .tp18-work-description-icon{
        min-width:12px;
        font-size:12px;
    }
}

</style>


<!-- =========================================================
     GIAO DIỆN CHÍNH
========================================================= -->

<div
    class="tp18-wrap locked"
    id="tp18-wrap"
>

    <!-- =====================================================
         HEADER
    ====================================================== -->

    <div class="tp18-header">

        <h2 id="tp18-title">
            Tác phẩm học viên
        </h2>

        <p id="tp18-subtitle">
            Tác phẩm · Điểm số · Nhận xét · Linh Thú
        </p>

    </div>


    <!-- =====================================================
         ADMIN NOTE
    ====================================================== -->

    <div
        class="tp18-admin-note"
        id="tp18-admin-note"
    ></div>


    <!-- =====================================================
         STATUS
    ====================================================== -->

    <div
        class="tp18-status"
        id="tp18-status"
    ></div>


    <!-- =====================================================
         TOAST
    ====================================================== -->

    <div
        class="tp18-toast"
        id="tp18-toast"
    ></div>


    <!-- =====================================================
         NỘP BÀI
    ====================================================== -->

    <div
        class="tp18-submit"
        id="tp18-submit"
    >

        <a
            href="https://docs.google.com/forms/d/e/1FAIpQLScfsWRNbv1h3XdJq1K_llPnkmmWOll480qRB5e8aWgvJj5eUw/viewform"
            target="_blank"
            rel="noopener noreferrer"
        >
            📝 Nộp bài tập
        </a>

    </div>


    <!-- =====================================================
         TÌM KIẾM
    ====================================================== -->

    <div
        class="tp18-admin-search"
        id="tp18-admin-search"
    >

        <div class="tp18-search-box">

            <div
                class="tp18-search-icon"
                id="tp18-search-icon"
            ></div>

            <input
                autocomplete="off"
                class="tp18-search-input"
                id="tp18-search-input"
                placeholder="Tìm theo tên, mã học viên hoặc mô tả bài tập..."
                spellcheck="false"
                type="search"
            />

            <button
                class="tp18-search-clear"
                id="tp18-search-clear"
                type="button"
            >
                Xóa
            </button>

        </div>

        <div
            class="tp18-search-info"
            id="tp18-search-info"
        ></div>

    </div>


    <!-- =====================================================
         STUDENT BAR
    ====================================================== -->

    <div
        class="tp18-student-bar"
        id="tp18-student-bar"
    >

        <strong
            id="tp18-student-title"
        ></strong>

        <button
            class="tp18-grade"
            id="tp18-back"
            type="button"
        >
            Quay lại
        </button>

    </div>


    <!-- =====================================================
         PHÂN TRANG TRÊN
    ====================================================== -->

    <div
        class="tp18-pages"
        id="tp18-pages-top"
    ></div>


    <!-- =====================================================
         LOADING
    ====================================================== -->

    <div
        class="tp18-loading"
        id="tp18-loading"
    >
        Đang tải dữ liệu...
    </div>


    <!-- =====================================================
         ERROR
    ====================================================== -->

    <div
        class="tp18-error"
        id="tp18-error"
    ></div>


    <!-- =====================================================
         EMPTY
    ====================================================== -->

    <div
        class="tp18-empty"
        id="tp18-empty"
    >
        Chưa có tác phẩm.
    </div>


    <!-- =====================================================
         GRID
    ====================================================== -->

    <div
        class="tp18-grid"
        id="tp18-grid"
    ></div>


    <!-- =====================================================
         PHÂN TRANG DƯỚI
    ====================================================== -->

    <div
        class="tp18-pages"
        id="tp18-pages-bottom"
    ></div>

</div>


<!-- =========================================================
     IFRAME FORM
========================================================= -->

<iframe
    id="tp18-gift-frame"
    name="tp18-gift-frame"
    style="display:none;width:0;height:0;border:0;"
></iframe>


<!-- =========================================================
     STUDENT WORK v18.5.1F LOADER
========================================================= -->

<script>

(function(){

"use strict";


/* =========================================================
   CHỐNG KHỞI ĐỘNG LẶP
========================================================= */

if(window.TP18_V1851F_STARTED){
    return;
}

window.TP18_V1851F_STARTED = true;


/* =========================================================
   CONFIG
========================================================= */

const NOP_BAI_ID =
"1GJoTRsbq0kZfZrDdh0uCC667PwS3Bgkje2fHQnwnCKs";


/*
   FORM RESPONSES 1

   Tab chứa:
   - Dấu thời gian
   - Họ và tên
   - Mã học viên
   - Tổ
   - Khóa
   - Tải bài tập lên
   - Mô tả bài tập
*/

const FORM_RESPONSES_GID =
"1837470623";


const DESCRIPTION_URL =

"https://docs.google.com/spreadsheets/d/"
+
NOP_BAI_ID
+
"/export?format=csv&gid="
+
FORM_RESPONSES_GID;


/* =========================================================
   CORE v18.5.1

   Đây là bản Core mới:
   - Reward Core Canonical Assets
   - Không tự replay tài sản tại trang Admin
   - Dùng StudentRewardSystem chung
========================================================= */

const CORE_URL =

"https://cdn.jsdelivr.net/gh/"
+
"vnthudao-source/ocd4-assets"
+
"@a6ad656fe020956371cfc96609e6708e2736e949/"
+
"student-work.js";


/* =========================================================
   STATE
========================================================= */

const descriptionIndex = {

    byDriveId:
        new Map(),

    byUrl:
        new Map(),

    byStudentTime:
        new Map()

};


/* =========================================================
   CLEAN
========================================================= */

function clean(value){

    return String(
        value == null
        ?
        ""
        :
        value
    )
    .replace(
        /^\uFEFF/,
        ""
    )
    .trim();
}


/* =========================================================
   NORMALIZE
========================================================= */

function normalizeLocal(value){

    return clean(value)

    .toLowerCase()

    .normalize("NFD")

    .replace(
        /[\u0300-\u036f]/g,
        ""
    )

    .replace(
        /đ/g,
        "d"
    )

    .replace(
        /\s+/g,
        " "
    )

    .trim();
}


/* =========================================================
   CSV PARSER
========================================================= */

function parseCSV(text){

    const rows = [];

    let row = [];
    let value = "";
    let quoted = false;

    text =
    String(text || "")
    .replace(
        /^\uFEFF/,
        ""
    );

    for(
        let i = 0;
        i < text.length;
        i++
    ){

        const char =
        text[i];

        const next =
        text[i + 1];


        /* =================================================
           DOUBLE QUOTE TRONG CELL
        ================================================= */

        if(
            char === '"' &&
            quoted &&
            next === '"'
        ){

            value += '"';

            i++;

            continue;
        }


        /* =================================================
           MỞ / ĐÓNG QUOTE
        ================================================= */

        if(
            char === '"'
        ){

            quoted =
            !quoted;

            continue;
        }


        /* =================================================
           NGĂN CỘT
        ================================================= */

        if(
            char === "," &&
            !quoted
        ){

            row.push(
                value
            );

            value = "";

            continue;
        }


        /* =================================================
           NGĂN DÒNG
        ================================================= */

        if(
            (
                char === "\n" ||
                char === "\r"
            )
            &&
            !quoted
        ){

            if(
                char === "\r" &&
                next === "\n"
            ){

                i++;
            }

            row.push(
                value
            );

            value = "";

            if(
                row.some(
                    function(cell){

                        return(
                            clean(cell) !==
                            ""
                        );
                    }
                )
            ){

                rows.push(
                    row
                );
            }

            row = [];

            continue;
        }

        value += char;
    }


    /* =====================================================
       DÒNG CUỐI
    ====================================================== */

    if(
        value ||
        row.length
    ){

        row.push(
            value
        );

        if(
            row.some(
                function(cell){

                    return(
                        clean(cell) !==
                        ""
                    );
                }
            )
        ){

            rows.push(
                row
            );
        }
    }

    return rows;
}


/* =========================================================
   URL ĐẦU TIÊN TRONG CELL
========================================================= */

function firstUrl(value){

    const text =
    clean(value);

    if(!text){
        return "";
    }

    const match =
    text.match(
        /https?:\/\/[^\s,]+/i
    );

    return(
        match
        ?
        match[0]
        :
        text
    );
}


/* =========================================================
   GOOGLE DRIVE FILE ID
========================================================= */

function getDriveId(value){

    const url =
    firstUrl(
        value
    );

    if(!url){
        return "";
    }

    const patterns = [

        /\/file\/d\/([^/?&#]+)/i,

        /\/d\/([^/?&#]+)/i,

        /[?&]id=([^&#]+)/i

    ];

    for(
        let i = 0;
        i < patterns.length;
        i++
    ){

        const match =
        url.match(
            patterns[i]
        );

        if(
            match &&
            match[1]
        ){

            return clean(
                match[1]
            );
        }
    }

    return "";
}


/* =========================================================
   NORMALIZE URL
========================================================= */

function normalizeUrl(value){

    return firstUrl(
        value
    )

    .replace(
        /&amp;/gi,
        "&"
    )

    .replace(
        /\/+$/,
        ""
    )

    .trim();
}


/* =========================================================
   NORMALIZE TIME
========================================================= */

function normalizeTime(value){

    return clean(value)

    .replace(
        /\s+/g,
        " "
    );
}


/* =========================================================
   STUDENT + TIME KEY
========================================================= */

function studentTimeKey(
    code,
    time
){

    return(

        normalizeLocal(code)

        +

        "||"

        +

        normalizeTime(time)
    );
}


/* =========================================================
   TÌM CỘT HEADER
========================================================= */

function findHeaderIndex(
    header,
    keyword,
    fallback
){

    const normalizedKeyword =
    normalizeLocal(
        keyword
    );

    for(
        let i = 0;
        i < header.length;
        i++
    ){

        if(
            normalizeLocal(
                header[i]
            )
            .includes(
                normalizedKeyword
            )
        ){

            return i;
        }
    }

    return fallback;
}


/* =========================================================
   BUILD DESCRIPTION INDEX
========================================================= */

function buildDescriptionIndex(csvText){

    const rows =
    parseCSV(
        csvText
    );

    if(
        !rows ||
        rows.length < 2
    ){

        throw new Error(
            "Form Responses 1 không có dữ liệu."
        );
    }

    const header =
    rows[0];


    /*
       A = 0 : Dấu thời gian
       C = 2 : Mã học viên
       F = 5 : Tải bài tập lên
       G = 6 : Mô tả bài tập

       Tìm theo header trước,
       fallback theo vị trí cột.
    */

    const timeColumn =
    findHeaderIndex(
        header,
        "Dấu thời gian",
        0
    );

    const codeColumn =
    findHeaderIndex(
        header,
        "Mã học viên",
        2
    );

    const workColumn =
    findHeaderIndex(
        header,
        "Tải bài tập lên",
        5
    );

    const descriptionColumn =
    findHeaderIndex(
        header,
        "Mô tả bài tập",
        6
    );


    if(
        descriptionColumn < 0
    ){

        throw new Error(
            "Không tìm thấy cột Mô tả bài tập."
        );
    }


    descriptionIndex
    .byDriveId
    .clear();

    descriptionIndex
    .byUrl
    .clear();

    descriptionIndex
    .byStudentTime
    .clear();


    /* =====================================================
       DUYỆT TỪ DÒNG 2
    ====================================================== */

    for(
        let r = 1;
        r < rows.length;
        r++
    ){

        const row =
        rows[r];

        const submittedAt =
        clean(
            row[timeColumn]
        );

        const studentCode =
        clean(
            row[codeColumn]
        );

        const workUrl =
        clean(
            row[workColumn]
        );

        const description =
        clean(
            row[descriptionColumn]
        );


        /*
           Không có mô tả
           -> không tạo label.
        */

        if(!description){
            continue;
        }


        /* =================================================
           1. DRIVE FILE ID
        ================================================= */

        const driveId =
        getDriveId(
            workUrl
        );

        if(driveId){

            descriptionIndex
            .byDriveId
            .set(
                driveId,
                description
            );
        }


        /* =================================================
           2. URL
        ================================================= */

        const normalizedWorkUrl =
        normalizeUrl(
            workUrl
        );

        if(normalizedWorkUrl){

            descriptionIndex
            .byUrl
            .set(
                normalizedWorkUrl,
                description
            );
        }


        /* =================================================
           3. STUDENT + TIME
        ================================================= */

        if(
            studentCode &&
            submittedAt
        ){

            descriptionIndex
            .byStudentTime
            .set(

                studentTimeKey(
                    studentCode,
                    submittedAt
                ),

                description
            );
        }
    }
}


/* =========================================================
   LẤY MÔ TẢ CỦA MỘT BÀI
========================================================= */

function getDescription(
    workUrl,
    studentCode,
    submittedAt
){

    /* =====================================================
       ƯU TIÊN 1:
       DRIVE FILE ID
    ====================================================== */

    const driveId =
    getDriveId(
        workUrl
    );

    if(
        driveId &&
        descriptionIndex
        .byDriveId
        .has(
            driveId
        )
    ){

        return(
            descriptionIndex
            .byDriveId
            .get(
                driveId
            )
            ||
            ""
        );
    }


    /* =====================================================
       ƯU TIÊN 2:
       URL
    ====================================================== */

    const normalizedWork =
    normalizeUrl(
        workUrl
    );

    if(
        normalizedWork &&
        descriptionIndex
        .byUrl
        .has(
            normalizedWork
        )
    ){

        return(
            descriptionIndex
            .byUrl
            .get(
                normalizedWork
            )
            ||
            ""
        );
    }


    /* =====================================================
       ƯU TIÊN 3:
       MÃ HỌC VIÊN + THỜI GIAN
    ====================================================== */

    if(
        studentCode &&
        submittedAt
    ){

        const key =
        studentTimeKey(
            studentCode,
            submittedAt
        );

        if(
            descriptionIndex
            .byStudentTime
            .has(
                key
            )
        ){

            return(
                descriptionIndex
                .byStudentTime
                .get(
                    key
                )
                ||
                ""
            );
        }
    }

    return "";
}


/* =========================================================
   BRIDGE MÔ TẢ CHO STUDENT-WORK CORE
========================================================= */

window.TP18_WORK_DESCRIPTION = {

    get:
        function(
            workUrl,
            studentCode,
            submittedAt
        ){

            return getDescription(
                workUrl,
                studentCode,
                submittedAt
            );
        },

    count:
        function(){

            return(
                descriptionIndex
                .byDriveId
                .size
            );
        }

};


/* =========================================================
   FETCH
========================================================= */

async function fetchText(
    url,
    noCache
){

    let finalUrl =
    url;

    if(noCache){

        finalUrl +=
        (
            url.includes("?")
            ?
            "&"
            :
            "?"
        )
        +
        "_="
        +
        Date.now();
    }

    const response =
    await fetch(
        finalUrl,
        {
            cache:
                noCache
                ?
                "no-store"
                :
                "default"
        }
    );

    if(!response.ok){

        throw new Error(
            "HTTP "
            +
            response.status
            +
            ": "
            +
            url
        );
    }

    return await response.text();
}


/* =========================================================
   REPLACE BẮT BUỘC
========================================================= */

function replaceRequired(
    source,
    regex,
    replacement,
    name
){

    regex.lastIndex =
    0;

    if(
        !regex.test(
            source
        )
    ){

        throw new Error(
            "Không tìm thấy vị trí nâng cấp: "
            +
            name
        );
    }

    regex.lastIndex =
    0;

    return source.replace(
        regex,
        replacement
    );
}


/* =========================================================
   PATCH CORE v18.5.1
   THÊM MODULE MÔ TẢ BÀI TẬP

   LƯU Ý:
   Không patch Reward Core.
   Không patch Linh Thạch.
   Không patch tài sản.
   Không patch quyền sở hữu Linh Thú.

   Những phần đó do student-work.js v18.5.1
   + StudentRewardSystem xử lý.
========================================================= */

function patchCore(source){

    let code =
    String(
        source || ""
    );


    /* =====================================================
       PATCH 1
       THÊM DESCRIPTION VÀO ITEM
    ====================================================== */

    code =
    replaceRequired(

        code,

        /item\.__workUrl\s*=\s*originalUrl\(\s*item\.__rawWork\s*\);/,

        [
            "item.__workUrl =",
            "originalUrl(",
            "    item.__rawWork",
            ");",
            "",
            "item.__exerciseDescription =",
            "(",
            "    window.TP18_WORK_DESCRIPTION",
            "    &&",
            "    typeof window.TP18_WORK_DESCRIPTION.get === \"function\"",
            ")",
            "?",
            "window.TP18_WORK_DESCRIPTION.get(",
            "    item.__rawWork,",
            "    item.__code,",
            "    item.__timestampText",
            ")",
            ":",
            "\"\";",
            "",
            "item.__exerciseDescriptionNorm =",
            "normalize(",
            "    item.__exerciseDescription",
            ");"
        ]
        .join(
            "\n"
        ),

        "Mô tả bài tập"
    );


    /* =====================================================
       PATCH 2
       SEARCH:
       TÊN + MÃ + MÔ TẢ
    ====================================================== */

    code =
    replaceRequired(

        code,

        /item\.__nameNorm\s*\.includes\(\s*query\s*\)\s*\|\|\s*item\.__codeNorm\s*\.includes\(\s*query\s*\)/,

        [
            "item.__nameNorm",
            ".includes(query)",
            "",
            "||",
            "",
            "item.__codeNorm",
            ".includes(query)",
            "",
            "||",
            "",
            "(",
            "    item.__exerciseDescriptionNorm",
            "    ||",
            "    \"\"",
            ")",
            ".includes(query)"
        ]
        .join(
            "\n"
        ),

        "Bộ lọc mô tả"
    );


    /* =====================================================
       PATCH 3
       HIỂN THỊ DESCRIPTION SAU TÊN

       Không dùng HTML entity cho icon.
    ====================================================== */

    code =
    replaceRequired(

        code,

        /(<div class="tp18-name">\s*\$\{escapeHTML\(\s*name\s*\|\|\s*"Học viên"\s*\)\}\s*<\/div>)(\s*<div class="tp18-meta">)/,

        function(
            full,
            nameBlock,
            metaBlock
        ){

            return(

                nameBlock

                +

                "\n\n"

                +

                "            ${\n"

                +

                "                item.__exerciseDescription\n"

                +

                "                ?\n"

                +

                "                `\n"

                +

                "                    <div class=\"tp18-work-description\">\n"

                +

                "                        <span class=\"tp18-work-description-icon\">${escapeHTML(String.fromCodePoint(0x270E))}</span>\n"

                +

                "                        <span class=\"tp18-work-description-text\">${escapeHTML(item.__exerciseDescription)}</span>\n"

                +

                "                    </div>\n"

                +

                "                `\n"

                +

                "                :\n"

                +

                "                \"\"\n"

                +

                "            }\n\n"

                +

                metaBlock
            );
        },

        "Hiển thị mô tả"
    );


    /* =====================================================
       PATCH 4
       SUBTITLE KẾT QUẢ SEARCH
    ====================================================== */

    code =
    code.replace(

        /Kết quả theo tên hoặc mã học viên/g,

        "Kết quả theo tên, mã học viên hoặc mô tả bài tập"
    );


    return code;
}


/* =========================================================
   CHẠY CORE
========================================================= */

function executeCore(
    source
){

    const script =
    document.createElement(
        "script"
    );

    script.type =
    "text/javascript";

    script.text =

    source

    +

    "\n//# sourceURL=student-work-v18.5.1F.js";

    document.body.appendChild(
        script
    );
}


/* =========================================================
   FALLBACK

   Nếu module Mô tả bài tập gặp lỗi,
   vẫn tải trực tiếp student-work.js v18.5.1.

   QUAN TRỌNG:
   Fallback KHÔNG quay về v18.4D.
   Như vậy trang không bao giờ quay lại
   công thức tài sản cũ.
========================================================= */

async function loadOriginalCoreOnly(){

    const core =
    await fetchText(
        CORE_URL,
        false
    );

    executeCore(
        core
    );
}


/* =========================================================
   START
========================================================= */

async function start(){

    try{

        /*
           TẢI SONG SONG:

           1. CSV mô tả
           2. student-work.js v18.5.1
        */

        const results =
        await Promise.all([

            fetchText(
                DESCRIPTION_URL,
                true
            ),

            fetchText(
                CORE_URL,
                false
            )

        ]);

        const descriptionCSV =
        results[0];

        const originalCore =
        results[1];


        /* =================================================
           INDEX MÔ TẢ
        ================================================= */

        buildDescriptionIndex(
            descriptionCSV
        );


        /* =================================================
           PATCH MODULE MÔ TẢ
        ================================================= */

        const upgradedCore =
        patchCore(
            originalCore
        );


        /* =================================================
           EXECUTE
        ================================================= */

        executeCore(
            upgradedCore
        );


        console.log(
            "STUDENT WORK v18.5.1F đã khởi động."
        );

        console.log(
            "Core:",
            "student-work.js @ v18.5.1"
        );

        console.log(
            "Số bài có mô tả:",
            window.TP18_WORK_DESCRIPTION.count()
        );


    }catch(error){

        console.error(
            "STUDENT WORK v18.5.1F:",
            error
        );


        /*
           Module mô tả lỗi:
           vẫn giữ trang Admin bằng Core v18.5.1.

           Tuyệt đối không fallback v18.4D.
        */

        try{

            await loadOriginalCoreOnly();

            console.warn(
                "Module mô tả gặp lỗi. Đã chạy trực tiếp Core v18.5.1."
            );

        }catch(fallbackError){

            console.error(
                "Không thể tải Core v18.5.1:",
                fallbackError
            );

            const loading =
            document.getElementById(
                "tp18-loading"
            );

            const errorBox =
            document.getElementById(
                "tp18-error"
            );

            if(loading){

                loading.style.display =
                "none";
            }

            if(errorBox){

                errorBox.style.display =
                "block";

                errorBox.textContent =
                "Không thể tải trang chấm bài.";
            }
        }
    }
}


/* =========================================================
   DOM READY
========================================================= */

if(
    document.readyState ===
    "loading"
){

    document.addEventListener(
        "DOMContentLoaded",
        start,
        {
            once:true
        }
    );

}else{

    start();
}

})();

</script>

</div>

Nhận xét

Bài đăng phổ biến từ blog này

theme/thudao-footer.kho.js · Gộp Reward Core (css v4.2.2 + js f827add) + Minh Hồng footer (css v1.5.7.4 + js 4bff33d) cho chân trang thudao.com

trang/thudao/dang-ky-hoc.html · Bản trước khi chuyển sang kho (tải từ GitHub/jsDelivr)

trang/thudao/bai-viet/benh-cua-thu-dan-duong-dai-the-trien-lam-xu-thu-va-nguy-menh-de-vuot-co-nhan.html · Bản trước khi chuyển sang kho (có video, tải từ GitHub/jsDelivr)