mini mvvm

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

Version vom 05.05.2022. Aktuellste Version

Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greasyfork.org/scripts/444466/1047420/mini%20mvvm.js

Autor
ayan0312
Version
0.0.2
Erstellt am
04.05.2022
Letzte Aktualisierung
05.05.2022
Größe
23,4 KB
Lizenz
n/a

指令

<div v-on:click="myClick"></div>
<div v-style="myStyle"></div>
<div v-show="display"></div>
<div v-show:display></div>
<div v-class="myClass"></div>
<div v-model="myModel"></div>
<div v-ref="myRef"></div>
<div v-text="myText"></div>
<div v-html="myHTML"></div>

全局

const state=observe({
  count:GM_getValue('count',1),
})

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

const Counter = {
  template: `
    <button v-on:click="decrease">-</button>
    <span>{{global.count}}</span>
    <button v-on:click="increase">+</button>
  `,
  data(){
    return {
      global:state,
    }
  },
  methods:{
    increase(){
      this.global.count+=1
    },
    decrease(){
      this.global.count-=1
    },
  }
}

组件

const MyTitle = {
    data() {
        return {
            title: '标题',
        }
    },
    template: `
        <div class="test">
            <h1 v-ref="title1"> {{ title }}1 </h2>
            <h2> {{ title }}2 </h2>
            <slot></slot>
            <label>
                标题:
                <input v-model="title" type="text" placeholder="修改标题" />
            </label>
        </div>
    `
}

const MyFooter = {
    data() {
        return {
            title: '尾部',
        }
    },
    template: `
        <div class="test">
            <h1 v-ref="title1"> {{ title }} </h2>
        </div>
    `,
    mounted() {
        this.$refs.title1.textContent = '尾部:ref取element改变文字'
        console.log('before nextTick')
        this.$nextTick().then(() => {
            console.log('nextTick(micro)')
        })
        console.log('after nextTick')
    }
}

const MyVM = {
    element: '#app',
    components: {
        'my-title': MyTitle,
        'my-footer': MyFooter
    },
    data: {
        number: {
            input: 0,
            click: 0
        },
        p: '这里是内容',
        display: true,
        tempObject: {},
        fontSizeStyle: { 'font-size': '14px' },
        inputNumberOver100Times: 0,
        numberInputClass: 'red',
        inputClassesIndex: 0,
        inputClasses: ['red', 'blue', 'yellow', 'grey', 'gold', 'black', 'white']
    },
    computed: {
        getInformation() {
            return `点击次数:${this.number.click} 增量:${this.number.input}`
        },
        getInputNumberOver100TimesInformation() {
            return `增量超过100的次数:${this.inputNumberOver100Times}`
        },
        getNewObject() {
            const a = this.tempObject.test
            return a ? `${a.name}` : a
        }
    },
    watch: {
        "number.input": function (newVal, oldVal) {
            if (newVal >= 100 && oldVal < 100)
                this.inputNumberOver100Times += 1
        },
        "number.click": function (newVal, oldVal) {
            this.fontSizeStyle = {
                'font-size': 14 + newVal + 'px'
            }
        },
        "inputNumberOver100Times": function () {
            this.$emit('over100Times')
        }
    },
    methods: {
        setNewObject(e) {
            this.tempObject.test = {
                name: 'test'
            }
            console.log('create test', this.tempObject)
        },

        deleteNewObject(e) {
            if ('test' in this.tempObject) {
                delete this.tempObject.test
            }
            console.log('delete test', this.tempObject)
        },
        displayController(e) {
            this.display = !this.display
        },
        increaseClickNumber() {
            this.number.click += 1
            this.$emit('click')
        },
        increase1(e) {
            this.number.input += 1
            this.increaseClickNumber()
        },
        increase10(e) {
            this.number.input += 10
            this.increaseClickNumber()
        },
        decrease10(e) {
            this.number.input -= 10
            this.increaseClickNumber()
        },
        switchInputTextColor(e) {
            this.inputClassesIndex += 1
            if (this.inputClassesIndex === this.inputClasses.length)
                this.inputClassesIndex = 0
            this.numberInputClass = this.inputClasses[this.inputClassesIndex]
            this.increaseClickNumber()
        }
    }
}

const vm = createVM(MyVM);
vm.$on('click', function () {
  console.log('clickNumber:', this.number.click)
})

vm.$once('over100Times', function () {
  console.log('over 100!')
})

const cVM = createComponent(MyTitle)
cVM.$mount('#app2')

用例

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()