CVNSS commited on
Commit
8057e65
·
verified ·
1 Parent(s): 545cdb2

Upload vietuni-cvnss.js

Browse files
Files changed (1) hide show
  1. vietuni-cvnss.js +532 -0
vietuni-cvnss.js ADDED
@@ -0,0 +1,532 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * VIETUNI-CVNSS PROFESSIONAL INTEGRATION
3
+ * Fixed: Spacebar Trigger, Modern Browser Support
4
+ */
5
+
6
+ /* ========================================================================== */
7
+ /* PHẦN 1: CORE CVNSS 4.0 CONVERTER (FULL DATA) */
8
+ /* ========================================================================== */
9
+ const CVNSSConverter = (function () {
10
+ const specialChars = [
11
+ "`", "“", "”", "<", ">", "@", "-", ";", "=", "…", ",", ".", "?", "!",
12
+ '"', "'", "(", ")", "[", "]", "{", "}", "%", "#", "$", "&", "_", "\\", "/",
13
+ "*", ":", "+", "~", "^", "|", "\r\n", "\r", "\n"
14
+ ];
15
+
16
+ const consonants = {
17
+ 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"],
18
+ 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"]
19
+ };
20
+
21
+ // FULL DATA VOWELS FROM YOUR FILE
22
+ const vowels = {
23
+ cqn: ["a","à","ả","ã","á","ạ","oa","òa","ỏa","õa","óa","ọa","oà","oả","oã","oá","oạ","oác","oạc","oách","oạch","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","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","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","ả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","á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","áp","ạp","át","ạt","au","àu","ảu","áu","ạu","ay","ày","ảy","ãy","áy","ạy","ắc","ặc","ăm","ằm","ẳm","ẵm","ắm","ặm","ăn","ằn","ẳn","ẵn","ắn","ặn","ăng","ằng","ẳng","ẵng","ắng","ặng","ắp","ặp","ắt","ặt","ấc","ậc","âm","ầm","ẩm","ẫm","ấ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","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","ấy","ậy","uây","uầy","uẩy","uẫy","uấy","uậy","e","è","ẻ","ẽ","é","ẹ","oe","òe","ỏe","õe","óe","ọe","éc","ẹc","em","è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","é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","ê","ề","ể","ễ","ế","ệ","uê","uề","uể","uễ","uế","uệ","ếch","ệch","uếch","uệch","êm","ềm","ểm","ễm","ếm","ệm","ên","ền","ể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","êu","ều","ểu","ễu","ếu","ệu","i","ì","ỉ","ĩ","í","ị","uy","ùy","ủy","ũy","úy","ụy","uỳ","uỷ","uỹ","uý","uỵ","ia","ì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","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","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","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","ìm","ỉm","ĩm","ím","ịm","in","ìn","ỉn","ĩn","ín","ịn","inh","ình","ỉnh","ĩnh","ính","ịnh","uynh","uỳnh","uỷnh","uỹnh","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","uỵu","uỳn","uỷn","uỹn","uýn","uỵn","o","ò","ỏ","õ","ó","ọ","óc","ọc","oi","òi","ỏi","õi","ói","ọi","om","òm","ỏm","õm","óm","ọm","on","òn","ỏn","õn","ón","ọn","ong","òng","ỏng","õng","óng","ọng","oóc","oong","oòng","oỏng","oõng","oóng","oòng","oọng","óp","ọp","ót","ọt","ô","ồ","ổ","ỗ","ố","ộ","ốc","ộc","ôi","ồi","ổi","ỗi","ối","ội","ôm","ồm","ổm","ỗm","ốm","ộm","ôn","ồn","ổn","ỗn","ốn","ộn","ông","ồng","ổng","ỗng","ống","ộng","ốp","ộp","ốt","ột","ơ","ờ","ở","ỡ","ớ","ợ","ơi","ời","ởi","ỡi","ới","ợi","ơm","ờm","ởm","ỡm","ớm","ợm","ơn","ờn","ởn","ỡn","ớn","ợn","ơng","ờng","ởng","ỡng","ớng","ợng","ớp","ợp","ớt","ợt","u","ù","ủ","ũ","ú","ụ","ua","ùa","ủa","ũa","úa","ụa","úc","ục","ui","ùi","ủi","ũi","úi","ụi","um","ùm","ủm","ũm","úm","ụm","un","ùn","ủn","ũn","ún","ụn","ung","ùng","ủng","ũng","ú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","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","uống","uộng","uốt","uột","uốp","uộp","úp","ụp","út","ụt","ư","ừ","ử","ữ","ứ","ự","ưa","ừa","ửa","ữa","ứa","ựa","ức","ực","ưi","ừi","ửi","ữi","ứi","ựi","ưm","ừm","ửm","ữm","ứm","ựm","ưn","ừn","ửn","ữn","ứn","ựn","ưng","ừng","ửng","ững","ứng","ựng","ước","ược","ươi","ười","ưởi","ưỡi","ưới","ượi","ươm","ườm","ưởm","ưỡm","ướm","ượm","ươn","ườn","ưởn","ưỡn","ướn","ượn","ương","ường","ưởng","ưỡng","ướng","ượng","ướp","ượp","ướt","ượt","ươu","ườu","ưởu","ưỡu","ướu","ượu","ứt","ựt","ưu","ừu","ửu","ữu","ứu","ựu","y","ỳ","ỷ","ỹ","ý","ỵ","ỳa","ỷa","ỹa","ýa","ỵa"],
24
+ cvn: ["a","al","az","as","aj","ar","oa","oal","oaz","oas","oaj","oar","oal","oaz","oas","oaj","oar","osj","osr","oakj","oakr","ojp","ojl","ojz","ojs","ojj","ojr","owp","owl","owz","ows","owj","owr","ofj","ofr","odj","odr","adx","adh","asx","ash","alo","alk","alv","alw","alx","alh","avo","avk","avv","avw","avx","avh","azo","azk","azv","azw","azx","azh","ajp","ajl","ajz","ajs","ajj","ajr","ac","acr","akj","akr","ai","ail","aiz","ais","aij","air","am","aml","amz","ams","amj","amr","an","anl","anz","ans","anj","anr","olp","oll","olz","ols","olj","olr","oahp","oahl","oahz","oahs","oahj","oahr","agp","agl","agz","ags","agj","agr","ozp","ozl","ozz","ozs","ozj","ozr","ahp","ahl","ahz","ahs","ahj","ahr","ao","aol","aoz","aos","aoj","aor","ap","apr","at","atr","au","aul","auz","auj","aur","ay","ayl","ayz","ays","ayj","ayr","acx","ach","amo","amk","amv","amw","amx","amh","ano","ank","anv","anw","anx","anh","ago","agk","agv","agw","agx","agh","apx","aph","atx","ath","acb","acf","amy","amd","amq","amg","amb","amf","any","and","anq","ang","anb","anf","agy","agd","agq","agg","agb","agf","aly","ald","alq","alg","alb","alf","azy","azd","azq","azg","azb","azf","apb","apf","atb","atf","adb","adf","auy","aud","auq","aug","aub","auf","ayy","ayd","ayq","ayg","ayb","ayf","ajy","ajd","ajq","ajg","ajb","ajf","e","el","ez","es","ej","er","oe","oel","oez","oes","oej","oer","ec","ecr","em","eml","emz","ems","emj","emr","en","enl","enz","ens","enj","enr","elp","ell","elz","els","elj","elr","egp","egl","egz","egs","egj","egr","eo","eol","eoz","eos","eoj","eor","ewp","ewl","ewz","ews","ewj","ewr","ep","epr","et","etr","edj","edr","ey","ed","eq","eg","eb","ef","uey","ued","ueq","ueg","ueb","uef","ekb","ekf","uekb","uekf","emy","emd","emq","emg","emb","emf","eny","end","enq","eng","enb","enf","ehy","ehd","ehq","ehg","ehb","ehf","uehy","uehd","uehq","uehg","uehb","uehf","epb","epf","etb","etf","euy","eud","euq","eug","eub","euf","i","il","iz","is","ij","ir","y","yl","yz","ys","yj","yr","yl","yz","ys","yj","yr","ia","ial","iaz","ias","iaj","iar","ya","ic","ikj","ikr","ykj","ykr","isb","isf","ivy","ivd","ivq","ivg","ivb","ivf","ily","ild","ilq","ilg","ilb","ilf","yly","yld","ylq","ylg","ylb","ylf","izy","izd","izq","izg","izb","izf","ifb","iff","idb","idf","ydb","ydf","iwy","iwd","iwq","iwg","iwb","iwf","idb","idf","ily","ild","ilq","ilg","ilb","ilf","ivy","ivd","ivq","ivg","ivb","ivf","izy","izd","izq","izg","izb","izf","iwy","iwd","iwq","iwg","iwb","iwf","im","iml","imz","ims","imj","imr","in","inl","inz","ins","inj","inr","ihp","ihl","ihz","ihs","ihj","ihr","yhp","yhl","yhz","yhs","yhj","yhr","ip","ipr","yp","ypr","it","itr","yt","ytr","iu","iul","iuz","ius","iuj","iur","yu","yul","yuz","yus","yuj","yur","ynl","ynz","yns","ynj","ynr","o","ol","oz","os","oj","or","oc","ocr","oi","oil","oiz","ois","oij","oir","om","oml","omz","oms","omj","omr","on","onl","onz","ons","onj","onr","ogp","ogl","ogz","ogs","ogj","ogr","ooc","oog","oogl","oogz","oogs","oogj","oogl","oogr","op","opr","ot","otr","oy","od","oq","og","ob","of","ocb","ocf","oiy","oid","oiq","oig","oib","oif","omy","omd","omq","omg","omb","omf","ony","ond","onq","ong","onb","onf","ogy","ogd","ogq","ogg","ogb","ogf","opb","opf","otb","otf","oo","ok","ov","ow","ox","oh","oio","oik","oiv","oiw","oix","oih","omo","omk","omv","omw","omx","omh","ono","onk","onv","onw","onx","onh","ogo","ogk","ogv","ogw","ogx","ogh","opx","oph","otx","oth","u","ul","uz","us","uj","ur","ua","ual","uaz","uas","uaj","uar","uc","ucr","ui","uil","uiz","uis","uij","uir","um","uml","umz","ums","umj","umr","un","unl","unz","uns","unj","unr","ugp","ugl","ugz","ugs","ugj","ugr","uoo","uok","uov","uow","uox","uoh","olo","olk","olv","olw","olx","olh","odx","odh","usb","usf","ujy","ujd","ujq","ujg","ujb","ujf","uvy","uvd","uvq","uvg","uvb","uvf","uly","uld","ulq","ulg","ulb","ulf","uzy","uzd","uzq","uzg","uzb","uzf","udb","udf","ufb","uff","up","upr","ut","utr","uo","uk","uv","uw","ux","uh","uao","uak","uav","uaw","uax","uah","ucx","uch","uio","uik","uiv","uiw","uix","uih","umo","umk","umv","umw","umx","umh","uno","unk","unv","unw","unx","unh","ugo","ugk","ugv","ugw","ugx","ugh","usx","ush","ujo","ujk","ujv","ujw","ujx","ujh","uvo","uvk","uvv","uvw","uvx","uvh","ulo","ulk","ulv","ulw","ulx","ulh","uzo","uzk","uzv","uzw","uzx","uzh","ufx","ufh","udx","udh","uwo","uwk","uwv","uww","uwx","uwh","utx","uth","uuo","uuk","uuv","uuw","uux","uuh","i","il","iz","is","ij","ir","ial","iaz","ias","iaj","iar"],
25
+ cvss: ["a","al","az","as","aj","ar","oa","oal","oaz","oas","oaj","oar","oal","oaz","oas","oaj","oar","osj","osr","oakj","oakr","ojp","ojl","ojz","ojs","ojj","ojr","owp","owl","owz","ows","owj","owr","ofj","ofr","odj","odr","adx","adh","asx","ash","alo","alk","alv","alw","alx","alh","avo","avk","avv","avw","avx","avh","azo","azk","azv","azw","azx","azh","ajp","ajl","ajz","ajs","ajj","ajr","ac","acr","akj","akr","ai","ail","aiz","ais","aij","air","am","aml","amz","ams","amj","amr","an","anl","anz","ans","anj","anr","olp","oll","olz","ols","olj","olr","oahp","oahl","oahz","oahs","oahj","oahr","agp","agl","agz","ags","agj","agr","ozp","ozl","ozz","ozs","ozj","ozr","ahp","ahl","ahz","ahs","ahj","ahr","ao","aol","aoz","aos","aoj","aor","ap","apr","at","atr","au","aul","auz","auj","aur","ay","ayl","ayz","ays","ayj","ayr","acx","ach","amo","amk","amv","amw","amx","amh","ano","ank","anv","anw","anx","anh","ago","agk","agv","agw","agx","agh","apx","aph","atx","ath","acb","acf","amy","amd","amq","amg","amb","amf","any","and","anq","ang","anb","anf","agy","agd","agq","agg","agb","agf","aly","ald","alq","alg","alb","alf","azy","azd","azq","azg","azb","azf","apb","apf","atb","atf","adb","adf","auy","aud","auq","aug","aub","auf","ayy","ayd","ayq","ayg","ayb","ayf","ajy","ajd","ajq","ajg","ajb","ajf","e","el","ez","es","ej","er","oe","oel","oez","oes","oej","oer","ec","ecr","em","eml","emz","ems","emj","emr","en","enl","enz","ens","enj","enr","elp","ell","elz","els","elj","elr","egp","egl","egz","egs","egj","egr","eo","eol","eoz","eos","eoj","eor","ewp","ewl","ewz","ews","ewj","ewr","ep","epr","et","etr","edj","edr","ey","ed","eq","eg","eb","ef","uey","ued","ueq","ueg","ueb","uef","ekb","ekf","uekb","uekf","emy","emd","emq","emg","emb","emf","eny","end","enq","eng","enb","enf","ehy","ehd","ehq","ehg","ehb","ehf","uehy","uehd","uehq","uehg","uehb","uehf","epb","epf","etb","etf","euy","eud","euq","eug","eub","euf","i","il","iz","is","ij","ir","y","yl","yz","ys","yj","yr","yl","yz","ys","yj","yr","ia","ial","iaz","ias","iaj","iar","ya","ic","ikj","ikr","ykj","ykr","isb","isf","ivy","ivd","ivq","ivg","ivb","ivf","ily","ild","ilq","ilg","ilb","ilf","yly","yld","ylq","ylg","ylb","ylf","izy","izd","izq","izg","izb","izf","ifb","iff","idb","idf","ydb","ydf","iwy","iwd","iwq","iwg","iwb","iwf","idb","idf","ily","ild","ilq","ilg","ilb","ilf","ivy","ivd","ivq","ivg","ivb","ivf","izy","izd","izq","izg","izb","izf","iwy","iwd","iwq","iwg","iwb","iwf","im","iml","imz","ims","imj","imr","in","inl","inz","ins","inj","inr","ihp","ihl","ihz","ihs","ihj","ihr","yhp","yhl","yhz","yhs","yhj","yhr","ip","ipr","yp","ypr","it","itr","yt","ytr","iu","iul","iuz","ius","iuj","iur","yu","yul","yuz","yus","yuj","yur","ynl","ynz","yns","ynj","ynr","o","ol","oz","os","oj","or","oc","ocr","oi","oil","oiz","ois","oij","oir","om","oml","omz","oms","omj","omr","on","onl","onz","ons","onj","onr","ogp","ogl","ogz","ogs","ogj","ogr","ooc","oog","oogl","oogz","oogs","oogj","oogl","oogr","op","opr","ot","otr","oy","od","oq","og","ob","of","ocb","ocf","oiy","oid","oiq","oig","oib","oif","omy","omd","omq","omg","omb","omf","ony","ond","onq","ong","onb","onf","ogy","ogd","ogq","ogg","ogb","ogf","opb","opf","otb","otf","oo","ok","ov","ow","ox","oh","oio","oik","oiv","oiw","oix","oih","omo","omk","omv","omw","omx","omh","ono","onk","onv","onw","onx","onh","ogo","ogk","ogv","ogw","ogx","ogh","opx","oph","otx","oth","u","ul","uz","us","uj","ur","ua","ual","uaz","uas","uaj","uar","uc","ucr","ui","uil","uiz","uis","uij","uir","um","uml","umz","ums","umj","umr","un","unl","unz","uns","unj","unr","ugp","ugl","ugz","ugs","ugj","ugr","uoo","uok","uov","uow","uox","uoh","olo","olk","olv","olw","olx","olh","odx","odh","usb","usf","ujy","ujd","ujq","ujg","ujb","ujf","uvy","uvd","uvq","uvg","uvb","uvf","uly","uld","ulq","ulg","ulb","ulf","uzy","uzd","uzq","uzg","uzb","uzf","udb","udf","ufb","uff","up","upr","ut","utr","uo","uk","uv","uw","ux","uh","uao","uak","uav","uaw","uax","uah","ucx","uch","uio","uik","uiv","uiw","uix","uih","umo","umk","umv","umw","umx","umh","uno","unk","unv","unw","unx","unh","ugo","ugk","ugv","ugw","ugx","ugh","usx","ush","ujo","ujk","ujv","ujw","ujx","ujh","uvo","uvk","uvv","uvw","uvx","uvh","ulo","ulk","ulv","ulw","ulx","ulh","uzo","uzk","uzv","uzw","uzx","uzh","ufx","ufh","udx","udh","uwo","uwk","uwv","uww","uwx","uwh","utx","uth","uuo","uuk","uuv","uuw","uux","uuh","i","il","iz","is","ij","ir","ial","iaz","ias","iaj","iar"]
26
+ };
27
+
28
+ const baseVowels = [
29
+ "aàảãáạ", "ăằẳẵắặ", "âầẩẫấậ", "eèẻẽéẹ", "êềểễếệ", "iìỉĩíị",
30
+ "oòỏõóọ", "ôồổỗốộ", "ơờởỡớợ", "uùủũúụ", "ưừửữứự", "yỳỷỹýỵ"
31
+ ];
32
+
33
+ const specialReplacements = { y: "yỳỷỹýỵ", i: "iìỉĩíị" };
34
+ const consonantAdjustments = { phu_am: ["ngh", "gh", "k"], phu_am_chuyen_doi: ["ng", "g", "c"], nguyen_am: "ieê" };
35
+
36
+ function isUpperCase(str) {
37
+ return /^[A-ZÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬĐÈÉẺẼẸÊỀẾỂỄỆÌÍỈĨỊÒÓỎÕỌÔỒỐỔỖỘƠỜỚỞỠỢÙÚỦŨỤƯỪỨỬỮỰỲÝỶỸỴ]+$/.test(str);
38
+ }
39
+
40
+ function getBaseVowel(char) {
41
+ for (const vowelGroup of baseVowels) {
42
+ if (vowelGroup.includes(char)) return vowelGroup[0];
43
+ }
44
+ return char;
45
+ }
46
+
47
+ function adjustConsonantVowel(cqnPad, cqnVan) {
48
+ const firstChar = cqnVan[0] || "";
49
+ if (cqnPad === "qu" && getBaseVowel(firstChar) === "u") { cqnPad = "q"; }
50
+ if (!cqnPad && getBaseVowel(firstChar) === "i") {
51
+ cqnVan = cqnVan.replace(firstChar, specialReplacements.y[specialReplacements.i.indexOf(firstChar)]);
52
+ }
53
+ if (cqnPad === "gi" && getBaseVowel(firstChar) === "i") { cqnPad = "g"; }
54
+ if (consonantAdjustments.phu_am.includes(cqnPad) && !consonantAdjustments.nguyen_am.includes(getBaseVowel(firstChar))) {
55
+ cqnPad = consonantAdjustments.phu_am_chuyen_doi[consonantAdjustments.phu_am.indexOf(cqnPad)];
56
+ }
57
+ return { cqnPad, cqnVan };
58
+ }
59
+
60
+ function cvnToCqnAndCvss(word) {
61
+ if (!word) return { cqn: word, cvn: word, cvss: word };
62
+ const lowerWord = word.toLowerCase();
63
+ let consonant = "", vowelPart = lowerWord, cqnConsonant = "", cqnResult = "", cvnResult = "", cvssResult = "";
64
+
65
+ for (const c of consonants.cvn) {
66
+ if (lowerWord.startsWith(c)) {
67
+ consonant = c;
68
+ cqnConsonant = consonants.cqn[consonants.cvn.indexOf(c)];
69
+ vowelPart = lowerWord.replace(c, "");
70
+ break;
71
+ }
72
+ }
73
+ const vowelIndex = vowels.cvn.indexOf(vowelPart);
74
+ if (vowelIndex !== -1) {
75
+ cqnResult = vowels.cqn[vowelIndex];
76
+ cvnResult = vowelPart;
77
+ cvssResult = vowels.cvss[vowelIndex];
78
+ } else {
79
+ cqnResult = vowelPart;
80
+ cvnResult = vowelPart;
81
+ cvssResult = vowelPart;
82
+ }
83
+ if (consonant === "j" && vowelPart === "ịa") { cqnConsonant = "gi"; cqnResult = "ỵa"; }
84
+
85
+ const adjusted = adjustConsonantVowel(cqnConsonant, cqnResult);
86
+ cqnConsonant = adjusted.cqnPad;
87
+ cqnResult = adjusted.cqnVan;
88
+
89
+ let cqnOutput = cqnConsonant + cqnResult;
90
+ if (word[0] !== word[0].toLowerCase()) { cqnOutput = cqnOutput.charAt(0).toUpperCase() + cqnOutput.slice(1); }
91
+ if (isUpperCase(word)) { cqnOutput = cqnOutput.toUpperCase(); }
92
+
93
+ return { cqn: cqnOutput, cvn: consonant + cvnResult, cvss: consonant + cvssResult };
94
+ }
95
+
96
+ return {
97
+ cvnToCqn: cvnToCqnAndCvss,
98
+ specialChars
99
+ };
100
+ })();
101
+
102
+ /* ========================================================================== */
103
+ /* PHẦN 2: VIETUNI LIBRARY (MODERNIZED & BRIDGED) */
104
+ /* ========================================================================== */
105
+
106
+ var supported = (document.all || document.getElementById);
107
+ var disabled = false;
108
+ var keymodeid = 4;
109
+ var linebreak = 0;
110
+ var theTyper = null;
111
+ var cvnssMode = false;
112
+
113
+ reset = function(){}
114
+ initTyper = telexingVietUC;
115
+
116
+ function setTypingMode(mode) {
117
+ keymodeid = mode;
118
+ if (mode === 5) {
119
+ cvnssMode = true;
120
+ if (theTyper) theTyper.keymode = new CVKOff();
121
+ } else {
122
+ cvnssMode = false;
123
+ if (theTyper) theTyper.keymode = initKeys();
124
+ }
125
+ }
126
+
127
+ initCharMap = function() { return new CVietUniCodeMap(); }
128
+
129
+ initKeys = function() {
130
+ switch (keymodeid) {
131
+ case 1: return new CTelexKeys();
132
+ case 2: return new CVniKeys();
133
+ case 3: return new CViqrKeys();
134
+ case 4: return new CAllKeys();
135
+ default: return new CVKOff();
136
+ }
137
+ }
138
+
139
+ function telexingVietUC (txtarea, nextfunc) {
140
+ txtarea.vietarea= true;
141
+ txtarea.onkeyup = nextfunc;
142
+ if (!supported) return;
143
+
144
+ // FIXED: Sử dụng onkeydown cho Space và Punctuation để xử lý trước khi ký tự được in ra
145
+ txtarea.onkeydown = function(evt) {
146
+ if (cvnssMode) {
147
+ return handleCVNSSKeyDown(evt, this);
148
+ }
149
+ return onKeyDown(evt);
150
+ };
151
+
152
+ // Legacy support
153
+ txtarea.onkeypress = function(evt) {
154
+ if (!cvnssMode) return vietTyping.call(this, evt);
155
+ };
156
+
157
+ txtarea.getCurrentWord= getCurrentWord;
158
+ txtarea.replaceWord= replaceWord;
159
+ txtarea.onmousedown= onMouseDown;
160
+ if(!theTyper) theTyper = new CVietString("");
161
+ }
162
+
163
+ /**
164
+ * FIXED: LOGIC XỬ LÝ CVNSS CHUYÊN NGHIỆP
165
+ * Bắt sự kiện Space/Enter/Dấu câu để convert từ ngay trước con trỏ
166
+ */
167
+ function handleCVNSSKeyDown(evt, txtarea) {
168
+ var code = evt.keyCode || evt.which;
169
+ var char = evt.key; // Modern browsers
170
+
171
+ // Nếu bấm Space (32) hoặc Enter (13) hoặc các ký tự đặc biệt
172
+ if (code === 32 || code === 13 || CVNSSConverter.specialChars.includes(char)) {
173
+
174
+ // 1. Lấy từ ngay trước con trỏ
175
+ var wordInfo = getLastWordBeforeCaret(txtarea);
176
+
177
+ if (wordInfo && wordInfo.word.length > 0) {
178
+ // 2. Thử convert
179
+ var result = CVNSSConverter.cvnToCqn(wordInfo.word);
180
+
181
+ // 3. Nếu có sự thay đổi (convert thành công)
182
+ if (result.cqn !== wordInfo.word) {
183
+ // Thay thế từ cũ bằng từ mới
184
+ replaceRange(txtarea, wordInfo.start, wordInfo.end, result.cqn);
185
+ // Sau đó để mặc định trình duyệt in dấu space/enter/dấu câu ra
186
+ }
187
+ }
188
+ }
189
+ return true; // Luôn cho phép phím hoạt động bình thường
190
+ }
191
+
192
+ /**
193
+ * FIXED: Hàm lấy từ hiện đại, chính xác theo vị trí con trỏ
194
+ */
195
+ function getLastWordBeforeCaret(el) {
196
+ var end = el.selectionStart;
197
+ if (end === 0) return null;
198
+
199
+ var text = el.value;
200
+ var start = end - 1;
201
+
202
+ // Lùi lại cho đến khi gặp khoảng trắng hoặc dấu câu hoặc đầu dòng
203
+ while (start >= 0) {
204
+ var c = text.charAt(start);
205
+ if (/\s|[\.,;?!]/.test(c)) {
206
+ break;
207
+ }
208
+ start--;
209
+ }
210
+ start++; // Đẩy lên 1 vị trí sau khoảng trắng
211
+
212
+ if (start >= end) return null;
213
+
214
+ return {
215
+ word: text.substring(start, end),
216
+ start: start,
217
+ end: end
218
+ };
219
+ }
220
+
221
+ /**
222
+ * FIXED: Hàm thay thế text an toàn cho trình duyệt hiện đại
223
+ */
224
+ function replaceRange(el, start, end, newText) {
225
+ if (el.setRangeText) {
226
+ el.setRangeText(newText, start, end, 'end');
227
+ } else {
228
+ // Fallback cũ
229
+ var val = el.value;
230
+ el.value = val.substring(0, start) + newText + val.substring(end);
231
+ el.selectionStart = el.selectionEnd = start + newText.length;
232
+ }
233
+ }
234
+
235
+ // --- CÁC HÀM CŨ CỦA VIETUNI (GIỮ NGUYÊN CHO MODE 1,2,3) ---
236
+
237
+ function getEvt(evt) {
238
+ if (typeof(evt)=='string') return evt.charCodeAt(0);
239
+ return document.all? event.keyCode: (evt && evt.which)? evt.which: 0;
240
+ }
241
+
242
+ function onKeyDown(evt) {
243
+ var c= getEvt(evt);
244
+ if ((c==10) || (c==13)) { reset(1); linebreak= 1; }
245
+ else if ((c<49) && (c!=16) && (c!=20)) { linebreak= 0; reset(c==32); }
246
+ return true;
247
+ }
248
+
249
+ function onMouseDown(evt) { reset(0); linebreak= 0; return true; }
250
+
251
+ function vietTyping(evt) {
252
+ var c= getEvt(evt);
253
+ theTyper.value= this.getCurrentWord();
254
+ var changed= ((c>32) && theTyper.typing(c));
255
+ if (changed) this.replaceWord(theTyper.value);
256
+ return !changed;
257
+ }
258
+
259
+ function getCurrentWord() {
260
+ if(!document.all) return this.value;
261
+ var caret = this.document.selection.createRange();
262
+ if (caret.text) return null;
263
+ var backward = -10;
264
+ do {
265
+ var caret2 = caret.duplicate();
266
+ caret2.moveStart("character", backward++);
267
+ } while (caret2.parentElement() != this && backward <0);
268
+ this.curword = caret2.duplicate();
269
+ return caret2.text;
270
+ }
271
+
272
+ function replaceWord(newword) {
273
+ if(!document.all) { this.value= newword; return; }
274
+ this.curword.text = newword;
275
+ this.curword.collapse(false);
276
+ }
277
+
278
+ // "class": CVietString
279
+ function CVietString(str) {
280
+ this.value= str;
281
+ this.keymode= initKeys();
282
+ this.charmap= initCharMap();
283
+ this.ctrlchar= '-';
284
+ this.changed= 0;
285
+ this.typing= typing;
286
+ this.Compose= Compose;
287
+ this.Correct= Correct;
288
+ this.findCharToChange= findCharToChange;
289
+ return this;
290
+ }
291
+
292
+ function typing(ctrl) {
293
+ this.changed = 0;
294
+ this.ctrlchar = String.fromCharCode(ctrl);
295
+ if (linebreak) linebreak= 0; else this.keymode.getAction(this);
296
+ this.Correct();
297
+ return this.changed;
298
+ }
299
+
300
+ function Compose(type) {
301
+ if(!this.value) return;
302
+ var info = this.findCharToChange(type);
303
+ if (!info || !info[0]) return;
304
+ var telex;
305
+ if (info[0]=='\\') telex= [1,this.ctrlchar,1];
306
+ else if (type>6) telex= this.charmap.getAEOWD(info[0], type, info[3]);
307
+ else telex= this.charmap.getDau(info[0], type);
308
+ if (!(this.changed = telex[0])) return;
309
+ this.value = this.value.replaceAt(info[1],telex[1],info[2]);
310
+ if (!telex[2]) { spellerror= 1; this.value+= this.ctrlchar; }
311
+ }
312
+
313
+ function Correct() {
314
+ if (this.charmap.maxchrlen || !document.all) return 0;
315
+ var tmp= this.value;
316
+ if ('nNcC'.indexOf(this.ctrlchar)>=0) tmp+= this.ctrlchar;
317
+ var er= /[^\x01-\x7f](hn|hc|gn)$/i.exec(tmp);
318
+ if (er) {
319
+ this.value= tmp.substring(0,tmp.length-2)+er[1].charAt(1)+er[1].charAt(0);
320
+ this.changed= 1;
321
+ }
322
+ else if(!this.changed) return 0;
323
+ er= /\w([^\x01-\x7f])(\w*)([^\x01-\x7f])\S*$/.exec(this.value);
324
+ if (!er) return 0;
325
+ var i= this.charmap.isVowel(er[1]);
326
+ var ri= (i-1)%24 + 1, ci= (i-ri)/24;
327
+ var i2= this.charmap.isVowel(er[3]);
328
+ if (!ci || !i2) return 0;
329
+ var ri2= (i2-1)%24 + 1, ci2= (i2-ri2)/24;
330
+ var nc= this.charmap.charAt(ri)+ er[2]+ this.charmap.charAt(ci*24+ri2);
331
+ this.value= this.value.replace(new RegExp(er[1]+er[2]+er[3],'g'), nc);
332
+ }
333
+
334
+ function findCharToChange(type) {
335
+ var lastchars= this.charmap.lastCharsOf(this.value, 5);
336
+ var i= 0, c=lastchars[0][0], chr=0;
337
+ if (c=='\\') return [c,this.value.length-1,1];
338
+ if (type==15) while (!(chr=this.charmap.isVD(c))) {
339
+ if ((c < 'A') || (i>=4) || !(c=lastchars[++i][0])) return null;
340
+ }
341
+ else while( "cghmnptCGHMNPT".indexOf(c)>=0) {
342
+ if ((c < 'A') || (i>=2) || !(c=lastchars[++i][0])) return null;
343
+ }
344
+ c = lastchars[0][0].toLowerCase();
345
+ var pc = lastchars[1][0].toLowerCase();
346
+ var ppc = lastchars[2][0].toLowerCase();
347
+ if (i==0 && type!=15) {
348
+ if ( (chr=this.charmap.isVowel(lastchars[1][0]))
349
+ && ("uyoia".indexOf(c)>=0) && !this.charmap.isUO(pc,c)
350
+ && !((pc=='o' && c=='a') || (pc=='u' && c=='y'))
351
+ && !((ppc=='q' && pc=='u') || (ppc=='g' && pc=='i')) ) ++i;
352
+ if (c=='a' && (type==9 || type==7)) i= 0;
353
+ }
354
+ c= lastchars[i][0];
355
+ if ((i==0 || chr==0) && type!=15) chr= this.charmap.isVowel(c);
356
+ if (!chr) return null;
357
+ var clen= lastchars[i][1], isuo=0;
358
+ if ((i>0) && (type==7 || type==8 || type==11)) {
359
+ isuo=this.charmap.isUO(lastchars[i+1][0],c);
360
+ if (isuo) { chr=isuo; clen+=lastchars[++i][1]; isuo=1; }
361
+ }
362
+ var pos= this.value.length;
363
+ for (var j=0; j<= i; j++) pos -= lastchars[j][1];
364
+ return [chr, pos, clen, isuo];
365
+ }
366
+
367
+ function CVietCharMap(){
368
+ this.vietchars = null;
369
+ this.length = 149;
370
+ this.chr_cache = new Array(20);
371
+ this.ind_cache = new Array(20);
372
+ this.cptr = 0;
373
+ this.caching= function(chr, ind) {
374
+ this.chr_cache[this.cptr] = chr;
375
+ this.ind_cache[this.cptr++] = ind;
376
+ this.cptr %= 20;
377
+ }
378
+ return this;
379
+ }
380
+
381
+ CVietCharMap.prototype.charAt= function(ind){
382
+ var chrcode = this.vietchars[ind];
383
+ return chrcode ? String.fromCharCode(chrcode) : null;
384
+ }
385
+
386
+ CVietCharMap.prototype.isVowel= function(chr){
387
+ var i = 0;
388
+ while ((i<20) && (chr != this.chr_cache[i])) ++i;
389
+ if (i<20) return this.ind_cache[i];
390
+
391
+ i = this.length-5;
392
+ while ((chr != this.charAt(i)) && i) --i;
393
+ this.caching(chr, i);
394
+ return i;
395
+ }
396
+
397
+ CVietCharMap.prototype.isVD= function (chr){
398
+ var ind = this.length-5;
399
+ while ((chr != this.charAt(ind)) && (ind < this.length)) ++ind;
400
+ return (ind<this.length)? ind: 0;
401
+ }
402
+
403
+ CVietCharMap.prototype.isUO= function (c1, c2){
404
+ if (!c1 || !c2) return 0;
405
+ var ind1 = this.isVowel(c1);
406
+ var ci = (ind1-1)%12;
407
+ if ((ci!=9) && (ci!=10)) return 0;
408
+ var ind2 = this.isVowel(c2);
409
+ ci = (ind2-1)%12;
410
+ if ((ci!=6) && (ci!=7) && (ci!=8)) return 0;
411
+ return [ind1,ind2];
412
+ }
413
+
414
+ CVietCharMap.prototype.getDau= function (ind, type){
415
+ var accented= (ind < 25)? 0: 1;
416
+ var ind_i= (ind-1) % 24 +1;
417
+ var charset= (type == 6)? 0 : type;
418
+ if ((type== 6) && !accented) return [0];
419
+ var newind= charset*24 + ind_i;
420
+ if (newind == ind) newind= ind_i;
421
+ var chr= this.charAt(newind);
422
+ if (!chr) chr= this.lowerCaseOf(0,newind);
423
+ return [1, chr, newind>24 || type==6];
424
+ }
425
+
426
+ var map=[
427
+ [7,7,7,8,8, 8,9,10,11,15],
428
+ [0,3,6,0,6, 9,0, 3, 6, 0],
429
+ [1,4,7,2,8,10,1, 4, 7, 1]
430
+ ];
431
+ CVietCharMap.prototype.getAEOWD= function(ind, type, isuo) {
432
+ var c=0, i1=isuo? ind[0]: ind;
433
+ var vc1= (type==15)? (i1-1)%2 : (i1-1)%12;
434
+ if (isuo) {
435
+ var base= ind[1]-(ind[1]-1)%12;
436
+ if (type==7 || type==11) c= this.charAt(i1-vc1+9)+this.charAt(base+7);
437
+ else if (type==8) c= this.charAt(i1-vc1+10)+this.charAt(base+8);
438
+ return [c!=0, c, 1];
439
+ }
440
+ var i= -1, shift= 0, del= 0;
441
+ while (shift==0 && ++i<map[0].length) {
442
+ if (map[0][i]==type) {
443
+ if(map[1][i]==vc1) shift= map[2][i]-vc1;
444
+ else if(map[2][i]==vc1) shift= map[1][i]-vc1;
445
+ }
446
+ }
447
+ if (shift==0) {
448
+ if (type==7 && (vc1==2 || vc1==8)) shift=-1;
449
+ else if ((type==9 && vc1==2) || (type==11 && vc1==8)) shift=-1;
450
+ else if (type==8 && (vc1==1 || vc1==7)) shift=1;
451
+ del= 1;
452
+ } else del=(shift>0);
453
+ i1 += shift;
454
+ var chr= this.charAt(i1);
455
+ if (i1<145) this.caching(chr, i1);
456
+ if (!chr) chr= this.lowerCaseOf(0, i1);
457
+ return [shift!=0, chr, del];
458
+ }
459
+
460
+ CVietCharMap.prototype.lastCharsOf= function(str, num){
461
+ if (!num) return [str.charAt(str.length-1),1];
462
+ var vchars = new Array(num);
463
+ for (var i=0; i< num; i++) { vchars[i]= [str.charAt(str.length-i-1),1]; }
464
+ return vchars;
465
+ }
466
+
467
+ String.prototype.replaceAt= function(i,newchr,clen){
468
+ return this.substring(0,i)+ newchr + this.substring(i+clen);
469
+ }
470
+
471
+ function CVietUniCodeMap(){ var map= new CVietCharMap();
472
+ map.vietchars = new Array(
473
+ "UNICODE",
474
+ 97, 226, 259, 101, 234, 105, 111, 244, 417, 117, 432, 121,
475
+ 65, 194, 258, 69, 202, 73, 79, 212, 416, 85, 431, 89,
476
+ 225, 7845, 7855, 233, 7871, 237, 243, 7889, 7899, 250, 7913, 253,
477
+ 193, 7844, 7854, 201, 7870, 205, 211, 7888, 7898, 218, 7912, 221,
478
+ 224, 7847, 7857, 232, 7873, 236, 242, 7891, 7901, 249, 7915, 7923,
479
+ 192, 7846, 7856, 200, 7872, 204, 210, 7890, 7900, 217, 7914, 7922,
480
+ 7841, 7853, 7863, 7865, 7879, 7883, 7885, 7897, 7907, 7909, 7921, 7925,
481
+ 7840, 7852, 7862, 7864, 7878, 7882, 7884, 7896, 7906, 7908, 7920, 7924,
482
+ 7843, 7849, 7859, 7867, 7875, 7881, 7887, 7893, 7903, 7911, 7917, 7927,
483
+ 7842, 7848, 7858, 7866, 7874, 7880, 7886, 7892, 7902, 7910, 7916, 7926,
484
+ 227, 7851, 7861, 7869, 7877, 297, 245, 7895, 7905, 361, 7919, 7929,
485
+ 195, 7850, 7860, 7868, 7876, 296, 213, 7894, 7904, 360, 7918, 7928,
486
+ 100, 273, 68, 272);
487
+ return map;
488
+ }
489
+
490
+ function CVietKeys() {
491
+ this.getAction= function(typer){
492
+ var i= this.keys.indexOf(typer.ctrlchar.toLowerCase());
493
+ if(i>=0) typer.Compose(this.actions[i]);
494
+ }
495
+ return this;
496
+ }
497
+
498
+ function CVKOff() {
499
+ this.off = true;
500
+ this.getAction= function(){};
501
+ return this;
502
+ }
503
+
504
+ function CTelexKeys() {
505
+ var k= new CVietKeys();
506
+ k.keys= "sfjrxzaeowd";
507
+ k.actions= [1,2,3,4,5,6,9,10,11,8,15];
508
+ k.istelex= true;
509
+ return k;
510
+ }
511
+
512
+ function CVniKeys() {
513
+ var k= new CVietKeys();
514
+ k.keys= "0123456789";
515
+ k.actions= [6,1,2,4,5,3,7,8,8,15];
516
+ return k;
517
+ }
518
+
519
+ function CViqrKeys() {
520
+ var k= new CVietKeys();
521
+ k.keys= "\xB4'\u2019`.?~-^(*+d";
522
+ k.actions= [1,1,1,2,3,4,5,6,7,8,8,8,15];
523
+ return k;
524
+ }
525
+
526
+ function CAllKeys() {
527
+ var k= new CVietKeys();
528
+ k.keys= "sfjrxzaeowd0123456789\xB4'`.?~-^(*+d";
529
+ k.actions= [1,2,3,4,5,6,9,10,11,8,15,6,1,2,4,5,3,7,8,8,15,1,1,2,3,4,5,6,7,8,8,8,15];
530
+ k.istelex= true;
531
+ return k;
532
+ }