MediaWiki:FileUploadWizard.js

Chú ý: Sau khi lưu thay đổi trang, bạn phải xóa bộ nhớ đệm của trình duyệt để nhìn thấy các thay đổi. Google Chrome, Firefox, Internet ExplorerSafari: Giữ phím ⇧ Shift và nhấn nút Reload/Tải lại trên thanh công cụ của trình duyệt. Để biết chi tiết và hướng dẫn cho các trình duyệt khác, xem Trợ giúp:Xóa bộ nhớ đệm.

/*
* ===============================================================
*                    FileUploadWizard.js
* Mã tải tập tin thông qua một mẫu hỏi đáp xử lý động.
* Mã này được dùng kèm với [[Wikipedia:Trình tải lên tập tin]].
* ===============================================================
*/

var fuwTesting = false;
var fuwDefaultTextboxLength = 60;
var fuwDefaultTextareaWidth = '90%';
var fuwDefaultTextareaLines = 3;

// ================================================================
// Constructor function of global fuw (= File Upload Wizard) object
// ================================================================
function fuwGlobal() {

   // Loading the accompanying .css
   mw.loader.load( mw.config.get('wgServer') + mw.config.get('wgScriptPath') +
      '/index.php?title=MediaWiki:FileUploadWizard.css&action=raw&ctype=text/css', 
      'text/css'  );

   // see if user is logged in, autoconfirmed, experienced etc.
   this.getUserStatus();

   fuwSetVisible('warningLoggedOut', (this.userStatus == 'anon'));
   fuwSetVisible('warningNotConfirmed', (this.userStatus == 'notAutoconfirmed'));
   this.disabled = (this.userStatus == 'anon') || (this.userStatus == 'notAutoconfirmed');
   if (this.disabled) {
      return;
   }
   fuwSetVisible('fuwStartScriptLink', false);

   // create the form element to wrap the main ScriptForm area
   // containing input elements of Step2 and Step3
   var frm = fuwGet('fuwScriptForm');
   if (! frm) {
      frm = document.createElement('form');
      frm.id = "fuwScriptForm";
      var area = fuwGet('placeholderScriptForm');
      var parent = area.parentNode;
      parent.insertBefore(frm, area);
      parent.removeChild(area);
      frm.appendChild(area);
   }
   this.ScriptForm = frm;

   // create the TargetForm element that contains the filename
   // input box, together with hidden input controls.
   // This is the form that is actually submitted to the api.php.
   frm = fuwGet('TargetForm');
   if (! frm) {
      frm = document.createElement('form');
      frm.id = "TargetForm";
      frm.method = "post";
      frm.enctype = "multipart/form-data";
      // "enctype" doesn't work properly on IE; need "encoding" instead:
      frm.encoding = "multipart/form-data"; 
      // we'll submit via api.php, not index.php, mainly because that
      // allows us to use a proper edit summary different from the page content
      frm.action = mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/api.php';

      // However, since api.php sends back a response page that humans won't want to read,
      // we'll have to channel that response away and discard it. We'll use a hidden iframe
      // for that purpose.
      // Unfortunately, it doesn't seem possible to submit file upload content through an 
      // Xmlhtml object via Ajax.
 
      frm.target = "TargetIFrame";
      //testing:
      //frm.target = "_blank";
      var area = fuwGet('placeholderTargetForm');
      var parent = area.parentNode;
      parent.insertBefore(frm, area);
      parent.removeChild(area);
      frm.appendChild(area);
   }
   this.TargetForm = frm;

   // For the testing version, create a third form that will display
   // the contents to be submitted, at the bottom of the page
   if (fuwTesting) {
      frm = fuwGet('fuwTestForm');
      if (! frm) {
         frm = document.createElement('form');
         frm.id = "fuwTestForm";
         var area = fuwGet('placeholderTestForm');
         var parent = area.parentNode;
         parent.insertBefore(frm, area);
         parent.removeChild(area);
         frm.appendChild(area);
      }
      this.TestForm = frm;
   }

   // objects to hold cached results during validation and processing
   this.opts = { };
   this.warn = { };

   // create the input filename box
   var filebox  = document.createElement('input');
   filebox.id   = 'file';
   filebox.name = 'file';
   filebox.type = 'file';
   filebox.size = fuwDefaultTextboxLength;
   filebox.onchange = fuwValidateFile;
   filebox.accept = 'image/png,image/jpeg,image/gif,image/svg+xml,image/tiff,image/x-xcf,application/pdf,image/vnd.djvu,audio/ogg,video/ogg,audio/rtp-midi,audio/mp3,image/webp,video/webm,audio/opus,video/mpeg,audio/wav,audio/flac';
   fuwAppendInput('file', filebox);

   // create hidden controls for sending the remaining API parameters:
   fuwMakeHiddenfield('action', 'upload', 'apiAction');
   fuwMakeHiddenfield('format', 'xml', 'apiFormat');
   fuwMakeHiddenfield('filename', '', 'apiFilename');
   fuwMakeHiddenfield('text', '', 'apiText');
   fuwMakeHiddenfield('comment', '', 'apiComment');
   fuwMakeHiddenfield('token', mw.user.tokens.get('csrfToken'), 'apiToken');
   fuwMakeHiddenfield('ignorewarnings', 1, 'apiIgnorewarnings');
   fuwMakeHiddenfield('watch', 1, 'apiWatch');

   if (fuwTesting) {
      fuwMakeHiddenfield('title', mw.config.get('wgPageName') + "/nháp", 'SandboxTitle');
      fuwMakeHiddenfield('token', mw.user.tokens.get('csrfToken'), 'SandboxToken');
      fuwMakeHiddenfield('recreate', 1, 'SandboxRecreate');
   }

   // create a hidden IFrame to send the api.php response to
   var ifr = document.createElement('iframe');
   ifr.id   = "TargetIFrame";
   ifr.name = "TargetIFrame";
   //ifr.setAttribute('style', 'float:right;width:150px;height:150px;');
   ifr.style.display = "none";
   ifr.src = "";

   fuwAppendInput('TargetIFrame', ifr);

   if (fuwTesting) {

      // create the sandbox submit button
      btn = document.createElement('input');
      btn.id = 'SandboxButton';
      btn.value = 'Chỗ thử';
      btn.name  = 'Sandbox';
      btn.disabled = true;
      btn.type = 'button';
      btn.style.width = '12em';
      btn.onclick = fuwSubmitSandbox;
      fuwAppendInput('SandboxButton', btn);

   }

   // create the real submit button
   btn = document.createElement('input');
   btn.id = "SubmitButton";
   btn.value = "Tải lên";
   btn.name = "Upload";
   btn.disabled = true;
   btn.type = "button";
   btn.onclick = fuwSubmitUpload;
   btn.style.width = '12em';
   fuwAppendInput('SubmitButton', btn);

   // create the Commons submit button
   btn = document.createElement('input');
   btn.id = "CommonsButton";
   btn.value = "Tải lên Commons";
   btn.name  = "Upload_on_Commons";
   btn.disabled = true;
   btn.type = "button";
   btn.onclick = fuwSubmitCommons;
   btn.style.width = '12em';
   fuwAppendInput('CommonsButton', btn);

   // create reset buttons
   for (i = 1; i<=2; i++) {
      btn = document.createElement('input');
      btn.id = 'ResetButton' + i;
      btn.value = "Đặt lại biểu mẫu";
      btn.name  = "Reset form";
      btn.type  = "button";
      btn.onclick = fuwReset;
      btn.style.width = '12em';
      fuwAppendInput('ResetButton' + i, btn);
   }

   // names of radio button fields
   var optionRadioButtons = {
      // top-level copyright status choice
      'FreeOrNonFree' : ['OptionFree','OptionNonFree','OptionNoGood'],
      // main subsections under OptionFree
      'FreeOptions'   : ['OptionOwnWork', 'OptionThirdParty', 'OptionFreeWebsite',
                         'OptionPDOld', 'OptionPDOther'],
      // main subsections under OptionNonFree
      'NonFreeOptions': ['OptionNFSubject','OptionNF3D','OptionNFExcerpt',
                         'OptionNFCover','OptionNFLogo','OptionNFPortrait',
                         'OptionNFMisc'],
      // response options inside warningFileExists
      'FileExistsOptions': 
                        ['NoOverwrite','OverwriteSame','OverwriteDifferent'],
      // choice of evidence in OptionThirdParty subsection
      'ThirdPartyEvidenceOptions' : 
                        ['ThirdPartyEvidenceOptionLink',
                         'ThirdPartyEvidenceOptionOTRS',
                         'ThirdPartyEvidenceOptionOTRSForthcoming',
                         'ThirdPartyEvidenceOptionNone'],
      // choice of PD status in OptionPDOld subsection
      'PDOldOptions'  : ['PDUSExpired','PDURAA','PDFormality','PDOldOther'],
      // choice of PD status in OptionPDOther subsection
      'PDOtherOptions': ['PDOtherUSGov','PDOtherOfficial','PDOtherSimple',
                         'PDOtherOther'],
      // whether target article is wholly or only partly dedicated to discussing non-free work:
      'NFSubjectCheck': ['NFSubjectCheckDedicated','NFSubjectCheckDiscussed'],
      'NF3DCheck'     : ['NF3DCheckDedicated','NF3DCheckDiscussed'],
      // choice about copyright status of photograph in OptionNF3D
      'NF3DOptions'   : ['NF3DOptionFree','NF3DOptionSame']
   };
   for (var group in optionRadioButtons) {
      var op = optionRadioButtons[group];
      for (i=0; i<op.length; i++) {
         fuwMakeRadiobutton(group, op[i]);
      }
   }
   this.ScriptForm.NoOverwrite.checked = true;
   
   // input fields that trigger special
   // onchange() event handlers for validation:
   fuwMakeTextfield('InputName', fuwValidateFilename);
   fuwMakeTextfield('NFArticle', fuwValidateNFArticle);

   // names of input fields that trigger normal
   // validation event handler
   var activeTextfields = [
      'Artist3D','Country3D',
      'Date','OwnWorkCreation','OwnWorkPublication',
      'Author','Source',
      'Permission','ThirdPartyOtherLicense',
      'ThirdPartyEvidenceLink','ThirdPartyOTRSTicket',
      'FreeWebsiteOtherLicense',
      'PDOldAuthorLifetime','Publication',
      'PDOldCountry','PDOldPermission',
      'PDOfficialPermission','PDOtherPermission',
      'NFSubjectPurpose', 'NF3DOrigDate', 'NF3DPurpose',
      'NF3DCreator',
      'NFPortraitDeceased',
      'EditSummary'
   ];
   for (i=0; i<activeTextfields.length; i++) {
      fuwMakeTextfield(activeTextfields[i]);
   }

   // names of multiline textareas
   var activeTextareas = [
      'InputDesc','NF3DPermission',
      'NFCommercial','NFPurpose','NFReplaceableText',
      'NFReplaceable','NFCommercial','NFMinimality','AnyOther'
   ];
   for (i=0; i<activeTextareas.length; i++) {
      fuwMakeTextarea(activeTextareas[i]);
   };

   var checkboxes = [
      'NFCoverCheckDedicated','NFLogoCheckDedicated','NFPortraitCheckDedicated'
   ];
   for (i=0; i<checkboxes.length; i++) {
      fuwMakeCheckbox(checkboxes[i]);
   };

   var licenseLists = {
      'OwnWorkLicense' : 
        // array structure as expected for input to fuwMakeSelection() function.
        // any entry that is a two-element array will be turned into an option
        // (first element is the value, second element is the display string).
        // Entries that are one-element arrays will be the label of an option group.
        // Zero-element arrays mark the end of an option group.
        [
        ['Cho phép mọi cách dùng lại miễn là ghi công bạn và chia sẻ với cùng điều kiện'],
        ['self|GFDL|cc-by-sa-4.0|chuyển đổi=trùng lắp', 
         'Creative Commons Ghi công–Chia sẻ tương tự 4.0 + GFDL (khuyên dùng)',
         true],
        ['self|cc-by-sa-4.0',
         'Creative Commons Ghi công–Chia sẻ tương tự 4.0'],
        [],
        ['Cho phép mọi cách dùng lại miễn là ghi công bạn'],
        ['self|cc-by-4.0',
         'Creative Commons Ghi công 4.0'],
        [],
        ['Không bảo lưu quyền nào'],
        ['cc-zero',
         'CC-zero Hiến cho công chúng'],
        []
        ],   
      'ThirdPartyLicense' :
        [
        ['', 'vui lòng chọn giấy phép đúng...'],
        ['Được cấp phép tự do:'],
        ['cc-by-sa-4.0', 'Creative Commons Ghi công–Chia sẻ tương tự (cc-by-sa-4.0)'],
        ['cc-by-4.0', 'Creative Commons Ghi công (cc-by-4.0)'],
        ['GFDL', 'Giấy phép Tài liệu Tự do GNU (GFDL)'],
        [],
        ['Không bảo lưu quyền nào:'],
        ['PVCC-tác giả', 'Phạm vi công cộng'],
        [],
        ['Khác (xem ở dưới)'],
        []
        ],   
      'FreeWebsiteLicense' :
        [
        ['', 'vui lòng chọn giấy phép đúng...'],
        ['Được cấp phép tự do:'],
        ['cc-by-sa-4.0', 'Creative Commons Ghi công-Chia sẻ tương tự (cc-by-sa-4.0)'],
        ['cc-by-4.0', 'Creative Commons Ghi công (cc-by-4.0)'],
        ['GFDL', 'Giấy phép Tài liệu Tự do GNU (GFDL)'],
        [],
        ['Không bảo lưu quyền nào:'],
        ['PVCC-tác giả', 'Phạm vi công cộng'],
        [],
        ['Khác (xem ở dưới)'],
        []
        ],   
      'USGovLicense' :
       [
       ['PD-USGov', 'Chính quyền Liên bang Hoa Kỳ'],
       ['PD-USGov-NASA','NASA'],
       ['PD-USGov-Military-Navy','Hải quân Hoa Kỳ'],
       ['PD-USGov-NOAA','Cục Quản lý Đại dương và Khí quyển Quốc gia Hoa Kỳ'],
       ['PD-USGov-Military-Air_Force','Không lực Hoa Kỳ'],
       ['PD-USGov-Military-Army','Bộ binh Hoa Kỳ'],
       ['PD-USGov-CIA-WF','CIA World Factbook'],
       ['PD-USGov-USGS','Trung tâm Khảo sát Địa chất Hoa Kỳ']
       ],
      'IneligibleLicense' :
       [
       ['', 'vui lòng chọn...'],
       ['PVCC-hình dạng','Hình chỉ là dạng hình học đơn giản'],
       ['PVCC-chữ','Hình chỉ có một vài từ hoặc chữ cái'],
       ['PVCC-biểu trưng chữ','Biểu trưng hoặc hình tương tự chỉ có chữ và các dạng hình học đơn giản'],
       ['PVCC-hóa học','Công thức cấu trúc hóa học'],
       ['PVCC-không đủ chuẩn','Loại hình khác không chứa quyền tác giả gốc']
       ],
      'NFSubjectLicense' :
       [
       ['', 'vui lòng chọn...'],
       ['KTD-nghệ thuật 2 chiều', 'Tác phẩm nghệ thuật hai chiều (tranh vẽ, bản thảo,...)'], 
       ['KTD-hình lịch sử', 'Hình lịch sử độc nhất'], 
       ['Sử dụng hợp lý trong bài', 'khác (vui lòng mô tả trong mục mô tả ở đầu)']
       ],
      'NF3DLicense' :
       [
       [, 'vui lòng chọn...'],
       ['KTD-công trình kiến trúc', 'Bản minh họa công trình kiến trúc'],
       ['KTD-nghệ thuật 3 chiều', 'Tác phẩm sáng tạo 3 chiều khác (điêu khắc,...)']
       ],
      'NFCoverLicense' :
         [
         ['', 'vui lòng chọn...'],
         ['KTD-bìa sách', 'Trang bìa một cuốn sách'], 
         ['KTD-bìa đĩa nhạc', 'Bìa tác phẩm thu thanh (album, đĩa đơn, bài hát, CD)'], 
         ['KTD-bìa bộ trò chơi', 'Bìa trò chơi điện tử/máy tính'], 
         ['KTD-bìa tạp chí', 'Trang bìa một tạp chí'], 
         ['KTD-bìa phim', 'Bìa cuốn phim'], 
         ['KTD-bìa phần mềm', 'Bìa một sản phẩm phần mềm'], 
         ['KTD-bìa sản phẩm', 'Bao bì của sản phẩm thương mại'], 
         ['KTD-tiêu đề', 'Màn hình hiện tiêu đề của chương trình truyền hình'], 
         ['KTD-áp phích phim', 'Áp phích phim'], 
         ['KTD-áp phích', 'Áp phích chính thức của một sự kiện'], 
         ['Sử dụng hợp lý trong bài', 'khác (vui lòng mô tả trong mục mô tả ở đầu)']
         ],
      'NFExcerptLicense' :
         [
         ['', 'vui lòng chọn...'],
         ['KTD-hình chụp màn hình ti vi', 'Hình chụp màn hình truyền hình'], 
         ['KTD-hình chụp màn hình phim', 'Hình chụp màn hình phim'], 
         ['KTD-hình chụp màn hình trò chơi điện tử', 'Hình chụp màn hình trò chơi điện tử'], 
         ['KTD-hình chụp màn hình video', 'Hình chụp màn hình video'], 
         ['KTD-hình chụp màn hình video âm nhạc', 'Hình chụp màn hình video âm nhạc'], 
         ['KTD-hình chụp màn hình phần mềm', 'Hình chụp màn hình phần mềm'], 
         ['KTD-hình chụp màn hình trang mạng', 'Hình chụp màn hình trang mạng'], 
         ['KTD-diễn thuyết', 'Trích đoạn âm thanh của một bài diễn thuyết'], 
         ['KTD-đoạn âm thanh', 'Trích đoạn âm thanh từ một bản thu thanh'], 
         ['KTD-đoạn phim', 'Trích đoạn từ một video'], 
         ['KTD-nhạc phổ', 'Nhạc phổ trình bày một đoạn nhạc'], 
         ['KTD-hình truyện tranh', 'Khung truyện tranh, tiểu thuyết đồ họa, manga,...'], 
         ['KTD-biểu tượng máy tính', 'Biểu tượng máy tính'], 
         ['KTD-hình chụp báo', 'Trang báo'], 
         ['Sử dụng hợp lý trong bài', 'khác (vui lòng mô tả trong mục mô tả ở đầu)']
         ],      
      'NFLogoLicense' :
         [
         ['KTD-biểu trưng', 'Biểu trưng công ty, tổ chức,...'], 
         ['KTD-con dấu', 'Con dấu chính thức, huy hiệu,...'], 
         ['KTD-biểu tượng', 'Biểu tượng chính thức khác']
         ],
      'NFMiscLicense' :
         [
         ['Sử dụng hợp lý trong bài', 'khác (vui lòng mô tả trong mục mô tả ở đầu)'], 
         ['KTD-hình lịch sử', 'Hình lịch sử'], 
         ['KTD-nghệ thuật 2 chiều', 'Tác phẩm nghệ thuật hai chiều (tranh vẽ, bản thảo,...)'], 
         ['KTD-tiền tệ', 'Hình ảnh tiền tệ (tiền giấy, tiền xu,...)'], 
         ['KTD-công trình kiến trúc', 'Công trình kiến trúc'], 
         ['KTD-nghệ thuật 3 chiều', 'Tác phẩm sáng tạo 3 chiều khác (điêu khắc,...)'], 
         ['KTD-bìa sách', 'Trang bìa một cuốn sách'], 
         ['KTD-bìa đĩa nhạc', 'Bìa tác phẩm thu thanh (album, đĩa đơn, bài hát, CD)'], 
         ['KTD-bìa bộ trò chơi', 'Bìa trò chơi điện tử/máy tính'], 
         ['KTD-bìa tạp chí', 'Trang bìa một tạp chí'], 
         ['KTD-bìa phim', 'Bìa cuốn phim'], 
         ['KTD-bìa phần mềm', 'Bìa một sản phẩm phần mềm'], 
         ['KTD-bìa sản phẩm', 'Bao bì của sản phẩm thương mại'], 
         ['KTD-tiêu đề', 'Màn hình hiện tiêu đề của chương trình truyền hình'], 
         ['KTD-áp phích phim', 'Áp phích phim'], 
         ['KTD-áp phích', 'Áp phích chính thức của một sự kiện'], 
         ['KTD-hình chụp màn hình ti vi', 'Hình chụp màn hình ti vi'], 
         ['KTD-hình chụp màn hình phim', 'Hình chụp màn hình phim'], 
         ['KTD-hình chụp màn hình trò chơi điện tử', 'Hình chụp màn hình trò chơi điện tử'], 
         ['KTD-hình chụp màn hình video', 'Hình chụp màn hình video'], 
         ['KTD-hình chụp màn hình video âm nhạc', 'Hình chụp màn hình video âm nhạc'], 
         ['KTD-hình chụp màn hình phần mềm', 'Hình chụp màn hình phần mềm'], 
         ['KTD-hình chụp màn hình trang mạng', 'Hình chụp màn hình trang mạng'], 
         ['KTD-diễn thuyết', 'Trích đoạn âm thanh của một bài diễn thuyết'], 
         ['KTD-đoạn âm thanh', 'Đoạn âm thanh từ một bản thu thanh'], 
         ['KTD-đoạn phim', 'Trích đoạn từ một video'], 
         ['KTD-nhạc phổ', 'Nhạc phổ trình bày một đoạn nhạc'], 
         ['KTD-hình truyện tranh', 'Khung truyện tranh, manga, v.v.'], 
         ['KTD-biểu tượng máy tính', 'Biểu tượng máy tính'], 
         ['KTD-hình chụp báo', 'Trang báo'], 
         ['KTD-biểu trưng', 'Biểu trưng công ty, tổ chức, v.v.'], 
         ['KTD-con dấu', 'Con dấu chính thức, huy hiệu v.v.'], 
         ['KTD-biểu tượng', 'Biểu tượng chính thức khác'],
         ['KTD-trang phục thể thao', 'Trang phục thể thao'], 
         ['KTD-tem thư', 'Tem thư'],
         ['KTD-hình chụp màn hình Microsoft', 'Hình chụp màn hình Microsoft']
         ],
      'NFExtraLicense' :
         [
         ['', 'không'],
         ['Bản quyền hoàng gia và các nguồn chính phủ khác'],
         ['KTD-bản quyền Hoàng gia', 'Bản quyền Hoàng gia Liên hiệp Anh'],
         ['KTD-bản quyền Hoàng gia New Zealand', 'Bản quyền Hoàng gia New Zealand'],
         ['KTD-bản quyền Hoàng gia Canada', 'Bản quyền Hoàng gia Canada'],
         ['KTD-AUSPIC', 'AUSPIC (Cơ sở dữ liệu hình của Nghị viện Úc'],
         ['KTD-chính phủ Philippines', 'Chính phủ Philippines'],
         ['KTD-Lực lượng Phòng vệ Phần Lan', 'Lực lượng Phòng vệ Phần Lan'],
         [],
         ['Các nguồn riêng khác'],
         ['KTD-hình ảnh Thư viện Công cộng Denver', 'Thư viện Công cộng Denver'],
         ['KTD-phương tiện ESA', 'ESA (Cơ quan Vũ trụ châu Âu)'],
         [],
         ['Có thể thuộc phạm vi công cộng tại các nước khác'],
         ['KTD-cũ-50', 'Tác giả đã mất hơn 50 năm trước.'],
         ['KTD-cũ-70', 'Tác giả đã mất hơn 70 năm trước.'],
         [],
         ['Được trao một số quyền, nhưng không hoàn toàn tự do'],
         ['KTD-quảng bá', 'Từ bộ tài liệu quảng bá đính kèm'],
         ['KTD-không thương mại', 'Được quyền sử dụng, nhưng chỉ cho mục đích giáo dục và/hoặc phi thương mại.'],
         ['KTD-không phái sinh', 'Được quyền sử dụng, nhưng không cho phép tạo ra tác phẩm phái sinh.'],
         ['KTD-cho phép', 'Được quyền sử dụng, nhưng chỉ dành riêng cho Wikipedia'],
         []
         ]
   };
   for (var group in licenseLists) {
      fuwMakeSelection(group, licenseLists[group]);
   }


   this.knownCommonsLicenses = {
      'self|GFDL|cc-by-sa-all|migration=redundant' : 1,
      'self|Cc-zero' : 1,
      'PD-self' : 1,
      'self|GFDL|cc-by-sa-4.0|migration=redundant' : 1,
      'self|GFDL|cc-by-4.0|migration=redundant' : 1,
      'self|GFDL|cc-by-sa-3.0|migration=redundant' : 1,
      'self|GFDL|cc-by-3.0|migration=redundant' : 1,
      'self|cc-by-sa-4.0' : 1,
      'self|cc-by-sa-3.0' : 1,
      'cc-by-sa-4.0' : 1,
      'cc-by-sa-3.0' : 1,
      'cc-by-sa-2.5' : 1,
      'cc-by-4.0' : 1,
      'cc-by-3.0' : 1,
      'cc-by-2.5' : 1,
      'FAL' : 1,
      'PD-old-100' : 1,
      'PD-old' : 1,
      'PD-Art' : 1,
      'PD-US' : 1,
      'PD-USGov' : 1,
      'PD-USGov-NASA' : 1,
      'PD-USGov-Military-Navy' : 1,
      'PD-ineligible' : 1,
      'Attribution' : 1,
      'Copyrighted free use' : 1
   };


   // textfields that don't react directly
   // to user input and are used only for assembling stuff:
   if (fuwTesting) {
      fuwMakeTextfield('SandboxSummary', function(){void(0);});
      fuwMakeTextarea('SandboxText', function(){void(0);});
      fuwGet('SandboxSummary').disabled="disabled";
      fuwGet('SandboxText').disabled="disabled";
      fuwGet('SandboxText').rows = 12;
   }

   // set links to "_blank" target, so we don't accidentally leave the page,
   // because on some browsers that would destroy all the input the user has already entered
   $('.fuwOutLink a').each(function() {
      this.target = '_blank';
   });

   // make main area visible
   fuwSetVisible('UploadScriptArea', true);

}
// ====================================== 
// end of fuwGlobal constructor function
// ======================================



