瀏覽代碼

Create a file upload to CouchDB

Markus Ochel 13 年之前
父節點
當前提交
4c5bf142b6

+ 15 - 2
admin/controllers/essays.coffee

@@ -2,7 +2,8 @@ Spine       = require('spine/core')
 # $           = Spine.$
 templates   = require('duality/templates')
 
-MultiSelect = require('controllers/ui/multi-select')
+MultiSelectUI = require('controllers/ui/multi-select')
+FileUploadUI  = require('controllers/ui/file-upload')
 
 Essay       = require('models/essay')
 Author      = require('models/author')
@@ -22,6 +23,7 @@ class EssayForm extends Spine.Controller
     'select[name=author_id]':  'formAuthorId'
     'select[name=sponsor_id]': 'formSponsorId'
     '.collections-list':       'collectionsList'
+    '.files-list':             'filesList'
     '.save-button':            'saveButton'
     '.cancel-button':          'cancelButton'
 
@@ -51,6 +53,7 @@ class EssayForm extends Spine.Controller
       @item = {}
 
     @item.collections ?= []
+    @item._attachments ?= {}
     
     @item.sites = Site.all().sort(Site.nameSort)
     @item.sponsors = Sponsor.all().sort(Sponsor.nameSort)
@@ -66,6 +69,12 @@ class EssayForm extends Spine.Controller
       @formSite.val(@stack.stack.filterBox.siteId)
     @siteChange()
 
+    # Files upload area
+    @fileUpload = new FileUploadUI
+      docId: @item.id
+      attachments: @item._attachments
+    @filesList.find('.upload-ui').html @fileUpload.el
+
   siteChange: ->
     $siteSelected = @formSite.parents('.field').find('.site-selected')
     site = Site.exists(@formSite.val())
@@ -86,7 +95,7 @@ class EssayForm extends Spine.Controller
   
   makeCollectionsList: (site) ->
     collections = Collection.findAllByAttribute('site', site.id)
-    @collectionSelect = new MultiSelect
+    @collectionSelect = new MultiSelectUI
       items: collections
       selectedItems: (c.id for c in @item.collections)
       valueFields: ['id','slug']
@@ -104,6 +113,10 @@ class EssayForm extends Spine.Controller
 
     @item.collections = @collectionSelect.selected()
 
+    # TODO: Take care of files and photo
+    @item._attachments = @fileUpload.attachments
+    @item.photo = null
+
     # Take care of some dates if need be
     try
       if @item.published_at

+ 79 - 0
admin/controllers/ui/file-upload.coffee

@@ -0,0 +1,79 @@
+Spine     = require('spine/core')
+$         = Spine.$
+base64    = require('base64')
+
+
+class FileUploadUI extends Spine.Controller
+  tag: 'div'
+  className: 'ui-file-upload'
+  fieldName: 'file_upload'
+  dropzoneText: 'drop or click'
+  attachments: {}
+  docId: null
+
+  constructor: ->
+    super
+    @render()
+
+  render: ->
+    @dropzone = $("<div class=\"dropzone\">#{@dropzoneText}</div>")
+    @fileInput = $("<input type=\"file\" name=\"#{@fieldName}\" style=\"display: none;\"/>")
+    @fileName = $("<div class=\"filename\"/>")
+    @filesList = $("<ul class=\"list\"/>")
+    @el.append @dropzone, @fileInput, @fileName, @filesList
+    @setupList()
+    @setupEvents()
+
+  setupList: ->
+    if @docId
+      names = (prop for prop of @attachments)
+      for name in names
+        file = @attachments[name]
+        @filesList.append "<li><img src=\"/file/#{@docId}/#{name}\"></li>"
+
+  setupEvents: ->
+    @dropzone.on 'dragenter dragover', (e) ->
+      e.originalEvent.preventDefault()
+      e.originalEvent.stopPropagation()
+      e.originalEvent.dataTransfer.dropEffect = 'copy'
+
+    @dropzone.on 'drop', (e) =>
+      e.originalEvent.preventDefault()
+      e.originalEvent.stopPropagation()
+      @prepareFiles e.originalEvent.dataTransfer.files
+
+    @dropzone.on 'click', (e) =>
+      e.preventDefault()
+      @fileInput.click()
+
+    @fileInput.on 'change', (e) =>
+      @prepareFiles e.target.files
+
+  prepareFiles: (files) =>
+    if files.length
+      file = files[0]
+      name = encodeURIComponent(file.name)
+      type = file.type
+      reader = new FileReader
+      
+      reader.addEventListener 'load', (e) =>
+        dataURL = e.target.result
+        # Since the result is a data URL and already base64 encoded
+        # then just remove the first meta info and we get the file
+        # data the we need to save into _attachments in CouchDB
+        result = dataURL.replace(/^data:.*;base64,/, "")
+        @attachments[name] =
+          content_type: type
+          data: result
+        @fileName.html "#{name}"
+      
+      reader.addEventListener 'loadstart', (e) => return
+
+      reader.addEventListener 'progress', (e) => return
+      
+      reader.addEventListener 'error', (e) => return
+      
+      reader.readAsDataURL file
+
+
+module.exports = FileUploadUI

