Răsfoiți Sursa

Many changes to list

Markus Ochel 13 ani în urmă
părinte
comite
955fd139f7
46 a modificat fișierele cu 451 adăugiri și 169 ștergeri
  1. 4 0
      README.md
  2. 1 2
      admin/controllers/authors.coffee
  3. 21 2
      admin/controllers/blocks.coffee
  4. 1 2
      admin/controllers/contacts.coffee
  5. 12 17
      admin/controllers/essays.coffee
  6. 4 5
      admin/controllers/index.coffee
  7. 3 4
      admin/controllers/sites.coffee
  8. 16 2
      admin/controllers/sponsors.coffee
  9. 3 2
      admin/controllers/ui/multi-select.coffee
  10. 0 0
      admin/lib/sisyphus.js
  11. 14 5
      admin/lib/spine-couch-ajax.coffee
  12. 1 1
      admin/models/author.coffee
  13. 1 1
      admin/models/base.coffee
  14. 14 2
      admin/models/block.coffee
  15. 10 1
      admin/models/collection.coffee
  16. 1 1
      admin/models/contact.coffee
  17. 11 2
      admin/models/essay.coffee
  18. 7 2
      admin/models/site.coffee
  19. 2 2
      admin/models/sponsor.coffee
  20. 4 4
      admin/server/rewrites.coffee
  21. 3 3
      admin/server/validate.coffee
  22. 1 2
      admin/server/views.coffee
  23. 1 1
      admin/static/css/theme.styl
  24. 1 1
      admin/templates/author-form.html
  25. 3 0
      admin/templates/base.html
  26. 16 1
      admin/templates/block-form.html
  27. 1 1
      admin/templates/collection-form.html
  28. 1 1
      admin/templates/contact-form.html
  29. 1 1
      admin/templates/essay-form.html
  30. 10 1
      admin/templates/site-form.html
  31. 7 2
      admin/templates/sponsor-form.html
  32. 4 0
      site/kanso.json
  33. 44 1
      site/lib/app.coffee
  34. 3 0
      site/lib/utils.coffee
  35. 28 21
      site/server/lists.coffee
  36. 32 13
      site/server/rewrites.coffee
  37. 16 1
      site/server/shows.coffee
  38. 5 5
      site/server/views.coffee
  39. 11 3
      site/static/css/setup-theme.styl
  40. 68 31
      site/static/css/theme.styl
  41. 15 10
      site/templates/base.html
  42. 2 2
      site/templates/collection.html
  43. 1 1
      site/templates/collections.html
  44. 11 7
      site/templates/essay.html
  45. 23 0
      site/templates/feed.xml
  46. 13 3
      site/templates/home.html

+ 4 - 0
README.md

@@ -45,3 +45,7 @@ Donation URL
 ------------
 
     https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=N62XWCYGBQRQY
+
+Temp Stuff
+----------
+

+ 1 - 2
admin/controllers/authors.coffee

@@ -133,8 +133,7 @@ class AuthorList extends Spine.Controller
   render: =>
     context = 
       authors: Author.filter(@filterObj).sort(Author.nameSort)
-    @el.html templates.render('authors.html', {}, context)
-    @
+    @html templates.render('authors.html', {}, context)
 
   filter: (@filterObj) =>
     @render()

+ 21 - 2
admin/controllers/blocks.coffee

@@ -2,6 +2,8 @@ Spine       = require('spine/core')
 # $           = Spine.$
 templates   = require('duality/templates')
 
+FileUploadUI  = require('controllers/ui/file-upload')
+
 Block       = require('models/block')
 Site        = require('models/site')
 
@@ -14,6 +16,8 @@ class BlockForm extends Spine.Controller
     '.error-message':          'errorMessage'
     'form':                    'form'
     'select[name=site]':       'formSite'
+    'input[name=enabled]':     'formEnabled'
+    '.upload-ui':              'fileUploadContainer'
     '.save-button':            'saveButton'
     '.cancel-button':          'cancelButton'
 
@@ -41,6 +45,8 @@ class BlockForm extends Spine.Controller
     else
       @title = 'New Block'
       @item = {}
+
+    @item._attachments ?= {}
     
     @item.sites = Site.all().sort(Site.nameSort)
     @html templates.render('block-form.html', {}, @item)
@@ -50,8 +56,10 @@ class BlockForm extends Spine.Controller
     # Set few initial form values
     if @editing
       @formSite.val(@item.site)
+      @formEnabled.prop('checked', @item.enabled)
     else
       @formSite.val(@stack.stack.filterBox.siteId)
+      @formEnabled.prop('checked', true)
     @siteChange()
 
   siteChange: ->
@@ -62,12 +70,24 @@ class BlockForm extends Spine.Controller
     else
       $siteSelected.html ""
 
+    # Files upload area
+    @fileUploadUI = new FileUploadUI
+      docId: @item.id
+      selectedFile: @item.photo
+      attachments: @item._attachments
+    @fileUploadContainer.html @fileUploadUI.el
+
   save: (e) ->
     e.preventDefault()
     if @editing
       @item.fromForm(@form)
     else
       @item = new Block().fromForm(@form)
+
+    @item._attachments = @fileUploadUI.attachments
+
+    # Take care of some boolean checkboxes
+    @item.enabled = @formEnabled.is(':checked')
     
     # Save the item and make sure it validates
     if @item.save()
@@ -117,8 +137,7 @@ class BlockList extends Spine.Controller
   render: =>
     context = 
       blocks: Block.filter(@filterObj).sort(Block.titleSort)
-    @el.html templates.render('blocks.html', {}, context)
-    @
+    @html templates.render('blocks.html', {}, context)
 
   filter: (@filterObj) =>
     @render()

+ 1 - 2
admin/controllers/contacts.coffee

@@ -98,8 +98,7 @@ class ContactList extends Spine.Controller
   render: =>
     context = 
       contacts: Contact.filter(@filterObj).sort(Contact.nameSort)
-    @el.html templates.render('contacts.html', {}, context)
-    @
+    @html templates.render('contacts.html', {}, context)
 
   filter: (@filterObj) =>
     @render()

+ 12 - 17
admin/controllers/essays.coffee

@@ -90,18 +90,6 @@ class EssayForm extends Spine.Controller
       attachments: @item._attachments
     @fileUploadContainer.html @fileUploadUI.el
 