function fuwRadioClick(e) {
   var ev = e || event;
   var src = ev.target || ev.srcElement;
   //alert('onclick event from ' + src + ' (' + src.value + ')');
   fuwUpdateOptions();
   return true;
}

/* 
* =============================================================
* function fuwUpdateOptions
* =============================================================
* This is the onchange event handler for most of the input
* elements in the main form. It changes visibility and disabled
* status for the various sections of the input form in response
* to which options are chosen.
*/
function fuwUpdateOptions() {

   var fuw = window.fuw;
   var warn = fuw.warn || {};
   var opts = fuw.opts = { };
   opts.InputFilename = $('#TargetForm input#file').val();

   var widgets = fuw && fuw.ScriptForm ? fuw.ScriptForm.elements : [];
   for (i = 0; i < widgets.length; i++) {
      var w = widgets[i];
      if (w.type == "radio") {
         var nm = w.name;
         var id = w.id;
         var vl = w.checked && !w.disabled && fuwIsVisible(w);
         opts[id] = vl;
         if (vl) opts[nm] = id;
      }
      else {
         var id = w.id;
         var active = !w.disabled && fuwIsVisible(w);
         if (active) {
            var value = ((type == 'checkbox') ? w.checked : w.value);
            opts[id] = value;         
         }
      }
   };
   opts.MainOption = opts.FreeOptions || opts.NonFreeOptions;
   
   // some parts of the input form are re-used across sections
   // and must be moved into the currently active input section:

   // minimality section is shared between all NF sections
   fuwMove('NFMinimalitySection', 'detailsNFSubject', (opts.OptionNFSubject)) ||
   fuwMove('NFMinimalitySection', 'detailsNF3D', (opts.OptionNF3D)) ||
   fuwMove('NFMinimalitySection', 'detailsNFExcerpt', (opts.OptionNFExcerpt)) ||
   fuwMove('NFMinimalitySection', 'detailsNFCover', (opts.OptionNFCover)) ||
   fuwMove('NFMinimalitySection', 'detailsNFLogo', (opts.OptionNFLogo)) ||
   fuwMove('NFMinimalitySection', 'detailsNFPortrait', (opts.OptionNFPortrait)) ||
   fuwMove('NFMinimalitySection', 'detailsNFMisc', true);

   // AnyOtherInfo section is shared between all
   fuwMove('AnyOtherInfo', 'detailsOwnWork', opts.OptionOwnWork) ||
   fuwMove('AnyOtherInfo', 'detailsThirdParty', opts.OptionThirdParty) ||
   fuwMove('AnyOtherInfo', 'detailsFreeWebsite', opts.OptionFreeWebsite) ||
   fuwMove('AnyOtherInfo', 'detailsPDOld', opts.OptionPDOld) ||
   fuwMove('AnyOtherInfo', 'detailsPDOther', opts.OptionPDOther) ||
   fuwMove('AnyOtherInfo', 'detailsNFSubject', opts.OptionNFSubject) ||
   fuwMove('AnyOtherInfo', 'detailsNF3D', opts.OptionNF3D) ||
   fuwMove('AnyOtherInfo', 'detailsNFExcerpt', opts.OptionNFExcerpt) ||
   fuwMove('AnyOtherInfo', 'detailsNFCover', opts.OptionNFCover) ||
   fuwMove('AnyOtherInfo', 'detailsNFLogo', opts.OptionNFLogo) ||
   fuwMove('AnyOtherInfo', 'detailsNFPortrait', opts.OptionNFPortrait) ||
   fuwMove('AnyOtherInfo', 'detailsNFMisc', opts.OptionNFMisc);

   // author input field is shared between all sections except "Own Work".
   // (will serve for the immediate/photographic author, in those cases where there
   // are two author fields)
   fuwMove('Author', 'placeholderFreeWebsiteAuthor', (opts.OptionFreeWebsite)) ||
   fuwMove('Author', 'placeholderPDOldAuthor', (opts.OptionPDOld)) ||
   fuwMove('Author', 'placeholderPDOtherAuthor', (opts.OptionPDOther)) ||
   fuwMove('Author', 'placeholderNFSubjectAuthor', (opts.OptionNFSubject)) ||
   fuwMove('Author', 'placeholderNF3DAuthor', (opts.OptionNF3D)) ||
   fuwMove('Author', 'placeholderNFExcerptAuthor', (opts.OptionNFExcerpt)) ||
   fuwMove('Author', 'placeholderNFCoverAuthor', (opts.OptionNFCover)) ||
   fuwMove('Author', 'placeholderNFPortraitAuthor', (opts.OptionNFPortrait)) ||
   fuwMove('Author', 'placeholderNFMiscAuthor', (opts.OptionNFMisc)) ||
   fuwMove('Author', 'placeholderAuthor', true);

   // source input field is shared between all sections except "Own Work".
   // (will serve for immediate/web source, in those cases where there are two
   // source fields involved)
   fuwMove('Source', 'placeholderFreeWebsiteSource', (opts.OptionFreeWebsite)) ||
   fuwMove('Source', 'placeholderPDOldSource', (opts.OptionPDOld)) ||
   fuwMove('Source', 'placeholderPDOtherSource', (opts.OptionPDOther)) ||
   fuwMove('Source', 'placeholderNFSubjectSource', (opts.OptionNFSubject)) ||
   fuwMove('Source', 'placeholderNF3DSource', (opts.OptionNF3D)) ||
   fuwMove('Source', 'placeholderNFExcerptSource', (opts.OptionNFExcerpt)) ||
   fuwMove('Source', 'placeholderNFCoverSource', (opts.OptionNFCover)) ||
   fuwMove('Source', 'placeholderNFLogoSource', (opts.OptionNFLogo)) ||
   fuwMove('Source', 'placeholderNFPortraitSource', (opts.OptionNFPortrait)) ||
   fuwMove('Source', 'placeholderNFMiscSource', (opts.OptionNFMisc)) ||
   fuwMove('Source', 'placeholderSource', true);

   // date input field is shared between all sections except "Logo", which doesn't need it.
   // will serve for derived/photographic date in the case of 3D items
   fuwMove('Date', 'placeholderFreeWebsiteDate', (opts.OptionFreeWebsite)) ||
   fuwMove('Date', 'placeholderThirdPartyDate', (opts.OptionThirdParty)) ||
   fuwMove('Date', 'placeholderPDOldDate', (opts.OptionPDOld)) ||
   fuwMove('Date', 'placeholderPDOtherDate', (opts.OptionPDOther)) ||
   fuwMove('Date', 'placeholderNFSubjectDate', (opts.OptionNFSubject)) ||
   fuwMove('Date', 'placeholderNF3DDate', (opts.OptionNF3D)) ||
   fuwMove('Date', 'placeholderNFExcerptDate', (opts.OptionNFExcerpt)) ||
   fuwMove('Date', 'placeholderNFCoverDate', (opts.OptionNFCover)) ||
   fuwMove('Date', 'placeholderNFPortraitDate', (opts.OptionNFPortrait)) ||
   fuwMove('Date', 'placeholderNFMiscDate', (opts.OptionNFMisc)) ||
   fuwMove('Date', 'placeholderDate', true);
   
   // permission field is shared between ThirdParty and FreeWebsite sections
   fuwMove('Permission', 'placeholderFreeWebsitePermission', (opts.OptionFreeWebsite)) ||
   fuwMove('Permission', 'placeholderPermission', true);

   // publication field is shared between PDOld, NFPortrait and NFMisc
   fuwMove('Publication', 'placeholderNFPortraitPublication', (opts.OptionNFPortrait)) ||
   fuwMove('Publication', 'placeholderNFMiscPublication', (opts.OptionNFMisc)) ||
   fuwMove('Publication', 'placeholderPublication', true);

   // Purpose, Commercial, Replaceable and ReplaceableText FUR fields are shared
   // between some but not all of the non-free sections
   fuwMove('NFPurpose', 'placeholderNFExcerptPurpose', (opts.OptionNFExcerpt)) ||
   fuwMove('NFPurpose', 'placeholderNFPurpose');
   fuwMove('NFCommercial', 'placeholderNFPortraitCommercial', (opts.OptionNFPortrait)) ||
   fuwMove('NFCommercial', 'placeholderNFCommercial');
   fuwMove('NFReplaceable', 'placeholderNFPortraitReplaceable', (opts.OptionNFPortrait)) ||
   fuwMove('NFReplaceable', 'placeholderNFReplaceable');
   fuwMove('NFReplaceableText', 'placeholderNFExcerptReplaceable', (opts.OptionNFExcerpt)) ||
   fuwMove('NFReplaceableText', 'placeholderNFReplaceableText', true);

   // submit button goes to Step1 if user has chosen a plain overwrite of an existing file,
   // and to the active section of Step3 if otherwise
   fuwMove('fuwSubmit', 'UploadScriptStep1', (warn.ImageExists && opts.OverwriteSame)) ||
   fuwMove('fuwSubmit', 'detailsOwnWork', opts.OptionOwnWork) ||
   fuwMove('fuwSubmit', 'detailsThirdParty', opts.OptionThirdParty) ||
   fuwMove('fuwSubmit', 'detailsFreeWebsite', opts.OptionFreeWebsite) ||
   fuwMove('fuwSubmit', 'detailsPDOld', opts.OptionPDOld) ||
   fuwMove('fuwSubmit', 'detailsPDOther', opts.OptionPDOther) ||
   fuwMove('fuwSubmit', 'detailsNFSubject', opts.OptionNFSubject) ||
   fuwMove('fuwSubmit', 'detailsNF3D', opts.OptionNF3D) ||
   fuwMove('fuwSubmit', 'detailsNFExcerpt', opts.OptionNFExcerpt) ||
   fuwMove('fuwSubmit', 'detailsNFCover', opts.OptionNFCover) ||
   fuwMove('fuwSubmit', 'detailsNFLogo', opts.OptionNFLogo) ||
   fuwMove('fuwSubmit', 'detailsNFPortrait', opts.OptionNFPortrait) ||
   fuwMove('fuwSubmit', 'fuwSubmitHost', true);


   // Show and hide warnings:

   // filename-related warnings:
   fuwSetVisible('warningIllegalChars', warn.IllegalChars);
   fuwSetVisible('warningBadFilename',  warn.BadFilename);
   fuwSetVisible('warningImageOnCommons', warn.ImageOnCommons);
   fuwSetVisible('warningImageExists', warn.ImageExists);
   fuwMove('warningImageThumb', 'warningImageOnCommons', warn.ImageOnCommons, true) ||
   fuwMove('warningImageThumb', 'warningImageExists', true, true);


   // notices related to the top-level options:
   fuwSetVisible('warningWhyNotCommons', opts.OptionFree);
   fuwSetVisible('warningNF', opts.OptionNonFree);
   fuwSetVisible('warningNoGood', opts.OptionNoGood);

   // warnings related to non-free "used in" article
   fuwSetVisible('warningNFArticleNotFound', warn.NFArticleNotFound);
   fuwSetVisible('warningNFArticleNotMainspace', warn.NFArticleNotMainspace);
   fuwSetVisible('warningUserspaceDraft', warn.UserspaceDraft);
   fuwSetVisible('warningNFArticleDab', warn.NFArticleDab);
   fuwSetVisible('NFArticleOK', warn.NFArticleOK);

   // warnings depending on user status:
   if (fuw.userStatus.match(/problem|newbie|notAutoconfirmed/)) {
      fuwSetVisible('warningFreeWebsite', opts.OptionFreeWebsite);
      fuwSetVisible('warningOwnWork', opts.OptionOwnWork);
      fuwSetVisible('warningPDOther', opts.OptionPDOther);
      fuwSetVisible('warningNFSubject', opts.OptionNFSubject);
   }

   // hide main sections in case of intended plain overwrite:   
   fuwSetVisible('UploadScriptStep2', !(warn.ImageExists && opts.OverwriteSame));
   fuwSetVisible('UploadScriptStep3', !(warn.ImageExists && opts.OverwriteSame));

   // show/hide top-level options
   fuwSetVisible('detailsFreeStatus', opts.OptionFree);
   fuwSetVisible('sendToCommons', opts.OptionFree);

   // show/hide details sections
   fuwSetVisible('detailsNFArticle', opts.OptionNonFree);
   fuwSetVisible('detailsNFWorkType', opts.OptionNonFree);
   fuwSetVisible('detailsOwnWork', opts.OptionOwnWork);
   fuwSetVisible('detailsThirdParty', opts.OptionThirdParty);
   fuwSetVisible('detailsFreeWebsite', opts.OptionFreeWebsite);
   fuwSetVisible('detailsPDOld', opts.OptionPDOld);
   fuwSetVisible('detailsPDOther', opts.OptionPDOther);
   fuwSetVisible('detailsNFSubject', opts.OptionNFSubject);
   fuwSetVisible('detailsNF3D', opts.OptionNF3D);
   fuwSetVisible('detailsNFExcerpt', opts.OptionNFExcerpt);
   fuwSetVisible('detailsNFCover', opts.OptionNFCover);
   fuwSetVisible('detailsNFLogo', opts.OptionNFLogo);
   fuwSetVisible('detailsNFPortrait', opts.OptionNFPortrait);
   fuwSetVisible('detailsNFMisc', opts.OptionNFMisc);

   fuwSetVisible('EditSummaryDiv', opts.OverwriteSame || opts.OverwriteDifferent);

   // set enabled/disabled
   // It might be useful to adapt this to be more liberal about
   // the order of input, at least for experienced users.

   //fuwSetEnabled('Artist3D', opts.PD3D);
   //fuwSetEnabled('Country3D', opts.FOP3D);
   fuwSetEnabled('ThirdPartyEvidenceLink', opts.ThirdPartyEvidenceOptionLink);
   fuwSetEnabled('ThirdPartyOTRSTicket', opts.ThirdPartyEvidenceOptionOTRS);
   fuwSetEnabled('NFSubjectPurpose', opts.NFSubjectCheckDiscussed);
   fuwSetEnabled('NF3DPurpose', opts.NF3DCheckDiscussed);
   fuwSetEnabled('NF3DPermission', opts.NF3DOptionFree);
   fuwSetEnabled('USGovLicense', opts.PDOtherUSGov);
   fuwSetEnabled('PDOfficialPermission', opts.PDOtherOfficial);
   fuwSetEnabled('IneligibleLicense', opts.PDOtherSimple);
   fuwSetEnabled('PDOtherPermission', opts.PDOtherOther);
   fuwSetEnabled('AnyOther', true);

   // need to re-collect the remaining (non-radiobutton) input into the opts object again,
   // preparing for validation:
   for (i = 0; i < widgets.length; i++) {
      var w = widgets[i];
      var type = w.type;

      if (type != "radio") {
         var id = w.id;
         var active = !w.disabled && fuwIsVisible(w);
         if (active) {
            var value = ((type == 'checkbox') ? w.checked : w.value);
            opts[id] = value;         
         }
      }
   };

   // final step of validation: check if input is sufficient for
   // setting the submit buttons active
   var valid = fuw.validateInput();
   var validForCommons = valid && opts.OptionFree && !(opts.OverwriteSame || opts.OverwriteDifferent)
         && !opts.ThirdPartyEvidenceOptionNone;
   fuwSetVisible('sendToCommons', opts.OptionFree);
   fuwSetEnabled('CommonsButton', validForCommons);
   fuwGet('fuwSubmitText').innerHTML = opts.OptionFree ? 
         ("<b>Không</b>, tôi chỉ muốn tải tập tin lên wiki này thôi.<br/>" + 
          "<small>Bằng cách này tập tin chỉ có thể sử dụng tại Wikipedia tiếng Việt. Tuy nhiên, " +
          "người nào đó vẫn có thể quyết định chuyển tập tin sang Commons hoặc dùng tại nơi khác. Nếu bạn " +
          "không muốn tập tin được chuyển sang Commons và bị xóa cục bộ, hãy cân nhắc thêm thẻ " +
          "{{tl|Keep local}}.</small>") :
         "Tải tập tin này lên.";
   var submitbtn = fuwGet('SubmitButton');
   if(submitbtn) submitbtn.value = validForCommons ? "Tải lên dự án địa phương" : "Tải lên";   
   fuwSetEnabled('EditSummary', true);
   fuwSetEnabled('SubmitButton', valid && (fuw.userStatus != 'notAutoconfirmed'));
   if (fuwTesting) {
      fuwSetEnabled('SandboxButton', valid);
   }

   // if we're in testing mode, update the Sandbox display fields
   // after each input change. In normal mode, collectInput() will
   // only be needed on submit.
   if (fuwTesting) {
      fuw.collectInput();
      fuw.formatOutput(false);
      fuwSetVisible('placeholderTestForm', true);
   }  
}