+ 2 - 2
admin/controllers/ui/multi-select.coffee

@@ -1,7 +1,7 @@
 Spine     = require('spine/core')
 $         = Spine.$
 
-class MultiSelect extends Spine.Controller
+class MultiSelectUI extends Spine.Controller
   tag: 'ul'
   className: 'ui-multi-select'
   tagClass: ''
@@ -63,4 +63,4 @@ class MultiSelect extends Spine.Controller
     return items
 
 
-module.exports = MultiSelect
+module.exports = MultiSelectUI

+ 1 - 0
admin/kanso.json

@@ -21,6 +21,7 @@
     "modules": null,
     "settings": null,
     "attachments": null,
+    "base64": null,
     "properties": null,
     "db": null,
     "coffee-script-precompiler": null,

+ 1 - 1
admin/models/essay.coffee

@@ -4,7 +4,7 @@ require('lib/spine-couch-ajax')
 BaseModel = require('models/base')
 
 class Essay extends BaseModel
-  @configure "Essay", "site", "slug", "title", "intro", "body", "published", "published_at", "updated_at", "author_id", "sponsor_id", "sponsor_start", "sponsor_end", "collections"
+  @configure "Essay", "site", "slug", "title", "intro", "body", "photo", "published", "published_at", "updated_at", "author_id", "sponsor_id", "sponsor_start", "sponsor_end", "collections", "_attachments"
   
   @extend Spine.Model.CouchAjax
   

+ 3 - 0
admin/server/rewrites.coffee

@@ -46,6 +46,9 @@ module.exports = [
     }
   }
 
+  # File attachments paths
+  { from: '/file/:id/:filename', to: '../../:id/:filename' }
+
   # show color page
   { from: "/_colors", to: "_show/colors" }
 

+ 34 - 1
admin/static/css/theme.styl

@@ -184,7 +184,7 @@ span.label
         float: left
         width: 65%
         min-height: 100%
-        padding: 20px 3%
+        padding: 20px 3% 40px 3%
         background: #fff
 
       .sidebar
@@ -394,3 +394,36 @@ ul.ui-multi-select
       font-weight: $boldFont
       border-color: $primaryColor
       outline: 2px solid rgba($primaryColor, 0.5)
+
+.ui-file-upload
+  
+  .dropzone
+    padding: 20px
+    border: 4px dashed #fafafa
+    color: #fafafa
+    font-size: 2em
+    text-align: center
+    cursor: default
+
+  .filename
+    background: #fafafa
+    padding: 0 5px
+    margin-top: -6px
+    line-height: 1.7em
+    text-align: center
+    overflow: hidden
+
+  ul.list
+    list-style-type: none
+    margin: 0
+    padding: 0
+
+    li
+      position: relative
+      float: left
+      width: 49%
+      margin-right: 1%
+      overflow: hidden
+
+      img
+        width: 100%

+ 5 - 0
admin/templates/essay-form.html

@@ -63,6 +63,11 @@
 
     <div class="top-spacer"></div>
 
+    <h3 class="heading">Files</h3>
+    <div class="field files-list">
+      <div class="upload-ui"></div>
+    </div>
+
     <h3 class="heading">Sponsorship</h3>
     <div class="field">
       <div class="field-left">

+ 8 - 4
site/server/rewrites.coffee

@@ -35,13 +35,13 @@ module.exports = [
   }
 
   # Collection's page - list of essays
-  # `:cid` is the collection's _id
+  # `:slug` is the collection's slug
   {
-    from: '/render/:site/collection/:cid',
+    from: '/render/:site/collection/:slug',
     to: '_list/collection/essays_by_collection',
     query: {
-      startkey: [':site', ':cid', {}],
-      endkey: [':site', ':cid'],
+      startkey: [':site', ':slug', {}],
+      endkey: [':site', ':slug'],
       descending: 'true',
       include_docs: 'true'
     }
@@ -59,6 +59,10 @@ module.exports = [
     }
   }
 
+  # File attachments paths
+  { from: '/file/:id/:filename', to: '../../:id/:filename' }
+  { from: '/render/:site/file/:id/:filename', to: '../../:id/:filename' }
+
   # Redirected old URLs
   # moved '/posts/some-old-path', '/some-new-path'