-    # Use Sisyphus to auto save forms to LocalStorage
-    @form.sisyphus
-      customKeyPrefix: 'CUSTOM_PREFIX'
-      timeout: 0
-      autoRelease: true
-      name: 'ABC123XXX' #if @editing then @item.id else 'new-essay'
-      # onSave: -> console.log "Saved to local storage"
-      # onBeforeRestore: -> alert "About to restore from local storage"
-      # onRestore: -> alert "Restored from local storage"
-      # onRelease: -> console.log "Local storage was released"
-      excludeFields: []
-
   siteChange: ->
     $siteSelected = @formSite.parents('.field').find('.site-selected')
     site = Site.exists(@formSite.val())
@@ -152,9 +140,12 @@ class EssayForm extends Spine.Controller
         type: 'GET'
         url: url
         success: (res) =>
-          $content = $(res.responseText).find('.entry')
-          @log res
-          @log $content
+          $html = $(res.responseText)
+          $title = $html.find('.post > h2:first > a')
+          $author = $html.find('.post .entry-author > a:first')
+          $date = $html.find('.post .entry-date > .published')
+          $content = $html.find('.post .entry:first')
+          $image = $content.find('img:first')
           if $content
             $content.find('.addthis_toolbox, .author-bio').remove()
             options =
@@ -175,6 +166,11 @@ class EssayForm extends Spine.Controller
             markdown = reMarker.render($content.html())
             @formBody.val(markdown)
 
+          @formTitle.val($title.text()) if $title
+          @form.find('input[name=slug]').val($title.attr('href').replace("http://#{@formSite.val()}", '')) if $title
+          @formAuthorId.val($author.text()) if $author
+          @form.find('input[name=published_at]').val($date.text()) if $date
+
   save: (e) ->
     e.preventDefault()
     if @editing
@@ -236,8 +232,7 @@ class EssayList extends Spine.Controller
   render: =>
     context = 
       essays: Essay.filter(@filterObj).sort(Essay.titleSort)
-    @el.html templates.render('essays.html', {}, context)
-    @
+    @html templates.render('essays.html', {}, context)
 
   filter: (@filterObj) =>
     @render()

+ 4 - 5
admin/controllers/index.coffee

@@ -2,7 +2,6 @@ Spine       = require('spine/core')
 require('spine/route')
 require('spine/manager')
 require('lib/fastclick')
-require('lib/sisyphus')
 
 templates   = require('duality/templates')
 session     = require('session')
@@ -28,16 +27,16 @@ class App extends Spine.Controller
 
   checkSession: ->
     session.info (err, info) =>
-      if '_admin' in info.userCtx.roles
+      if 'manager' in info.userCtx.roles
         @startApp()
       else
-        username = 'admin'
-        pass = 'couchaxs'
+        username = 'evita'
+        pass = 'n3wst@rt'
         session.login username, pass, (err, resp) =>
           if err
             alert "Error logging in as #{username}: #{err}"
           else
-            if '_admin' in resp.roles
+            if 'manager' in resp.roles
               @startApp()
             else
               alert "User #{username} does not have permission"

+ 3 - 4
admin/controllers/sites.coffee

@@ -13,7 +13,7 @@ class SiteForm extends Spine.Controller
     '.item-title':        'itemTitle'
     '.error-message':     'errorMessage'
     'form':               'form'
-    'select[name=_id]':   'formSiteId'
+    'input[name=_id]':    'formSiteId'
     'select[name=theme]': 'formTheme'
     '.save-button':       'saveButton'
     '.cancel-button':     'cancelButton'
@@ -50,7 +50,7 @@ class SiteForm extends Spine.Controller
     # Set few initial form values
     if @editing
       @formTheme.val(@item.theme)
-      @formSiteId.attr('readonly', 'readonly')
+      @formSiteId.prop('readonly', true)
 
   save: (e) ->
     e.preventDefault()
@@ -107,8 +107,7 @@ class SiteList extends Spine.Controller
   render: =>
     context = 
       sites: Site.filter(@filterObj).sort(Site.nameSort)
-    @el.html templates.render('sites.html', {}, context)
-    @
+    @html templates.render('sites.html', {}, context)
 
   filter: (@filterObj) =>
     @render()

+ 16 - 2
admin/controllers/sponsors.coffee

@@ -2,6 +2,8 @@ Spine       = require('spine/core')
 # $           = Spine.$
 templates   = require('duality/templates')
 
+FileUploadUI  = require('controllers/ui/file-upload')
+
 Sponsor     = require('models/sponsor')
 Contact     = require('models/contact')
 
@@ -15,6 +17,7 @@ class SponsorForm extends Spine.Controller
     'form':                    'form'
     'select[name=contact_id]': 'formContactId'
     'select[name=format]':     'formFormat'
+    '.upload-ui':              'fileUploadContainer'
     '.save-button':            'saveButton'
     '.cancel-button':          'cancelButton'
 
@@ -41,6 +44,8 @@ class SponsorForm extends Spine.Controller
     else
       @title = 'New Sponsor'
       @item = {}
+
+    @item._attachments ?= {}
     
     @item.contacts = Contact.all().sort(Contact.nameSort)
     @html templates.render('sponsor-form.html', {}, @item)
@@ -52,12 +57,22 @@ class SponsorForm extends Spine.Controller
       @formContactId.val(@item.contact_id)
       @formFormat.val(@item.format)
 
+    # Files upload area
+    @fileUploadUI = new FileUploadUI
+      docId: @item.id
+      selectedFieldName: 'image'
+      selectedFile: @item.image
+      attachments: @item._attachments
+    @fileUploadContainer.html @fileUploadUI.el
+
   save: (e) ->
     e.preventDefault()
     if @editing
       @item.fromForm(@form)
     else
       @item = new Sponsor().fromForm(@form)
+
+    @item._attachments = @fileUploadUI.attachments
     
     # Save the item and make sure it validates
     if @item.save()
@@ -107,8 +122,7 @@ class SponsorList extends Spine.Controller
   render: =>
     context = 
       sponsors: Sponsor.filter(@filterObj).sort(Sponsor.nameSort)
-    @el.html templates.render('sponsors.html', {}, context)
-    @
+    @html templates.render('sponsors.html', {}, context)
 
   filter: (@filterObj) =>
     @render()

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