// ============================================================
// methods of the global fuw object
// ============================================================

// ============================================================
// report validation status of filename information
// ============================================================
// This is called from within fuw.validateInput(), i.e. every
// time anything in the whole form is changed. It only reports
// results that have previously been cached in the opts and warn
// objects. The actual checking is done in the event handler
// of the file input boxes.
fuwGlobal.prototype.hasValidFilename = function() {
   var opts = this.opts;
   var warn = this.warn;
   var valid =   
      opts.InputName &&
      opts.InputFilename &&
      !warn.BadFilename &&
      !warn.ImageOnCommons &&
      // if image exists on enwiki, accept only if user has confirmed overwrite:
      !(warn.ImageExists && !(opts.OverwriteSame || opts.OverwriteDifferent));
   //alert("HasValidFilename: " + valid);
   return valid;
};

// ============================================================
// validation status for common input elements for all free
// options
// ============================================================
fuwGlobal.prototype.hasValidCommonFreeInput = function() {
   var opts = this.opts;
   var warn = this.warn;
   var valid = opts.InputDesc;
   //alert("HasValidCommonFreeInput: " + valid);
   return valid;
};
// ============================================================
// validation status for common input elements for all non-free
// options
// ============================================================
fuwGlobal.prototype.hasValidCommonNFInput = function() {
   var opts = this.opts;
   var warn = this.warn;
   var valid =
      opts.OptionNonFree &&
      opts.InputDesc && 
      opts.NFArticle &&
      opts.Source &&
      opts.NFMinimality &&
      !warn.NFArticleNotFound &&
      !warn.NFArticleNotMainspace &&
      !warn.NFArticleDab;
   //alert("hasValidCommonNFInput: " + valid);
   return valid;
};
// ============================================================
// Main validation routine. Modify this to tweak which fields
// are to be considered obligatory for each case group
// ============================================================
fuwGlobal.prototype.validateInput = function() {
   var opts = this.opts;
   var warn = this.warn;
   var valid = (
      this.hasValidFilename()
      &&
      (! (opts.OverwriteDifferent && ! opts.EditSummary))
      &&
      (
       ( // overwriting is okay if there is an edit summary
        opts.OverwriteSame && opts.EditSummary
       )
       ||
       ( // free options
         this.hasValidCommonFreeInput() &&
         (
          (opts.OptionOwnWork &&
           opts.Date &&
           opts.OwnWorkLicense)
          ||
          (opts.OptionThirdParty &&
           opts.Author &&
           opts.Source &&
           opts.Permission &&
           (opts.ThirdPartyOtherLicense || opts.ThirdPartyLicense) &&
           ((opts.ThirdPartyEvidenceOptionLink && opts.ThirdPartyEvidenceLink) ||
            opts.ThirdPartyEvidenceOptionOTRS ||
            opts.ThirdPartyEvidenceOptionOTRSForthcoming ||
            opts.ThirdPartyEvidenceOptionNone))
          ||
          (opts.OptionFreeWebsite &&
           opts.Author &&
           opts.Source &&
           (opts.FreeWebsiteOtherLicense || opts.FreeWebsiteLicense) &&
           opts.Permission)
          ||
          (opts.OptionPDOld &&
           opts.Author &&
           opts.PDOldAuthorLifetime &&
           opts.Publication &&
           opts.Date &&
           opts.Source &&
           opts.PDOldOptions && 
           (! (opts.PDOldOther && ! opts.PDOldPermission)))
          ||
          (opts.OptionPDOther &&
           opts.Author &&
           opts.Source &&
           ((opts.PDOtherUSGov && opts.USGovLicense) ||
            (opts.PDOtherOfficial && opts.PDOfficialPermission) ||
            (opts.PDOtherSimple && opts.IneligibleLicense) ||
            (opts.PDOtherOther && opts.PDOtherPermission)))
        )
       ) // end of free options
       ||
       ( // non-free options
         this.hasValidCommonNFInput() &&
         (
          (opts.OptionNFSubject &&
           opts.NFSubjectLicense &&
           opts.Author &&
           (opts.NFSubjectCheckDedicated ||
           (opts.NFSubjectCheckDiscussed && opts.NFSubjectPurpose)))
          ||
          (opts.OptionNF3D &&
           opts.NF3DLicense &&
           opts.NF3DCreator &&
           opts.Author &&
           (opts.NF3DOptionSame ||
           (opts.NF3DOptionFree || opts.NF3DPermission)) && 
           (opts.NF3DCheckDedicated ||
            (opts.NF3DCheckDiscussed && opts.NF3DPurpose)))
          ||
          (opts.OptionNFExcerpt &&
           opts.NFExcerptLicense &&
           opts.Author &&
           opts.NFPurpose)
          ||
          (opts.OptionNFCover &&
           opts.NFCoverLicense &&
           opts.Author &&
           opts.NFCoverCheckDedicated 
          )
          ||
          (opts.OptionNFLogo &&
           opts.NFLogoLicense &&
           opts.NFLogoCheckDedicated
          )
          ||
          (opts.OptionNFPortrait &&
           opts.Publication &&
           opts.NFPortraitDeceased &&
           opts.Author &&
           opts.NFPortraitCheckDedicated &&
           opts.NFReplaceable &&
           opts.NFCommercial)
          ||
          (opts.OptionNFMisc &&
           opts.NFMiscLicense &&
           opts.Author &&
           opts.Publication &&
           opts.NFPurpose &&
           opts.NFReplaceableText &&
           opts.NFReplaceable &&
           opts.NFCommercial)
         )
       ) // end of non-free options
      )
   );
   return valid;
};

