mini mvvm

自用,bug较多,if和for指令不能使用

Tính đến 04-05-2022. Xem phiên bản mới nhất.

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greasyfork.org/scripts/444466/1047054/mini%20mvvm.js

Tác giả
ayan0312
Phiên bản
0.0.1
Đã tạo
04-05-2022
Đã cập nhật
04-05-2022
Size
20 KB
Giấy phép
N/A

用例

const TIME_OUT = 60 * 1000
const FILTER_URLS = [
  'https://static.myfigurecollection.net/ressources/nsfw.png',
  'https://static.myfigurecollection.net/ressources/spoiler.png'
]

const globalState =observe({
  group:GM_getValue('groupCount',1),
  count:GM_getValue('picCount',1),
  componentDownloadStatus:{},
  downloadStates:{
    normal:0,
    loading:0,
    error:0,
    timeout:0,
    downloaded:0,
  }
})

new Watcher(null,()=>{
  return globalState.count
},(newVal)=>{
  GM_setValue('picCount',newVal)
})

new Watcher(null,()=>{
  return globalState.group
},(newVal)=>{
  GM_setValue('groupCount',newVal)
  globalState.count = 0
})

new Watcher(null,()=>{
  return globalState.componentDownloadStatus
},(newVal)=>{
  Object.assign(globalState.downloadStates,{
    normal:0,
    loading:0,
    error:0,
    timeout:0,
    downloaded:0,
  })
  const states = globalState.downloadStates
  Object.keys(newVal).forEach(key=>{
    const status = newVal[key]
    if(states[status] != null)
        states[status]+=1
  })
},true)

function beforeDownload(){
  if(GM_getValue('groupCount',1) != globalState.group){
    const curCount = GM_getValue('picCount',1) + 1
    globalState.group = GM_getValue('groupCount',1)
    globalState.count = curCount
  }else{
    globalState.group = GM_getValue('groupCount',1)
    globalState.count = GM_getValue('picCount',1) + 1
  }

  return {group:globalState.group,count:globalState.count}
}

const DownloadSequence = {
  template: `
    <span>当前组:</span>
    <button v-on:click="decreaseGroup">-</button>
    <span>{{global.group}}</span>
    <button v-on:click="increaseGroup">+</button>
    <span>当前图片:</span>
    <button v-on:click="decreaseCount">-</button>
    <span>{{global.count}}</span>
    <button v-on:click="increaseCount">+</button>
  `,
  data(){
    return {
      global:globalState,
    }
  },
  methods:{
    increaseCount(){
      this.global.count+=1
    },
    decreaseCount(){
      this.global.count-=1
    },
    increaseGroup(){
      this.global.group+=1
    },
    decreaseGroup(){
      this.global.group-=1
    }
  }
}

const REQEUST_BUTTON_STYLES = {
  normal:{},
  loading: { background: 'white',color:'black',cursor:'wait'},
  error:{background:'red',color:'white'},
  timeout:{background:'yellow',color:'black'},
  downloaded: {background:'green',color:'white'}
}

const DownloadButton = {
    template: `
      <button v-on:click="download" v-style="downloadBtnStyle">
        {{downloadedMsg}}
      </button>
    `,
    data(){
      return {
        oldStatus:'normal',
        downloadStatus:'normal' // 'normal' 'loading' 'error' 'timeout' 'downloaded'
      }
    },
    computed:{
      downloadBtnStyle(){
        return REQEUST_BUTTON_STYLES[this.downloadStatus]
      },
      downloadedMsg(){
        const messages = {
          normal:'下载原图' ,
          loading: '正在下载...',
          error:'下载失败',
          timeout:'下载超时',
          downloaded:  '重新下载'
        }
        return messages[this.downloadStatus]
      }
    },
    watch:{
      downloadStatus(newStatus,oldStatus){
        this.oldStatus = oldStatus
        globalState.componentDownloadStatus[this.cid] = newStatus
      }
    },
    created(){
      globalState.componentDownloadStatus[this.cid] = this.downloadStatus
    },
    destoryed(){
      delete globalState.componentDownloadStatus[this.cid]
    },
    methods:{
      download(){
        if(this.downloadStatus === 'loading') return
        this.downloadStatus = 'loading'
        if(this.oldStatus !== 'error' && this.oldStatus !== 'timeout')
          this.value = beforeDownload()
        this.$emit('download',this.value)
      },
    }
}