@@ -48,7 +48,7 @@ class MultiSelectUI extends Spine.Controller
       # add the created option to the list
       @el.append $option
 
-    setTimeout(@scrollToSelected, 1000) if @jumpToFirst
+    @delay(@scrollToSelected, 1000) if @jumpToFirst
     @
 
   selected: =>
@@ -66,7 +66,8 @@ class MultiSelectUI extends Spine.Controller
     return items
 
   scrollToSelected: =>
-    @el.scrollTop(@el.find('.selected:first').position().top)
+    position = @el.find('.selected:first')?.position()
+    @el.scrollTop(position?.top - 12) if position
 
 
 module.exports = MultiSelectUI

Fișier diff suprimat deoarece este prea mare
+ 0 - 0
admin/lib/sisyphus.js


+ 14 - 5
admin/lib/spine-couch-ajax.coffee

@@ -25,8 +25,7 @@ Spine.Model.extend
     if typeof objects is 'string'
       objects = JSON.parse(objects)
     if Spine.isArray(objects)
-      for value in objects
-        make_object(value)
+      (make_object(value) for value in objects)
     else
       make_object(objects)
 
@@ -39,9 +38,16 @@ CouchAjax =
   requests: []
 
   disable: (callback) ->
-    @enabled = false
-    do callback
-    @enabled = true
+    if @enabled
+      @enabled = false
+      try
+        do callback
+      catch e
+        throw e
+      finally
+        @enabled = true
+    else
+      do callback
 
   requestNext: ->
     next = @requests.shift()
@@ -206,10 +212,13 @@ Model.CouchAjax =
     @extend Extend
     @include Include
     
+  # Private
+
   ajaxFetch: ->
     @ajax().fetch(arguments...)
     
   ajaxChange: (record, type, options = {}) ->
+    return if options.ajax is false
     record.ajax()[type](options.ajax, options)
     
 Model.CouchAjax.Methods = 

+ 1 - 1
admin/models/author.coffee

@@ -8,7 +8,7 @@ BaseModel = require('models/base')
 class Author extends BaseModel
   @configure "Author", "site", "name", "email", "bio", "links", "photo"
   
-  @extend Spine.Model.CouchAjax
+  @extend @CouchAjax
   
   @queryOn: ['name','email']
     

+ 1 - 1
admin/models/base.coffee

@@ -1,6 +1,6 @@
 Spine = require('spine/core')
 
-class BaseModel extends Spine.Model 
+class BaseModel extends Spine.Model
 
   @nameSort: (a, b) ->
     if a.name > b.name then 1 else -1

+ 14 - 2
admin/models/block.coffee

@@ -6,9 +6,9 @@ utils = require('lib/utils')
 BaseModel = require('models/base')
 
 class Block extends BaseModel
-  @configure "Block", "site", "code", "name", "content"
+  @configure "Block", "site", "code", "name", "content", "photo", "enabled", "_attachments"
   
-  @extend Spine.Model.CouchAjax
+  @extend @CouchAjax
   
   @queryOn: ['name','code']
     
@@ -20,6 +20,18 @@ class Block extends BaseModel
     return 'Name is required' unless @name
     return 'Content is required' unless @content
 
+    # Validate the `code` to be unique within site
+    found = Block.select (block) =>
+      matched = block.site is @site and block.code is @code
+      if @isNew()
+        matched
+      else
+        block.id isnt @id and matched
+    return 'Code has been already used for this site.' if found.length
+
+    # Convert some boolean properties
+    @enabled = Boolean(@enabled)
+
     # Some content transformation
     @content = utils.cleanContent @content
 

+ 10 - 1
admin/models/collection.coffee

@@ -9,7 +9,7 @@ BaseModel = require('models/base')
 class Collection extends BaseModel
   @configure "Collection", "site", "slug", "name", "intro", "photo", "pinned", "updated_at", "sponsor_id", "sponsor_start", "sponsor_end", "sponsors_history", "_attachments"
 
-  @extend Spine.Model.CouchAjax
+  @extend @CouchAjax
 
   @queryOn: ['name','slug']
     
@@ -19,6 +19,15 @@ class Collection extends BaseModel
     return 'Site is required' unless @site
     return 'Slug is required' unless @slug
     return 'Name is required' unless @name
+
+    # Validate the `slug` to be unique within site
+    found = Collection.select (collection) =>
+      matched = collection.site is @site and collection.slug is @slug
+      if @isNew()
+        matched
+      else
+        collection.id isnt @id and matched
+    return 'Slug has been already used for this site.' if found.length
     
     # Take care of some dates
     updated_at = moment(@updated_at)

+ 1 - 1
admin/models/contact.coffee

@@ -8,7 +8,7 @@ BaseModel = require('models/base')
 class Contact extends BaseModel
   @configure "Contact", "name", "email", "note"
   
-  @extend Spine.Model.CouchAjax
+  @extend @CouchAjax
   
   @queryOn: ['name','email']
     

+ 11 - 2
admin/models/essay.coffee

@@ -7,9 +7,9 @@ moment = require('lib/moment')
 BaseModel = require('models/base')
 
 class Essay extends BaseModel
-  @configure "Essay", "site", "slug", "title", "intro", "body", "photo", "published", "published_at", "published_at", "author_id", "sponsor_id", "sponsor_start", "sponsor_end", "sponsors_history", "collections", "_attachments"
+  @configure "Essay", "site", "slug", "title", "intro", "body", "photo", "published", "published_at", "updated_at", "author_id", "sponsor_id", "sponsor_start", "sponsor_end", "sponsors_history", "collections", "_attachments"
   
-  @extend Spine.Model.CouchAjax
+  @extend @CouchAjax
   
   @titleSort: (a, b) ->
     if (a.title or a.published_at) > (b.title or b.published_at) then 1 else -1
@@ -26,6 +26,15 @@ class Essay extends BaseModel
     return 'Slug is required' unless @slug
     return 'Title is required' unless @title
 
+    # Validate the `slug` to be unique within site
+    found = Essay.select (essay) =>
+      matched = essay.site is @site and essay.slug is @slug
+      if @isNew()
+        matched
+      else
+        essay.id isnt @id and matched
+    return 'Slug has been already used for this site.' if found.length
+
     # Take care of some dates
     @updated_at = moment.utc().format()
 