// =============================================================
// return which template name will be used as the main
// description template
// =============================================================
fuwGlobal.prototype.getDescriptionTemplateName = function() {
   // standard "Information" template for free files:
   if (this.opts.OptionFree) return "Information";
   // experimental new version of fair-use rationale template,
   // designed to fit the fields used in the wizard
   else if (this.opts.OptionNonFree) return "Non-free use rationale 2";
   return undefined;
};

// =============================================================
// get the license tag code from the appropriate input element
// =============================================================

fuwGlobal.prototype.getStandardLicense = function() {
   var opts = this.opts;
   
}

fuwGlobal.prototype.getLicense = function() {
   var opts = this.opts;
      // ThirdParty and FreeWebsite have alternative input fields
      // for manual entry of other licenses:
   var license = {};
   if (opts.PDOtherOther || opts.PDOldOther) {
      license.special = opts.PDOtherOther ? opts.PDOtherPermission : opts.PDOldPermission;
      if (! (license.special.match(/^\s*\{\{.+\}\}\s*$/))) {
         license.special = '{{PVCC-bởi vì|' + license.special + '}}';
      }
   }
   else {
      license.special = 
         opts.ThirdPartyOtherLicense || 
         opts.FreeWebsiteOtherLicense ||
         (opts.PDOtherOfficial ? ('{{PVCC-bởi vì|tác phẩm chính thức được miễn quyền tác giả một cách hợp pháp tại quốc gia xuất xứ}}') : null) ||
         (opts.OptionNFPortrait ? ('{{KTD-hình tiểu sử|' + opts.NFArticle + '}}') : null);
   }   
   if (! license.special) {
      // standard, non-parametrized tag licenses from dropdownbox.
      var simpleLicense = (opts.OptionOwnWork ? opts.OwnWorkLicense : null) ||
          (opts.OptionThirdParty ? opts.ThirdPartyLicense : null) ||
          (opts.OptionFreeWebsite ? opts.FreeWebsiteLicense : null) ||
          (opts.OptionNFSubject ? opts.NFSubjectLicense : null) ||
          (opts.OptionNF3D ? opts.NF3DLicense : null) ||
          (opts.OptionNFExcerpt ? opts.NFExcerptLicense : null) ||
          (opts.OptionNFCover ? opts.NFCoverLicense : null) ||
          (opts.OptionNFLogo ? opts.NFLogoLicense : null) ||
          (opts.OptionNFMisc ? opts.NFMiscLicense : null) ||
          (opts.PDOtherUSGov ? opts.USGovLicense : null) ||
          (opts.PDOtherSimple ? opts.IneligibleLicense : null) ||
          (opts.PDUSExpired ? 'PD-US-expired' : null) ||
          (opts.PDURAA   ? 'PVCC-URAA' : null) ||
          (opts.PDFormality ? 'PVCC-Hoa Kỳ' : null);

       // "PD-author" needs parameter, at least on Commons
       if (simpleLicense == 'PVCC-tác giả') {
          license.special = '{{PVCC-tác giả|' + opts.Author + '}}';
       }
       else if (this.knownCommonsLicenses[simpleLicense]) {
       // make sure we send only those licenses as "standard" licenses
       // that exist in the Commons license dropdown box
          license.standard = simpleLicense;
       }
       else {
          license.special = '\{\{' + simpleLicense + '\}\}';
       }
   }
   return license;
};