const DownloadState = {
  template:`
    <div style="display:flex;flex-direction:row;padding:5px">
      <div style="margin-right:5px;color:grey">
        <span style="">未下载:</span>
        <span>{{states.normal}}</span>
      </div>
      <div style="margin-right:5px;color:green">
        <span style="">已下载:</span>
        <span>{{states.downloaded}}</span>
      </div>
      <div style="margin-right:5px;color:black">
        <span style="">正在下载:</span>
        <span>{{states.loading}}</span>
      </div>
      <div style="margin-right:5px;color:brown">
        <span style="">下载超时:</span>
        <span>{{states.timeout}}</span>
      </div>
      <div style="color:red">
        <span>下载报错:</span>
        <span>{{states.error}}</span>
      </div>
    </div>
  `,
  data(){
    return {
      states:globalState.downloadStates
    }
  },
  methods:{
    download(value){
      this.$emit('download',value)
    }
  }
}

const PictureDownload = {
  components:{
    'download-button':DownloadButton,
    'download-sequence':DownloadSequence,
  },
  template:`
    <div>
      <download-sequence></download-sequence>
      <span v-show:downloaded style="margin:0 10px">{{msg}}</span>
      <download-button v-ref="downloadButton" v-on:download="download"></download-button>
    </div>
  `,
  data(){
    return {
      group:0,
      count:0,
      downloaded:false
    }
  },
  computed:{
    msg(){
      return `${this.group}.${this.count}`
    }
  },
  mounted(){
      const value = GM_getValue(window.location.href)
      if(!value) return
      this.$refs.downloadButton.downloadStatus = value.downloadStatus
      this.$refs.downloadButton.value = {
        group: value.group,
        count: value.count
      }
      this.downloaded = true
      this.group = value.group
      this.count = value.count
  },
  methods:{
    download(value){
      this.downloaded = true
      this.group = value.group
      this.count = value.count
      this.$emit('download',value)
    }
  }
}

function insertAfter(targetNode, afterNode){
  const parentNode = afterNode.parentNode
  const beforeNode = afterNode.nextElementSibling
  if(beforeNode == null)
    parentNode.appendChild(targetNode)
  else
    parentNode.insertBefore(targetNode, beforeNode)
}

function mountVM(node,component){
  const vm = new MVVMComponent(component)
  vm.$mount(node)
  return vm
}

function download({vm,picture,group,count,origin}){
  const values = origin.split('/')
  const fileType = values[values.length - 1].split('.')[1]
  const name = `${group}.${count}.${fileType}`
  const end = (status)=>{
    return ()=>{
      vm.downloadStatus = status
      GM_setValue(picture,{origin,group,count,downloadStatus:status})
    }
  }

  GM_download({
    url:origin,
    name,
    timeout:TIME_OUT,
    onload:end('downloaded'),
    onerror:end('error'),
    ontimeout:end('timeout'),
  })
}

function renderPictureExtension(objectMeta,thePicture){
  const origin = thePicture.href
  const div = document.createElement('div')
  objectMeta.appendChild(div)
  const vm = mountVM(div,PictureDownload)
  vm.$on('download',(value)=>{
    download({
      ...value,
      origin,
      picture:window.location.href,
      vm:vm.$refs.downloadButton,
    })
  })
}