+ 7 - 2
admin/models/site.coffee

@@ -6,9 +6,9 @@ utils = require('lib/utils')
 BaseModel = require('models/base')
 
 class Site extends BaseModel
-  @configure "Site", "_id", "name", "name_html", "tagline", "menu_html", "footer_html", "link", "theme", "css", "seo_description", "seo_keywords", "google_analytics_code"
+  @configure "Site", "_id", "name", "name_html", "tagline", "menu_html", "footer_html", "link", "theme", "css", "seo_description", "seo_keywords", "google_analytics_code", "editor_email", "admin_email"
   
-  @extend Spine.Model.CouchAjax
+  @extend @CouchAjax
   
   @queryOn: ['name','tagline','_id']
     
@@ -17,6 +17,11 @@ class Site extends BaseModel
     return 'Name is required' unless @name
     return 'Name HTML is required' unless @name_html
 
+    # Validate the `_id` to be unique in the system
+    if @isNew()
+      found = Site.exists(@_id)
+      return 'Site ID has been already used.' if found
+
     return false
 
 module.exports = Site

+ 2 - 2
admin/models/sponsor.coffee

@@ -8,9 +8,9 @@ BaseModel = require('models/base')
 utils = require('lib/utils')
 
 class Sponsor extends BaseModel
-  @configure "Sponsor", "format", "name", "link", "label", "content", "note", "contact_id"
+  @configure "Sponsor", "format", "name", "link", "label", "content", "image", "note", "contact_id", "_attachments"
   
-  @extend Spine.Model.CouchAjax
+  @extend @CouchAjax
 
   @queryOn: ['name','content','link','format']
     

+ 4 - 4
admin/server/rewrites.coffee

@@ -29,8 +29,8 @@ module.exports = [
     to: "_view/docs_by_type",
     method: "GET",
     query: {
-      start_key: [":type"],
-      end_key: [":type", {}],
+      startkey: [":type"],
+      endkey: [":type", {}],
       include_docs: "true"
     }
   }
@@ -40,8 +40,8 @@ module.exports = [
     to: "_view/docs_by_type",
     method: "GET",
     query: {
-      start_key: [":type", ":id"],
-      end_key: [":type", ":id", {}],
+      startkey: [":type", ":id"],
+      endkey: [":type", ":id", {}],
       include_docs: "true"
     }
   }

+ 3 - 3
admin/server/validate.coffee

@@ -2,10 +2,10 @@ utils = require('lib/utils')
 
 exports.validate_doc_update = (newDoc, oldDoc, userCtx) ->
 
-  is_admin = if '_admin' in userCtx.roles then true else false
+  access = if 'admin' in userCtx.roles or 'manager' in userCtx.roles then true else false
 
-  if not is_admin
-    throw unauthorized: 'You are not a database admin'
+  if not access
+    throw unauthorized: 'You must have the role admin or manager to make changes'
 
   if newDoc.type is 'essay'
     if not newDoc.site

+ 1 - 2
admin/server/views.coffee

@@ -6,8 +6,7 @@ exports.docs_by_site =
       emit ['global', doc.type, doc._id], doc.title or doc.name
     else
       emit ['global', '_doc_', doc._id], doc.title or doc.name
-  reduce: (key, values, rereduce) ->
-    key.length
+  reduce: '_count'
 
 exports.docs_by_type =
   map: (doc) ->

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

@@ -321,7 +321,7 @@ span.label
           font-size: 1.5em
 
         textarea[name='body'],
-        textarea[name='content']
+        textarea[name='content']:not(.default)
           height: 600px
 
         input[type='checkbox']

+ 1 - 1
admin/templates/author-form.html

@@ -48,7 +48,7 @@
     <div class="buttons">
       <button class="save-button">Save</button>
       <button class="cancel-button plain">Cancel</button>