function fuwSubst(template) {
   return '{{subst:' + template + '}}';
}

// ===================================================================
// Produce code for local tracking categories
// ===================================================================
fuwGlobal.prototype.getTrackingCategory = function() {
   var opts = this.opts;
   var cat = "";
   if (opts.OptionFreeWebsite) { cat = "Tập tin từ nguồn bên ngoài được cấp phép tự do"; }
   else if (opts.OptionThirdParty) { cat = "Tập tin được bên thứ ba cấp phép"; }
   else if (opts.PDOtherOther || opts.PDOldOther) { cat = "Tập tin với tuyên bố phạm vi công cộng không theo chuẩn"; }
   else if (opts.OptionNFSubject || opts.OptionNF3D) { cat = "Tập tin không tự do được tải lên với mục đích bình luận về đối tượng"; }
   if (cat) {
      cat = "\n\{\{Category ordered by date|" + cat + "|" + 
      fuwSubst("CURRENTYEAR") + "|" + fuwSubst("CURRENTMONTH") + "|" + fuwSubst("CURRENTDAY2") + "\}\}";
   }
   return cat;
};

// ===================================================================
// Get or create an edit summary for the upload
// ===================================================================
// Note: if we work with the api.php interface, we can have separate
// data for the edit summary and the description page, which is far
// better than the way the index.php interface does it.
// TO DO: need to actually define an input element for a manually
// entered edit summary. Must be obligatory when overwriting files.
// In other cases we'll use an automatic edit summary.
// ===================================================================
fuwGlobal.prototype.getEditSummary = function() {
   var opts = this.opts;
   return (
      (opts.EditSummary ? (opts.EditSummary + ' ([[' + mw.config.get('wgPageName') + '|Trình tải lên tập tin]])') : null)||
      ("Tải lên " +
       (
        (opts.OptionOwnWork ? 'tập tin do người tải tự sáng tạo ra ' : false) ||
        (opts.OptionThirdParty ? 'tập tin từ một người khác ' : false) ||
        (opts.OptionFreeWebsite ? 'tập tin từ một nguồn được xuất bản tự do ' : false) ||
        (opts.OptionPDOld       ? 'tác phẩm cũ thuộc phạm vi công cộng ' : false) ||
        (opts.OptionPDOther     ? 'tác phẩm thuộc phạm vi công cộng ' : false) ||
        (opts.OptionNFSubject   ? 'tác phẩm không tự do với mục đích bình luận về đối tượng ' : false) ||
        (opts.OptionNF3D        ? 'miêu tả một tác phẩm nghệ thuật 3 chiều không tự do ' : false) ||
        (opts.OptionNFExcerpt   ? 'đoạn trích từ một tác phẩm không tự do ' : false) ||
        (opts.OptionNFCover     ? 'hình bìa nghệ thuật không tự do ' : false) ||
        (opts.OptionNFLogo      ? 'biểu trưng không tự do ' : false) ||
        (opts.OptionNFPortrait    ? 'hình ảnh lịch sử không tự do ' : false) ||
        (opts.OptionNFMisc      ? 'tập tin không tự do ' : "")
       )
       + 
       ("sử dụng [[" + mw.config.get('wgPageName') + "|Trình tải lên tập tin]]")
      ));
};


function fuwPackInfo(text, forCommons) {
   if (forCommons) {
      // reformat wikilinks embedded in description fields to adapt them for Commons
      text = text.replace(/\[\[([^\]]+)\]\]/g, 
         function(str, p1, offset, s) {

            // mark File links as local
            if (p1.match(/^:(File|Image):/)) {
               return "[[:vi" + p1 + "]]";
            }      
            // leave prefixed links unchanged:
            else if (p1.match(/^:[\w\-]+:/)) {
               return str;
            }
            // if the link is piped, add a prefix only
            else if (p1.match(/.+\|/)) {
               return "[[:vi:" + p1 + "]]";
            }
            // introduce a pipe
            else {
               return "[[:vi:" + p1 + "|" + p1 + "]]";
            }
         }
      );
      return "{{vi|" + text + "}}";
   } else return text;
}

// ================================================================
// This is the main method called by the event handler for the 
// (experimental) submit button. Its main task is to collect the 
// input into a single string of wikitext for the description page.
// ================================================================
fuwGlobal.prototype.collectInput = function() {
   var opts = this.opts;

   // object representing template fields for filling in
   // the description template. Pre-loaded with some
   // standard settings:
   var descFields = this.descFields = { 
      'Description' : opts.InputDesc,
      'Author'      : opts.Author,
      'Date'        : opts.Date,
      'Source'      : opts.Source
   };
   // "other information" (outside the template)
   this.otherInfo = null;
   
   if (opts.OptionNonFree) {
      descFields.Article = opts.NFArticle;
   }
   
   // add/modify option-specific fields:
   switch (opts.MainOption) {
      case 'OptionOwnWork':
      
         // use standard "source" field for optional "how created?" and 
         // "previously published" input fields.
         descFields.Source = fuwAppendLines([
            (opts.OwnWorkCreation || "{{own}}"), 
            "<br/>\n", 
            fuwSurroundString("'''Đã xuất bản trước đây:''' ", opts.OwnWorkPublication)]);
         var username = mw.user.getName();
         descFields.Author = '[[User:' + username + '|' + username + ']]';
         break;

      case 'OptionThirdParty':
      
         // use standard "permission" field for a compilation of the
         // "permission" input field and the various "evidence" options
         var evidence = (
            opts.ThirdPartyEvidenceOptionLink ? 
               ("Tuyên bố về cấp phép có thể xem tại: " + opts.ThirdPartyEvidenceLink) :
               (opts.ThirdPartyEvidenceOptionOTRS ? 
               ("Thỏa thuận cấp phép đã được gửi về OTRS." + 
                  fuwSurroundString(" Ticket: ", opts.ThirdPartyOTRSTicket) + "\{\{Chờ OTRS|year=" + fuwSubst("CURRENTYEAR") + 
                                                                              "|month=" + fuwSubst("CURRENTMONTH") + 
                                                                              "|day=" + fuwSubst("CURRENTDAY2") + "\}\}") :
               (opts.ThirdPartyEvidenceOptionOTRSForthcoming ? 
               "Thỏa thuận cấp phép sẽ sớm được gửi về OTRS. \{\{Chờ OTRS|year=" + fuwSubst("CURRENTYEAR") + 
                                                                              "|month=" + fuwSubst("CURRENTMONTH") + 
                                                                              "|day=" + fuwSubst("CURRENTDAY2") + "\}\}" :
               (opts.ThirdPartyEvidenceOptionNone ?
               "Sẽ cung cấp theo yêu cầu." : null))));
         descFields.Permission = fuwAppendLines([
            opts.ThirdPartyPermission,
            "<br/>\n",
            fuwSurroundString("'''Bằng chứng:''' ", evidence)]);
         break;
         
      case 'OptionFreeWebsite':
         descFields.Permission = opts.Permission;
         break;
         
      case 'OptionPDOld':
         // add "lifetime" input to "author" field
         descFields.Author = fuwAppendLines([
            opts.Author,
            "<br/>\n",
            fuwSurroundString("(Cuộc đời: ", opts.PDOldAuthorLifetime, ")")
         ]);
         
         // combine original and direct source into standard "source" field: 
         descFields.Source = fuwAppendLines([
            fuwSurroundString("'''Xuất bản lần đầu''': ", opts.Publication),
            "<br/>\n",
            fuwSurroundString("'''Nguồn kiểm chứng nhanh''': ", opts.Source)
         ]);
         
         // no standard tag available for "lack-of-registration" PD-US. Need
         // to put this into the "permission" field
         if (opts.PDFormality) 
            descFields.Permission = 
               "Bản quyền đã hết hạn vì tác phẩm được xuất bản mà không có quyền tác giả " +
               "được ghi nhận và/hoặc không được đăng ký các quyền sở hữu cần thiết.";
      
         // add optional "explanation" input to "permission" field
         if (opts.PDOldPermission) {
            descFields.Permission = fuwAppendLines([
               descFields.Permission,
               "\n\n",
               opts.PDOldPermission
            ]);
         }
         break;
         
      case 'OptionPDOther':
         // Need "permission" field in case of "official item" option
         if (opts.PDOtherOfficial) 
            descFields.Permission = opts.PDOfficialPermission;
         break; 

      case 'OptionNFSubject':
         // most FUR elements can be automatically provided:
         descFields.Purpose = (
            opts.NFSubjectCheckDedicated ? 
             ("Để xác định một cách trực quan đối tượng của bài viết. " +
              "Toàn bộ bài viết được đặc biệt dành riêng cho việc bàn luận về tác phẩm này.") :
            (opts.NFSubjectCheckDiscussed ?
             ("Để hỗ trợ cho việc bàn luận trong văn cảnh bách khoa về tác phẩm này trong bài viết này. " +
              "Việc sử dụng minh họa là đặc biệt cần thiết nhằm hỗ trợ (các) vấn đề sau: " +
              "<br/>\n" + opts.NFSubjectPurpose) : null)
         );

         descFields.Replaceability = "Bất kỳ tác phẩm phái sinh nào dựa trên tác phẩm này đều là vi phạm bản quyền, vì vậy không thể tạo ra một hình ảnh tự do tương đương.";
         descFields.Commercial     = "Việc sử dụng hình ảnh có độ phân giải thấp của tác phẩm này sẽ không ảnh hưởng đến khả năng thương mại của tác phẩm.";
         break;

      case 'OptionNF3D':
         // complex case: we need to assemble attribution and FUR both for the 
         // original 3D work and for the photographic depiction. Both might be 
         // non-free.
         descFields.Author = fuwAppendLines([
            fuwSurroundString("'''Tác phẩm gốc:''' ", opts.NF3DCreator),
            "<br/>\n",
            fuwSurroundString("'''Miêu tả:''' ", opts.Author)
         ]);
         descFields.Date = fuwAppendLines([
            fuwSurroundString("'''Tác phẩm gốc:''' ", opts.NF3DOrigDate),
            "<br/>\n",
            fuwSurroundString("'''Miêu tả:''' ", opts.Date)
         ]);
         descFields.Purpose = (
            opts.NF3DCheckDedicated ? 
             ("Để xác định một cách trực quan đối tượng của bài viết. " +
              "Toàn bộ bài viết được đặc biệt dành riêng cho việc bàn luận về tác phẩm này.") :
            (opts.NF3DCheckDiscussed ? 
             ("Để hỗ trợ cho việc bàn luận trong văn cảnh bách khoa về tác phẩm này trong bài viết này. " +
              "Việc sử dụng minh họa là đặc biệt cần thiết nhằm hỗ trợ (các) vấn đề sau: " +
              "<br/>\n" + opts.NF3DPurpose) : null)
         );
         descFields.Replaceability = "Bất kỳ tác phẩm phái sinh nào dựa trên tác phẩm này đều là vi phạm bản quyền, vì vậy không thể tạo ra một hình ảnh tự do tương đương.";
         descFields.Commercial     = "Việc sử dụng hình ảnh có độ phân giải thấp của tác phẩm này sẽ không ảnh hưởng đến khả năng thương mại của tác phẩm.";
         descFields["Other information"] = (
            opts.NF3DOptionSame ?
            ("Hình ảnh được tạo ra và xuất bản bởi cùng một tác giả, người cũng đồng thời " +
             "sở hữu các quyền đối với đối tượng gốc, và không có hình thức miêu tả thay thế nào " +
             "có thể tạo ra theo cách phù hợp hơn.") :
            ("Tác giả của hình ảnh đã phát hành tác phẩm nhiếp ảnh dưới một " +
             "giấy phép tự do, hoặc đưa nó ra phạm vi công cộng: " + opts.NF3DPermission)
         );
         break; 
         
      case 'OptionNFExcerpt':
         // FURs for screenshots etc. don't normally need to bother
         // about replaceability (with free images) and with commercial role,
         // but do need to bother about purpose and about replaceability with text.
         descFields.Purpose        = opts.NFPurpose;
         descFields.Replaceability_text = opts.NFReplaceableText;
         descFields.Replaceability = "Phần mềm hoặc trang web trong ảnh chụp màn hình này có bản quyền và không được phát hành theo giấy phép tự do, vì vậy không thể tạo một hình ảnh tự do tương đương.";
         descFields.Commercial     = "Việc sử dụng ảnh chụp màn hình có độ phân giải thấp từ phần mềm hoặc trang web sẽ không ảnh hưởng đến khả năng thương mại của phần mềm hoặc trang web đó.";
         break;
         
      case 'OptionNFCover':
         // cover art gets standard rationales.
         descFields.Purpose = 
            "đóng vai trò là phương tiện chính yếu để nhận dạng một cách trực quan " +
            "khi đặt ở phần đầu bài viết dành riêng cho tác phẩm được đề cập.";
         descFields.Replaceability = "Bất kỳ tác phẩm phái sinh nào dựa trên ảnh bìa này đều là vi phạm bản quyền, vì vậy không thể tạo ra một hình ảnh tự do tương đương.";
         descFields.Commercial     = "Việc sử dụng hình ảnh có độ phân giải thấp của ảnh bìa này sẽ không ảnh hưởng đến khả năng thương mại của tác phẩm đó.";
         break;
 
      case 'OptionNFLogo':
         // logos get standard rationales.
         descFields.Purpose = 
            "đóng vai trò là phương tiện chính yếu để nhận dạng một cách trực quan " +
            "khi đặt ở phần đầu bài viết dành riêng cho thực thể được đề cập.";
         descFields.Replaceability = "Bất kỳ tác phẩm phái sinh nào dựa trên biểu trưng này đều là vi phạm bản quyền, vì vậy không thể tạo ra một hình ảnh tự do tương đương.";
         descFields.Commercial     = "Việc sử dụng hình ảnh có độ phân giải thấp của biểu trưng của tổ chức được đề cập trong bài viết về tổ chức đó sẽ không ảnh hưởng đến khả năng thương mại của biểu trưng đó.";
         break;

      case 'OptionNFPortrait':
         // as with other historic photographs, it is useful to have both
         // original publication and direct source
         descFields.Source = fuwAppendLines([
            fuwSurroundString("'''Xuất bản lần đầu''': ", opts.Publication),
            "<br/>\n",
            fuwSurroundString("'''Nguồn kiểm chứng nhanh''': ", opts.Source)
         ]);   
         descFields.Purpose = 
            "để nhận diện một cách trực quan về người được đề cập, " +
            "đặt tại phần đầu bài viết tiểu sử về chính họ";
         descFields.Replaceability = opts.NFReplaceable;
         descFields.Commercial = opts.NFCommercial;
         descFields['Other information'] = 
            "Đối tượng của bức ảnh đã mất từ: " + opts.NFPortraitDeceased;
         break;
         
      case 'OptionNFMisc':
         descFields.Source = fuwAppendLines([
            fuwSurroundString(
               "'''Xuất bản lần đầu''': ", 
               opts.Publication,
               "<br/>\n'''Nguồn kiểm chứng nhanh:''' "),
            "",
            opts.Source
         ]);   
         descFields.Purpose = opts.NFPurpose;
         descFields.Replaceability = opts.NFReplaceable;
         descFields.Replaceability_text = opts.NFReplaceable_text;
         descFields.Commercial = opts.NFCommercial; 
         break;     
   };

   if (opts.OptionNonFree) {
      // common stuff for all non-free files:
      
      // Minimality field (same for all NF options):
      descFields.Minimality = opts.NFMinimality;
      
      // append optional "extra license" selector and "AnyOther" fields
      // to "Other information" field:
      descFields['Other information'] = fuwAppendLines([
         descFields['Other information'],
         "<br/>\n",
         fuwSurroundString('\{\{', opts.NFExtraLicense, '\}\}'),
         "<br/>\n",
         opts.AnyOther
      ]);
   }
   else {
      // common stuff for all free files:
      descFields.Other_versions = ''
      this.otherInfo = fuwAppendLines([this.otherInfo, "\n\n", opts.AnyOther]);
   
   }

};

