浏览代码

merge master into feature/restrictions

Artur Paikin 7 年之前
父节点
当前提交
d271ba30eb

+ 18 - 15
CHANGELOG.md

@@ -72,43 +72,46 @@ maybe later:
 
 ## 0.18.0
 
+To be released: 2017-07-28
+
 - [ ] test: add https://github.com/pa11y/pa11y for automated accessibility testing (@arturi)
 - [ ] webcam: look into simplifying / improving webcam plugin (@arturi, @goto-bus-stop)
 - [ ] core: add error in file progress state? (@arturi)
+- [ ] core: research !important styles to be immune to any environment/page. Maybe use smth like `postcss-safe-important`. Or increase specificity (with .Uppy) (@arturi)
 - [ ] uppy-server: add uppy-server metrics to Librato (@ifedapoolarewaju)
 - [ ] dashboard: error UI, question mark button, `core:error` (@arturi)
 - [ ] uploaders: add direct-to-s3 upload plugin and test it with the flow to then upload to transloadit, stage 2, release (@goto-bus-stop)
+- [ ] server: what if smth changed in GDrive while it was open in Uppy? refresh file list? (@ifedapoolarewaju)
+- [ ] core: see if we can figure out css-in-js, while keeping non-random classnames (ideally prefixed) and useful preprocessor features. also see simple https://github.com/codemirror/CodeMirror/blob/master/lib/codemirror.css (@arturi, @goto-bus-stop)
+- [ ] test: add tests for `npm install uppy` and running in different browsers, the real world use case (@arturi)
+- [ ] provider: improve UI: add icons for file types (@arturi)
 
 ## 0.17.0
 
 To be released: 2017-06-30
 
-- [ ] core: add `uppy.reset()` as discussed in #179 (@arturi)
-- [ ] core: research !important styles to be immune to any environment/page. Maybe use smth like https://www.npmjs.com/package/postcss-safe-important. Or increase specificity (with .Uppy) (@arturi)
 - [ ] core: restrictions — by file type, size, number of files (@arturi)