const createPictruePreview = ({thumb,origin,picture}) => {
  const commonStyle={
    'margin':'10px 10px',
    'border':'1px solid #000',
    'padding':'10px',
    'border-radius':'5px',
    'background':'#fff',
    'transition':'all 0.5s',
    'box-sizing': 'border-box'
  }

  return {
      components:{
        'download-button':DownloadButton
      },
      template: `
          <div v-style="containerStyle">
            <div style="margin-bottom:10px;display:flex;flex-direction:row;justify-content:center;align-items:center;width:100%;">
              <div style="margin:0 10px;flex-shrink:0"><img style="cursor:pointer" v-on:click="openPicturePage" [src]="thumb" /></div>
              <div style="flex-shrink:1" v-show="originalImage"><img style="width:100%" [src]="src" /></div>
            </div>
            <div style="display:flex;justify-content:center;align-items:center;flex-direction:column">
                <download-button v-ref="downloadButton" v-on:download="download"></download-button>
                <br v-show:downloaded />
                <div v-show:downloaded>
                  <span>组:</span>
                  <span >{{group}}</span>
                  <span>图片:</span>
                  <span >{{count}}</span>
                </div>
                <br />
                <button v-on:click="toggle">{{msg}}</button>
                <br />
                <button v-show:refresh v-on:click="refreshOrigin" v-style="refreshBtnStyle">
                  {{refreshMsg}}
                </button>
            </div>
          </div>
      `,
      data(){
        return {
          thumb,
          origin,
          picture,
          refresh:FILTER_URLS.includes(origin),
          originalImage:false,
          group:0,
          count:0,
          downloaded:false,
          refreshStatus:'normal' // 'normal' 'loading' 'error' 'timeout' 'downloaded'
        }
      },
      computed:{
        src(){
          return this.originalImage ? this.origin : this.thumb
        },
        msg(){
          return this.originalImage ? '关闭预览' : '预览原图'
        },
        containerStyle(){
          return Object.assign({},commonStyle,this.originalImage ? {
            width:'100%'
          }:{} ) 
        },
        refreshBtnStyle(){
          return REQEUST_BUTTON_STYLES[this.refreshStatus]
        },
        refreshMsg(){
          const messages = {
            normal:'获取隐藏图片' ,
            loading: '正在获取...',
            error:'获取失败',
            timeout:'获取超时',
            downloaded:  '重新获取'
          }
          return messages[this.refreshStatus]
        }
      },
      mounted(){
        const value = GM_getValue(picture)
        if(!value) return
        this.$refs.downloadButton.downloadStatus = value.downloadStatus
        this.$refs.downloadButton.value = {
          group: value.group,
          count: value.count
        }
        this.downloaded = true
        this.group = value.group
        this.count = value.count
      },
      methods:{
        download(value){
          this.downloaded = true
          this.group = value.group
          this.count = value.count

          if(this.refresh && this.refreshStatus !== 'downloaded')
            this.refreshOrigin()
              .then(()=>{
                this.$emit('download',{...value,origin:this.origin})
              })
          else
            this.$emit('download',{...value,origin:this.origin})
        },
        toggle(){
          if(this.refresh && !this.originalImage && this.refreshStatus !== 'downloaded')
            this.refreshOrigin()

          this.originalImage = !this.originalImage
        },
        refreshOrigin(){
          if(this.refreshStatus === 'loading') return
          this.refreshStatus = 'loading'
          return new Promise((resolve,reject)=>{
            GM_xmlhttpRequest({
              url:this.picture,
              responseType:'document',
              timeout:TIME_OUT,
              onload:(data)=>{
                const doc = data.response
                const a = doc.querySelector('.the-picture>a')
                if(a){
                  this.origin = a.href
                  const thumb = a.href.split('/')
                  thumb.splice(thumb.length - 1,0,'thumbnails')
                  this.thumb = thumb.join('/')
                  this.refreshStatus = 'downloaded'
                  resolve()
                  return                
                }
                this.refreshStatus = 'error'
                reject()
              },
              onerror:()=>{
                this.refreshStatus = 'error'
                reject()
              },
              ontimeout:()=>{
                this.refreshStatus = 'timeout'
                reject()
              }
            })
          })
        },
        openPicturePage(){
          window.open(picture)
        }
      }
  }
}

function parseOriginalImageURL(thumb_url){
  if(thumb_url.indexOf('thumbnails/') > -1){
    url = thumb_url.split('thumbnails/').join('')
  } else {
    const paths = thumb_url.split('pictures/')[1].split('/')
    if(paths.length > 2){
      paths.splice(3,1)
      url = [thumb_url.split('pictures/')[0],paths.join('/')].join('pictures/')
    }
  }

  return url
}

function getImageURLs(node){
  const picture = node.querySelector('a').href
  const viewport = node.querySelector('.viewport')
  const thumb = viewport.style.background.split('"')[1]
  let origin = thumb
  if(FILTER_URLS.includes(origin))
    origin = thumb
  else
    origin = parseOriginalImageURL(origin)

  return {thumb,origin,picture}
}

function renderPicturesExtension(thumbs){
  function _initParentNode(parentNode){
    parentNode.innerHTML = ''
    parentNode.style.setProperty('display','flex')
    parentNode.style.setProperty('flex-direction','row')
    parentNode.style.setProperty('flex-wrap','wrap')
    parentNode.style.setProperty('justify-content','center')
    parentNode.style.setProperty('align-items','center')
    const div1 = document.createElement('div')
    parentNode.parentNode.insertBefore(div1,parentNode)
    mountVM(div1,DownloadSequence)
    const div2 = document.createElement('div')
    const pageCount = document.querySelector('.listing-count-pages') || parentNode
    pageCount.parentNode.insertBefore(div2,pageCount)
    mountVM(div2,DownloadState)
  }

  const parentNode = thumbs[0].parentNode
  _initParentNode(parentNode)
  thumbs.forEach(thumb_node=>{
    const imageURLs = getImageURLs(thumb_node)
    const preview = createPictruePreview(imageURLs)
    const div3 = document.createElement('div')
    parentNode.appendChild(div3)
    const previewVM = mountVM(div3,preview)
    previewVM.$on('download',(value)=>{
      download({
        ...value,
        picture:imageURLs.picture,
        vm:previewVM.$refs.downloadButton,
      })
    })
  })
}

function render(){
  const objectMeta = document.querySelector('.object-meta')
  const thePicture = document.querySelector('.the-picture>a')
  if(objectMeta && thePicture)
    renderPictureExtension(objectMeta,thePicture)

  const thumbs = []
  document.querySelectorAll('.picture-icon.tbx-tooltip').forEach(thumb=>{
    thumbs.push(thumb)  
  })
  if(thumbs.length > 0)
    renderPicturesExtension(thumbs.reverse())
}

render()