fuwGlobal.prototype.formatOutput = function(forCommons) {
   var baseForm = this.ScriptForm;
   var targetForm = this.TargetForm;
   if (fuwTesting) {
      var testForm   = this.TestForm;
   }
   var opts = this.opts;
   var otherInfo = this.otherInfo;
   var descFields = this.descFields;

   var summary = "{{" + this.getDescriptionTemplateName();

   // assemble all fields into the wikitext of the description page:
   var fieldOrder = [
      'Source', 'Date', 'Author', 'Permission', 'Other_versions',
      'Article', 'Purpose', 'Replaceability', 'Replaceability_text', 
      'Minimality', 'Commercial', 'Other information'
   ];
   summary += "\n|Description = " + fuwPackInfo(descFields['Description'], forCommons);
   for (var i = 0; i < fieldOrder.length; i++) {
      if (descFields[fieldOrder[i]]) {
         summary += "\n|" + fieldOrder[i] + " = " + descFields[fieldOrder[i]];
      }
   }
   summary += "\n}}\n";
   if (otherInfo) {
      summary += "\n;Thông tin khác:\n" + fuwPackInfo(otherInfo, forCommons) + "\n";
   }

   var editSummary = this.getEditSummary();
   
   var license = this.getLicense();
   
   if (forCommons) {
      // pack our description info into an url pointing to the 
      // standard Commons Special:Upload
      // with pre-loaded description fields

      summary = fuwSubst("Đánh dấu tải lên bởi vi.wp UW") + "\n" + summary;
      summary = summary.replace(/\{\{Chờ OTRS\}\}/g, fuwSubst("OP"));

      if (license.special) {
         // manually format the whole description page including the license tag, if it
         // isn't one of the bare standard licenses in the dropdown box. Otherwise,
         // submit description summary and license as two separate url parameters.
         summary = summary + "\n\n" + license.special;
      }
      return (fuwGetCommonsURL() +
         "?title=Đặc biệt:Tải lên" +
         "&wpUploadDescription=" +
         encodeURIComponent(summary) +
         (license.standard ? 
          ("&wpLicense=" + encodeURIComponent(license.standard)) : '') +
         "&wpDestFile=" + 
         encodeURIComponent(opts.InputName));
   }
   else {
      // pack all description into a single "text" parameter to be submitted
      // to the local api.php upload.
      summary = "==Miêu tả==\n" + 
         summary + 
         "\n==Giấy phép==\n" + 
         (license.standard ? ("\{\{" + license.standard + "\}\}") : license.special) +
         this.getTrackingCategory();
         
      if (fuwTesting) {
         // Testing mode: show our data in the dummy form
         // at the bottom of the page.
         fuwGet('placeholderSandboxFilename').innerHTML = opts.InputName;
         this.TestForm.SandboxSummary.value = editSummary;
         this.TestForm.SandboxText.value = summary;
         fuwSetVisible('placeholderTestForm', true);
      }
      // write output parameters into target form
      // I can't believe IE7 is too stupid to simply understand "this.TargetForm.filename.value".
      ($('#TargetForm [name="filename"]')[0]).value = opts.InputName;
      ($('#TargetForm [name="text"]'    )[0]).value = summary;
      ($('#TargetForm [name="comment"]' )[0]).value = editSummary;
      ($('#TargetForm [name="token"]'   )[0]).value = mw.user.tokens.get('csrfToken');

   }
   
};

function fuwHasUserGroup(group) {
   // workaround because old IE versions don't have array.indexOf :-(
   for (i = 0; i < mw.config.get('wgUserGroups').length; i++) {
      if (mw.config.get('wgUserGroups')[i] == group) {
         return true;
      }
   }
   return false
}

fuwGlobal.prototype.getUserStatus = function() {
   // function to determine the experience status and userrights of the current user:
   // 'anon': not logged in; can't use script.
   // 'notAutoconfirmed': can't use local upload, but may use script to prepare upload for Commons
   // 'newbie': autoconfirmed but editcount < 100 
   //    (may be used in future to adapt instructions more to newbie needs)
   // 'problem': autoconfirmed but has 3 or more image-related warnings or deletion notifications among recent user talk entries
   //    (may be used in future to produce more strongly worded instructions)
   // 'autoconfirmed': regular user
   // 'sysop'

   if (mw.config.get('wgUserName')) {
      if (fuwHasUserGroup('sysop')) {
         this.userStatus = 'sysop';
      }
      else if (fuwHasUserGroup('autoconfirmed') || fuwHasUserGroup('confirmed')) {
         this.userStatus = 'autoconfirmed';
         $.ajax({
            url     : mw.util.wikiScript( 'api' ),
            type    : 'GET',
            dataType: 'xml',
            traditional : true,
            data:   {
                     format: 'xml',
                     action: 'query',
                     meta  : 'userinfo',
                     uiprop: 'editcount',
                     prop  : 'revisions',
                     titles: 'User talk' + mw.config.get('wgUserName'),
                     rvprop: 'comment|user',
                     rvlimit: 30
                    },      
            success: function(data) {
            // callback func     
               var fuw = window.fuw;
               if (data) {
                  var ui = data.getElementsByTagName('userinfo');
                  if (ui) {
                     var editcount = ui[0].getAttribute('editcount');
                     if (editcount < 100) {
                        fuw.userStatus = 'newbie';
                     }
                  }
                  var revs = data.getElementsByTagName('rev');
                  var countWarn = 0;
                  for (i = 0; i < revs.length; i++) {
                     var rev = revs[i];
                     var usr = rev.getAttribute('user');
                     var cmt = rev.getAttribute('comment');
                     if ((usr == 'ImageTaggingBot') ||
                         (cmt.search(/(đánh dấu xóa \[\[tập tin)|(các tập tin tải lên bị thiếu)|(tập tn (nguồn và )?có vấn đề về giấy phép bản quyền)|(Yêu cầu xóa nhanh \[\[tập tin)|(Thông báo: liệt kê \[\[các tập tin có thể không tự do)/) >= 0)) {
                        countWarn += 1;   
                     }
                  }
                  if (countWarn >= 3) {
                     fuw.userStatus = 'problem';
                  }
               }
            }
         });
      }
      else {
         this.userStatus = 'notAutoconfirmed';
      }
   }
   else {
      this.userStatus = 'anon';
   }
};

// =================================================================
// Convenience function for getting the regular index.php
// interface of Commons. Not very elegant.
// =================================================================
function fuwGetCommonsURL() {
   if (document.URL.match(/^https:/)) 
      return "https://commons.wikimedia.org/w/index.php";
   else
      return "http://commons.wikimedia.org/w/index.php";
}  

// ==================================================================
// functions for building form elements
// ==================================================================
fuwMakeRadiobutton = function(group, option, checked, event) {
   // Stupid IE7 doesn't get "value" attribute unless it's created in this convoluted way.
   // Annoying.   
   var node = $('<input type="radio" id="' + option + '" name="' + group + '" value="' + option + '"></input>')[0];
   if (checked) node.checked = true;
   node.onclick = event || fuwRadioClick;
   node.onclick = event || fuwRadioClick;
   fuwAppendInput(option, node);
};
fuwMakeTextfield = function(label, event) {
   var node  = document.createElement('input');
   node.type = 'text';
   node.name = label;
   node.size = fuwDefaultTextboxLength;
   node.onchange = event || fuwUpdateOptions;
   // only for testing:
   //node.value = label;
   fuwAppendInput(label, node);
};
fuwMakeTextarea = function(label, event) {
   var node  = document.createElement('textarea');
   node.name = label;
   node.rows = fuwDefaultTextareaLines;
   node.style.width = fuwDefaultTextareaWidth;
   node.onchange = event || fuwUpdateOptions;
   //only for testing:
   //node.innerHTML = label;
   fuwAppendInput(label, node);
};
fuwMakeCheckbox = function(label, checked, event) {
   var node  = document.createElement('input');
   node.name = label;
   node.type = 'checkbox';
   //only for testing:
   //node.title= label;
   node.checked = checked;
   node.onchange = event || fuwUpdateOptions;
   fuwAppendInput(label, node);
}
fuwMakeHiddenfield = function(name, value, id) {
   var node   = document.createElement('input');
   node.name  = name;
   node.type  = 'hidden';
   node.value = value;
   fuwAppendInput((id || name), node);
};
fuwMakeAnchor = function(label, href, content) {
   var node   = document.createElement('a');
   node.name  = label;
   node.target= "_blank";
   node.href  = href;
   node.innerHTML = content;
   fuwAppendInput(label, node);
};
fuwMakeSelection = function(name, values) {
   var root = document.createElement('select');
   var current = root;
   try {
      for (i=0; i<values.length; i++) {
         var line = values[i];
         var entry;
         if (line.length == 0) {
            current = root;
         }
         else if (line.length == 1) {
            entry = document.createElement('optgroup');
            entry.setAttribute('label', line[0]);
            root.appendChild(entry);
            current = entry;
         }
         else {
            entry = document.createElement('option');
            entry.setAttribute('value', line[0]);
            entry.setAttribute('title', '{{' + line[0] + '}}');
            entry.innerHTML = line[1];
            if (line.length > 2) {
               entry.setAttribute('selected', 'selected');
            } 
            current.appendChild(entry);
         }
      }
   } catch (e) { alert("Tên: " + name + ", i=" + i); }
   root.name = name;
   root.onchange = fuwUpdateOptions;
   fuwAppendInput(name, root);
};
function fuwMakeWikilink(place, target, redlink, display) {
   
   place = fuwGet(place);
   var id = place.id;
   var anchor;
   if (place.tagName == 'A') {
      anchor = place;
   }
   else {
      anchor = document.createElement('a');
      place.appendChild(anchor);
   }
   anchor.href = mw.util.getUrl(target);
   anchor.title = target;
   anchor.innerHTML = target;
   anchor.className = (redlink ? 'new' : null);
}

function fuwAppendInput(label, content) {
   // append a newly created input element to an existing
   // span element marked as id="placeholderXYZ"
   var node = fuwGet('placeholder' + label);
   var old  = fuwGet(label);
   if (old) {
      old.parentNode.removeChild(old);
   }
   content.id = content.id || label;
   if (node) {
      while (node.hasChildNodes()) {
         node.removeChild(node.firstChild);
      }
      node.appendChild(content);
   }
}

// ======================================================
// move an element away from its current position
// and append it to a target element if condition is true
// ======================================================
function fuwMove(mv, tg, condition, toStart) {
   if (condition) {
      move   = fuwGet(mv);
      target = fuwGet(tg);
      if (move && target) {
         var parent = move.parentNode;
         if (! (target===parent)) {
            parent.removeChild(move);
            if (toStart) {
               target.insertBefore(move, target.firstChild);
            }
            else {
               target.appendChild(move);
            }
         }
      }
      else {
         alert("Không tìm thấy yếu tố: move=" + mv + "(" + move + "), target=" + tg + "(" + target + ")");
      } 
   }
   return condition;
}

// ===================================================
// make an element visible/invisible
// ===================================================
function fuwSetVisible(tg, condition) {
   target = fuwGet(tg);
   if (target) {
      if (condition) {
         $(target).show();
      }
      else {
         $(target).hide();
      }
   }
   else {
      alert("Không tìm thấy yếu tố: " +  (tg.nodeType ? tg.id : tg));
   }
}

// ===================================================
// set enabled/disabled status for an element and/or
// all input controls contained in it.
// ===================================================
function fuwSetEnabled(tg, condition) {
   target = fuwGet(tg);
   try {
      var elements = (target.tagName.match(/^(input|textarea|select|button|a)$/i) ? 
         [target] :
         $('#' + target.id + ' *'));
      for (i = 0; i<elements.length; i++) {
         if (elements[i].tagName.match(/^(input|textarea|select|button|a)$/i)) {
            elements[i].disabled = (condition ? null : "disabled");
         }
      }
   } catch (e) { alert("Không tìm thấy yếu tố: " +  (tg.nodeType ? tg.id : tg)); }
}

// ===================================================
// convenience function to check whether a given
// element is currenly visible. Needs to check display
// property of the element and its ancestors
// ===================================================
function fuwIsVisible(el) {
   element = fuwGet(el);
   if (!element) return false;
   el = element.id;
   
   var visible = true;
   while (! (element === document.body)) {
      if (element.style.display == "none") {
         visible = false;
         break;
      }
      element = element.parentNode;
   }
   return visible;
}

// ===================================================
// cleanup filename
// ===================================================
function fuwCleanFilename() {
   var nameBox = window.fuw.ScriptForm.InputName;
   var oldname = name = $.trim(nameBox.value);

   if (name) {
      // strip accidentally added [[   ]] or [[:  ]] brackets
      name = name.replace(/(^\[\[:?)|(\]\]$)/g, "");
      // strip accidentally added "File:" prefix
      name = name.replace(/^(File|Image|Tập tin|Hình):/, "");
      // replace underscores with spaces
      name = name.replace(/_/g, " ");
      // uppercase first letter
      name = name[0].toUpperCase() + name.slice(1);
   }
   if (oldname != name) {
      nameBox.value = name;
   }
   // always return true so the next validation step will proceed:
   return true;
}


// ==================================================
// check filename for technically illegal 
// characters, trying to fix them automatically
// ==================================================
function fuwCheckLegalFilename() {
   var nameBox = window.fuw.ScriptForm.InputName;
   var oldname = name = $.trim(nameBox.value);

   if (name) {
      // resolve accidentally entered html entities and URI-encoded %XX character codes
      name = name.replace(/\&[a-z]+;/g, fuwHtmlEntityDecode);
      name = name.replace(/(\%[A-F0-9]{2,2})/g, decodeURI);
      // remove illegal characters # < > [ ] | { } /:
      // using a best guess for an acceptable replacement
      name = name.replace(/[<\[\{]/g, "(");
      name = name.replace(/[>\]\}]/g, ")");
      name = name.replace(/[#:\|]/g,  ",");
      name = name.replace(/\//g, "-");
      // remove sequences of tildes
      name = name.replace(/\~{3,}/g, "---");
      // remove initial slash
      name = name.replace(/^\//, "");
   }
   
   if (oldname != name) {
      window.fuw.warn.IllegalChars = true;
      nameBox.value = name;
      return false;
   }
   else {
      window.fuw.warn.IllegalChars = false;
      return true;
   }
}
function fuwHtmlEntityDecode(str) {
   // hack to translate accidentally entered html entity code
   // into actual characters
   var ta=document.createElement('textarea');
   ta.innerHTML=str.replace(/</g,'&lt;').replace(/>/g,'&gt;');
   return ta.value;
}

// =======================================================
// Check against various common patterns of poorly chosen
// filenames (too short / too generic)
// =======================================================
function fuwCheckPoorFilename() {
   var nameBox = window.fuw.ScriptForm.InputName;
   var name = $.trim(nameBox.value);
	name = name.replace(/\.(png|gif|jpg|jpeg|xcf|pdf|mid|ogg|ogv|svg|djvu|tiff|tif|oga|mp3|webp|webm|opus|mpg|mpeg|wav|wave|flac)$/i, "");

   // name should be at least 10 characters long, excluding file type extension
   var tooShort = (name.length < 10);
   
   // common generic filename patterns: 
   // IMG......jpg
   // Image....jpg
   // DSC......jpg
   // Picture......jpg
   // Pic..........jpg
   // anything that has fewer than 3 alphabetic letters and then just numbers
   var pattern = /^(img|image|dsc|picture|pic)?(\\s*|\\_*|[a-z]{,3})?\\d+$/i;
   var auto = name.match(pattern);

   window.fuw.warn.BadFilename = (tooShort || auto);
   return !tooShort && !auto;
}

// =======================================================
// check if file extensions match between local filename
// and target filename input box. Automatically append 
// appropriate extension to target filename if they don't.
// =======================================================
function fuwCheckFileExtension() {
   var nameBox = window.fuw.ScriptForm.InputName;
   var name = $.trim(nameBox.value);
   var fileBox = window.fuw.TargetForm.file;
   var file = fileBox.value;
   
   // cancel check if no filename has been provided yet
   if (!file || !name) return true;
   
   var extensions = /.+\.(png|gif|jpg|jpeg|xcf|pdf|mid|ogg|ogv|svg|djvu|tiff|tif|oga|mp3|webp|webm|opus|mpg|mpeg|wav|wave|flac)$/i;
   var mimetypes = {
      "png"  : "image/png",
      "gif"  : "image/gif",
      "jpg"  : "image/jpeg",
      "jpeg" : "image/jpeg",
      "xcf"  : "image/x-xcf",
      "webp" : "image/webp",
      "pdf"  : "application/pdf",
      "mid"  : "audio/rtp-midi",
      "ogg"  : "audio/ogg",
      "mp3"	 : "audio/mp3",
      "opus" : "audio/opus",
      "wav"  : "audio/wav",
      "wave" : "audio/wav",
      "flac" : "audio/flac",
      "ogv"  : "video/ogg",
      "svg"  : "image/svg+xml",
      "djvu" : "image/vnd.djvu",
      "tiff" : "image/tiff",
      "tif"  : "image/tiff",
      "oga"  : "video/ogg",
      "webm" : "video/webm",
      "mpg"  : "video/mpeg",
      "mpeg" : "video/mpeg"
   };   

   var found = extensions.exec(file);
   var fileExt = found ? found[1].toLowerCase() : "";
   found = extensions.exec(name);
   var nameExt = found ? found[1].toLowerCase() : "";
   var mime = mimetypes[fileExt]; 
   
   if (fileExt && mime && (mimetypes[nameExt] != mime)) {
      nameBox.value = name.replace(/\.?$/, ('.' + fileExt));
   }
   return true;
}

// ============================================================
// Check if a file under the chosen name already exists,
// either locally or on Commons.
// Store results in the fuw.warn object, so warnings will
// be displayed on the next fuwUpdateOptions() call
// ============================================================
function fuwCheckFileExists() {
   // this is an asynchronous AJAX function.
   // results won't yet be present when this function returns.

   var nameBox = window.fuw.ScriptForm.InputName;
   var name = $.trim(nameBox.value);

   // using the jQuery wrapper for the Ajax functionality:
   $.ajax({
      url     : mw.util.wikiScript( 'api' ),
      type    : 'GET',
      dataType: 'xml',
      traditional : true,
      data:   {
               format: 'xml',
               action: 'query',
               titles: 'File:' + name,
               prop  : 'imageinfo',
               iiprop: 'url|user',
               iiurlwidth: 120
              },      
      success: function(resp) {
      // callback function, called when API query has succeeded:
         // see if the request has returned info from an existing image:
         var foundlist = resp.getElementsByTagName('ii');
         var exists = (foundlist.length >= 1);
         var isCommons = false;
         if (exists) {

            // extract description data from http response.
            // see https://www.mediawiki.org/wiki/API:Properties#imageinfo_.2F_ii 
            // for structure of API response
            var foundImg = foundlist[0];
            isCommons = (foundImg.parentNode.parentNode.getAttribute('imagerepository')=='shared');

            // need this data for creating our own image thumb link
            var width = foundImg.getAttribute('thumbwidth');
            var height = foundImg.getAttribute('thumbheight');
            var thumbURL = foundImg.getAttribute('thumburl');
            var lastUser = foundImg.getAttribute('user');
            var descURL = foundImg.getAttribute('descriptionurl');

            // API returns link to local description page even for Commons images.
            // However, we want a direct link to Commons.
            if (isCommons) {
               descURL = descURL.replace(/vi\.wikipedia\.org/, "commons.wikimedia.org");
               descURL = descURL.replace(/\/\/secure\.wikimedia\.org\/wikipedia\/vi/, "commons.wikimedia.org");
            }

            // build the image info into the warning section of our page:
            thumbDiv = fuwGet('warningImageThumb');
            if (thumbDiv) {
            
               // make all links point to description page:
               var thumbA = thumbDiv.getElementsByTagName('a');
               for (i = 0; i<thumbA.length; i++) {
                  thumbA[i].setAttribute('href', descURL);
               }
               // insert the image itself:
               var thumbImg = thumbDiv.getElementsByTagName('img');
               if (thumbImg.length > 0) {
                  thumbImg = thumbImg[0];
                  thumbImg.setAttribute('src', thumbURL);
                  thumbImg.setAttribute('width', width);
                  thumbImg.setAttribute('height', height);
               }
               // insert the name of the last uploader:
               var thumbSpan = fuwGet('existingImageUploader');
               // TO DO: turn this into a proper link
               if (thumbSpan) thumbSpan.innerHTML = lastUser;
            }

            
         }
         warn = window.fuw.warn;
         warn.ImageOnCommons = exists && isCommons;
         warn.ImageExists    = exists && !isCommons;

         fuwUpdateOptions();
      }
   });
}

// ===========================================================
// onchange event handler for the local filename box
// ===========================================================
fuwValidateFile = function() {
   fuwCheckFileExtension();
   fuwUpdateOptions();
}

// ===========================================================
// onchange event handler for the name input box
// ===========================================================
fuwValidateFilename = function() {
   fuwCleanFilename();
   if (
      fuwCheckLegalFilename() &&
      fuwCheckPoorFilename() &&
      fuwCheckFileExtension()) {
      // after fuwCheckFileExists(),
      // fuwUpdateOptions will be triggered
      // by the callback function after Ajax completion
      fuwCheckFileExists();
   }
   else {
      // if there's been no Ajax call.
      fuwUpdateOptions();
   }
};

// ==========================================================
// function fuwValidateNFArticle()
// ==========================================================
// This is the validation routine for the obligatory
// article-to-be-used-in field for non-free files. It queries
// api.php about the target article through an Ajax call.
// It will store error info in the fuw.warn object,
// triggering the following error on the next updateOptions():
// * warningNFArticleNotFound : target page doesn't exist.
// * warningNFArticleNotMainspace : target is not an article. 
// * warningNFArticleDab : target is a disambiguation page.
// Redirects will automatically be substituted.
// ==========================================================
fuwValidateNFArticle = function() {
   
   var nameBox = window.fuw.ScriptForm.NFArticle;
   oldname = name = nameBox.value;
   
   // cleanup article name:
   // automatically fix accidentally added [[ ... ]] and
   // regularize underscores
   name = $.trim(name);
   name = name.replace(/(^\[\[)|(\]\]$)/g, "");
   // automatically fix article names entered as full urls:
   name = name.replace(/^https?:\/\/vi\.wikipedia\.org\/wiki\//, "");
   name = name.replace(/^https?:\/\/vi\.wikipedia\.org\/w\/index\.php\?title=/, "");
   name = name.replace(/_/g, " ");
   if (name != oldname) nameBox.value = name;
   
   // do nothing more if field was blank
   if (!name) return;

   // using the jQuery wrapper for the Ajax functionality:
   $.ajax({
      url     : mw.util.wikiScript( 'api' ),
      type    : 'GET',
      dataType: 'xml',
      traditional : true,
      data:   {
               format: 'xml',
               action: 'query',
               titles: name,
               prop  : 'info|categories|links'
              },      
      success: function(resp) {
      // callback function, called when API query has succeeded:
         var errorType = 0;
         var pg = resp.getElementsByTagName('page')[0];
         var title = pg.getAttribute('title');
         var target = title;
         if (pg.getAttribute('missing') != null) {
            // no page found under this title.
            errorType = 1;
         }           
         else {
            var userspace = false;
            var ns = pg.getAttribute('ns');
            var rd = pg.getAttribute('redirect');
            if (ns != 0) {
               // not a mainspace page!
               errorType = 2;

               // try to detect if the target might be a user space draft:               
               if (title.match(new RegExp("User( talk)?:" + mw.config.get('wgUserName')))) {
                  userspace = true;
               }
            }
            else if (rd != null) {
               // redirect page
               // API returns an empty redirect="" attribute if
               // the page is a redirect
               var targets = pg.getElementsByTagName('pl');
               for (i=0; i<targets.length; i++) {
                  var link = targets[i];
                  if (link.getAttribute('ns')==0) {
                     target = link.getAttribute('title');
                     errorType = 3;
                     break;
                  }
               }
            }
            else {
               // check for disambiguation categories
               var cats = pg.getElementsByTagName('cl');
               for (i=0; i<cats.length; i++) {
                  var cat = cats[i];
                  if (cat.getAttribute('title') == "Thể loại:Tất cả các trang định hướng") {
                     errorType = 4;
                     break;
                  }
               }  
            }
         }
         warn = window.fuw.warn;
         warn.NFArticleNotFound = (errorType==1);
         warn.NFArticleNotMainspace = (errorType==2);
         warn.UserspaceDraft = ((errorType==2) && userspace);
         warn.NFArticleDab = (errorType==4);
         warn.NFArticleOK  = (errorType==0);

         // fix links in error messages:
         if (warn.NFArticleNotFound) {
            fuwMakeWikilink(fuwGet('warningNFArticleNotFound').getElementsByTagName('A')[0], target, true);
         }
         else if (warn.NFArticleNotMainspace) {
            fuwMakeWikilink(fuwGet('warningNFArticleNotMainspace').getElementsByTagName('A')[0], target);
         }
         else if (warn.NFArticleDab) {
            fuwMakeWikilink(fuwGet('warningNFArticleDab').getElementsByTagName('A')[0], target);
         }
         else if (warn.NFArticleOK) {
            fuwMakeWikilink(fuwGet('NFArticleOK').getElementsByTagName('A')[0], target);
         }
                       
         if (errorType==3) {
            // automatically replace title with redirect target
            window.fuw.ScriptForm.NFArticle.value = target;
            // need to recursively call validation again now
            //if (confirm(name + " is a redirect. Follow it to " + target + "?")) {
               fuwValidateNFArticle();
            //}
         }
         else {
            fuwUpdateOptions();
         }          
      }
   });
};

// ================================================
// manually reload script (just for testing)
// ================================================
function fuwReload() {
   mw.loader.load( 'http://localhost/script/uploadscript.js' );
   fuwReset();
}

// ================================================
// reset forms
// TO DO: add a button that actually triggers this.
// ================================================
function fuwReset() {
   var forms = mw.util.$content[0].getElementsByTagName('form');
   for (i = 0; i < forms.length; i++) {
      forms[i].reset();
      window.fuw.warn = { };
      window.fuw.opts = { };
   }
   fuwSetVisible('UploadScriptArea', true);
   fuwSetVisible('fuwSuccess', false);
   fuwSetVisible('fuwWaiting', false);
   fuwUpdateOptions();
}

// ===============================================
// convenience functions for string handling
// ===============================================
function fuwAppendLines(parts) {
   // assemble a string from an array of strings.
   // treat every second element as a conditional
   // separator that will be included only if 
   // surrounding elements are non-empty.
   var build = "";
   for (var i = 0; i < parts.length; i += 2) {
      if (parts[i]) {
         if (build) build += parts[i - 1];
         build += parts[i];
      }
   }
   return build;
}
function fuwSurroundString(prefix, content, suffix) {
   // put a prefix and a suffix on a string, 
   // if the input string is non-empty.
   if (content) 
      return (prefix ? prefix : "") + content + (suffix ? suffix : ""); 
   else return "";
}

// ========================================================
// convenience function for accessing the contents of the
// dummy TargetIFrame
// ========================================================
function fuwGetDocumentFromIFrame(iframe) {
   var doc = (iframe.contentDocument ? iframe.contentDocument : iframe.contentWindow.document);
   if (doc.XMLDocument) {
      doc = doc.XMLDocument;
   }
   return doc;
}
// ========================================================
// event handler for the dummy TargetIFrame's onload event.
// TO DO: expand stub to add real notification of success,
// link to new file page, instructions about how to include
// file in articles, etc.
// ========================================================
function fuwUploadCompleted() {
   var doc = fuwGetDocumentFromIFrame(fuwGet('TargetIFrame'));
   if (doc) {
      //alert(doc);
      fuwSetVisible('successThumb', false);

      var fuw = window.fuw;
      var name = fuw.opts.InputName;

      var uploads = doc.getElementsByTagName('upload');
      var success = false;
      for (i = uploads.length-1; i>=0; i--) {
         if (uploads[i].getAttribute('result') == 'Thành công') {
            success = true;
            // need to get the real resulting filename here; might be different from the requested one in some cases.
            name = uploads[i].getAttribute('filename');
            break;
         }
      }
      if (success) {

         // need another ajax call to check the file is actually there,
         // and to retrieve its direct thumb img url:
         $.ajax({
            url     : mw.util.wikiScript( 'api' ),
            type    : 'GET',
            dataType: 'xml',
            traditional : true,
            data:   {
                     format: 'xml',
                     action: 'query',
                     titles: 'File:' + name,
                     prop  : 'imageinfo',
                     iiprop: 'url',
                     iiurlwidth: 120
                    },      
            success: function(resp) {
               // callback function, called when API query has succeeded:
               // see if the request has returned info from an existing image:

               var foundImg = resp.getElementsByTagName('ii')[0];
               if (foundImg) {

                  // need this data for creating our own image thumb link
                  var width = foundImg.getAttribute('thumbwidth');
                  var height = foundImg.getAttribute('thumbheight');
                  var thumbURL = foundImg.getAttribute('thumburl');
                  var lastUser = foundImg.getAttribute('user');
                  var descURL = foundImg.getAttribute('descriptionurl');

                  // build the thumbnail in the success message:
                  thumbDiv = fuwGet('successThumb');
                  
                  // make link point to description page:
                  var thumbA = thumbDiv.getElementsByTagName('a')[0];
                  thumbA.href = descURL;

                  // insert the image itself:
                  var thumbImg = thumbDiv.getElementsByTagName('img')[0];
                  thumbImg.setAttribute('src', thumbURL);
                  thumbImg.setAttribute('width', width);
                  thumbImg.setAttribute('height', height);
                  
                  fuwSetVisible(thumbDiv, true);
               }
            }
         });
         fuwMakeWikilink(
            fuwGet('fuwSuccessLink2').getElementsByTagName('a')[0],
            'File:' + name);
         fuwGet('placeholderExFilename1').innerHTML = name;
         fuwGet('placeholderExFilename2').innerHTML = name;
         fuwSetVisible('fuwSuccess', true);
         fuwSetVisible('fuwWaiting', false);
      }
      else {
         var err = doc.getElementsByTagName('error');
         if (err) {
            var info = err[0].getAttribute('info');
            var details = err[0].getElementsByTagName('detail');
            var add = "";
            for (i = 0; i < details.length; i++) {
               if (add.length > 0) add += ", ";
               add += details[i].textContent;
            }
            if (add) {
               info = info + " (" + add + ")";
            }
            alert("Lỗi tải lên: " + info);
         }
         else {
            alert("Lỗi không xác định: tải lên thất bại.");
         }
      }
   }
}

// ========================================================
// clean out dummy IFrame before submitting a request
// ========================================================
function fuwResetTargetIFrame() {
   var doc = fuwGetDocumentFromIFrame(fuwGet('TargetIFrame'));
   if (doc) {
      while (doc.hasChildNodes()) {
         doc.removeChild(doc.firstChild);
      }
   }
}

// ========================================================
// Event handler for the real submit button
// ========================================================
function fuwSubmitUpload() {
   
   var fuw = window.fuw;
   var frm = fuw.TargetForm;

   fuw.collectInput();
   fuw.formatOutput(false);

   // we will use the iframe's onload event to trigger a function that
   // adds success notification etc.
   fuwResetTargetIFrame();
   var ifr = fuwGet('TargetIFrame');
   if (ifr.attachEvent) {
      // workaround for IE, according to 
      // http://www.nczonline.net/blog/2009/09/15/iframes-onload-and-documentdomain/
      ifr.attachEvent("onload", fuwUploadCompleted);
   }
   else {
      // all other browsers
      ifr.onload = fuwUploadCompleted;
   }

   if (fuwTesting) {
      fuwSetVisible('placeholderTestForm', false);
   }
   fuwSetVisible('UploadScriptArea', false);

   fuwMakeWikilink(
     fuwGet('fuwSuccessLink').getElementsByTagName('a')[0], 'File:' + fuw.opts.InputName);
   fuwSetVisible('fuwWaiting', true);

   frm.submit();
   var opts = window.fuw.opts;
   // the API won't overwrite the description page text while overwriting
   // a file, which is really, really, really annoying and stupid.
   // So in the opts.OverwriteDifferent scenario, we need to edit
   // the description page through a separate ajax call. Dang.
   if (opts.OverwriteDifferent) {
      $.ajax({
         url   : mw.util.wikiScript('api'),
         type  : 'POST',
         dataType : 'xml',
         data  : {
                  format : 'xml',
                  action : 'edit',
                  title  : 'File:' + opts.InputName,
                  token  : mw.user.tokens.get('csrfToken'),
                  summary : opts.EditSummary,
                  text   : ($('#TargetForm .[name="text"]')[0]).value

                 }
      });
   }
}

// =======================================================
// Event handler for the Commons submit button
// =======================================================
function fuwSubmitCommons() {
   var fuw = window.fuw;
   fuw.collectInput();
   var url = fuw.formatOutput(true);
   alert("Bây giờ bạn sẽ được chuyển hướng sang Commons. \nVui lòng sử dụng biểu mẫu tải lên của Commons để thêm thể loại vào trang miêu tả tập tin, và sau đó hoàn tất công việc tải lên.");
   window.location = url;
}

// =======================================================
// Event handler for the test submit button
// (write description string to sandbox only)
// =======================================================
function fuwSubmitSandbox() {
   var frm = window.fuw.TestForm;
   $.ajax({
      url     : mw.util.wikiScript( 'api' ),
      type    : 'POST',
      dataType: 'xml',
      data:   {
               format: 'xml',
               action: 'edit',
               title : mw.config.get('wgPageName') + "/sandbox",
               token : mw.user.tokens.get('csrfToken'),
               recreate : 1,
               summary  : frm.SandboxSummary.value,
               text     : frm.SandboxText.value
              },      
      success: function(resp) {
         alert("Đã sửa trang chỗ thử!");
      }
   });
}



// ========================================================
// convenience wrapper function to replace calls to
// document.getElementById() 
// to avoid browser incompatibility
// ========================================================
function fuwGet(target) {
   if (target && target.nodeType) return target;
   else {
      var found = $('#' + target);
      if (found) return found[0];
   }
   return undefined;
}

// ========================================================
// onload hook function, loading this script
// ========================================================
$(function() { 
   if (fuwGet('UploadScriptArea')) {
      window.fuw = new fuwGlobal();
      if (! window.fuw.disabled) {
         fuwUpdateOptions();
      }
   }
});