-      {{#if id}}<a class="delete-button" href="#">delete {{type}}</a>{{/if}}
+      {{#if _id}}<a class="delete-button" href="#">delete {{type}}</a>{{/if}}
     </div>
 
     <div class="top-spacer"></div>

+ 3 - 0
admin/templates/base.html

@@ -8,6 +8,9 @@
   <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1, maximum-scale=1.0, user-scalable=0">
   <meta name="apple-mobile-web-app-capable" content="yes">
 
+  <link rel="shortcut icon" href="{{baseURL}}/static/img/favicon.ico">
+  <!-- <link rel="apple-touch-icon-precomposed" href="{{baseURL}}/static/img/apple-touch-icon-precomposed.png"> -->
+
   <link rel="stylesheet" href="{{baseURL}}/static/css/index.css" type="text/css" media="screen" charset="utf-8">
 
   <script src="{{baseURL}}/modules.js"></script>

+ 16 - 1
admin/templates/block-form.html

@@ -36,10 +36,25 @@
     <div class="buttons">
       <button class="save-button">Save</button>
       <button class="cancel-button plain">Cancel</button>
-      {{#if id}}<a class="delete-button" href="#">delete {{type}}</a>{{/if}}
+      {{#if _id}}<a class="delete-button" href="#">delete {{type}}</a>{{/if}}
     </div>
 
     <div class="top-spacer"></div>
+
+    <div class="field">
+      <div class="field-left">
+        <label>Enabled</label>
+        <input type="checkbox" name="enabled">
+      </div>
+      <div class="field-right">
+        <!-- another spot -->
+      </div>
+    </div>
+
+    <h3 class="heading">Files</h3>
+    <div class="field">
+      <div class="upload-ui"></div>
+    </div>
   </div>
 
 </form>

+ 1 - 1
admin/templates/collection-form.html

@@ -44,7 +44,7 @@
     <div class="buttons">
       <button class="save-button">Save</button>
       <button class="cancel-button plain">Cancel</button>
-      {{#if id}}<a class="delete-button" href="#">delete {{type}}</a>{{/if}}
+      {{#if _id}}<a class="delete-button" href="#">delete {{type}}</a>{{/if}}
     </div>
 
     <div class="top-spacer"></div>

+ 1 - 1
admin/templates/contact-form.html

@@ -24,7 +24,7 @@
     <div class="buttons">
       <button class="save-button">Save</button>
       <button class="cancel-button plain">Cancel</button>
-      {{#if id}}<a class="delete-button" href="#">delete {{type}}</a>{{/if}}
+      {{#if _id}}<a class="delete-button" href="#">delete {{type}}</a>{{/if}}
     </div>
 
     <div class="top-spacer"></div>

+ 1 - 1
admin/templates/essay-form.html

@@ -48,7 +48,7 @@
     <div class="buttons">
       <button class="save-button">Save</button>
       <button class="cancel-button plain">Cancel</button>
-      {{#if id}}<a class="delete-button" href="#">delete {{type}}</a>{{/if}}
+      {{#if _id}}<a class="delete-button" href="#">delete {{type}}</a>{{/if}}
     </div>
 
     <div class="top-spacer"></div>

+ 10 - 1
admin/templates/site-form.html

@@ -68,10 +68,19 @@
     <div class="buttons">
       <button class="save-button">Save</button>
       <button class="cancel-button plain">Cancel</button>
-      {{#if id}}<a class="delete-button" href="#">delete {{type}}</a>{{/if}}
+      {{#if _id}}<a class="delete-button" href="#">delete {{type}}</a>{{/if}}
     </div>
 
     <div class="top-spacer"></div>
+
+    <div class="field">
+      <label>Editor Email</label>
+      <input type="text" name="editor_email" value="{{editor_email}}" placeholder="editor@example.com">
+    </div>
+    <div class="field">
+      <label>Admin Email</label>
+      <input type="text" name="admin_email" value="{{admin_email}}" placeholder="admin@example.com">
+    </div>
   </div>
 
 </form>

+ 7 - 2
admin/templates/sponsor-form.html

@@ -30,7 +30,7 @@
     </div>
     <div class="field">
       <label>Content</label>
-      <textarea name="content" placeholder="Type text or paste embed code of an image or video">{{content}}</textarea>
+      <textarea class="default" name="content" placeholder="Type text or paste embed code of an image or video">{{content}}</textarea>
     </div>
     <div class="field">
       <label>Note</label>
@@ -51,10 +51,15 @@
     <div class="buttons">
       <button class="save-button">Save</button>
       <button class="cancel-button plain">Cancel</button>
-      {{#if id}}<a class="delete-button" href="#">delete {{type}}</a>{{/if}}
+      {{#if _id}}<a class="delete-button" href="#">delete {{type}}</a>{{/if}}
     </div>
 
     <div class="top-spacer"></div>
+
+    <h3 class="heading">Files</h3>
+    <div class="field">
+      <div class="upload-ui"></div>
+    </div>
   </div>
 
 </form>

+ 4 - 0
site/kanso.json

@@ -31,5 +31,9 @@
     "duality-handlebars": null,
     "showdown": null,
     "jquery": null
+  },
+
+  "app": {
+    "content_types": ["essay","scene","video","profile"]
   }
 }

+ 44 - 1
site/lib/app.coffee

@@ -1,5 +1,6 @@
 # App's main client script
-$ = require('jquery')
+$       = require('jquery')
+moment  = require('lib/moment')
 require('lib/fastclick')
 
 exports.initialize = (config) ->
@@ -10,6 +11,13 @@ exports.initialize = (config) ->
   # is needed.
   new FastClick(document.body)
 
+  setupNavMenus()
+  setupSmoothScrolling()
+  setupTimestampFormatting()
+  setupClickTracking()
+
+
+setupNavMenus = ->
   $mainNav = $('.main-nav')
   $mainNavIcon = $mainNav.find('> .icon')
   $mainNavList = $mainNav.find('> ul')
@@ -74,3 +82,38 @@ exports.initialize = (config) ->
       e.stopPropagation()
       hidePopups($collectionNavList)
       $collectionNavList.toggle()
+
+
+setupSmoothScrolling = ->
+  smoothScroll = (hash) ->
+    $target = $(hash)
+    $target = $target.length and $target or $('[name=' + hash.slice(1) +']')
+    if $target.length
+      adjustment = 15
+      targetOffset = $target.offset().top - adjustment
+      $('body').animate({scrollTop: targetOffset}, 400)
+      return true
+   
+  # Add some smooth scrolling to anchor links
+  $('body').on 'click', 'a[href*="#"]', (e) ->
+    if location.pathname.replace(/^\//,'') is @pathname.replace(/^\//,'') and location.hostname is @hostname
+      e.preventDefault()
+      smoothScroll(@hash)
+  
+  # In case this is a distination to a specific page anchor, let's smooth scroll
+  smoothScroll(location.hash) if location.hash.length
+  
+
+setupTimestampFormatting = ->
+  # Convert UTC dates to local time
+  $('.timestamp').each ->
+    timestamp = $(@).text()
+    $(@).html(moment(timestamp).local().format('MMM D, YYYY h:mm A'))
+
+
+setupClickTracking = ->
+  # Track some analytic events
+  $('body').on 'click', '[data-track-click]', (e) ->
+    label = $(@).attr('data-track-click');
+    _gaq?.push(['_trackEvent', 'Site Navigation', 'Click', label])
+    return true

+ 3 - 0
site/lib/utils.coffee

@@ -3,6 +3,9 @@ moment    = require('lib/moment')
 exports.prettyDate = (date) ->
   moment.utc(date).local().format('MMM Do YYYY')
 
+exports.halfDate = (date) ->
+  moment.utc(date).local().format('MMM D')
+
 exports.isItFresh = (date) ->
   if moment.utc().eod() < moment.utc(date).add('days', 30).eod()
     return true

+ 28 - 21
site/server/lists.coffee

@@ -1,5 +1,6 @@
 templates = require('duality/templates')
 dutils    = require('duality/utils')
+settings  = require('settings/root')
 Showdown  = require('showdown')
 _         = require('underscore')._
 utils     = require('lib/utils')
@@ -28,6 +29,7 @@ exports.home = (head, req) ->
         doc.intro.replace(/\{\{?baseURL\}?\}/g, dutils.getBaseURL(req))
       )
     doc.updated_at_html = utils.prettyDate(doc.updated_at)
+    doc.updated_at_half = utils.halfDate(doc.updated_at)
     doc.fresh = utils.isItFresh(doc.updated_at)
     return doc
 
@@ -232,28 +234,33 @@ exports.essay = (head, req) ->
     }
 
 
-###
-exports.rssfeed = function (head, req) {
-    start({code: 200, headers: {'Content-Type': 'application/rss+xml'}});
+exports.rssfeed = (head, req) ->
+  # start code: 200, headers: {'Content-Type': 'application/rss+xml'}
+  start code: 200, headers: {'Content-Type': 'text/plain'}
 
-    var md = new Showdown.converter();
+  md = new Showdown.converter()
+  docs = []
+  site = null
 
-    var rows = getRows(function (row) {
-        var doc = row.doc;
-        doc.markdown_html = md.makeHtml(
-            doc.markdown.replace(/\{\{?baseURL\}?\}/g, dutils.getBaseURL(req))
-        );
-        doc.guid = 'http://caolanmcmahon.com' + (
-            doc.oldurl || '/posts/' + doc.slug
-        );
-        return row;
-    });
+  while row = getRow()
+    doc = row.doc
+    docs.push(doc) if doc.type in settings.app.content_types
+    site ?= doc if doc.type is 'site'
 
-    return templates.render('feed.xml', req, {
-        rows: rows,
-        published_at: rows[0].doc.published_at,
-        builddate: new Date()
-    });
-};
+  docs = _.map docs, (doc) ->
+    doc.intro_html = md.makeHtml(
+      doc.intro.replace(/\{\{?baseURL\}?\}/g, dutils.getBaseURL(req))
+    )
+    doc.body_html = md.makeHtml(
+      doc.body.replace(/\{\{?baseURL\}?\}/g, dutils.getBaseURL(req))
+    )
+    doc.published_at_html = utils.prettyDate(doc.published_at)
+    doc.full_url = "#{site.link}/#{doc.type}/#{doc.slug}"
+    doc.full_html = "#{doc.intro_html}<br>#{doc.body_html}"
+    return doc
 
-###
+  return templates.render 'feed.xml', req,
+    site: site
+    docs: docs
+    published_at: new Date(docs[0].published_at)
+    build_date: new Date()

+ 32 - 13
site/server/rewrites.coffee

@@ -1,6 +1,10 @@
 moved = (from, to) ->
   { from: from, to: '_show/moved', query: {loc: to} }
 
+movedPattern = (from, to) ->
+  { from: from, to: '_show/moved_pattern', query: {loc: to} }
+
+
 module.exports = [
   # Static files from the root
   { from: '/static/*', to: 'static/*' }
@@ -22,18 +26,6 @@ module.exports = [
   { from: '/render/:site/modules.js',  to: 'modules.js' }
   { from: '/render/:site/duality.js',  to: 'duality.js' }
 
-  # List of all Essays sorted by `updated_at`
-  {
-    from: '/render/:site/essays',
-    to: '_list/essays/essays_by_date',
-    query: {
-      startkey: [':site', {}],
-      endkey: [':site'],
-      descending: 'true',
-      include_docs: 'true'
-    }
-  }
-
   # Collection's page - list of essays
   # `:slug` is the collection's slug
   {
@@ -72,12 +64,39 @@ module.exports = [
     }
   }
 
+  # List of all Essays sorted by `updated_at`
+  {
+    from: '/render/:site/essays',
+    to: '_list/essays/essays_by_date',
+    query: {
+      startkey: [':site', {}],
+      endkey: [':site'],
+      descending: 'true',
+      include_docs: 'true'
+    }
+  }
+
+  # RSS Feed of all Essays sorted by `updated_at`
+  {
+    from: '/render/:site/essays/feed',
+    to: '_list/rssfeed/essays_by_date',
+    query: {
+      startkey: [':site', {}],
+      endkey: [':site'],
+      descending: 'true',
+      include_docs: 'true'
+    }
+  }
+
   # File attachments paths
   { from: '/file/:id/:filename', to: '../../:id/:filename' }
   { from: '/render/:site/file/:id/:filename', to: '../../:id/:filename' }
 
-  # Redirected old URLs
+  # Redirect some direct paths
   # moved '/posts/some-old-path', '/some-new-path'
+  
+  # Redirected old URLs using a pattern
+  movedPattern '/render/:site/posts/:id/:slug', '/:type/:slug'
 
   # 404 not found 
   { from: '/not-found', to: '_show/not_found' }

+ 16 - 1
site/server/shows.coffee

@@ -7,4 +7,19 @@ exports.not_found = (doc, req) ->
 
 exports.moved = (doc, req) ->
   code: 301
-  headers: { location: req.query.loc }
+  headers: { location: req.query.loc }
+
+exports.moved_pattern = (doc, req) ->
+  loc = req.query.loc
+  switch req.query.site
+    when 'www.evitaochel.com'
+      type = 'essay'
+    else
+      type = 'essay'
+  loc = loc.replace(/\:type/g, type)
+  loc = loc.replace(/\:slug/g, req.query.slug)
+  loc = loc.replace(/\:id/g, req.query.id)
+  return {
+    code: 301
+    headers: { location: loc }
+  }

+ 5 - 5
site/server/views.coffee

@@ -16,8 +16,8 @@ exports.docs_for_home =
 
 exports.essays_by_collection =
   map: (doc) ->
-    if doc.site and doc.type is 'essay' and doc.collections and doc.updated_at and doc.published
-      timestamp = new Date(doc.updated_at).getTime()
+    if doc.site and doc.type is 'essay' and doc.collections and doc.published_at and doc.published
+      timestamp = new Date(doc.published_at).getTime()
       for c, i in doc.collections
         emit [doc.site, c.slug, 'essay', timestamp], null
     else if doc.site and doc.type is 'collection'
@@ -34,9 +34,9 @@ exports.essays_by_collection =
 
 exports.essays_by_date =
   map: (doc) ->
-    # List of essays sorted by `updated_at` along with their collection references
-    if doc.site and doc.type is 'essay' and doc.updated_at and doc.published
-      timestamp = new Date(doc.updated_at).getTime()
+    # List of essays sorted by `published_at` along with their collection references
+    if doc.site and doc.type is 'essay' and doc.published_at and doc.published
+      timestamp = new Date(doc.published_at).getTime()
       emit [doc.site, timestamp, doc._id, {}], null
       for c, i in doc.collections
         # To get each collection's doc

+ 11 - 3
site/static/css/setup-theme.styl

@@ -33,7 +33,9 @@ setupTheme($primaryColor = $blueColor, $secondaryColor = $lightGrey, $linkColor
   .site-promo
     color: $primaryColor
 
-  .main-nav
+  .main-nav,
+  .toc-nav,
+  .collection-nav
     .icon
       background: $primaryColor
       &:hover
@@ -53,12 +55,18 @@ setupTheme($primaryColor = $blueColor, $secondaryColor = $lightGrey, $linkColor
         background: $primaryColor
 
   article.home
-    .collections      
+    .collections    
       > ul
         > li
-          border-color: $primaryColor
           .updated
             background: $primaryColor
+            border-color: $primaryColor
+          &:hover
+            border-color: $primaryColor
+            a
+              background: $primaryColor
+            .updated
+              color: $primaryColor
 
   article.essay
     .author

+ 68 - 31
site/static/css/theme.styl

@@ -56,6 +56,7 @@
     background: $primaryColor
     cursor: pointer
     outline: 4px solid #fff
+    white-space: nowrap
 
     &:hover
       background: darken($primaryColor, 10%)
@@ -96,6 +97,19 @@
     > li
       padding: 0
 
+      a
+        display: block
+        padding: 10px 20px
+        font-size: 1.2em
+        font-weight: $normalFont
+        color: #fff
+
+        &:hover
+          background: darken($primaryColor, 5%)
+
+        &.active
+          background: darken($primaryColor, 10%)
+
       &.heading
         padding: 7px 20px
         font-size: 1.1em
@@ -109,18 +123,10 @@
           font-weight: $normalFont
           line-height: 1em
 
-      a
-        display: block
-        padding: 10px 20px
-        font-size: 1.2em
-        font-weight: $normalFont
-        color: #fff
-
-        &:hover
-          background: darken($primaryColor, 5%)
-
-        &.active
-          background: darken($primaryColor, 10%)
+        a
+          padding: 0
+          font-size: inherit
+          font-weight: inherit
 
       &.search-box
         padding: 10px
@@ -139,16 +145,24 @@
 
   .icon
 
+    // OLD for the TOC icon <div>TOC</div><span></span>
+    // div
+    //   width: 48px
+    //   height: 24px
+    //   color: #fff
+    //   font-size: 22px
+    //   font-weight: $heavyFont
+    //   line-height: 1em
+    //   text-align: center
+    //   margin-left: -4px
+    //   overflow: hidden
+
     div
-      width: 48px
-      height: 24px
-      color: #fff
-      font-size: 22px
-      font-weight: $heavyFont
-      line-height: 1em
-      text-align: center
-      margin-left: -4px
-      overflow: hidden
+      height: 31px
+      background: transparent
+      border: 5px solid white
+      padding: 7px 5px 0 5px
+      margin-bottom: 5px
 
   > ul
 
@@ -162,8 +176,10 @@
   // see above
   right: 140px
 
+  $iconWidth = 144px
+
   .icon
-    width: auto
+    width: $iconWidth
 
     div
       width: auto
@@ -174,12 +190,14 @@
       line-height: 1em
       text-align: center
       margin: 3px 6px
+      border: 0
+      padding: 0
       overflow: hidden
 
   > ul
 
     &:before
-      width: 144px
+      width: $iconWidth
 
 
 article
@@ -255,14 +273,17 @@ article
     padding: 0
 
     > li
-      margin-bottom: 1em
-      clearfix()
+      margin-bottom: 2.5em
       
       .date
+        display: none
         float: right
         padding: 5px 10px
         color: $lightGrey
 
+        &.fresh-true
+          // display: block
+
       .meta
         font-size: 0.875em
         > a
@@ -272,39 +293,55 @@ article
 article.home
 
   .collections
+
+    > h3
+      margin-bottom: 0.5em
     
     > ul
       list-style-type: none
       margin: 0
       padding: 0
+      clearfix()
 
       > li
         display: inline-block
         position: relative
         width: 48%
         padding: 0
-        margin: 0.5em 0.5em 0 0
-        border: solid 1px $primaryColor
+        margin: 0.8em 0.5em 0 0
+        border: solid 1px $faintGrey
 
         .name
           margin: 0
           a
             display: block
-            color: inherit
             padding: 1em
 
         .updated
           display: none
           position: absolute
-          bottom: 0
-          right: 0
-          padding: 10px 20px
+          top: -6px
+          right: -6px
+          padding: 2px 10px
           background: $primaryColor
           color: #fff
+          border: 1px solid $primaryColor
+          min-height: 20px
 
           &.fresh-true
             display: block
 
+        &:hover
+          border-color: $primaryColor
+
+          a
+            color: #fff
+            background: $primaryColor
+
+          .updated
+            color: $primaryColor
+            background: #fff
+
 article.essay
 
   .body

+ 15 - 10
site/templates/base.html

@@ -12,8 +12,12 @@
   <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1, maximum-scale=3.0, user-scalable=1">
   <meta name="apple-mobile-web-app-capable" content="yes">
   
+  {{#if site.favicon}}
+  <link rel="shortcut icon" href="{{baseURL}}/file/{{site._id}}/{{site.favicon}}">
+  {{else}}
   <link rel="shortcut icon" href="{{baseURL}}/static/img/favicon.ico">
-  <link rel="apple-touch-icon-precomposed" href="{{baseURL}}/static/img/apple-touch-icon-precomposed.png">
+  {{/if}}
+  <!-- <link rel="apple-touch-icon-precomposed" href="{{baseURL}}/static/img/apple-touch-icon-precomposed.png"> -->
 
   <link rel="stylesheet" href="{{baseURL}}/static/css/index.css" type="text/css" media="all" charset="utf-8">
   {{#if site.theme}}
@@ -31,29 +35,30 @@
     <h1 class="site-name"><a href="{{baseURL}}/">{{{site.name_html}}}</a></h1>
     <h2 class="site-tagline">{{{site.tagline}}}</h2>
 
-    <section class="site-promo">
-      {{{blocks.site_promo.content}}}
-    </section>
-
     {{{content}}}
 
     <nav class="main-nav">
-      <div class="icon"><span></span><span></span><span></span><i></i></div>
+      <div class="icon" data-track-click="Main Nav Icon">
+        <span></span><span></span><span></span><i></i>
+      </div>
       <ul>
+        <li class="heading">Main Menu</li>
         {{{site.menu_html}}}
         <li class="search-box">
-          <input type="text" placeholder="Search">
+          <input type="text" placeholder="Search" data-track-click="Search Box">
         </li>
       </ul>
     </nav>
 
-    {{#if site.footer_html}}<footer>{{{site.footer_html}}}</footer>{{/if}}
+    {{#if site.footer_html}}
+    <footer>{{{site.footer_html}}}</footer>
+    {{/if}}
   </div>
 
   <script src="{{baseURL}}/modules.js"></script>
   <script type="text/javascript">
     var $ = require('jquery');
-    var _gaq = { push: function(x){} };
+    var _gaq = { push: function(x){ console.log(x); } };
     $(function() {
       var app = require('lib/app');
       app.initialize({
@@ -69,7 +74,7 @@
   <!-- <script type="text/javascript">
     var _gaq = [];
     _gaq.push(['_setAccount', '{{site.google_analytics_code}}']);
-    _gaq.push(['_setDomainName', '{{site._id}}']);
+    _gaq.push(['_setDomainName', 'auto']);
     _gaq.push(['_trackPageview']);
     (function() {
       var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;

+ 2 - 2
site/templates/collection.html

@@ -39,9 +39,9 @@
     {{#if sponsor.image_format}}
     <div class="image">
       {{#if sponsor.link}}
-        <a href="{{sponsor.link}}" target="_blank"><img src="{{sponsor.content}}" alt=""></a>
+        <a href="{{sponsor.link}}" target="_blank"><img src="{{#if sponsor.image}}{{sponsor.image}}{{else}}{{sponsor.content}}{{/if}}" alt=""></a>
       {{else}}
-        <img src="{{sponsor.content}}" alt="">
+        <img src="{{#if sponsor.image}}{{sponsor.image}}{{else}}{{sponsor.content}}{{/if}}" alt="">
       {{/if}}
     </div>
     {{/if}}

+ 1 - 1
site/templates/collections.html

@@ -6,7 +6,7 @@
   {{#each collections}}
     <li class="collection">
       <h4 class="name"><a href="{{baseURL}}/collection/{{slug}}">{{name}}</a></h4>
-      <div class="updated fresh-{{fresh}}">{{{updated_at_html}}}</div>
+      <div class="updated fresh-{{fresh}}" title="Updated on {{updated_at_html}}"></div>
     </li>
   {{/each}}
   </ul>

+ 11 - 7
site/templates/essay.html

@@ -1,6 +1,6 @@
 {{#if essay.css}}<style>{{{essay.css}}}</style>{{/if}}
 
-<article class="view essay">
+<article class="view essay" id="TOC-Introduction">
   <h2 class="title"><a href="{{baseURL}}/essay/{{essay.slug}}">{{essay.title}}</a></h2>
 
   {{#if essay.photo}}
@@ -9,7 +9,7 @@
   </section>
   {{/if}}
 
-  <section class="intro" id="TOC-Introduction">
+  <section class="intro">
     {{{essay.intro_html}}}
   </section>
 
@@ -37,9 +37,9 @@
     {{#if sponsor.image_format}}
     <div class="image">
       {{#if sponsor.link}}
-        <a href="{{sponsor.link}}" target="_blank"><img src="{{sponsor.content}}" alt=""></a>
+        <a href="{{sponsor.link}}" target="_blank"><img src="{{#if sponsor.image}}{{sponsor.image}}{{else}}{{sponsor.content}}{{/if}}" alt=""></a>
       {{else}}
-        <img src="{{sponsor.content}}" alt="">
+        <img src="{{#if sponsor.image}}{{sponsor.image}}{{else}}{{sponsor.content}}{{/if}}" alt="">
       {{/if}}
     </div>
     {{/if}}
@@ -81,7 +81,9 @@
 </article>
 
 <nav class="toc-nav">
-  <div class="icon"><div>TOC</div><span></span><i></i></div>
+  <div class="icon" data-track-click="Document Nav Icon">
+    <div><span></span></div><i></i>
+  </div>
   <ul>
     <li class="heading">Essay's Table of Content</li>
     <li><a href="#TOC-Introduction">Introduction</a></li>
@@ -89,11 +91,13 @@
 </nav>
 
 <nav class="collection-nav" data-id="{{collection._id}}" data-slug="{{collection.slug}}">
-  <div class="icon"><div>Collection</div><i></i></div>
+  <div class="icon" data-track-click="Collection Nav Icon">
+    <div>Collection</div><i></i>
+  </div>
   <ul>
     <li class="heading">
       <small>5 Essays in Collection:</small><br>
-      {{collection.name}}
+      <a href="{{baseURL}}/collection/{{collection.slug}}">{{collection.name}}</a>
     </li>
   </ul>
 </nav>

+ 23 - 0
site/templates/feed.xml

@@ -0,0 +1,23 @@
+<rss version="2.0"> 
+    <channel> 
+        <title>{{site.name}}</title> 
+        <link>{{site.link}}</link> 
+        <description>{{site.seo_description}}</description> 
+        <language>en-us</language> 
+        <pubDate>{{published_at}}</pubDate> 
+ 
+        <lastBuildDate>{{build_date}}</lastBuildDate> 
+        <managingEditor>{{site.editor_email}}</managingEditor> 
+        <webMaster>{{site.admin_email}}</webMaster> 
+
+        {{#each docs}}
+        <item> 
+            <title>{{title}}</title> 
+            <link>{{full_url}}</link> 
+            <description><![CDATA[{{{full_html}}}]]></description> 
+            <pubDate>{{published_at}}</pubDate> 
+            <guid>{{full_url}}</guid>
+        </item>
+        {{/each}}
+    </channel>
+</rss>

+ 13 - 3
site/templates/home.html

@@ -1,7 +1,17 @@
+{{#if blocks.site_promo.enabled}}
+<section class="site-promo">{{{blocks.site_promo.content}}}</section>
+{{/if}}
+
 <article class="home">
-  <section class="intro">
-    {{{blocks.site_intro.content}}}
-  </section>
+  {{#if blocks.site_intro.enabled}}
+    <section class="intro">{{{blocks.site_intro.content}}}</section>
+
+    {{#if blocks.site_intro.photo}}
+    <section class="photo">
+      <img src="/file/{{blocks.site_intro._id}}/{{blocks.site_intro.photo}}">
+    </section>
+    {{/if}}
+  {{/if}}
 
   {{{include "collections.html"}}}
 </article>

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff