User:Irukaza/common.js:修订间差异

H萌娘,万物皆可H的百科全书!
跳到导航 跳到搜索
imported>=海豚=
无编辑摘要
imported>Irukaza
(清空页面)
 
第1行: 第1行:
/*
该插件为一个快捷的文件上传工具,免去先进入图站再上传的步骤。
同时支持拖拽上传、批量上传、添加前缀等。
注意:批量上传时,单次上传的所有文件将共享设置的分类和前缀。
因为测试需要上传图片,上传后还得提请挂删,该插件没有经过充分的测试,遭遇一些问题后可能会没有对应的提示,
因此,上传发生异常时请进入图站的监视列表进行确认,一切以图站的数据为准。
*/
$(function () {
  if(typeof Promise == 'undefined'){
  mw.notify('你的浏览器不支持该插件,请升级浏览器或将该插件移除')
  return
  }
  function request(data) {
   data.origin = "https://zh.moegirl.org"
   return new Promise(function (resolve, reject) {
    $.ajax({
     url: 'https://commons.moegirl.org/api.php',
     type: 'post',
     timeout: 5000,
     xhrFields: { withCredentials: true },
     data: data
    }).done(resolve).fail(reject)
   })
  }


  function getHints(word) {
   return request({
    "action": "query",
    "format": "json",
    "list": "search",
    "srsearch": word,
    "srnamespace": "14",
    "srlimit": "7"
   })
  }
  function getToken() {
   return request({
    "action": "query",
    "format": "json",
    "meta": "tokens",
   }).then(function (data) {
    return data.query.tokens.csrftoken
   })
  }
  function upload(file, name, comment) {
   return new Promise(function (resolve, reject) {
    getToken().then(function (token) {
     var data = {
      filename: name,
      comment: comment,
      action: 'upload',
      ignorewarnings: true,
      token: token,
      origin: "https://zh.moegirl.org"
     }
    
     if(typeof file == 'string'){
     data.url = file
     }else{
     data.file = file
     }
     var formData = new FormData()
     Object.keys(data).forEach(function (key) {
      formData.append(key, data[key])
     })
     $.ajax({
      url: 'https://commons.moegirl.org/api.php',
      type: 'post',
      timeout: 5000,
      xhrFields: { withCredentials: true },
      contentType: false,
      processData: false,
      data: formData
     }).done(resolve).fail(reject)
    })
   })
  }
  function checkFileName(name) {
   return request({
    action: 'query',
    format: 'json',
    formatversion: 2,
    titles: 'File:' + name,
    prop: 'imageinfo',
    iiprop: 'uploadwarning',
    errorformat: 'html',
    errorlang: 'zh - hans'
   }).then(function (data) {
    return data.query.pages[0].missing
   })
  }
  var bodyHTML = [' <div id="widget-fileUploader" style="display:none">',
   '   <input type="file" id="file-uploader" multiple="multiple" accept=".ogg, .ogv, .oga, .flac, .opus, .wav, .webm, .mp3, .png, .gif, .jpg, .jpeg, .webp, .svg, .pdf, .ppt, .jp2, .doc, .docx, .xls, .xlsx, .psd, .sai, .swf, .mp4">',
   '   <div id="file-close-btn">&times;</div>',
   '   <div class="main">',
   '    <label for="file-uploader">',
   '     <div class="view" id="file-view">',
   '      <div class="promptbox" id="file-promptbox">',
   '       <div class="prompt">点此添加文件,或将文件拖放至此</div>',
   '      </div>',
   '      <div class="images-view" id="file-images"></div>',
   '     </div>',
   '    </label>',
   '    <div class="form">',
   '     <div class="row">',
   '      <div class="input-container">',
   '       <label for="file-filename">文件名:</label>',
   '       <input type="text" id="file-filename" data-type="name">',
   '      </div>',
   '      <div class="input-container">',
   '       <label for="file-categories-input">分&emsp;类:</label>',
   '       <input type="text" id="file-category-input">',
   '       <div class="input-hint">按下回车添加分类</div>',
   '       <div id="file-category-hints" tabindex="0" style="display:none"></div>',
   '      </div>',
   '      <div id="file-categories"></div>',
   '     </div>',
   '     <div class="row">',
   '      <div class="input-container">',
   '       <label for="file-charaname">角色名:</label>',
   '       <input type="text" id="file-charaname" data-type="charaName">',
   '      </div>',
   '      <div class="input-container">',
   '       <label for="file-author">作&emsp;者:</label>',
   '       <input type="text" id="file-author" data-type="author">',
   '      </div>',
   '      <div class="input-container">',
   '       <label for="file-source">源地址:</label>',
   '       <input type="text" id="file-source" data-type="source">',
   '      </div>',
   '     </div>',
   '     <div class="row" style="flex-direction:column; justify-content:space-around;">',
   '      <div class="input-container">',
   '       <label for="file-prefix">添加前缀:</label>',
   '       <input type="text" id="file-prefix" data-type="prefix" style="width:calc(100% - 6em)">',
   '      </div>',  
   '      <button id="file-addUrlFile">添加源地址文件</button>',
   '      <button id="file-submit">提交</button>',
   '     </div>',
   '    </div>',
   '   </div>',
   '  </div>',
   '  <style>',
   '   #widget-fileUploader{'<a id="wgCopyURL-url-copy-<!--{$id}-->" href="javascript:void(0);" onclick="copyURL_<!--{$id}-->(this)" data-clipboard-text=""><!--{$title}--><span style="display:none" id="wgCopyURL-url-<!--{$id}-->"><!--{$url}--></span></a>
<script>
clip_board = new ClipboardJS('#wgCopyURL-url-copy-<!--{$id}-->');
function copyURL_<!--{$id}-->(btn)
{
var copyText = document.getElementById("wgCopyURL-url-<!--{$id}-->").innerHTML.replace(/&amp;/g, "&");
btn.setAttribute("data-clipboard-text", copyText);
alert("链接已复制: " + copyText);
}
</script>
   '   }',
   '   #file-close-btn{',
   '    font-size: 30px;',
   '    font-weight: bold;',
   '    color: white;',
   '    font-family: SimSun;',
   '    position: fixed;',
   '    top: 10px;',
   '    right: 20px;',
   '    transition: transform 0.3s;',
   '    z-index: 10001;',
   '    cursor: pointer;   ',
   '   }',
   '   #file-close-btn:hover{',
   '    transform: rotate(90deg);',
   '   }',
   '   #widget-fileUploader .main{',
   '    width: 60%;',
   '    min-width: 650px;',
   '    height: 500px;',
   '    background: white;',
   '    border-radius: 10px;',
   '    border: 5px #eee solid;',
   '    position: absolute;',
   '    top: 0; left: 0; bottom: 0; right: 0;',
   '    margin: auto;',
   '   }',
   '   #widget-fileUploader .view{',
   '    height: 70%;',
   '    background: white;',
   '    border-radius: 10px 10px 0 0;',
   '    position: relative;',
   '    border-bottom: 3px #ccc solid;',
   '    box-sizing: border-box;',
   '    overflow: auto;',
   '    cursor: pointer;',
   '   }',
   '   #widget-fileUploader .view .promptbox{',
   '    position: absolute;',
   '    width: 100%;',
   '    height: 100%;',
   '    top: 0;',
   '    left: 0;',
   '   }',
   '   #widget-fileUploader .view .promptbox::before,',
   '   #widget-fileUploader .view .promptbox::after{',
   '    content: \'\';',
   '    width: 40px;',
   '    height: 150px;',
   '    background: #ddd;',
   '    position: absolute;',
   '    top: 0; left: 0; bottom: 0; right: 0;',
   '    margin: auto;    ',
   '   }',
   '   #widget-fileUploader .view .promptbox::after{',
   '    width: 150px;',
   '    height: 40px;',
   '   }',
   '   #widget-fileUploader .view .prompt{',
   '    font-size: 22px;',
   '    color: #ddd;',
   '    position: absolute;',
   '    left: 50%;',
   '    transform: translateX(-50%);',
   '    bottom: 10px;',
   '    white-space: nowrap;',
   '   }',
   '   #file-uploader{',
   '    display: none;',
   '   }',
   '   #file-images{',
   '    height: 100%;',
   '    overflow: auto;',
   '    box-sizing: border-box;',
   '    padding: 10px;',
   '   }',
   '   #file-images .imagebox,',
   '   #file-images .lastbox{',
   '    width: 200px;',
   '    height: 150px;',
   '    box-sizing: border-box;',
   '    background: white;',
   '    border: 1px #ccc solid;',
   '    margin: 10px;',
   '    display: inline-block;',
   '    position: relative;',
   '    cursor: pointer;',
   '    vertical-align: middle;',
   '   }',
   '   #file-images .typebox{',
   '    width: 100%;',
   '    height: 100%;',
   '    display: flex;',
   '    justify-content: center;',
   '    align-items: center;',
   '    font-size: 17px;',
   '    color: #666;',
   ' }',
   '   #file-images .lastbox::before,',
   '   #file-images .lastbox::after{',
   '    content: \'\';',
   '    width: 15px;',
   '    height: 60px;',
   '    background: #ddd;',
   '    position: absolute;',
   '    top: 0; left: 0; bottom: 0; right: 0;',
   '    margin: auto;    ',
   '   }',
   '   #file-images .lastbox::after{',
   '    width: 60px;',
   '    height: 15px;',
   '   }',
   '   #file-images .imagebox.selected::after{',
   '    content: \'\';',
   '    display: block;',
   '    position: absolute;',
   '    width: 100%;',
   '    height: 100%;',
   '    top: 0;',
   '    left: 0;',
   '    box-sizing: border-box;',
   '    border: 3px #ccc solid;',
   '    pointer-events: none;',
   '   }',
   '   .file-remove-btn{',
   '    width: 20px;',
   '    height: 20px;',
   '    border-radius: 50%;',
   '    text-align: center;',
   '    line-height: 20px;',
   '    font-weight: bold;',
   '    font-family: \'黑体\';',
   '    position: absolute;',
   '    top: 5px;',
   '    right: 5px;',
   '   }',
   '   .file-remove-btn:hover{',
   '    background: #666;',
   '    color: white;',
   '   }',
   '   #file-images .imagebox > img{',
   '    width: 100%;',
   '    height: 100%;',
   '    padding: 5px;',
   '    box-sizing: border-box;',
   '    object-fit: scale-down;',
   '   }',
   '   #file-images .imagebox::before{',
   '    content: attr(title);',
   '    display: block;',
   '    width: 100%;',
   '    position: absolute;',
   '    bottom: 0;',
   '    left: 0;',
   '    background: rgba(0, 0, 0, 0.5);',
   '    color: white;',
   '    font-size: 13px;',
   '    text-align: center;',
   '    line-height: 25px;',
   '   }',
   '   #widget-fileUploader .form{',
   '    height: calc(30% - 20px);',
   '    padding: 10px;',
   '    display: flex;',
   '   }',
   '   #widget-fileUploader .form .row{',
   '    display: flex;',
   '    flex: 1;',
   '    flex-wrap: wrap;',
   '    align-items: center;',
   '    height: 100%;',
   '    padding: 0 10px;',
   '   }',
   '   #widget-fileUploader .form .row .input-container{',
   '    min-width: 240px;',
   '    position: relative;',
   '   }',
   '   #widget-fileUploader .form .row .input-container > *{',
   '    vertical-align: middle;',
   '    font-size: 14px;',
   '   }',
   '   #widget-fileUploader .form .row .input-container input{',
   '    box-sizing: border-box;',
   '    width: calc(100% - 5em);',
   '    min-width: 150px;',
   '   }',
   '   #widget-fileUploader .input-container .input-hint{',
   '    opacity: 0;',
   '    transition: opacity 0.2s;',
   '    background: #fffeee;',
   '    border: 1px #ccc solid;',
   '    padding: 2px 10px;',
   '    position: absolute;',
   '    bottom: calc(100% - 7px);',
   '    left: calc(100% - 7px);',
   '    z-index: 1;',
   '    border-radius: 5px;',
   '    white-space: nowrap;',
   '   }',
   '   #widget-fileUploader .input-container input:focus + .input-hint{',
   '    opacity: 1;',
   '   }',
   '   #file-categories{',
   '    width: 100%;',
   '    height: 23px;',
   '    border: 1px #ccc solid;',
   '    border-radius: 5px;',
   '    overflow: auto;',
   '    margin-right: 5px;',
   '    box-sizing: border-box;',
   '   }',
   '   #file-category-hints{',
   '    min-width: 170px;',
   '    max-height: 140px;',
   '    background: white;',
   '    white-space: nowrap;',
   '    overflow: auto;',
   '    position: absolute;',
   '    right: 9px;',
   '    bottom: 100%;',
   '    border: 1px #666 solid;',
   '    box-sizing: border-box;',
   '    border-bottom: none;',
   '   }',
   '   #file-category-hints .category-hint{',
   '    line-height: 20px;',
   '    box-sizing: border-box;',
   '    padding: 0 5px;',
   '    width: 100%;',
   '    overflow: hidden;',
   '   text-overflow: ellipsis;',
   '   white-space: nowrap;',
   '   }',
   '   #file-category-hints .category-hint.selected{',
   '    background: #ccc;',
   '   }',
   '   #file-categories .categorybox{',
   '    display: inline-block;',
   '    line-height: 15px;',
   '    text-align: center;',
   '    border: 1px #666 solid;',
   '    background: #eee;',
   '    margin: 2px 3px;',
   '    padding: 0 5px;',
   '   font-size: 14px',
   '   }',
   '   #file-submit{',
   '    min-width: 70px;',
   '    text-align: center;',
   '    background: #eee;',
   '    border: 1px #ccc solid;',
   '    color: #666;',
   '    padding: 5 0px;',
   '    font-size: 16px;',
   '    cursor: pointer;',
   '   }',
   '   #file-submit:hover{',
   '    opacity: 0.8;',
   '   }',
   '  </style>'].join('');
  $('body').append(bodyHTML)
  $('#p-cactions ul').append('<li id="btn-fileUploader"><a title="上传文件">上传文件</a></li>')
  $('#btn-fileUploader').click(function () {
   $('#widget-fileUploader').fadeIn(200)
  })
  var filename = $('#file-filename'),
   categoryInput = $('#file-category-input'),
   categoryHint = $('#file-category-hints'),
   categories = $('#file-categories'),
   charaName = $('#file-charaname'),
   author = $('#file-author'),
   source = $('#file-source'),
   submitBtn = $('#file-submit'),
   view = $('#file-view'),
   promptbox = $('#file-promptbox'),
   imagesbox = $('#file-images'),
   uploader = $('#file-uploader'),
   addUrlFileBtn = $('#file-addUrlFile'),
   closeBtn = $('#file-close-btn'),
   body = $('#widget-fileUploader')
  var hasFile = false,
   selectedItem = -1,
   forceOpen = false // 防止loaded文件后无法再打开上传窗口
  var categoriesHandler = {
   data: [],
   updateView: function () {
    var _this = this
    categories.empty()
    this.data.forEach(function (val, ind) {
     var block = $('<div class="categorybox">' + val + '</div>')
     block.click(function () {
      _this.remove(ind)
     })
     categories.append(block)
    })
   },
   push: function (val) {
    var _this = this
    this.data.some(function (cate, ind) {
     if (cate == val) {
      _this.data.splice(ind, 1)
      return true
     }
    })
    this.data.push(val)
    this.updateView()
   },
   remove: function (ind) {
    this.data.splice(ind, 1)
    this.updateView()
   },
   format: function () {
    return this.data.map(function (val) { return '[[分类:' + val + ']]' }).join(' ')
   }
  }
  var categoryHintHandler = {
   data: [],
   updateView: function () {
    categoryHint.empty()
    this.data.length ? categoryHint.show() : categoryHint.hide()
    this.data.forEach(function (val) {
     var item = $('<div class="category-hint">').text(val)
     item.click(function () {
      categoryInput.val('').focus()
      categoriesHandler.push(val)
      categoryHint.hide()
     })
     categoryHint.append(item)
    })
   },
   write: function (data) {
    this.data = data
    this.updateView()
   },
   clear: function () {
    this.data = []
    this.updateView()
   }
  }
  var filesHandler = {
   data: [],
   updateView: function () {
    imagesbox.empty()
    if (this.data.length) {
     hasFile = true
     promptbox.hide()
     var _this = this
     this.data.forEach(function (file, ind) {
      var box = $('<div class="imagebox">').attr('title', file.info.name)
      if (typeof file.body == 'string') {
       if (/\.(jpe?g|png|gif|webp|svg)$/.test(file.body)) {
        box.append($('<img>').attr('src', file.body))
       } else {
        box.append($('<div class="typebox">').text('不支持预览的文件类型'))
       }
      } else {
       if (file.body.type.split('/')[0] == 'image') {
        var url = URL.createObjectURL(file.body)
        box.append($('<img>').attr('src', url))
       } else {
        box.append($('<div class="typebox">').text(file.body.type.split('/')[1] + '文件'))
       }
      }
      box.click(function () {
       selectedItem = $(this).index()
       imagesbox.find('.imagebox').removeClass('selected')
       $(this).addClass('selected')
       filename.val(file.info.name)
       charaName.val(file.info.charaName)
       author.val(file.info.author)
       source.val(file.info.source)
      })
      var removeBtn = $('<div class="file-remove-btn">&times;</div>').click(function (e) {
       _this.remove(ind)
      })
      box.append(removeBtn)
      imagesbox.append(box)
     })
     var lastbox = $('<label class="lastbox" for="file-uploader">')
     lastbox.mousedown(function () {
      forceOpen = true
     })
     imagesbox.append(lastbox)
    } else {
     setTimeout(function () {
      hasFile = false
     }, 1)
     promptbox.show()
    }
   },
   push: function (file) {
    this.data.push({
     body: file,
     info: {
      name: typeof file == 'string' ? file.replace(/^.+\/(.+)$/, '$1') : file.name,
      charaName: '',
      author: '',
      source: ''
     }
    })
    this.updateView()
   },
   remove: function (index) {
    this.data.splice(index, 1)
    this.updateView()
   },
   clear: function () {
    this.data = []
    this.updateView()
   },
   format: function () {
    return this.data.map(function (file) {
     var comment =
      (file.info.charaName ? '[[分类:' + file.info.charaName + ']]' : '')
      + (file.info.author ? '[[分类:作者:' + file.info.author + ']]' : '')
      + (file.info.source ? '源地址:' + file.info.source : '')
     return {
      file: file.body,
      filename: $('#file-prefix').val().trim() + file.info.name,
      comment: comment,
     }
    })
   },
  
   selectLast: function(){
   $(Array.from($('#file-images > .imagebox').removeClass('selected')).pop()).click().get(0).scrollIntoView()
   }
  }
  closeBtn.click(function () {
   body.fadeOut(200)
  })
  // 拖拽上传
  $(view).on('dragenter dragover drop', function (e) {
   e.preventDefault()
  })
  view[0].addEventListener('drop', function (e) {
   var original = e.dataTransfer.files
   Array.prototype.forEach.call(original, function (file) {
    if (['ogg', 'ogv', 'oga', 'flac', 'opus', 'wav', 'webm', 'mp3', 'png', 'gif', 'jpg', 'jpeg', 'webp', 'svg', 'pdf', 'ppt', 'jp2', 'doc', 'docx', 'xls', 'xlsx', 'psd', 'sai', 'swf', 'mp4'].includes(file.type.split('/')[1])) {
     filesHandler.push(file)
    }
   })
   filesHandler.selectLast()
   uploader.val('')
  })
  uploader.click(function (e) {
   if (hasFile && !forceOpen) {
    e.preventDefault()
    console.log('123')
   }
   forceOpen = false
  })
  uploader.on('change', function (e) {
   var original = e.target.files
   Array.prototype.forEach.call(original, function (file) {
    filesHandler.push(file)
   })
filesHandler.selectLast()
   uploader.val('')
  })
  new Array(filename, charaName, author, source).forEach(function (ele) {
   ele.on('input', function (e) {
    if (selectedItem >= 0) {
     var value = e.target.value.trim()
     ele.attr('id') == 'file-filename' && $('#file-images > .imagebox').eq(selectedItem).attr('title', value)
     filesHandler.data[selectedItem].info[this.dataset.type] = value
    }
   })
  })
  var setTimeouKey = 0
  categoryInput.on('input', function (e) {
   if (!e.target.value) { return }
   var word = e.target.value.trim()
   clearTimeout(setTimeouKey)
   setTimeoutKey = setTimeout(function () {
    getHints(word).then(function (data) {
     var hints = data.query.search.map(function (val) { return val.title.split('Category:')[1] })
     categoryHintHandler.write(hints)
    })
   }, 500)
  })
  categoryInput.keydown(function (e) {
   if (!e.target.value) { return }
   if (e.keyCode == 13) {
    categoriesHandler.push(e.target.value)
    $(this).val('')
   }
   if (e.keyCode == 38) {
    e.preventDefault()
    categoryHint.focus()
    categoryHint.find('.category-hint:last-child').addClass('selected')
   }
  })
  $(document).click(function () {
   categoryHint.hide()
  })
  categoryHint.keydown(function (e) {
   if (e.keyCode == 38) {
    e.preventDefault()
    var index = $(this).find('.category-hint.selected').index() - 1
    if (index < 0) { index = categoryHintHandler.data.length - 1 }
    $(this).find('.category-hint').removeClass('selected').eq(index).addClass('selected')
   }
   if (e.keyCode == 40) {
    e.preventDefault()
    var index = $(this).find('.category-hint.selected').index() + 1
    $(this).find('.category-hint').removeClass('selected')
    if (index > categoryHintHandler.data.length - 1) {
     categoryInput.focus()
    } else {
     $(this).find('.category-hint').eq(index).addClass('selected')
    }
   }
   if (e.keyCode == 13) {
    $(this).find('.category-hint.selected').click()
   }
  })
  addUrlFileBtn.click(function () {
   var url = (prompt('请输入文件地址:') || '').trim()
   if (!url) { return }
   filesHandler.push(url)
  })
  submitBtn.click(function () {
   var requests = filesHandler.format()
   if (!requests.length) {
    mw.notify('您还没有上传任何文件', { type: 'warn' })
    return
   }
   requests.forEach(function (val) {
    if (!(val.filename || '').trim()) {
     mw.notify('存在文件名为空的文件', { type: 'warn' })
     return
    }
   })
  
   var confirm = window.confirm('确定要开始上传吗?')
   if(!confirm){ return }
   var report = {
    content: [],
    push: function (val) {
     this.content.push(val)
     if (this.content.length == requests.length) {
      var log = this.content.join('\n')
      console.log(log)
      setTimeout(function () {
       alert(log)
      }, 1000)
      submitBtn.removeAttr('disabled')
     }
    }
   }
   var filenames = requests.map(function (val) { return val.filename })
   var duplicatedFile = ''
   var isDuplication = filenames.some(function (val) {
    var count = 0
    filenames.forEach(function (val2) {
     if (val == val2) {
      count++
     }
    })
    if (count > 1) {
     duplicatedFile = val
     return true
    }
   })
   if (isDuplication) {
    mw.notify('名为【' + duplicatedFile + '】的文件发生了重复,请不要给要上传的图片设置相同的名称', { type: 'error' })
    return
   }
   mw.notify('开始上传...')
   submitBtn.attr('disabled', 'disabled')
   Promise.all(requests.map(function (file) { return checkFileName(file.filename) }))
    .then(function (results) {
     var isAllMessing = results.every(function (messing, index) {
      !messing && mw.notify('已经存在名为【' + requests[index].filename + '】的文件,请更换名称后再次上传')
      return messing
     })
     if (!isAllMessing) { return }
     Promise.all(requests.map(function (file) {
      return new Promise(function (resolve) {
       upload(file.file, file.filename, file.comment + categoriesHandler.format())
        .then(function () { mw.notify('【' + file.filename + '】上传成功'); resolve({ name: file.filename, result: true }) })
        .catch(function () { mw.notify('【' + file.filename + '】上传失败'); resolve({ name: file.filename, result: false }) })
      })
     })).then(function (results) {
      alert('全部上传结果:\n' + results.map(function(item, index){
      return ++index + '. 【' + item.name + '】 上传' + (item.result ? '成功' : '失败')
      }).join('\n'))
      submitBtn.removeAttr('disabled')
     })
    })
  })
})

2022年3月17日 (四) 18:22的最新版本