CVNSS commited on
Commit
4ed0260
·
verified ·
1 Parent(s): e501b13

Upload 2 files

Browse files
Files changed (2) hide show
  1. cvnss4.0-converter.js +417 -0
  2. toolCVNSS.html +225 -0
cvnss4.0-converter.js ADDED
@@ -0,0 +1,417 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const CVNSSConverter = (function () {
2
+ // Bảng ánh xạ ký tự đặc biệt
3
+ const specialChars = [
4
+ "`", "“", "”", "<", ">", "@", "-", ";", "=", "…", " ", ",", ".", "?", "!",
5
+ '"', "'", "(", ")", "[", "]", "{", "}", "%", "#", "$", "&", "_", "\\", "/",
6
+ "*", ":", "+", "~", "^", "|", "\r\n", "\r", "\n"
7
+ ];
8
+
9
+ // Bảng ánh xạ phụ âm
10
+ const consonants = {
11
+ cqn: ["ngh", "ng", "ch", "gh", "kh", "nh", "ph", "th", "tr", "gi", "qu", "b", "k", "d", "đ", "g", "h", "c", "l", "m", "n", "r", "s", "t", "v", "x"],
12
+ cvn: ["w", "w", "ch", "g", "k", "nh", "f", "th", "tr", "j", "q", "b", "c", "z", "d", "g", "h", "c", "l", "m", "n", "r", "s", "t", "v", "x"]
13
+ };
14
+
15
+ // Bảng ánh xạ nguyên âm
16
+ const vowels = {
17
+ cqn: [
18
+ "a", "à", "ả", "ã", "á", "ạ", "oa", "òa", "ỏa", "õa", "óa", "ọa", "oà", "oả", "oã", "oá", "oạ", "oác", "oạc", "oách", "oạch",
19
+ "oai", "oài", "oải", "oãi", "oái", "oại", "oao", "oào", "oảo", "oão", "oáo", "oạo", "oáp", "oạp", "oát", "oạt", "oắt", "oặt", "oắc", "oặc",
20
+ "oăn", "oằn", "oẳn", "oẵn", "oắn", "oặn", "oăm", "oằm", "oẳm", "oẵm", "oắm", "oặm", "oăng", "oằng", "oẳng", "oẵng", "oắng", "oặng", "oay", "oày",
21
+ "oảy", "oãy", "oáy", "oạy", "ác", "ạc", "ách", "ạch", "ai", "ài", "ải", "ãi", "ái", "ại", "am", "àm", "ảm", "ãm", "ám", "ạm", "an", "àn",
22
+ "ản", "ãn", "án", "ạn", "oan", "oàn", "oản", "oãn", "oán", "oạn", "oanh", "oành", "oảnh", "oãnh", "oánh", "oạnh", "ang", "àng", "ảng", "ãng",
23
+ "áng", "ạng", "oang", "oàng", "oảng", "oãng", "oáng", "oạng", "anh", "ành", "ảnh", "ãnh", "ánh", "ạnh", "ao", "ào", "ảo", "ão", "áo", "ạo",
24
+ "áp", "ạp", "át", "ạt", "au", "àu", "ảu", "áu", "ạu", "ay", "ày", "ảy", "ãy", "áy", "ạy", "ắc", "ặc", "ăm", "ằm", "ẳm", "ẵm", "ắm", "ặm",
25
+ "ăn", "ằn", "ẳn", "ẵn", "ắn", "ặn", "ăng", "ằng", "ẳng", "ẵng", "ắng", "ặng", "ắp", "ặp", "ắt", "ặt", "ấc", "ậc", "âm", "ầm", "ẩm", "ẫm",
26
+ "ấm", "ậm", "ân", "ần", "ẩn", "ẫn", "ấn", "ận", "âng", "ầng", "ẩng", "ẫng", "ấng", "ậng", "uân", "uần", "uẩn", "uẫn", "uấn", "uận", "uâng",
27
+ "uầng", "uẩng", "uẫng", "uấng", "uậng", "ấp", "ập", "ất", "ật", "uất", "uật", "âu", "ầu", "ẩu", "ẫu", "ấu", "ậu", "ây", "ầy", "ẩy", "ẫy",
28
+ "ấy", "ậy", "uây", "uầy", "uẩy", "uẫy", "uấy", "uậy", "e", "è", "ẻ", "ẽ", "é", "ẹ", "oe", "òe", "ỏe", "õe", "óe", "ọe", "éc", "ẹc", "em",
29
+ "èm", "ẻm", "ẽm", "ém", "ẹm", "en", "èn", "ẻn", "ẽn", "én", "ẹn", "oen", "oèn", "oẻn", "oẽn", "oén", "oẹn", "eng", "èng", "ẻng", "ẽng",
30
+ "éng", "ẹng", "eo", "èo", "ẻo", "ẽo", "éo", "ẹo", "oeo", "oèo", "oẻo", "oẽo", "oéo", "oẹo", "ép", "ẹp", "ét", "ẹt", "oét", "oẹt", "ê",
31
+ "ề", "ể", "ễ", "ế", "ệ", "uê", "uề", "uể", "uễ", "uế", "uệ", "ếch", "ệch", "uếch", "uệch", "êm", "ềm", "ểm", "ễm", "ếm", "ệm", "ên", "ền",
32
+ "ển", "ễn", "ến", "ện", "ênh", "ềnh", "ểnh", "ễnh", "ếnh", "ệnh", "uênh", "uềnh", "uểnh", "uễnh", "uếnh", "uệnh", "ếp", "ệp", "ết", "ệt",
33
+ "êu", "ều", "ểu", "ễu", "ếu", "ệu", "i", "ì", "ỉ", "ĩ", "í", "ị", "uy", "ùy", "ủy", "ũy", "úy", "ụy", "uỳ", "uỷ", "uỹ", "uý", "uỵ", "ia",
34
+ "ìa", "ỉa", "ĩa", "ía", "ịa", "uya", "íc", "ích", "ịch", "uých", "uỵch", "iếc", "iệc", "iêm", "iềm", "iểm", "iễm", "iếm", "iệm", "iên",
35
+ "iền", "iển", "iễn", "iến", "iện", "uyên", "uyền", "uyển", "uyễn", "uyến", "uyện", "iêng", "iềng", "iểng", "iễng", "iếng", "iệng", "iếp",
36
+ "iệp", "iết", "iệt", "uyết", "uyệt", "iêu", "iều", "iểu", "iễu", "iếu", "iệu", "yêt", "yệt", "yên", "yền", "yển", "yễn", "yến", "yện",
37
+ "yêm", "yềm", "yểm", "yễm", "yếm", "yệm", "yêng", "yềng", "yểng", "yễng", "yếng", "yệnh", "yêu", "yều", "yểu", "yễu", "yếu", "yệu", "im",
38
+ "ìm", "ỉm", "ĩm", "ím", "ịm", "in", "ìn", "ỉn", "ĩn", "ín", "ịn", "inh", "ình", "ỉnh", "ĩnh", "ính", "ịnh", "uynh", "uỳnh", "uỷnh", "uỹnh",
39
+ "uýnh", "uỵnh", "íp", "ịp", "uýp", "uỵp", "ít", "ịt", "uýt", "uỵt", "iu", "ìu", "ỉu", "ĩu", "íu", "ịu", "uyu", "uỳu", "uỷu", "uỹu", "uýu",
40
+ "uỵu", "uỳn", "uỷn", "uỹn", "uýn", "uỵn", "o", "ò", "ỏ", "õ", "ó", "ọ", "óc", "ọc", "oi", "òi", "ỏi", "õi", "ói", "ọi", "om", "òm", "ỏm",
41
+ "õm", "óm", "ọm", "on", "òn", "ỏn", "õn", "ón", "ọn", "ong", "òng", "ỏng", "õng", "óng", "ọng", "oóc", "oong", "oòng", "oỏng", "oõng",
42
+ "oóng", "oòng", "oọng", "óp", "ọp", "ót", "ọt", "ô", "ồ", "ổ", "ỗ", "ố", "ộ", "ốc", "ộc", "ôi", "ồi", "ổi", "ỗi", "ối", "ội", "ôm", "ồm",
43
+ "ổm", "ỗm", "ốm", "ộm", "ôn", "ồn", "ổn", "ỗn", "ốn", "ộn", "ông", "ồng", "ổng", "ỗng", "ống", "ộng", "ốp", "ộp", "ốt", "ột", "ơ", "ờ",
44
+ "ở", "ỡ", "ớ", "ợ", "ơi", "ời", "ởi", "ỡi", "ới", "ợi", "ơm", "ờm", "ởm", "ỡm", "ớm", "ợm", "ơn", "ờn", "ởn", "ỡn", "ớn", "ợn", "ơng",
45
+ "ờng", "ởng", "ỡng", "ớng", "ợng", "ớp", "ợp", "ớt", "ợt", "u", "ù", "ủ", "ũ", "ú", "ụ", "ua", "ùa", "ủa", "ũa", "úa", "ụa", "úc", "ục",
46
+ "ui", "ùi", "ủi", "ũi", "úi", "ụi", "um", "ùm", "ủm", "ũm", "úm", "ụm", "un", "ùn", "ủn", "ũn", "ún", "ụn", "ung", "ùng", "ủng", "ũng",
47
+ "úng", "ụng", "uơ", "uờ", "uở", "uỡ", "uớ", "uợ", "uơn", "uờn", "uởn", "uỡn", "uớn", "uợn", "uớt", "uợt", "uốc", "uộc", "uôi", "uồi", "uổi",
48
+ "uỗi", "uối", "uội", "uôm", "uồm", "uổm", "uỗm", "uốm", "uộm", "uôn", "uồn", "uổn", "uỗn", "uốn", "uộn", "uông", "uồng", "uổng", "uỗng",
49
+ "uống", "uộng", "uốt", "uột", "uốp", "uộp", "úp", "ụp", "út", "ụt", "ư", "ừ", "ử", "ữ", "ứ", "ự", "ưa", "ừa", "ửa", "ữa", "ứa", "ựa",
50
+ "ức", "ực", "ưi", "ừi", "ửi", "ữi", "ứi", "ựi", "ưm", "ừm", "ửm", "ữm", "ứm", "ựm", "ưn", "ừn", "ửn", "ữn", "ứn", "ựn", "ưng", "ừng",
51
+ "ửng", "ững", "ứng", "ựng", "ước", "ược", "ươi", "ười", "ưởi", "ưỡi", "ưới", "ượi", "ươm", "ườm", "ưởm", "ưỡm", "ướm", "ượm", "ươn",
52
+ "ườn", "ưởn", "ưỡn", "ướn", "ượn", "ương", "ường", "ưởng", "ưỡng", "ướng", "ượng", "ướp", "ượp", "ướt", "ượt", "ươu", "ườu", "ưởu",
53
+ "ưỡu", "ướu", "ượu", "ứt", "ựt", "ưu", "ừu", "ửu", "ữu", "ứu", "ựu", "y", "ỳ", "ỷ", "ỹ", "ý", "ỵ", "ỳa", "ỷa", "ỹa", "ýa", "ỵa"
54
+ ],
55
+ cvn: [
56
+ "a", "al", "az", "as", "aj", "ar", "oa", "oal", "oaz", "oas", "oaj", "oar", "oal", "oaz", "oas", "oaj", "oar", "osj", "osr", "oakj", "oakr",
57
+ "ojp", "ojl", "ojz", "ojs", "ojj", "ojr", "owp", "owl", "owz", "ows", "owj", "owr", "ofj", "ofr", "odj", "odr", "adx", "adh", "asx", "ash",
58
+ "alo", "alk", "alv", "alw", "alx", "alh", "avo", "avk", "avv", "avw", "avx", "avh", "azo", "azk", "azv", "azw", "azx", "azh", "ajp", "ajl",
59
+ "ajz", "ajs", "ajj", "ajr", "ac", "acr", "akj", "akr", "ai", "ail", "aiz", "ais", "aij", "air", "am", "aml", "amz", "ams", "amj", "amr",
60
+ "an", "anl", "anz", "ans", "anj", "anr", "olp", "oll", "olz", "ols", "olj", "olr", "oahp", "oahl", "oahz", "oahs", "oahj", "oahr", "agp",
61
+ "agl", "agz", "ags", "agj", "agr", "ozp", "ozl", "ozz", "ozs", "ozj", "ozr", "ahp", "ahl", "ahz", "ahs", "ahj", "ahr", "ao", "aol", "aoz",
62
+ "aos", "aoj", "aor", "ap", "apr", "at", "atr", "au", "aul", "auz", "auj", "aur", "ay", "ayl", "ayz", "ays", "ayj", "ayr", "acx", "ach",
63
+ "amo", "amk", "amv", "amw", "amx", "amh", "ano", "ank", "anv", "anw", "anx", "anh", "ago", "agk", "agv", "agw", "agx", "agh", "apx", "aph",
64
+ "atx", "ath", "acb", "acf", "amy", "amd", "amq", "amg", "amb", "amf", "any", "and", "anq", "ang", "anb", "anf", "agy", "agd", "agq", "agg",
65
+ "agb", "agf", "aly", "ald", "alq", "alg", "alb", "alf", "azy", "azd", "azq", "azg", "azb", "azf", "apb", "apf", "atb", "atf", "adb", "adf",
66
+ "auy", "aud", "auq", "aug", "aub", "auf", "ayy", "ayd", "ayq", "ayg", "ayb", "ayf", "ajy", "ajd", "ajq", "ajg", "ajb", "ajf", "e", "el",
67
+ "ez", "es", "ej", "er", "oe", "oel", "oez", "oes", "oej", "oer", "ec", "ecr", "em", "eml", "emz", "ems", "emj", "emr", "en", "enl", "enz",
68
+ "ens", "enj", "enr", "elp", "ell", "elz", "els", "elj", "elr", "egp", "egl", "egz", "egs", "egj", "egr", "eo", "eol", "eoz", "eos", "eoj",
69
+ "eor", "ewp", "ewl", "ewz", "ews", "ewj", "ewr", "ep", "epr", "et", "etr", "edj", "edr", "ey", "ed", "eq", "eg", "eb", "ef", "uey", "ued",
70
+ "ueq", "ueg", "ueb", "uef", "ekb", "ekf", "uekb", "uekf", "emy", "emd", "emq", "emg", "emb", "emf", "eny", "end", "enq", "eng", "enb", "enf",
71
+ "ehy", "ehd", "ehq", "ehg", "ehb", "ehf", "uehy", "uehd", "uehq", "uehg", "uehb", "uehf", "epb", "epf", "etb", "etf", "euy", "eud", "euq",
72
+ "eug", "eub", "euf", "i", "il", "iz", "is", "ij", "ir", "y", "yl", "yz", "ys", "yj", "yr", "yl", "yz", "ys", "yj", "yr", "ia", "ial", "iaz",
73
+ "ias", "iaj", "iar", "ya", "ic", "ikj", "ikr", "ykj", "ykr", "isb", "isf", "ivy", "ivd", "ivq", "ivg", "ivb", "ivf", "ily", "ild", "ilq",
74
+ "ilg", "ilb", "ilf", "yly", "yld", "ylq", "ylg", "ylb", "ylf", "izy", "izd", "izq", "izg", "izb", "izf", "ifb", "iff", "idb", "idf", "ydb",
75
+ "ydf", "iwy", "iwd", "iwq", "iwg", "iwb", "iwf", "idb", "idf", "ily", "ild", "ilq", "ilg", "ilb", "ilf", "ivy", "ivd", "ivq", "ivg", "ivb",
76
+ "ivf", "izy", "izd", "izq", "izg", "izb", "izf", "iwy", "iwd", "iwq", "iwg", "iwb", "iwf", "im", "iml", "imz", "ims", "imj", "imr", "in",
77
+ "inl", "inz", "ins", "inj", "inr", "ihp", "ihl", "ihz", "ihs", "ihj", "ihr", "yhp", "yhl", "yhz", "yhs", "yhj", "yhr", "ip", "ipr", "yp",
78
+ "ypr", "it", "itr", "yt", "ytr", "iu", "iul", "iuz", "ius", "iuj", "iur", "yu", "yul", "yuz", "yus", "yuj", "yur", "ynl", "ynz", "yns",
79
+ "ynj", "ynr", "o", "ol", "oz", "os", "oj", "or", "oc", "ocr", "oi", "oil", "oiz", "ois", "oij", "oir", "om", "oml", "omz", "oms", "omj",
80
+ "omr", "on", "onl", "onz", "ons", "onj", "onr", "ogp", "ogl", "ogz", "ogs", "ogj", "ogr", "ooc", "oog", "oogl", "oogz", "oogs", "oogj",
81
+ "oogl", "oogr", "op", "opr", "ot", "otr", "oy", "od", "oq", "og", "ob", "of", "ocb", "ocf", "oiy", "oid", "oiq", "oig", "oib", "oif",
82
+ "omy", "omd", "omq", "omg", "omb", "omf", "ony", "ond", "onq", "ong", "onb", "onf", "ogy", "ogd", "ogq", "ogg", "ogb", "ogf", "opb",
83
+ "opf", "otb", "otf", "oo", "ok", "ov", "ow", "ox", "oh", "oio", "oik", "oiv", "oiw", "oix", "oih", "omo", "omk", "omv", "omw", "omx",
84
+ "omh", "ono", "onk", "onv", "onw", "onx", "onh", "ogo", "ogk", "ogv", "ogw", "ogx", "ogh", "opx", "oph", "otx", "oth", "u", "ul", "uz",
85
+ "us", "uj", "ur", "ua", "ual", "uaz", "uas", "uaj", "uar", "uc", "ucr", "ui", "uil", "uiz", "uis", "uij", "uir", "um", "uml", "umz", "ums",
86
+ "umj", "umr", "un", "unl", "unz", "uns", "unj", "unr", "ugp", "ugl", "ugz", "ugs", "ugj", "ugr", "uoo", "uok", "uov", "uow", "uox", "uoh",
87
+ "olo", "olk", "olv", "olw", "olx", "olh", "odx", "odh", "usb", "usf", "ujy", "ujd", "ujq", "ujg", "ujb", "ujf", "uvy", "uvd", "uvq", "uvg",
88
+ "uvb", "uvf", "uly", "uld", "ulq", "ulg", "ulb", "ulf", "uzy", "uzd", "uzq", "uzg", "uzb", "uzf", "udb", "udf", "ufb", "uff", "up", "upr",
89
+ "ut", "utr", "uo", "uk", "uv", "uw", "ux", "uh", "uao", "uak", "uav", "uaw", "uax", "uah", "ucx", "uch", "uio", "uik", "uiv", "uiw", "uix",
90
+ "uih", "umo", "umk", "umv", "umw", "umx", "umh", "uno", "unk", "unv", "unw", "unx", "unh", "ugo", "ugk", "ugv", "ugw", "ugx", "ugh", "usx",
91
+ "ush", "ujo", "ujk", "ujv", "ujw", "ujx", "ujh", "uvo", "uvk", "uvv", "uvw", "uvx", "uvh", "ulo", "ulk", "ulv", "ulw", "ulx", "ulh", "uzo",
92
+ "uzk", "uzv", "uzw", "uzx", "uzh", "ufx", "ufh", "udx", "udh", "uwo", "uwk", "uwv", "uww", "uwx", "uwh", "utx", "uth", "uuo", "uuk", "uuv",
93
+ "uuw", "uux", "uuh", "i", "il", "iz", "is", "ij", "ir", "ial", "iaz", "ias", "iaj", "iar"
94
+ ],
95
+ cvss: [
96
+ "a", "al", "az", "as", "aj", "ar", "oa", "oal", "oaz", "oas", "oaj", "oar", "oal", "oaz", "oas", "oaj", "oar", "osj", "osr", "oakj", "oakr",
97
+ "ojp", "ojl", "ojz", "ojs", "ojj", "ojr", "owp", "owl", "owz", "ows", "owj", "owr", "ofj", "ofr", "odj", "odr", "adx", "adh", "asx", "ash",
98
+ "alo", "alk", "alv", "alw", "alx", "alh", "avo", "avk", "avv", "avw", "avx", "avh", "azo", "azk", "azv", "azw", "azx", "azh", "ajp", "ajl",
99
+ "ajz", "ajs", "ajj", "ajr", "ac", "acr", "akj", "akr", "ai", "ail", "aiz", "ais", "aij", "air", "am", "aml", "amz", "ams", "amj", "amr",
100
+ "an", "anl", "anz", "ans", "anj", "anr", "olp", "oll", "olz", "ols", "olj", "olr", "oahp", "oahl", "oahz", "oahs", "oahj", "oahr", "agp",
101
+ "agl", "agz", "ags", "agj", "agr", "ozp", "ozl", "ozz", "ozs", "ozj", "ozr", "ahp", "ahl", "ahz", "ahs", "ahj", "ahr", "ao", "aol", "aoz",
102
+ "aos", "aoj", "aor", "ap", "apr", "at", "atr", "au", "aul", "auz", "auj", "aur", "ay", "ayl", "ayz", "ays", "ayj", "ayr", "acx", "ach",
103
+ "amo", "amk", "amv", "amw", "amx", "amh", "ano", "ank", "anv", "anw", "anx", "anh", "ago", "agk", "agv", "agw", "agx", "agh", "apx", "aph",
104
+ "atx", "ath", "acb", "acf", "amy", "amd", "amq", "amg", "amb", "amf", "any", "and", "anq", "ang", "anb", "anf", "agy", "agd", "agq", "agg",
105
+ "agb", "agf", "aly", "ald", "alq", "alg", "alb", "alf", "azy", "azd", "azq", "azg", "azb", "azf", "apb", "apf", "atb", "atf", "adb", "adf",
106
+ "auy", "aud", "auq", "aug", "aub", "auf", "ayy", "ayd", "ayq", "ayg", "ayb", "ayf", "ajy", "ajd", "ajq", "ajg", "ajb", "ajf", "e", "el",
107
+ "ez", "es", "ej", "er", "oe", "oel", "oez", "oes", "oej", "oer", "ec", "ecr", "em", "eml", "emz", "ems", "emj", "emr", "en", "enl", "enz",
108
+ "ens", "enj", "enr", "elp", "ell", "elz", "els", "elj", "elr", "egp", "egl", "egz", "egs", "egj", "egr", "eo", "eol", "eoz", "eos", "eoj",
109
+ "eor", "ewp", "ewl", "ewz", "ews", "ewj", "ewr", "ep", "epr", "et", "etr", "edj", "edr", "ey", "ed", "eq", "eg", "eb", "ef", "uey", "ued",
110
+ "ueq", "ueg", "ueb", "uef", "ekb", "ekf", "uekb", "uekf", "emy", "emd", "emq", "emg", "emb", "emf", "eny", "end", "enq", "eng", "enb", "enf",
111
+ "ehy", "ehd", "ehq", "ehg", "ehb", "ehf", "uehy", "uehd", "uehq", "uehg", "uehb", "uehf", "epb", "epf", "etb", "etf", "euy", "eud", "euq",
112
+ "eug", "eub", "euf", "i", "il", "iz", "is", "ij", "ir", "y", "yl", "yz", "ys", "yj", "yr", "yl", "yz", "ys", "yj", "yr", "ia", "ial", "iaz",
113
+ "ias", "iaj", "iar", "ya", "ic", "ikj", "ikr", "ykj", "ykr", "isb", "isf", "ivy", "ivd", "ivq", "ivg", "ivb", "ivf", "ily", "ild", "ilq",
114
+ "ilg", "ilb", "ilf", "yly", "yld", "ylq", "ylg", "ylb", "ylf", "izy", "izd", "izq", "izg", "izb", "izf", "ifb", "iff", "idb", "idf", "ydb",
115
+ "ydf", "iwy", "iwd", "iwq", "iwg", "iwb", "iwf", "idb", "idf", "ily", "ild", "ilq", "ilg", "ilb", "ilf", "ivy", "ivd", "ivq", "ivg", "ivb",
116
+ "ivf", "izy", "izd", "izq", "izg", "izb", "izf", "iwy", "iwd", "iwq", "iwg", "iwb", "iwf", "im", "iml", "imz", "ims", "imj", "imr", "in",
117
+ "inl", "inz", "ins", "inj", "inr", "ihp", "ihl", "ihz", "ihs", "ihj", "ihr", "yhp", "yhl", "yhz", "yhs", "yhj", "yhr", "ip", "ipr", "yp",
118
+ "ypr", "it", "itr", "yt", "ytr", "iu", "iul", "iuz", "ius", "iuj", "iur", "yu", "yul", "yuz", "yus", "yuj", "yur", "ynl", "ynz", "yns",
119
+ "ynj", "ynr", "o", "ol", "oz", "os", "oj", "or", "oc", "ocr", "oi", "oil", "oiz", "ois", "oij", "oir", "om", "oml", "omz", "oms", "omj",
120
+ "omr", "on", "onl", "onz", "ons", "onj", "onr", "ogp", "ogl", "ogz", "ogs", "ogj", "ogr", "ooc", "oog", "oogl", "oogz", "oogs", "oogj",
121
+ "oogl", "oogr", "op", "opr", "ot", "otr", "oy", "od", "oq", "og", "ob", "of", "ocb", "ocf", "oiy", "oid", "oiq", "oig", "oib", "oif",
122
+ "omy", "omd", "omq", "omg", "omb", "omf", "ony", "ond", "onq", "ong", "onb", "onf", "ogy", "ogd", "ogq", "ogg", "ogb", "ogf", "opb",
123
+ "opf", "otb", "otf", "oo", "ok", "ov", "ow", "ox", "oh", "oio", "oik", "oiv", "oiw", "oix", "oih", "omo", "omk", "omv", "omw", "omx",
124
+ "omh", "ono", "onk", "onv", "onw", "onx", "onh", "ogo", "ogk", "ogv", "ogw", "ogx", "ogh", "opx", "oph", "otx", "oth", "u", "ul", "uz",
125
+ "us", "uj", "ur", "ua", "ual", "uaz", "uas", "uaj", "uar", "uc", "ucr", "ui", "uil", "uiz", "uis", "uij", "uir", "um", "uml", "umz", "ums",
126
+ "umj", "umr", "un", "unl", "unz", "uns", "unj", "unr", "ugp", "ugl", "ugz", "ugs", "ugj", "ugr", "uoo", "uok", "uov", "uow", "uox", "uoh",
127
+ "olo", "olk", "olv", "olw", "olx", "olh", "odx", "odh", "usb", "usf", "ujy", "ujd", "ujq", "ujg", "ujb", "ujf", "uvy", "uvd", "uvq", "uvg",
128
+ "uvb", "uvf", "uly", "uld", "ulq", "ulg", "ulb", "ulf", "uzy", "uzd", "uzq", "uzg", "uzb", "uzf", "udb", "udf", "ufb", "uff", "up", "upr",
129
+ "ut", "utr", "uo", "uk", "uv", "uw", "ux", "uh", "uao", "uak", "uav", "uaw", "uax", "uah", "ucx", "uch", "uio", "uik", "uiv", "uiw", "uix",
130
+ "uih", "umo", "umk", "umv", "umw", "umx", "umh", "uno", "unk", "unv", "unw", "unx", "unh", "ugo", "ugk", "ugv", "ugw", "ugx", "ugh", "usx",
131
+ "ush", "ujo", "ujk", "ujv", "ujw", "ujx", "ujh", "uvo", "uvk", "uvv", "uvw", "uvx", "uvh", "ulo", "ulk", "ulv", "ulw", "ulx", "ulh", "uzo",
132
+ "uzk", "uzv", "uzw", "uzx", "uzh", "ufx", "ufh", "udx", "udh", "uwo", "uwk", "uwv", "uww", "uwx", "uwh", "utx", "uth", "uuo", "uuk", "uuv",
133
+ "uuw", "uux", "uuh", "i", "il", "iz", "is", "ij", "ir", "ial", "iaz", "ias", "iaj", "iar"
134
+ ]
135
+ };
136
+
137
+ // Bảng ánh xạ nguyên âm cơ bản
138
+ const baseVowels = [
139
+ "aàảãáạ", "ăằẳẵắặ", "âầẩẫấậ", "eèẻẽéẹ", "êềểễếệ", "iìỉĩíị",
140
+ "oòỏõóọ", "ôồổỗốộ", "ơờởỡớợ", "uùủũúụ", "ưừửữứự", "yỳỷỹýỵ"
141
+ ];
142
+
143
+ // Bảng ánh xạ thay thế đặc biệt
144
+ const specialReplacements = {
145
+ y: "yỳỷỹýỵ",
146
+ i: "iìỉĩíị"
147
+ };
148
+
149
+ // Quy tắc điều chỉnh phụ âm
150
+ const consonantAdjustments = {
151
+ phu_am: ["ngh", "gh", "k"],
152
+ phu_am_chuyen_doi: ["ng", "g", "c"],
153
+ nguyen_am: "ieê"
154
+ };
155
+
156
+ // Hàm kiểm tra chữ in hoa
157
+ function isUpperCase(str) {
158
+ return /^[A-ZÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬĐÈÉẺẼẸÊỀẾỂỄỆÌÍỈĨỊÒÓỎÕỌÔỒỐỔỖỘƠỜỚỞỠỢÙÚỦŨỤƯỪỨỬỮỰỲÝỶỸỴ]+$/.test(str);
159
+ }
160
+
161
+ // Hàm lấy nguyên âm cơ bản
162
+ function getBaseVowel(char) {
163
+ for (const vowelGroup of baseVowels) {
164
+ if (vowelGroup.includes(char)) return vowelGroup[0];
165
+ }
166
+ return char;
167
+ }
168
+
169
+ // Hàm tách chuỗi thành token
170
+ function splitString(str) {
171
+ str = str.normalize("NFC");
172
+ const tokens = str.split(/([\s|,|;|`|@|<|>|“|”|.|=|…|?|!|\\|'|"|(|)|[|\]|{|}|%|#|$|&|\-|_|/|*|:|+|~|^|||\r\n|\n|\r])/gm);
173
+ return tokens.filter(token => token !== "");
174
+ }
175
+
176
+ // Hàm điều chỉnh phụ âm và nguyên âm
177
+ function adjustConsonantVowel(cqnPad, cqnVan) {
178
+ const firstChar = cqnVan[0] || "";
179
+ if (cqnPad === "qu" && getBaseVowel(firstChar) === "u") {
180
+ cqnPad = "q";
181
+ }
182
+ if (!cqnPad && getBaseVowel(firstChar) === "i") {
183
+ cqnVan = cqnVan.replace(firstChar, specialReplacements.y[specialReplacements.i.indexOf(firstChar)]);
184
+ }
185
+ if (cqnPad === "gi" && getBaseVowel(firstChar) === "i") {
186
+ cqnPad = "g";
187
+ }
188
+ if (consonantAdjustments.phu_am.includes(cqnPad) && !consonantAdjustments.nguyen_am.includes(getBaseVowel(firstChar))) {
189
+ cqnPad = consonantAdjustments.phu_am_chuyen_doi[consonantAdjustments.phu_am.indexOf(cqnPad)];
190
+ }
191
+ return { cqnPad, cqnVan };
192
+ }
193
+
194
+ // Hàm chuyển từ CQN sang CVN và CVSS
195
+ function cqnToCvnAndCvss(word) {
196
+ const lowerWord = word.toLowerCase();
197
+ let consonant = "", vowelPart = lowerWord, cvnConsonant = "", cqnResult = "", cvnResult = "", cvssResult = "";
198
+
199
+ // Tìm phụ âm
200
+ for (const c of consonants.cqn) {
201
+ if (lowerWord.startsWith(c)) {
202
+ consonant = c;
203
+ vowelPart = lowerWord.replace(c, "");
204
+ break;
205
+ }
206
+ }
207
+
208
+ // Điều chỉnh đặc biệt
209
+ if (consonant === "gi" && vowelPart !== "a" && vowels.cqn.includes("i" + vowelPart)) {
210
+ vowelPart = "i" + vowelPart;
211
+ }
212
+ if (consonant === "qu" && getBaseVowel(vowelPart[0]) === "y") {
213
+ vowelPart = "u" + vowelPart;
214
+ }
215
+ if (consonant === "g" && getBaseVowel(vowelPart[0]) === "i") {
216
+ consonant = "gi";
217
+ }
218
+
219
+ // Ánh xạ phụ âm
220
+ cvnConsonant = consonants.cqn.includes(consonant) ? consonants.cvn[consonants.cqn.indexOf(consonant)] : consonant;
221
+
222
+ // Ánh xạ nguyên âm
223
+ const vowelIndex = vowels.cqn.indexOf(vowelPart);
224
+ if (vowelIndex !== -1) {
225
+ cqnResult = vowelPart;
226
+ cvnResult = vowels.cvn[vowelIndex];
227
+ cvssResult = vowels.cvss[vowelIndex];
228
+ } else {
229
+ cqnResult = vowelPart;
230
+ cvnResult = vowelPart;
231
+ cvssResult = vowelPart;
232
+ }
233
+
234
+ // Kết hợp kết quả
235
+ let cvnOutput = cvnConsonant + cvnResult;
236
+ let cvssOutput = cvnConsonant + cvssResult;
237
+
238
+ // Xử lý chữ hoa
239
+ if (word[0] !== word[0].toLowerCase()) {
240
+ if (cqnResult.length) cqnResult = cqnResult[0].toUpperCase() + cqnResult.slice(1);
241
+ if (cvnOutput.length) cvnOutput = cvnOutput[0].toUpperCase() + cvnOutput.slice(1);
242
+ if (cvssOutput.length) cvssOutput = cvssOutput[0].toUpperCase() + cvssOutput.slice(1);
243
+ }
244
+ if (isUpperCase(word)) {
245
+ cqnResult = cqnResult.toUpperCase();
246
+ cvnOutput = cvnOutput.toUpperCase();
247
+ cvssOutput = cvssOutput.toUpperCase();
248
+ }
249
+
250
+ return { cqn: cqnResult, cvn: cvnOutput, cvss: cvssOutput };
251
+ }
252
+
253
+ // Hàm chuyển từ CVN sang CQN và CVSS
254
+ function cvnToCqnAndCvss(word) {
255
+ const lowerWord = word.toLowerCase();
256
+ let consonant = "", vowelPart = lowerWord, cqnConsonant = "", cqnResult = "", cvnResult = "", cvssResult = "";
257
+
258
+ // Tìm phụ âm
259
+ for (const c of consonants.cvn) {
260
+ if (lowerWord.startsWith(c)) {
261
+ consonant = c;
262
+ cqnConsonant = consonants.cqn[consonants.cvn.indexOf(c)];
263
+ vowelPart = lowerWord.replace(c, "");
264
+ break;
265
+ }
266
+ }
267
+
268
+ // Ánh xạ nguyên âm
269
+ const vowelIndex = vowels.cvn.indexOf(vowelPart);
270
+ if (vowelIndex !== -1) {
271
+ cqnResult = vowels.cqn[vowelIndex];
272
+ cvnResult = vowelPart;
273
+ cvssResult = vowels.cvss[vowelIndex];
274
+ } else {
275
+ cqnResult = vowelPart;
276
+ cvnResult = vowelPart;
277
+ cvssResult = vowelPart;
278
+ }
279
+
280
+ // Điều chỉnh đặc biệt
281
+ if (consonant === "j" && vowelPart === "ịa") {
282
+ cqnConsonant = "gi";
283
+ cqnResult = "ỵa";
284
+ }
285
+
286
+ // Điều chỉnh phụ âm và nguyên âm
287
+ const adjusted = adjustConsonantVowel(cqnConsonant, cqnResult);
288
+ cqnConsonant = adjusted.cqnPad;
289
+ cqnResult = adjusted.cqnVan;
290
+
291
+ // Kết hợp kết quả
292
+ let cqnOutput = cqnConsonant + cqnResult;
293
+ let cvssOutput = consonant + cvssResult;
294
+
295
+ // Xử lý chữ hoa
296
+ if (word[0] !== word[0].toLowerCase()) {
297
+ if (cqnOutput.length) cqnOutput = cqnOutput[0].toUpperCase() + cqnOutput.slice(1);
298
+ if (cvnResult.length) cvnResult = cvnResult[0].toUpperCase() + cvnResult.slice(1);
299
+ if (cvssOutput.length) cvssOutput = cvssOutput[0].toUpperCase() + cvssOutput.slice(1);
300
+ }
301
+ if (isUpperCase(word)) {
302
+ cqnOutput = cqnOutput.toUpperCase();
303
+ cvnResult = cvnResult.toUpperCase();
304
+ cvssOutput = cvssOutput.toUpperCase();
305
+ }
306
+
307
+ return { cqn: cqnOutput, cvn: cvnResult, cvss: cvssOutput };
308
+ }
309
+
310
+ // Hàm chuyển từ CVSS sang CQN và CVN
311
+ function cvssToCqnAndCvn(word) {
312
+ const lowerWord = word.toLowerCase();
313
+ let consonant = "", vowelPart = lowerWord, cqnConsonant = "", cqnResult = "", cvnResult = "", cvssResult = "";
314
+
315
+ // Tìm phụ âm
316
+ for (const c of consonants.cvn) {
317
+ if (lowerWord.startsWith(c)) {
318
+ consonant = c;
319
+ cqnConsonant = consonants.cqn[consonants.cvn.indexOf(c)];
320
+ vowelPart = lowerWord.replace(c, "");
321
+ break;
322
+ }
323
+ }
324
+
325
+ // Ánh xạ nguyên âm
326
+ const vowelIndex = vowels.cvss.indexOf(vowelPart);
327
+ if (vowelIndex !== -1) {
328
+ cqnResult = vowels.cqn[vowelIndex];
329
+ cvnResult = vowels.cvn[vowelIndex];
330
+ cvssResult = vowelPart;
331
+ } else {
332
+ cqnResult = vowelPart;
333
+ cvnResult = vowelPart;
334
+ cvssResult = vowelPart;
335
+ }
336
+
337
+ // Điều chỉnh đặc biệt
338
+ if (consonant === "j" && vowelPart === "iar") {
339
+ cqnConsonant = "gi";
340
+ cqnResult = "ỵa";
341
+ }
342
+ if (lowerWord === "it") {
343
+ cqnResult = "ít";
344
+ }
345
+ if (lowerWord === "ikj") {
346
+ cqnResult = "ích";
347
+ }
348
+
349
+ // Điều chỉnh phụ âm và nguyên âm
350
+ const adjusted = adjustConsonantVowel(cqnConsonant, cqnResult);
351
+ cqnConsonant = adjusted.cqnPad;
352
+ cqnResult = adjusted.cqnVan;
353
+
354
+ // Kết hợp kết quả
355
+ let cqnOutput = cqnConsonant + cqnResult;
356
+ let cvnOutput = consonant + cvnResult;
357
+
358
+ // Xử lý chữ hoa
359
+ if (word[0] !== word[0].toLowerCase()) {
360
+ if (cqnOutput.length) cqnOutput = cqnOutput[0].toUpperCase() + cqnOutput.slice(1);
361
+ if (cvnOutput.length) cvnOutput = cvnOutput[0].toUpperCase() + cvnOutput.slice(1);
362
+ if (cvssResult.length) cvssResult = cvssResult[0].toUpperCase() + cvssResult.slice(1);
363
+ }
364
+ if (isUpperCase(word)) {
365
+ cqnOutput = cqnOutput.toUpperCase();
366
+ cvnOutput = cvnOutput.toUpperCase();
367
+ cvssResult = cvssResult.toUpperCase();
368
+ }
369
+
370
+ return { cqn: cqnOutput, cvn: cvnOutput, cvss: cvssResult };
371
+ }
372
+
373
+ // Hàm chuyển đổi toàn bộ văn bản
374
+ function convertText(input, mode) {
375
+ const tokens = splitString(input);
376
+ const result = { cqn: [], cvn: [], cvss: [] };
377
+
378
+ tokens.forEach(token => {
379
+ if (specialChars.includes(token)) {
380
+ result.cqn.push(token);
381
+ result.cvn.push(token);
382
+ result.cvss.push(token);
383
+ } else {
384
+ let converted;
385
+ if (mode === "cqn") {
386
+ converted = cqnToCvnAndCvss(token);
387
+ } else if (mode === "cvn") {
388
+ converted = cvnToCqnAndCvss(token);
389
+ } else if (mode === "cvss") {
390
+ converted = cvssToCqnAndCvn(token);
391
+ }
392
+ result.cqn.push(converted.cqn);
393
+ result.cvn.push(converted.cvn);
394
+ result.cvss.push(converted.cvss);
395
+ }
396
+ });
397
+
398
+ return {
399
+ cqn: result.cqn.join(""),
400
+ cvn: result.cvn.join(""),
401
+ cvss: result.cvss.join("")
402
+ };
403
+ }
404
+
405
+ // Xuất module
406
+ return {
407
+ convert: convertText,
408
+ specialChars
409
+ };
410
+ })();
411
+
412
+ // Xuất module cho môi trường Node.js hoặc trình duyệt
413
+ if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
414
+ module.exports = CVNSSConverter;
415
+ } else {
416
+ window.CVNSSConverter = CVNSSConverter;
417
+ }
toolCVNSS.html ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="vi">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Bộ chuyển đổi CQN ⇄ CVNSS 4.0 (offline) - Tối ưu</title>
7
+ <style>
8
+ body {
9
+ font-family: Segoe UI, Arial, Helvetica, sans-serif; /* Cập nhật font chữ hiện đại hơn */
10
+ background: #f4f6f8; /* Màu nền nhẹ nhàng hơn */
11
+ margin: 30px;
12
+ text-align: center;
13
+ color: #333;
14
+ }
15
+ h1 {
16
+ margin-bottom: 24px;
17
+ font-size: 28px; /* Tăng kích thước tiêu đề */
18
+ color: #2c3e50; /* Màu tiêu đề đậm hơn */
19
+ }
20
+ .wrap {
21
+ display: flex;
22
+ gap: 25px;
23
+ flex-wrap: wrap;
24
+ justify-content: center;
25
+ margin-bottom: 20px; /* Thêm khoảng cách dưới */
26
+ }
27
+ textarea {
28
+ width: 420px; /* Điều chỉnh kích thước */
29
+ height: 280px;
30
+ padding: 15px;
31
+ font-size: 16px;
32
+ border: 1px solid #dcdcdc; /* Viền nhẹ hơn */
33
+ border-radius: 8px; /* Bo góc ít hơn một chút */
34
+ resize: vertical; /* Cho phép thay đổi chiều cao */
35
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
36
+ box-sizing: border-box;
37
+ transition: border-color 0.2s, box-shadow 0.2s; /* Thêm hiệu ứng chuyển tiếp */
38
+ }
39
+ textarea:focus {
40
+ border-color: #3498db; /* Màu viền khi focus nổi bật hơn */
41
+ box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2); /* Thêm hiệu ứng bóng mờ khi focus */
42
+ outline: none;
43
+ }
44
+ .controls { /* Đổi tên từ btn-row cho rõ ràng hơn */
45
+ display: flex;
46
+ gap: 15px;
47
+ justify-content: center;
48
+ align-items: center; /* Căn giữa các item theo chiều dọc */
49
+ margin-top: 15px;
50
+ }
51
+ button, .mode-select {
52
+ padding: 10px 20px; /* Đồng nhất padding */
53
+ font-size: 15px;
54
+ border-radius: 6px;
55
+ cursor: pointer;
56
+ transition: transform 0.1s, background-color 0.2s, box-shadow 0.2s; /* Cập nhật transition */
57
+ }
58
+ button {
59
+ min-width: 120px; /* Tăng chiều rộng tối thiểu */
60
+ border: none;
61
+ color: #fff;
62
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
63
+ }
64
+ button:hover {
65
+ filter: brightness(1.1);
66
+ box-shadow: 0 4px 8px rgba(0,0,0,0.15);
67
+ }
68
+ button:active {
69
+ transform: scale(0.97);
70
+ filter: brightness(1);
71
+ }
72
+ .copy {
73
+ background-color: #3498db; /* Xanh dương */
74
+ }
75
+ .reset {
76
+ background-color: #e74c3c; /* Đỏ */
77
+ }
78
+ .mode-select {
79
+ border: 1px solid #dcdcdc;
80
+ background-color: #fff;
81
+ min-width: 180px; /* Cho select rộng hơn một chút */
82
+ }
83
+ .mode-select:focus {
84
+ border-color: #3498db;
85
+ box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
86
+ outline: none;
87
+ }
88
+ .status-message { /* Kiểu cho thông báo lỗi/thành công */
89
+ margin-top: 15px;
90
+ font-size: 14px;
91
+ min-height: 20px; /* Giữ không gian cho thông báo */
92
+ }
93
+ .status-success { color: #27ae60; }
94
+ .status-error { color: #c0392b; }
95
+ </style>
96
+ </head>
97
+ <body>
98
+ <h1>Công cụ CVNSS4.0 ⇄ Chữ Quốc ngữ</h1>
99
+
100
+ <div class="wrap">
101
+ <textarea id="inputText" placeholder="Nhập văn bản (CQN, CVN hoặc CVSS)" aria-label="Văn bản đầu vào"></textarea>
102
+ <textarea id="outputText" placeholder="Kết quả chuyển đổi" readonly aria-label="Văn bản kết quả"></textarea>
103
+ </div>
104
+
105
+ <div class="controls">
106
+ <select id="mode" class="mode-select" aria-label="Chọn chế độ chuyển đổi">
107
+ <option value="cqn">CQN → CVN/CVSS</option>
108
+ <option value="cvn">CVN → CQN/CVSS</option>
109
+ <option value="cvss">CVSS → CQN/CVN</option>
110
+ </select>
111
+ <button class="copy" onclick="copyText()">Sao chép</button>
112
+ <button class="reset" onclick="resetAll()">Đặt lại</button>
113
+ </div>
114
+ <div id="statusMessage" class="status-message"></div>
115
+
116
+ <script src="cvnss4.0-converter.js"></script>
117
+ <script>
118
+ const inputBox = document.getElementById("inputText");
119
+ const outputBox = document.getElementById("outputText");
120
+ const modeSelect = document.getElementById("mode");
121
+ const statusMessageEl = document.getElementById("statusMessage");
122
+ let convertTimeout; // Di chuyển khai báo timeout ra ngoài debounce
123
+ let guard = false;
124
+
125
+ // Hàm debounce để tối ưu hóa sự kiện input và select
126
+ function debounce(fn, delay) {
127
+ return function (...args) {
128
+ clearTimeout(convertTimeout);
129
+ convertTimeout = setTimeout(() => fn.apply(this, args), delay); // Sử dụng apply để giữ context
130
+ };
131
+ }
132
+
133
+ // Hàm hiển thị thông báo
134
+ function showStatus(message, type = "success") {
135
+ statusMessageEl.textContent = message;
136
+ statusMessageEl.className = `status-message status-${type}`;
137
+ setTimeout(() => { statusMessageEl.textContent = ""; statusMessageEl.className = "status-message";}, 3000);
138
+ }
139
+
140
+ // Hàm chuyển đổi văn bản
141
+ function convertText() {
142
+ if (guard) return;
143
+ guard = true; // Bảo vệ chống gọi lại liên tục (dù debounce đã giảm thiểu)
144
+
145
+ const input = inputBox.value;
146
+ const mode = modeSelect.value;
147
+
148
+ try {
149
+ if (!input.trim()) { // Nếu input rỗng hoặc chỉ có khoảng trắng
150
+ outputBox.value = "";
151
+ guard = false;
152
+ return;
153
+ }
154
+ const result = CVNSSConverter.convert(input, mode);
155
+
156
+ // Logic hiển thị kết quả dựa trên mode
157
+ // Các lựa chọn trong select của bạn là:
158
+ // CQN → CVN/CVSS: Hiển thị CVN là chính, có thể cung cấp thêm CVSS
159
+ // CVN → CQN/CVSS: Hiển thị CQN là chính, có thể cung cấp thêm CVSS
160
+ // CVSS → CQN/CVN: Hiển thị CQN là chính, có thể cung cấp thêm CVN
161
+ // Hiện tại, ta sẽ hiển thị kết quả đầu tiên được gợi ý.
162
+ if (mode === "cqn") {
163
+ outputBox.value = result.cvn;
164
+ // Để hiển thị cả hai: outputBox.value = `CVN: ${result.cvn}\n\nCVSS: ${result.cvss}`;
165
+ } else if (mode === "cvn") {
166
+ outputBox.value = result.cqn;
167
+ // Để hiển thị cả hai: outputBox.value = `CQN: ${result.cqn}\n\nCVSS: ${result.cvss}`;
168
+ } else if (mode === "cvss") {
169
+ outputBox.value = result.cqn;
170
+ // Để hiển thị cả hai: outputBox.value = `CQN: ${result.cqn}\n\nCVN: ${result.cvn}`;
171
+ }
172
+ } catch (error) {
173
+ console.error("Lỗi chuyển đổi:", error);
174
+ outputBox.value = ""; // Xóa output nếu có lỗi
175
+ showStatus("Lỗi: " + error.message, "error");
176
+ } finally {
177
+ guard = false;
178
+ }
179
+ }
180
+
181
+ // Gắn sự kiện input và change với debounce
182
+ const debouncedConvert = debounce(convertText, 250); // Giảm nhẹ delay
183
+ inputBox.addEventListener("input", debouncedConvert);
184
+ modeSelect.addEventListener("change", debouncedConvert); // Áp dụng debounce cho cả select
185
+
186
+ // Hàm sao chép nâng cao với Clipboard API và fallback
187
+ async function copyText() {
188
+ if (!outputBox.value.trim()) {
189
+ showStatus("Không có nội dung để sao chép.", "error");
190
+ return;
191
+ }
192
+ try {
193
+ if (navigator.clipboard && navigator.clipboard.writeText) {
194
+ await navigator.clipboard.writeText(outputBox.value);
195
+ showStatus("Đã sao chép vào clipboard!");
196
+ } else {
197
+ // Fallback cho trình duyệt cũ hơn hoặc môi trường không an toàn (non-HTTPS)
198
+ outputBox.select(); // Chọn văn bản trong textarea
199
+ outputBox.setSelectionRange(0, 99999); // Đối với mobile
200
+ document.execCommand("copy");
201
+ showStatus("Đã sao chép (sử dụng phương pháp cũ)!");
202
+ }
203
+ } catch (err) {
204
+ console.error("Lỗi khi sao chép: ", err);
205
+ showStatus("Lỗi khi sao chép: " + err.message, "error");
206
+ }
207
+ }
208
+
209
+ // Hàm đặt lại
210
+ function resetAll() {
211
+ inputBox.value = "";
212
+ outputBox.value = "";
213
+ modeSelect.value = "cqn"; // Giữ lại giá trị mặc định
214
+ statusMessageEl.textContent = "";
215
+ statusMessageEl.className = "status-message";
216
+ inputBox.focus();
217
+ }
218
+
219
+ // Khởi tạo chuyển đổi nếu có sẵn giá trị trong input khi tải trang (tùy chọn)
220
+ // if (inputBox.value.trim()) {
221
+ // convertText();
222
+ // }
223
+ </script>
224
+ </body>
225
+ </html>