-- [ ] core: see if we can figure out css-in-js, while keeping non-random classnames (ideally prefixed) and useful preprocessor features. also see simple https://github.com/codemirror/CodeMirror/blob/master/lib/codemirror.css (@arturi, @goto-bus-stop)
-- [ ] fileinput: allow retriving fields/options from form (@arturi #153)
-- [ ] provider: improve UI: add icons for file types (@arturi)
-- [ ] provider: improve UI: improve overall look, breadcrumbs (@arturi)
-- [ ] provider: improve UI: steps towards making it responsive (@arturi)
-- [ ] server: what if smth changed in GDrive while it was open in Uppy? refresh file list? (@ifedapoolarewaju)
-- [x] statusbar: show status “Upload started...” when the remote upload has begun, but no progress events received yet (@arturi)
-- [ ] test: add tests for `npm install uppy` and running in different browsers, the real world use case (@arturi)
-- [x] uploaders: add direct-to-s3 upload plugin and test it with the flow to then upload to transloadit, stage 1, WIP (@goto-bus-stop)
-- [ ] uppy/uppy-server: Make a barely working Instagram Plugin (@ifedapoolarewaju / #21)
-- [x] uppy/uppy-server: some file types cannot be downloaded/uploaded on google drive (e.g google docs). How to handle that? (@ifedapoolarewaju)
-- [x] uppy: fix google drive uploads on mobile (double click issue) (@arturi)
+- [ ] provider: improve UI: improve overall look, breadcrumbs, more responsive (@arturi)
 - [ ] website: new demo video / gif (@arturi)
+- [x] core: css-in-js demos, try template-css (@arturi @goto-bus-stop #239)
+- [x] core: add `uppy.reset()` as discussed in #179 (@arturi)
 - [x] core: add nanoraf https://github.com/yoshuawuyts/choo/pull/135/files?diff=unified (@goto-bus-stop, @arturi)
 - [x] core: file type detection: archives, markdown (possible modules: file-type, identify-filetype) example: http://requirebin.com/?gist=f9bea9602030f1320a227cf7f140c45f, http://stackoverflow.com/a/29672957 (@arturi)
 - [x] dashboard: make file icons prettier: https://uppy.io/images/blog/0.16/service-logos.png (@arturi, @nqst / #215)
+- [x] fileinput: allow retriving fields/options from form (@arturi #153)
+- [x] server: configurable server port (@ifedapoolarewaju)
 - [x] server: support for custom providers (@ifedapoolarewaju)
 - [x] statusbar: also show major errors, add “error” state (@goto-bus-stop)
 - [x] statusbar: pre/postprocessing status updates in the StatusBar (@goto-bus-stop, #202)
+- [x] statusbar: show status “Upload started...” when the remote upload has begun, but no progress events received yet (@arturi)
 - [x] statusbar: work towards extracting StatusBar to a separate plugin, bundle that with Dashboard? (@goto-bus-stop, @arturi)
 - [x] tus/uppy-server: Support metadata in remote tus uploads (@ifedapoolarewaju, @goto-bus-stop / #210)
+- [x] uploaders: add direct-to-s3 upload plugin and test it with the flow to then upload to transloadit, stage 1, WIP (@goto-bus-stop)
+- [x] uppy/uppy-server: Make a barely working Instagram Plugin (@ifedapoolarewaju / #21)
+- [x] uppy/uppy-server: Make a barely working Instagram Plugin (@ifedapoolarewaju / #21)
 - [x] uppy/uppy-server: allow google drive/dropbox non-tus (i.e multipart) remote uploads (@arturi, @ifedapoolarewaju / #205)
-- [x] server: configurable server port (@ifedapoolarewaju)
+- [x] uppy/uppy-server: some file types cannot be downloaded/uploaded on google drive (e.g google docs). How to handle that? (@ifedapoolarewaju)
+- [x] uppy: fix google drive uploads on mobile (double click issue) (@arturi)
 
 ## 0.16.2 - 2017-05-31
 

+ 5 - 2
examples/bundled-example/index.html

@@ -7,12 +7,15 @@
   </head>
   <body>
     <h1>Uppy is here</h1>
-    <button id="uppyModalOpener">Open Modal</button>
+    <button id="uppyModalOpener">Choose Files</button>
 
     <div class="Uppy">
-      <form class="UppyForm" action="/">
+      <form class="MyForm" action="/">
         <input type="file" />
+        <input type="hidden" name="bla" value="12333">
+        <input type="text" name="yo" value="1">
         <button type="submit">Upload</button>
+        <input type="submit">
       </form>
     </div>
 

+ 10 - 5
examples/bundled-example/main.js

@@ -2,9 +2,12 @@ const Uppy = require('../../src/core/Core.js')
 const Dashboard = require('../../src/plugins/Dashboard')
 const GoogleDrive = require('../../src/plugins/GoogleDrive')
 const Dropbox = require('../../src/plugins/Dropbox')
-const Webcam = require('../../src/plugins/Webcam')
+const Instagram = require('../../src/plugins/Instagram')
+// const Webcam = require('../../src/plugins/Webcam')
 const Tus10 = require('../../src/plugins/Tus10')
 // const Multipart = require('../../src/plugins/Multipart')
+// const DragDrop = require('../../src/plugins/FileInput')
+// const FileInput = require('../../src/plugins/FileInput')
 const MetaData = require('../../src/plugins/MetaData')
 // const Informer = require('../../src/plugins/Informer')
 // const StatusBar = require('../../src/plugins/StatusBar')
@@ -43,10 +46,12 @@ const uppy = Uppy({
     trigger: '#uppyModalOpener',
     // maxWidth: 350,
     // maxHeight: 400,
-    // inline: false,
+    inline: false,
     // disableStatusBar: true,
     // disableInformer: true,
-    target: 'body',
+    setMetaFromTargetForm: true,
+    // replaceTargetContent: true,
+    target: '.MyForm',
     locale: {
       strings: {browse: 'wow'}
     },
@@ -54,6 +59,7 @@ const uppy = Uppy({
   })
   .use(GoogleDrive, {target: Dashboard, host: 'http://localhost:3020'})
   .use(Dropbox, {target: Dashboard, host: 'http://localhost:3020'})
+  .use(Instagram, {target: Dashboard, host: 'http://localhost:3020'})
   // .use(FileInput, {target: '.Uppy', locale: {
   //   strings: {selectToUpload: 'Выберите файл для загрузки'}
   // }})
@@ -61,8 +67,7 @@ const uppy = Uppy({
   //   strings: {chooseFile: 'Выберите файл'}
   // }})
   // .use(ProgressBar, {target: 'body'})
-  // .use(dummy)
-  .use(Webcam, {target: Dashboard})
+  // .use(Webcam, {target: Dashboard})
   // .use(Multipart, {endpoint: '//api2.transloadit.com'})
   .use(Tus10, {endpoint: TUS_ENDPOINT, resume: true})
   // .use(Informer, {target: Dashboard})

+ 5 - 0
package-lock.json

@@ -3449,6 +3449,11 @@
       "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=",
       "dev": true
     },
+    "get-form-data": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/get-form-data/-/get-form-data-1.2.5.tgz",
+      "integrity": "sha1-yQ+bix0tvbRulIDoi6xjKT/msYI="
+    },
     "get-stdin": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",

+ 1 - 0
package.json

@@ -76,6 +76,7 @@
   "dependencies": {
     "drag-drop": "2.13.2",
     "es6-promise": "3.2.1",
+    "get-form-data": "^1.2.5",
     "lodash.throttle": "4.1.1",
     "mime-match": "^1.0.2",
     "namespace-emitter": "1.0.0",

+ 28 - 9
src/core/Core.js

@@ -86,7 +86,8 @@ class Uppy {
       capabilities: {
         resumableUploads: false
       },
-      totalProgress: 0
+      totalProgress: 0,
+      meta: Object.assign({}, this.opts.meta)
     }
 
     // for debugging and testing
@@ -134,6 +135,14 @@ class Uppy {
     return this.state
   }
 
+  reset () {
+    this.emit('core:pause-all')
+    this.emit('core:cancel-all')
+    this.setState({
+      totalProgress: 0
+    })
+  }
+
   addPreProcessor (fn) {
     this.preProcessors.push(fn)
   }
@@ -167,6 +176,13 @@ class Uppy {
     }
   }
 
+  setMeta (data) {
+    const newMeta = Object.assign({}, this.getState().meta, data)
+    this.log('Adding metadata:')
+    this.log(data)
+    this.setState({meta: newMeta})
+  }
+
   updateMeta (data, fileID) {
     const updatedFiles = Object.assign({}, this.getState().files)
     const newMeta = Object.assign({}, updatedFiles[fileID].meta, data)
@@ -372,10 +388,9 @@ class Uppy {
     })
 
     this.on('core:cancel-all', () => {
-      const files = this.getState().files
-      Object.keys(files).forEach((file) => {
-        this.removeFile(files[file].id)
-      })
+      let updatedFiles = this.getState().files
+      updatedFiles = {}
+      this.setState({files: updatedFiles})
     })
 
     this.on('core:upload-started', (fileID, upload) => {
@@ -588,6 +603,8 @@ class Uppy {
    * Uninstall all plugins and close down this Uppy instance.
    */
   close () {
+    this.reset()
+
     this.iteratePlugins((plugin) => {
       plugin.uninstall()
     })
@@ -606,16 +623,18 @@ class Uppy {
     if (!this.opts.debug) {
       return
     }
+
+    if (type === 'error') {
+      console.error(`LOG: ${msg}`)
+      return
+    }
+
     if (msg === `${msg}`) {
       console.log(`LOG: ${msg}`)
     } else {
       console.dir(msg)
     }
 
-    if (type === 'error') {
-      console.error(`LOG: ${msg}`)
-    }
-
     global.uppyLog = global.uppyLog + '\n' + 'DEBUG LOG: ' + msg
   }
 

+ 2 - 2
src/generic-provider-views/Browser.js

@@ -40,11 +40,11 @@ module.exports = (props) => {
             activeRow: props.isActiveRow,
             sortByTitle: props.sortByTitle,
             sortByDate: props.sortByDate,
-            // handleRowClick: props.handleRowClick,
             handleFileClick: props.addFile,
             handleFolderClick: props.getNextFolder,
             getItemName: props.getItemName,
-            getItemIcon: props.getItemIcon
+            getItemIcon: props.getItemIcon,
+            handleScroll: props.handleScroll
           })}
         </main>
       </div>

+ 1 - 3
src/generic-provider-views/Table.js

@@ -17,13 +17,12 @@ module.exports = (props) => {
           ${headers}
         </tr>
       </thead>
-      <tbody>
+      <tbody onscroll=${props.handleScroll}>
         ${props.folders.map((folder) => {
           return Row({
             title: props.getItemName(folder),
             active: props.activeRow(folder),
             getItemIcon: () => props.getItemIcon(folder),
-            // handleClick: () => props.handleRowClick(folder),
             handleClick: () => props.handleFolderClick(folder),
             columns: props.columns
           })
@@ -33,7 +32,6 @@ module.exports = (props) => {
             title: props.getItemName(file),
             active: props.activeRow(file),
             getItemIcon: () => props.getItemIcon(file),
-            // handleClick: () => props.handleRowClick(file),
             handleClick: () => props.handleFileClick(file),
             columns: props.columns
           })

+ 39 - 38
src/generic-provider-views/index.js

@@ -34,6 +34,8 @@ const Utils = require('../core/Utils')
  *    @return {String} unique request path of the item when making calls to uppy server
  * getItemModifiedDate
  *    @return {object} or {String} date of when last the item was modified
+ * getItemThumbnailUrl
+ *    @return {String}
  */
 module.exports = class View {
   /**
@@ -49,7 +51,6 @@ module.exports = class View {
     this.filterQuery = this.filterQuery.bind(this)
     this.getFolder = this.getFolder.bind(this)
     this.getNextFolder = this.getNextFolder.bind(this)
-    // this.handleRowClick = this.handleRowClick.bind(this)
     this.logout = this.logout.bind(this)
     this.handleAuth = this.handleAuth.bind(this)
     this.handleDemoAuth = this.handleDemoAuth.bind(this)
@@ -57,6 +58,7 @@ module.exports = class View {
     this.sortByDate = this.sortByDate.bind(this)
     this.isActiveRow = this.isActiveRow.bind(this)
     this.handleError = this.handleError.bind(this)
+    this.handleScroll = this.handleScroll.bind(this)
 
     // Visual
     this.render = this.render.bind(this)
@@ -72,6 +74,18 @@ module.exports = class View {
     this.plugin.core.setState({[stateId]: Object.assign({}, state[stateId], newState)})
   }
 
+  _updateFilesAndFolders (res, files, folders) {
+    this.plugin.getItemSubList(res).forEach((item) => {
+      if (this.plugin.isFolder(item)) {
+        folders.push(item)
+      } else {
+        files.push(item)
+      }
+    })
+
+    this.updateState({ folders, files })
+  }
+
   /**
    * Based on folder ID, fetch a new folder and update it to state
    * @param  {String} id Folder id
@@ -94,18 +108,8 @@ module.exports = class View {
           updatedDirectories = state.directories.concat([{id, title: name || this.plugin.getItemName(res)}])
         }
 
-        this.plugin.getItemSubList(res).forEach((item) => {
-          if (this.plugin.isFolder(item)) {
-            folders.push(item)
-          } else {
-            files.push(item)
-          }
-        })
-
-        let data = {folders, files, directories: updatedDirectories}
-        this.updateState(data)
-
-        return data
+        this._updateFilesAndFolders(res, files, folders)
+        this.updateState({ directories: updatedDirectories })
       },
       this.handleError)
   }
@@ -124,7 +128,7 @@ module.exports = class View {
     const tagFile = {
       source: this.plugin.id,
       data: this.plugin.getItemData(file),
-      name: this.plugin.getItemName(file),
+      name: this.plugin.getItemName(file) || this.plugin.getItemId(file),
       type: this.plugin.getMimeType(file),
       isRemote: true,
       body: {
@@ -141,7 +145,7 @@ module.exports = class View {
 
     Utils.getFileType(tagFile).then(fileType => {
       if (Utils.isPreviewSupported(fileType[1])) {
-        tagFile.preview = `${this.plugin.opts.host}/${this.Provider.id}/thumbnail/${this.plugin.getItemRequestPath(file)}`
+        tagFile.preview = this.plugin.getItemThumbnailUrl(file)
       }
       this.plugin.core.log('Adding remote file')
       this.plugin.core.emitter.emit('core:file-add', tagFile)
@@ -167,19 +171,6 @@ module.exports = class View {
       }).catch(this.handleError)
   }
 
-  /**
-   * Used to set active file/folder.
-   * @param  {Object} file   Active file/folder
-   */
-  // handleRowClick (file) {
-  //   const state = this.plugin.core.getState()[this.plugin.stateId]
-  //   const newState = Object.assign({}, state, {
-  //     activeRow: this.plugin.getItemId(file)
-  //   })
-
-  //   this.updateState(newState)
-  // }
-
   filterQuery (e) {
     const state = this.plugin.core.getState()[this.plugin.stateId]
     this.updateState(Object.assign({}, state, {
@@ -297,17 +288,27 @@ module.exports = class View {
     this.updateState({ error })
   }
 
+  handleScroll (e) {
+    const scrollPos = e.target.scrollHeight - (e.target.scrollTop + e.target.offsetHeight)
+    const path = this.plugin.getNextPagePath ? this.plugin.getNextPagePath() : null
+
+    if (scrollPos < 50 && path && !this._isHandlingScroll) {
+      this.Provider.list(path)
+        .then((res) => {
+          const { files, folders } = this.plugin.core.getState()[this.plugin.stateId]
+          this._updateFilesAndFolders(res, files, folders)
+        }).catch(this.handleError)
+        .then(() => { this._isHandlingScroll = false }) // always called
+
+      this._isHandlingScroll = true
+    }
+  }
+
   // displays loader view while asynchronous request is being made.
   _loaderWrapper (promise, then, catch_) {
     promise
-      .then((result) => {
-        this.updateState({ loading: false })
-        then(result)
-      })
-      .catch((err) => {
-        this.updateState({ loading: false })
-        catch_(err)
-      })
+      .then(then).catch(catch_)
+      .then(() => this.updateState({ loading: false })) // always called.
     this.updateState({ loading: true })
   }
 
@@ -338,14 +339,14 @@ module.exports = class View {
       addFile: this.addFile,
       filterItems: this.filterItems,
       filterQuery: this.filterQuery,
-      handleRowClick: this.handleRowClick,
       sortByTitle: this.sortByTitle,
       sortByDate: this.sortByDate,
       logout: this.logout,
       demo: this.plugin.opts.demo,
       isActiveRow: this.isActiveRow,
       getItemName: this.plugin.getItemName,
-      getItemIcon: this.plugin.getItemIcon
+      getItemIcon: this.plugin.getItemIcon,
+      handleScroll: this.handleScroll
     })
 
     return Browser(browserProps)

+ 2 - 0
src/index.js

@@ -12,6 +12,7 @@ const DragDrop = require('./plugins/DragDrop/index.js')
 const FileInput = require('./plugins/FileInput.js')
 const GoogleDrive = require('./plugins/GoogleDrive/index.js')
 const Dropbox = require('./plugins/Dropbox/index.js')
+const Instagram = require('./plugins/Instagram/index.js')
 const Webcam = require('./plugins/Webcam/index.js')
 
 // Progressindicators
@@ -35,6 +36,7 @@ module.exports = {
   DragDrop,
   GoogleDrive,
   Dropbox,
+  Instagram,
   FileInput,
   Tus10,
   Multipart,

+ 2 - 0
src/plugins/Dashboard/Dashboard.js

@@ -58,6 +58,7 @@ module.exports = function Dashboard (props) {
           onload=${() => props.updateDashboardElWidth()}>
 
     <button class="UppyDashboard-close"
+            type="button"
             aria-label="${props.i18n('closeModal')}"
             title="${props.i18n('closeModal')}"
             onclick=${props.hideModal}>${closeIcon()}</button>
@@ -135,6 +136,7 @@ module.exports = function Dashboard (props) {
               ${props.i18n('importFrom')} ${props.activePanel ? props.activePanel.name : null}
             </h2>
             <button class="UppyDashboardContent-back"
+                    type="button"
                     onclick=${props.hideAllPanels}>${props.i18n('done')}</button>
           </div>
           ${props.activePanel ? props.activePanel.render(props.state) : ''}

+ 1 - 1
src/plugins/Dashboard/FileCard.js

@@ -29,7 +29,7 @@ module.exports = function fileCard (props) {
   return html`<div class="UppyDashboardFileCard" aria-hidden="${!props.fileCardFor}">
     <div class="UppyDashboardContent-bar">
       <h2 class="UppyDashboardContent-title">Editing <span class="UppyDashboardContent-titleFile">${file.meta ? file.meta.name : file.name}</span></h2>
-      <button class="UppyDashboardContent-back" title="Finish editing file"
+      <button class="UppyDashboardContent-back" type="button" title="Finish editing file"
               onclick=${() => props.done(meta, file.id)}>Done</button>
     </div>
     ${props.fileCardFor

+ 4 - 0
src/plugins/Dashboard/FileItem.js

@@ -49,6 +49,7 @@ module.exports = function fileItem (props) {
         </div>
         <div class="UppyDashboardItem-progress">
           <button class="UppyDashboardItem-progressBtn"
+                  type="button"
                   title="${isUploaded
                           ? 'upload complete'
                           : props.resumableUploads
@@ -97,6 +98,7 @@ module.exports = function fileItem (props) {
       </div>
       ${!uploadInProgressOrComplete
         ? html`<button class="UppyDashboardItem-edit"
+                       type="button"
                        aria-label="Edit file"
                        title="Edit file"
                        onclick=${(e) => props.showFileCard(file.id)}>
@@ -105,6 +107,7 @@ module.exports = function fileItem (props) {
       }
       ${file.uploadURL
         ? html`<button class="UppyDashboardItem-copyLink"
+                       type="button"
                        aria-label="Copy link"
                        title="Copy link"
                        onclick=${() => {
@@ -121,6 +124,7 @@ module.exports = function fileItem (props) {
     <div class="UppyDashboardItem-action">
       ${!isUploaded
         ? html`<button class="UppyDashboardItem-remove"
+                       type="button"
                        aria-label="Remove file"
                        title="Remove file"
                        onclick=${() => props.removeFile(file.id)}>

+ 13 - 12
src/plugins/Dashboard/index.js

@@ -42,6 +42,7 @@ module.exports = class DashboardUI extends Plugin {
     // set default options
     const defaultOptions = {
       target: 'body',
+      getMetaFromForm: true,
       inline: false,
       width: 750,
       height: 550,
@@ -89,7 +90,7 @@ module.exports = class DashboardUI extends Plugin {
     if (callerPluginType !== 'acquirer' &&
         callerPluginType !== 'progressindicator' &&
         callerPluginType !== 'presenter') {
-      let msg = 'Error: Modal can only be used by plugins of types: acquirer, progressindicator, presenter'
+      let msg = 'Dashboard: Modal can only be used by plugins of types: acquirer, progressindicator, presenter'
       this.core.log(msg)
       return
     }
@@ -163,9 +164,9 @@ module.exports = class DashboardUI extends Plugin {
     // focus on modal inner block
     this.target.querySelector('.UppyDashboard-inner').focus()
 
-    this.updateDashboardElWidth()
+    // this.updateDashboardElWidth()
     // to be sure, sometimes when the function runs, container size is still 0
-    setTimeout(this.updateDashboardElWidth, 300)
+    setTimeout(this.updateDashboardElWidth, 500)
   }
 
   // Close the Modal on esc key press
@@ -176,14 +177,14 @@ module.exports = class DashboardUI extends Plugin {
   }
 
   initEvents () {
-    // const dashboardEl = this.target.querySelector(`${this.opts.target} .UppyDashboard`)
-
     // Modal open button
     const showModalTrigger = findDOMElement(this.opts.trigger)
     if (!this.opts.inline && showModalTrigger) {
       showModalTrigger.addEventListener('click', this.showModal)
-    } else {
-      this.core.log('Modal trigger wasn’t found')
+    }
+
+    if (!this.opts.inline && !showModalTrigger) {
+      this.core.log('Dashboard modal trigger not found, you won’t be able to select files. Make sure `trigger` is set correctly in Dashboard options', 'error')
     }
 
     document.body.addEventListener('keyup', this.handleEscapeKeyPress)
@@ -224,7 +225,7 @@ module.exports = class DashboardUI extends Plugin {
 
   updateDashboardElWidth () {
     const dashboardEl = this.target.querySelector('.UppyDashboard-inner')
-    // console.log(dashboardEl.offsetWidth)
+    this.core.log(`Dashboard width: ${dashboardEl.offsetWidth}`)
 
     const modal = this.core.getState().modal
     this.core.setState({
@@ -304,9 +305,9 @@ module.exports = class DashboardUI extends Plugin {
       return target.type === 'progressindicator'
     })
 
-    const addFile = (file) => {
-      this.core.emitter.emit('core:file-add', file)
-    }
+    // const addFile = (file) => {
+    //   this.core.emitter.emit('core:file-add', file)
+    // }
 
     const removeFile = (fileID) => {
       this.core.emitter.emit('core:file-remove', fileID)
@@ -365,7 +366,7 @@ module.exports = class DashboardUI extends Plugin {
       i18n: this.containerWidth,
       pauseAll: this.pauseAll,
       resumeAll: this.resumeAll,
-      addFile: addFile,
+      addFile: this.core.addFile,
       removeFile: removeFile,
       info: info,
       note: this.opts.note,

+ 1 - 0
src/plugins/DragDrop/index.js

@@ -37,6 +37,7 @@ module.exports = class DragDrop extends Plugin {
     // Default options
     const defaultOpts = {
       target: '.UppyDragDrop',
+      getMetaFromForm: true,
       locale: defaultLocale
     }
 

+ 4 - 0
src/plugins/Dropbox/index.js

@@ -122,6 +122,10 @@ module.exports = class Dropbox extends Plugin {
     return item.modified
   }
 
+  getItemThumbnailUrl (item) {
+    return `${this.opts.host}/${this.Dropbox.id}/thumbnail/${this.getItemRequestPath(item)}`
+  }
+
   render (state) {
     return this.view.render(state)
   }

+ 4 - 3
src/plugins/FileInput.js

@@ -1,5 +1,5 @@
 const Plugin = require('./Plugin')
-const Utils = require('../core/Utils')
+const { toArray } = require('../core/Utils')
 const Translator = require('../core/Translator')
 const html = require('yo-yo')
 
@@ -7,7 +7,7 @@ module.exports = class FileInput extends Plugin {
   constructor (core, opts) {
     super(core, opts)
     this.id = 'FileInput'
-    this.title = 'FileInput'
+    this.title = 'File Input'
     this.type = 'acquirer'
 
     const defaultLocale = {
@@ -19,6 +19,7 @@ module.exports = class FileInput extends Plugin {
     // Default options
     const defaultOptions = {
       target: '.UppyForm',
+      getMetaFromForm: true,
       replaceTargetContent: true,
       multipleFiles: true,
       pretty: true,
@@ -42,7 +43,7 @@ module.exports = class FileInput extends Plugin {
   handleInputChange (ev) {
     this.core.log('All right, something selected through input...')
 
-    const files = Utils.toArray(ev.target.files)
+    const files = toArray(ev.target.files)
 
     files.forEach((file) => {
       this.core.emitter.emit('core:file-add', {

+ 4 - 0
src/plugins/GoogleDrive/index.js

@@ -113,6 +113,10 @@ module.exports = class Google extends Plugin {
     return item.modifiedByMeDate
   }
 
+  getItemThumbnailUrl (item) {
+    return `${this.opts.host}/${this.GoogleDrive.id}/thumbnail/${this.getItemRequestPath(item)}`
+  }
+
   render (state) {
     return this.view.render(state)
   }

+ 130 - 0
src/plugins/Instagram/index.js

@@ -0,0 +1,130 @@
+const html = require('yo-yo')
+const Plugin = require('../Plugin')
+
+const Provider = require('../../uppy-base/src/plugins/Provider')
+
+const View = require('../../generic-provider-views/index')
+
+module.exports = class Instagram extends Plugin {
+  constructor (core, opts) {
+    super(core, opts)
+    this.type = 'acquirer'
+    this.id = 'Instagram'
+    this.title = 'Instagram'
+    this.stateId = 'instagram'
+    this.icon = () => html`
+      <svg class="UppyIcon UppyModalTab-icon" width="28" height="28" viewBox="0 0 512 512">
+        <path
+          d="M256,49.471c67.266,0,75.233.257,101.8,1.469,24.562,1.121,37.9,5.224,46.778,8.674a78.052,78.052,0,0,1,28.966,18.845,78.052,78.052,0,0,1,18.845,28.966c3.45,8.877,7.554,22.216,8.674,46.778,1.212,26.565,1.469,34.532,1.469,101.8s-0.257,75.233-1.469,101.8c-1.121,24.562-5.225,37.9-8.674,46.778a83.427,83.427,0,0,1-47.811,47.811c-8.877,3.45-22.216,7.554-46.778,8.674-26.56,1.212-34.527,1.469-101.8,1.469s-75.237-.257-101.8-1.469c-24.562-1.121-37.9-5.225-46.778-8.674a78.051,78.051,0,0,1-28.966-18.845,78.053,78.053,0,0,1-18.845-28.966c-3.45-8.877-7.554-22.216-8.674-46.778-1.212-26.564-1.469-34.532-1.469-101.8s0.257-75.233,1.469-101.8c1.121-24.562,5.224-37.9,8.674-46.778A78.052,78.052,0,0,1,78.458,78.458a78.053,78.053,0,0,1,28.966-18.845c8.877-3.45,22.216-7.554,46.778-8.674,26.565-1.212,34.532-1.469,101.8-1.469m0-45.391c-68.418,0-77,.29-103.866,1.516-26.815,1.224-45.127,5.482-61.151,11.71a123.488,123.488,0,0,0-44.62,29.057A123.488,123.488,0,0,0,17.3,90.982C11.077,107.007,6.819,125.319,5.6,152.134,4.369,179,4.079,187.582,4.079,256S4.369,333,5.6,359.866c1.224,26.815,5.482,45.127,11.71,61.151a123.489,123.489,0,0,0,29.057,44.62,123.486,123.486,0,0,0,44.62,29.057c16.025,6.228,34.337,10.486,61.151,11.71,26.87,1.226,35.449,1.516,103.866,1.516s77-.29,103.866-1.516c26.815-1.224,45.127-5.482,61.151-11.71a128.817,128.817,0,0,0,73.677-73.677c6.228-16.025,10.486-34.337,11.71-61.151,1.226-26.87,1.516-35.449,1.516-103.866s-0.29-77-1.516-103.866c-1.224-26.815-5.482-45.127-11.71-61.151a123.486,123.486,0,0,0-29.057-44.62A123.487,123.487,0,0,0,421.018,17.3C404.993,11.077,386.681,6.819,359.866,5.6,333,4.369,324.418,4.079,256,4.079h0Z"/>
+        <path
+          d="M256,126.635A129.365,129.365,0,1,0,385.365,256,129.365,129.365,0,0,0,256,126.635Zm0,213.338A83.973,83.973,0,1,1,339.974,256,83.974,83.974,0,0,1,256,339.973Z"/>
+        <circle cx="390.476" cy="121.524" r="30.23"/>
+      </svg>
+    `
+
+    // writing out the key explicitly for readability the key used to store
+    // the provider instance must be equal to this.id.
+    this.Instagram = new Provider({
+      host: this.opts.host,
+      provider: 'instagram',
+      authProvider: 'instagram'
+    })
+
+    this.files = []
+
+    this.onAuth = this.onAuth.bind(this)
+    // Visual
+    this.render = this.render.bind(this)
+
+    // set default options
+    const defaultOptions = {}
+
+    // merge default options with the ones set by user
+    this.opts = Object.assign({}, defaultOptions, opts)
+  }
+
+  install () {
+    this.view = new View(this)
+    // Set default state for Google Drive
+    this.core.setState({
+      // writing out the key explicitly for readability the key used to store
+      // the plugin state must be equal to this.stateId.
+      instagram: {
+        authenticated: false,
+        files: [],
+        folders: [],
+        directories: [],
+        activeRow: -1,
+        filterInput: ''
+      }
+    })
+
+    const target = this.opts.target
+    const plugin = this
+    this.target = this.mount(target, plugin)
+
+    // catch error here.
+    this[this.id].auth().then(this.onAuth).catch(this.view.handleError)
+    return
+  }
+
+  uninstall () {
+    this.unmount()
+  }
+
+  onAuth (authenticated) {
+    this.view.updateState({authenticated})
+    if (authenticated) {
+      this.view.getFolder('recent')
+    }
+  }
+
+  isFolder (item) {
+    return false
+  }
+
+  getItemData (item) {
+    return item
+  }
+
+  getItemIcon (item) {
+    return html`<img width="100px" src=${item.images.thumbnail.url}/>`
+  }
+
+  getItemSubList (item) {
+    return item.data
+  }
+
+  getItemName (item) {
+    return ''
+  }
+
+  getMimeType (item) {
+    return item.type === 'video' ? 'video/mp4' : 'image/jpeg'
+  }
+
+  getItemId (item) {
+    return item.id
+  }
+
+  getItemRequestPath (item) {
+    return this.getItemId(item)
+  }
+
+  getItemModifiedDate (item) {
+    return item.created_time
+  }
+
+  getItemThumbnailUrl (item) {
+    return item.images.thumbnail.url
+  }
+
+  getNextPagePath () {
+    const { files } = this.core.getState()[this.stateId]
+    return `recent?max_id=${this.getItemId(files[files.length - 1])}`
+  }
+
+  render (state) {
+    return this.view.render(state)
+  }
+}

+ 2 - 2
src/plugins/MetaData.js

@@ -38,7 +38,7 @@ module.exports = class MetaData extends Plugin {
       metaFields: metaFields
     })
 
-    this.core.emitter.on('file-added', this.handleFileAdded)
+    this.core.emitter.on('core:file-added', this.handleFileAdded)
   }
 
   install () {
@@ -46,6 +46,6 @@ module.exports = class MetaData extends Plugin {
   }
 
   uninstall () {
-    this.core.emitter.off('file-added', this.handleFileAdded)
+    this.core.emitter.off('core:file-added', this.handleFileAdded)
   }
 }

+ 7 - 5
src/plugins/Plugin.js

@@ -1,6 +1,7 @@
 const yo = require('yo-yo')
 const nanoraf = require('nanoraf')
 const { findDOMElement } = require('../core/Utils')
+const getFormData = require('get-form-data')
 
 /**
  * Boilerplate that all Plugins share - and should not be used
@@ -23,7 +24,6 @@ module.exports = class Plugin {
 
     this.update = this.update.bind(this)
     this.mount = this.mount.bind(this)
-    // this.focus = this.focus.bind(this)
     this.install = this.install.bind(this)
     this.uninstall = this.uninstall.bind(this)
   }
@@ -59,6 +59,12 @@ module.exports = class Plugin {
     if (targetElement) {
       this.core.log(`Installing ${callerPluginName} to a DOM element`)
 
+      // attempt to extract meta from form element
+      if (this.opts.getMetaFromForm && targetElement.nodeName === 'FORM') {
+        const formMeta = getFormData(targetElement)
+        this.core.setMeta(formMeta)
+      }
+
       // clear everything inside the target container
       if (this.opts.replaceTargetContent) {
         targetElement.innerHTML = ''
@@ -89,10 +95,6 @@ module.exports = class Plugin {
     }
   }
 
-  // focus () {
-  //   return
-  // }
-
   install () {
     return
   }

+ 358 - 28
website/package-lock.json

@@ -3,6 +3,12 @@
   "version": "0.0.1",
   "lockfileVersion": 1,
   "dependencies": {
+    "abab": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.3.tgz",
+      "integrity": "sha1-uB3l9ydOxOdW15fNg08wNkJyTl0=",
+      "optional": true
+    },
     "abbrev": {
       "version": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz",
       "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU="
@@ -13,8 +19,13 @@
     },
     "acorn": {
       "version": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz",
-      "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=",
-      "dev": true
+      "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc="
+    },
+    "acorn-globals": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz",
+      "integrity": "sha1-VbtemGkVB7dFedBRNBMhfDgMVM8=",
+      "optional": true
     },
     "acorn-jsx": {
       "version": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
@@ -77,6 +88,11 @@
       "version": "https://registry.npmjs.org/aproba/-/aproba-1.0.4.tgz",
       "integrity": "sha1-JxNoB3XnYUyLoYbAZdTi5S0QcsA="
     },
+    "archy": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
+      "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA="
+    },
     "are-we-there-yet": {
       "version": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz",
       "integrity": "sha1-gORw6VoIR5T+GJkmLFZnxuiN4bM="
@@ -677,6 +693,11 @@
       "version": "https://registry.npmjs.org/base64id/-/base64id-0.1.0.tgz",
       "integrity": "sha1-As4P3u4M709ACA4ec+g08LG/zj8="
     },
+    "basic-auth": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz",
+      "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ="
+    },
     "batch": {
       "version": "https://registry.npmjs.org/batch/-/batch-0.5.3.tgz",
       "integrity": "sha1-PzQU84AyF0O/wQQvmoP/HVgk1GQ="
@@ -768,6 +789,11 @@
       "integrity": "sha1-B7VMowKGq9Fxig4qgwgD79yb+gQ=",
       "dev": true
     },
+    "browser-fingerprint": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/browser-fingerprint/-/browser-fingerprint-0.0.1.tgz",
+      "integrity": "sha1-jfPNyiW/fVs1QtYVRdcwBT/OYEo="
+    },
     "browser-pack": {
       "version": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.0.1.tgz",
       "integrity": "sha1-d5iHx5LqofZKRqIsjxBRzc2WdV8=",
@@ -912,6 +938,16 @@
       "integrity": "sha1-byIAO6rPADzNKHr+aHIVH93FhXk=",
       "dev": true
     },
+    "bunyan": {
+      "version": "1.8.10",
+      "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.10.tgz",
+      "integrity": "sha1-IB/t0mxwgLYy9BYHL1OpC5pSmBw="
+    },
+    "bytes": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.3.0.tgz",
+      "integrity": "sha1-1baAoWW2IBc5rLYRVCqrwtjOsHA="
+    },
     "caller-path": {
       "version": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
       "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=",
@@ -986,6 +1022,11 @@
       "integrity": "sha1-zEE6++2wNVTCMh+/+TVuyTdQXq8=",
       "dev": true
     },
+    "cheerio": {
+      "version": "0.20.0",
+      "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.20.0.tgz",
+      "integrity": "sha1-XHEPK6uVZTJyhCugHG6mGzVF7DU="
+    },
     "chokidar": {
       "version": "https://registry.npmjs.org/chokidar/-/chokidar-1.6.0.tgz",
       "integrity": "sha1-kMMq1IApAddxPeUy3ChOlqY60Fg="
@@ -1102,6 +1143,11 @@
       "version": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
       "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk="
     },
+    "command-exists": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.2.tgz",
+      "integrity": "sha1-EoGcZPr5VEbsCuB/5sr7brNwiyI="
+    },
     "commander": {
       "version": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
       "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q="
@@ -1118,6 +1164,35 @@
       "version": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
       "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM="
     },
+    "compressible": {
+      "version": "2.0.10",
+      "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.10.tgz",
+      "integrity": "sha1-/tocf3YXkScyspv4zyYlKiC57s0=",
+      "dependencies": {
+        "mime-db": {
+          "version": "1.28.0",
+          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.28.0.tgz",
+          "integrity": "sha1-/t00m+BtKGW3/FfYN8beTxfXrDw="
+        }
+      }
+    },
+    "compression": {
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/compression/-/compression-1.6.2.tgz",
+      "integrity": "sha1-zOsSHsydCcUtetDDNQ6pPd1AK8M=",
+      "dependencies": {
+        "accepts": {
+          "version": "1.3.3",
+          "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz",
+          "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo="
+        },
+        "negotiator": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
+          "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
+        }
+      }
+    },
     "concat-map": {
       "version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
@@ -1175,6 +1250,11 @@
       "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=",
       "dev": true
     },
+    "core-js": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
+      "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
+    },
     "core-util-is": {
       "version": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
       "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
@@ -1227,10 +1307,26 @@
       "version": "https://registry.npmjs.org/csso/-/csso-2.0.0.tgz",
       "integrity": "sha1-F4tDpEYhIhwndWCG9THgL0KQDug="
     },
+    "cssom": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz",
+      "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs="
+    },
+    "cssstyle": {
+      "version": "0.2.37",
+      "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz",
+      "integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=",
+      "optional": true
+    },
     "ctype": {
       "version": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz",
       "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8="
     },
+    "cuid": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/cuid/-/cuid-1.3.8.tgz",
+      "integrity": "sha1-S4deCWm612T37AcGz0T1+wgx9rc="
+    },
     "currently-unhandled": {
       "version": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
       "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o="
@@ -1274,6 +1370,11 @@
       "version": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
       "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
     },
+    "deep-assign": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-2.0.0.tgz",
+      "integrity": "sha1-6+BrHwfwja5ZdiDj3RYi83GhxXI="
+    },
     "deep-extend": {
       "version": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz",
       "integrity": "sha1-7+QRPQgIX05vlod1mBD4B0aeIlM=",
@@ -1281,8 +1382,7 @@
     },
     "deep-is": {
       "version": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
-      "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
-      "dev": true
+      "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
     },
     "defined": {
       "version": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
@@ -1477,6 +1577,12 @@
       "version": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
       "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8="
     },
+    "dtrace-provider": {
+      "version": "0.8.3",
+      "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.3.tgz",
+      "integrity": "sha1-uhv8ZJMoXM/PxqtpzVxh10wqQ78=",
+      "optional": true
+    },
     "duplexer2": {
       "version": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
       "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=",
@@ -1641,6 +1747,20 @@
       "version": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
       "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
     },
+    "escodegen": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz",
+      "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=",
+      "optional": true,
+      "dependencies": {
+        "source-map": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
+          "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=",
+          "optional": true
+        }
+      }
+    },
     "escope": {
       "version": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz",
       "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=",
@@ -1728,10 +1848,15 @@
         }
       }
     },
+    "estraverse": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz",
+      "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=",
+      "optional": true
+    },
     "esutils": {
       "version": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
-      "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
-      "dev": true
+      "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
     },
     "etag": {
       "version": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz",
@@ -1820,8 +1945,7 @@
     },
     "fast-levenshtein": {
       "version": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz",
-      "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk=",
-      "dev": true
+      "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk="
     },
     "faye-websocket": {
       "version": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
@@ -2666,6 +2790,11 @@
       "version": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
       "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ="
     },
+    "hexo": {
+      "version": "3.3.7",
+      "resolved": "https://registry.npmjs.org/hexo/-/hexo-3.3.7.tgz",
+      "integrity": "sha1-HL8Fz9NncJKf7k+J/WRv6LR3z7k="
+    },
     "hexo-browsersync": {
       "version": "https://registry.npmjs.org/hexo-browsersync/-/hexo-browsersync-0.2.0.tgz",
       "integrity": "sha1-HmwRSgbAqWS/ADPdEXWmQwe2eNg=",
@@ -2676,10 +2805,32 @@
         }
       }
     },
+    "hexo-cli": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/hexo-cli/-/hexo-cli-1.0.3.tgz",
+      "integrity": "sha1-ZKoT7aNKAmHcM5Fx4HrRMe9hCsc=",
+      "dependencies": {
+        "hexo-fs": {
+          "version": "0.2.0",
+          "resolved": "https://registry.npmjs.org/hexo-fs/-/hexo-fs-0.2.0.tgz",
+          "integrity": "sha1-U0qSeP35nYL1OsCXuDtOvFe3n+U="
+        },
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+        }
+      }
+    },
     "hexo-deployer-git": {
       "version": "https://registry.npmjs.org/hexo-deployer-git/-/hexo-deployer-git-0.2.0.tgz",
       "integrity": "sha1-8BOKWKs1Nh9MVZBrs3p6c51RL7A="
     },
+    "hexo-front-matter": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/hexo-front-matter/-/hexo-front-matter-0.2.3.tgz",
+      "integrity": "sha1-x8qO9CDqNr2F6ECKLoyb9J76YF4="
+    },
     "hexo-fs": {
       "version": "https://registry.npmjs.org/hexo-fs/-/hexo-fs-0.1.6.tgz",
       "integrity": "sha1-+YDMw7x50PuS7dvYh7wgpWUA0D8="
@@ -2716,6 +2867,16 @@
       "version": "https://registry.npmjs.org/hexo-generator-tag/-/hexo-generator-tag-0.2.0.tgz",
       "integrity": "sha1-xXFYRrtB5X2cIMHWbX2yGhq/emI="
     },
+    "hexo-i18n": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/hexo-i18n/-/hexo-i18n-0.2.1.tgz",
+      "integrity": "sha1-hPFBQyvwnYtVjth4xygWS20c1t4="
+    },
+    "hexo-log": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/hexo-log/-/hexo-log-0.1.3.tgz",
+      "integrity": "sha1-2pE/SFYOfKbCNC5mA16o9Ju+PVg="
+    },
     "hexo-pagination": {
       "version": "https://registry.npmjs.org/hexo-pagination/-/hexo-pagination-0.0.2.tgz",
       "integrity": "sha1-jPRwx9sN5bGKOSanbesZQBXffys="
@@ -2729,13 +2890,29 @@
       "integrity": "sha1-Mv04gNPDl5/XuAFewSGmxE/0n4Q="
     },
     "hexo-renderer-postcss": {
-      "version": "git+https://github.com/arturi/hexo-renderer-postcss.git#afca2bc12f5816067b15a9d24017c70e077b9f0b",
-      "integrity": "sha1-zAV0alKdH7lajj46AY/AQS3GC8o="
+      "version": "git+https://github.com/arturi/hexo-renderer-postcss.git#afca2bc12f5816067b15a9d24017c70e077b9f0b"
     },
     "hexo-renderer-scss": {
       "version": "https://registry.npmjs.org/hexo-renderer-scss/-/hexo-renderer-scss-1.0.2.tgz",
       "integrity": "sha1-oXVhdAEBJy34aGPol0noISY8ppc="
     },
+    "hexo-server": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/hexo-server/-/hexo-server-0.2.0.tgz",
+      "integrity": "sha1-NAtqnK9CJmW8v6Y3pJP1t4JKI4E=",
+      "dependencies": {
+        "mime": {
+          "version": "1.3.6",
+          "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.6.tgz",
+          "integrity": "sha1-WR2E02U6awtKO5343lqoEI5y5eA="
+        },
+        "opn": {
+          "version": "4.0.2",
+          "resolved": "https://registry.npmjs.org/opn/-/opn-4.0.2.tgz",
+          "integrity": "sha1-erwi5kTf9jsKltWrfyeQwPAavJU="
+        }
+      }
+    },
     "hexo-tag-emojis": {
       "version": "https://registry.npmjs.org/hexo-tag-emojis/-/hexo-tag-emojis-2.0.1.tgz",
       "integrity": "sha1-fltFq1ugS0MRa3s2R7XDOHfbHiE="
@@ -2774,6 +2951,18 @@
       "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=",
       "dev": true
     },
+    "htmlparser2": {
+      "version": "3.8.3",
+      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
+      "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=",
+      "dependencies": {
+        "entities": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
+          "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY="
+        }
+      }
+    },
     "http-errors": {
       "version": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz",
       "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI="
@@ -2954,6 +3143,11 @@
       "version": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
       "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8="
     },
+    "is-obj": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+      "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8="
+    },
     "is-path-cwd": {
       "version": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
       "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
@@ -2973,6 +3167,18 @@
       "version": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
       "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4="
     },
+    "is-plain-object": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.3.tgz",
+      "integrity": "sha1-wVvz5LZrYtcu+vKSWEhmPsvGGbY=",
+      "dependencies": {
+        "isobject": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+          "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+        }
+      }
+    },
     "is-posix-bracket": {
       "version": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz",
       "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q="
@@ -3077,6 +3283,12 @@
       "integrity": "sha1-ZQmH2g3XT06/WhE3eiqi0nPpff0=",
       "optional": true
     },
+    "jsdom": {
+      "version": "7.2.2",
+      "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-7.2.2.tgz",
+      "integrity": "sha1-QLQCdwwr2iNGkJa+6Rq2deOx/G4=",
+      "optional": true
+    },
     "jsesc": {
       "version": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
       "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
@@ -3115,8 +3327,7 @@
     },
     "jsonparse": {
       "version": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.2.0.tgz",
-      "integrity": "sha1-XAxWhRBxYOcv50ib3eoLRMK8Z70=",
-      "dev": true
+      "integrity": "sha1-XAxWhRBxYOcv50ib3eoLRMK8Z70="
     },
     "jsonpointer": {
       "version": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz",
@@ -3124,8 +3335,7 @@
     },
     "JSONStream": {
       "version": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.1.4.tgz",
-      "integrity": "sha1-vhGklZOOiC0nd3PRGYbzl0qLo3o=",
-      "dev": true
+      "integrity": "sha1-vhGklZOOiC0nd3PRGYbzl0qLo3o="
     },
     "jsprim": {
       "version": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.0.tgz",
@@ -3167,8 +3377,7 @@
     },
     "levn": {
       "version": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
-      "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
-      "dev": true
+      "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4="
     },
     "lexical-scope": {
       "version": "https://registry.npmjs.org/lexical-scope/-/lexical-scope-1.2.0.tgz",
@@ -3382,6 +3591,11 @@
       "version": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
       "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0="
     },
+    "markdown": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/markdown/-/markdown-0.5.0.tgz",
+      "integrity": "sha1-KCBbVlqK51kt4gdGPWY33BgnIrI="
+    },
     "markdown-table": {
       "version": "https://registry.npmjs.org/markdown-table/-/markdown-table-0.4.0.tgz",
       "integrity": "sha1-iQwsGzv+g/sA5BKbjkz+ZFJw+dE=",
@@ -3513,6 +3727,28 @@
       "version": "https://registry.npmjs.org/moment/-/moment-2.13.0.tgz",
       "integrity": "sha1-JBYtmVIebUD5muaTnoBtITnqrFI="
     },
+    "moment-timezone": {
+      "version": "0.5.13",
+      "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.13.tgz",
+      "integrity": "sha1-mc5cfYJyYusPH3AgRBd/YHRde5A="
+    },
+    "morgan": {
+      "version": "1.8.2",
+      "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.8.2.tgz",
+      "integrity": "sha1-eErHc05KRTqcbm6GgKkyknXItoc=",
+      "dependencies": {
+        "debug": {
+          "version": "2.6.8",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
+          "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw="
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        }
+      }
+    },
     "ms": {
       "version": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
       "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg="
@@ -3539,6 +3775,12 @@
       "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=",
       "dev": true
     },
+    "mv": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
+      "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=",
+      "optional": true
+    },
     "nan": {
       "version": "https://registry.npmjs.org/nan/-/nan-2.4.0.tgz",
       "integrity": "sha1-+zxZ1F/k7/4hXwuJD4rfbrMtIjI="
@@ -3548,6 +3790,12 @@
       "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
       "dev": true
     },
+    "ncp": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
+      "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=",
+      "optional": true
+    },
     "negotiator": {
       "version": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.3.tgz",
       "integrity": "sha1-Jp1cR2gQ7JLtvntsLygxY4T5p+g="
@@ -3561,6 +3809,11 @@
       "integrity": "sha1-P0QkpVuo7VDCVKE4WLJEVPQYJgI=",
       "dev": true
     },
+    "node-fingerprint": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/node-fingerprint/-/node-fingerprint-0.0.2.tgz",
+      "integrity": "sha1-Mcur63GmeufdWn3AQuUcPHWGhQE="
+    },
     "node-gyp": {
       "version": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.4.0.tgz",
       "integrity": "sha1-3aVYOTs+y74kyea4cDxxGUxj+jY=",
@@ -3662,6 +3915,12 @@
       "version": "https://registry.npmjs.org/nunjucks/-/nunjucks-2.4.2.tgz",
       "integrity": "sha1-gnCr5YEY19u4NqwpSR9BPOaOk98="
     },
+    "nwmatcher": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.1.tgz",
+      "integrity": "sha1-eumwew6oBNt+JfBctf5Al9TklJ8=",
+      "optional": true
+    },
     "oauth-sign": {
       "version": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
       "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
@@ -3692,6 +3951,11 @@
       "version": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
       "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc="
     },
+    "on-headers": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz",
+      "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c="
+    },
     "once": {
       "version": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
       "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA="
@@ -3721,8 +3985,7 @@
     },
     "optionator": {
       "version": "https://registry.npmjs.org/optionator/-/optionator-0.8.1.tgz",
-      "integrity": "sha1-4xtJMs3V+4Yqiw0QvGPT7h7H14s=",
-      "dev": true
+      "integrity": "sha1-4xtJMs3V+4Yqiw0QvGPT7h7H14s="
     },
     "options": {
       "version": "https://registry.npmjs.org/options/-/options-0.0.6.tgz",
@@ -3814,6 +4077,12 @@
       "integrity": "sha1-qeoXAuQZf+J4pQjn6z4c4zvLQHQ=",
       "dev": true
     },
+    "parse5": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz",
+      "integrity": "sha1-m387DeMr543CQBsXVzzK8Pb1nZQ=",
+      "optional": true
+    },
     "parsejson": {
       "version": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.1.tgz",
       "integrity": "sha1-mxDGwNglq1ieaFFTgm3go7oni8w="
@@ -4038,8 +4307,7 @@
     },
     "prelude-ls": {
       "version": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
-      "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
-      "dev": true
+      "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
     },
     "prepend-http": {
       "version": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
@@ -4049,6 +4317,11 @@
       "version": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",
       "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks="
     },
+    "pretty-hrtime": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
+      "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE="
+    },
     "private": {
       "version": "https://registry.npmjs.org/private/-/private-0.1.6.tgz",
       "integrity": "sha1-VcapdtD5uvuZJIUTUP5HubX7t8E=",
@@ -4384,6 +4657,12 @@
       "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=",
       "dev": true
     },
+    "safe-json-stringify": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz",
+      "integrity": "sha1-gaCY9Efku8P/MxKiQ1IbwGDvWRE=",
+      "optional": true
+    },
     "sass-graph": {
       "version": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.1.2.tgz",
       "integrity": "sha1-llEEviPoEDy35fcQ32WTWzF9pXs=",
@@ -4764,10 +5043,21 @@
       "version": "https://registry.npmjs.org/swig/-/swig-1.4.2.tgz",
       "integrity": "sha1-QIXKBFM2kQS11IPihBs5t64aq6U="
     },
+    "swig-extras": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/swig-extras/-/swig-extras-0.0.1.tgz",
+      "integrity": "sha1-tQP+3jcqucJMasaMr2VrzvGHIyg="
+    },
     "symbol": {
       "version": "https://registry.npmjs.org/symbol/-/symbol-0.2.3.tgz",
       "integrity": "sha1-O5hzuKkB5Hxu/iFSajrDcu8ou8c="
     },
+    "symbol-tree": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz",
+      "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=",
+      "optional": true
+    },
     "syntax-error": {
       "version": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.1.6.tgz",
       "integrity": "sha1-tFSXBtOGzBwdx8JCPxhXm2yt5xA=",
@@ -4784,8 +5074,7 @@
     },
     "text-table": {
       "version": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
-      "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
-      "dev": true
+      "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ="
     },
     "tfunk": {
       "version": "https://registry.npmjs.org/tfunk/-/tfunk-3.0.2.tgz",
@@ -4793,8 +5082,7 @@
     },
     "through": {
       "version": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
-      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
-      "dev": true
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
     },
     "through2": {
       "version": "https://registry.npmjs.org/through2/-/through2-2.0.1.tgz",
@@ -4818,6 +5106,11 @@
       "integrity": "sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=",
       "dev": true
     },
+    "tildify": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz",
+      "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo="
+    },
     "timers-browserify": {
       "version": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz",
       "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=",
@@ -4835,6 +5128,11 @@
         }
       }
     },
+    "titlecase": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/titlecase/-/titlecase-1.1.2.tgz",
+      "integrity": "sha1-eBE9EQgIa4MmMxoyR96o9aSeqFM="
+    },
     "to-absolute-glob": {
       "version": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz",
       "integrity": "sha1-HN+kcqnvUMI57maZm2YsoOs5k38=",
@@ -4863,6 +5161,12 @@
       "version": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.1.tgz",
       "integrity": "sha1-mcd9+7fYBCSeiimdTLD9gf7wg/0="
     },
+    "tr46": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+      "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
+      "optional": true
+    },
     "trim": {
       "version": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
       "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=",
@@ -4913,8 +5217,7 @@
     },
     "type-check": {
       "version": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
-      "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
-      "dev": true
+      "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I="
     },
     "type-is": {
       "version": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz",
@@ -5095,6 +5398,11 @@
       "version": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
       "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w="
     },
+    "vary": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz",
+      "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc="
+    },
     "vendors": {
       "version": "https://registry.npmjs.org/vendors/-/vendors-1.0.1.tgz",
       "integrity": "sha1-N61zyO5Bf7PVgOeFMSMH0nSEfyI="
@@ -5165,11 +5473,22 @@
       "integrity": "sha1-0bFPOdLiy0q4xAmPdW/ksWTkc9Q=",
       "dev": true
     },
+    "warehouse": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/warehouse/-/warehouse-2.2.0.tgz",
+      "integrity": "sha1-XQnWSUKZK+Zn2PfIagnCuK6gQGI="
+    },
     "watchify": {
       "version": "https://registry.npmjs.org/watchify/-/watchify-3.7.0.tgz",
       "integrity": "sha1-7i8sXIw3MSMD+Zi4GLKzRQ7v5kg=",
       "dev": true
     },
+    "webidl-conversions": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-2.0.1.tgz",
+      "integrity": "sha1-O/glj30xjHRDw28uFpQCoaZwNQY=",
+      "optional": true
+    },
     "websocket-driver": {
       "version": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz",
       "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=",
@@ -5190,6 +5509,12 @@
         }
       }
     },
+    "whatwg-url-compat": {
+      "version": "0.6.5",
+      "resolved": "https://registry.npmjs.org/whatwg-url-compat/-/whatwg-url-compat-0.6.5.tgz",
+      "integrity": "sha1-AImBEa9om7CXVBzVpFymyHmERb8=",
+      "optional": true
+    },
     "whet.extend": {
       "version": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz",
       "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE="
@@ -5212,8 +5537,7 @@
     },
     "wordwrap": {
       "version": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
-      "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
-      "dev": true
+      "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
     },
     "wrap-ansi": {
       "version": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.0.0.tgz",
@@ -5241,6 +5565,12 @@
       "version": "https://registry.npmjs.org/ws/-/ws-1.0.1.tgz",
       "integrity": "sha1-fQsqLljN3YGQOcKcneZQReGzEOk="
     },
+    "xml-name-validator": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz",
+      "integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=",
+      "optional": true
+    },
     "xmlhttprequest-ssl": {
       "version": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.1.tgz",
       "integrity": "sha1-O3dB/qSoZnWXbpCNKW1ERZYfqmc="

+ 4 - 4
website/package.json

@@ -3,12 +3,12 @@
   "version": "0.0.1",
   "private": true,
   "hexo": {
-    "version": "3.2.2"
+    "version": "3.3.7"
   },
   "dependencies": {
     "autoprefixer": "6.4.0",
     "cssnano": "3.7.3",
-    "hexo": "3.2.2",
+    "hexo": "^3.3.7",
     "hexo-browsersync": "0.2.0",
     "hexo-deployer-git": "0.2.0",
     "hexo-generator-archive": "0.1.4",
@@ -20,7 +20,7 @@
     "hexo-renderer-marked": "0.2.11",
     "hexo-renderer-postcss": "https://github.com/arturi/hexo-renderer-postcss#afca2bc12f5816067b15a9d24017c70e077b9f0b",
     "hexo-renderer-scss": "1.0.2",
-    "hexo-server": "0.2.0",
+    "hexo-server": "^0.2.1",
     "hexo-tag-emojis": "2.0.1",
     "hexo-util": "0.6.0",
     "js-yaml": "3.6.1",
@@ -44,4 +44,4 @@
     "remark": "5.0.1",
     "watchify": "3.7.0"
   }
-}
+}

+ 5 - 1
website/src/examples/dashboard/app.es6

@@ -2,10 +2,10 @@ const Uppy = require('uppy/lib/core')
 const Dashboard = require('uppy/lib/plugins/Dashboard')
 const GoogleDrive = require('uppy/lib/plugins/GoogleDrive')
 const Dropbox = require('uppy/lib/plugins/Dropbox')
+const Instagram = require('uppy/lib/plugins/Instagram')
 const Webcam = require('uppy/lib/plugins/Webcam')
 const Tus10 = require('uppy/lib/plugins/Tus10')
 const MetaData = require('uppy/lib/plugins/MetaData')
-const Informer = require('uppy/lib/plugins/Informer')
 
 const UPPY_SERVER = require('../env')
 
@@ -35,6 +35,10 @@ function uppyInit () {
     uppy.use(Dropbox, {target: Dashboard, host: UPPY_SERVER})
   }
 
+  if (opts.Instagram) {
+    uppy.use(Instagram, {target: Dashboard, host: UPPY_SERVER})
+  }
+
   if (opts.Webcam) {
     uppy.use(Webcam, {target: Dashboard})
   }

+ 3 - 0
website/src/examples/dashboard/app.html

@@ -7,6 +7,7 @@
   <input type="checkbox" id="opts-Webcam" checked/><label for="opts-Webcam">Webcam</label>
   <input type="checkbox" id="opts-GoogleDrive" checked/><label for="opts-GoogleDrive">Google Drive</label>
   <input type="checkbox" id="opts-Dropbox" checked/><label for="opts-Dropbox">Dropbox</label>
+  <input type="checkbox" id="opts-Instagram" checked/><label for="opts-Instagram">Instagram</label>
 </div>
 
 <!-- Modal trigger -->
@@ -24,6 +25,7 @@
     Webcam: document.querySelector('#opts-Webcam'),
     GoogleDrive: document.querySelector('#opts-GoogleDrive'),
     Dropbox: document.querySelector('#opts-Dropbox'),
+    Instagram: document.querySelector('#opts-Instagram'),
     autoProceed: document.querySelector('#opts-autoProceed')
   }
 
@@ -31,6 +33,7 @@
     DashboardInline: false,
     Webcam: true,
     GoogleDrive: true,
+    Instagram: true,
     Dropbox: false,
     autoProceed: false
   }