Merge branch 'andrewminer-item-edit'
This commit is contained in:
+8
-3
@@ -133,17 +133,18 @@ module.exports = (grunt)->
|
||||
tasks: ['browserify:dev', 'exorcise']
|
||||
jade:
|
||||
files: ['./src/**/*.jade']
|
||||
tasks: ['jade:pages', 'jade:templates', 'browserify:dev', 'exorcise']
|
||||
tasks: ['jade:pages', 'jade:templates']
|
||||
sass:
|
||||
files: ['./src/**/*.scss']
|
||||
tasks: ['sass', 'copy:styles']
|
||||
test:
|
||||
files: ['./src/**/*.coffee', './src/**/*.js', './test/**/*.coffee']
|
||||
tasks: ['test']
|
||||
|
||||
grunt.registerTask 'default', 'build'
|
||||
|
||||
grunt.registerTask 'build', ['rsync', 'sass:build', 'jade', 'copy', 'browserify:dev', 'exorcise']
|
||||
|
||||
grunt.registerTask 'clean-watch', ['clean', 'build', 'watch']
|
||||
|
||||
grunt.registerTask 'dist', ['rsync', 'sass:dist', 'jade', 'copy', 'browserify:prod', 'uglify']
|
||||
|
||||
grunt.registerTask 'prepublish', ['clean', 'coffee']
|
||||
@@ -155,6 +156,10 @@ module.exports = (grunt)->
|
||||
|
||||
grunt.registerTask 'test', ['mochaTest']
|
||||
|
||||
grunt.registerTask 'watch-dev', ['clean', 'build', 'watch']
|
||||
|
||||
grunt.registerTask 'watch-test', ['clean', 'build', 'watch:test']
|
||||
|
||||
args = process.argv[..]
|
||||
while args.length > 0
|
||||
switch args[0]
|
||||
|
||||
Binary file not shown.
@@ -66,7 +66,7 @@ Event.route = 'route'
|
||||
Event.sort = 'sort'
|
||||
Event.sync = 'sync' # model, response
|
||||
Event.transitionEnd = (->
|
||||
return unless document?
|
||||
return 'transitionend' unless document?
|
||||
transitions =
|
||||
'WebkitTransition': 'webkitTransitionEnd'
|
||||
'MozTransition': 'transitionend'
|
||||
@@ -88,6 +88,10 @@ Key.Escape = 27
|
||||
Key.UpArrow = 38
|
||||
Key.DownArrow = 40
|
||||
|
||||
exports.GitHub = GitHub = {}
|
||||
GitHub.file = {}
|
||||
GitHub.file.itemDescription = _.template "/data/<%= modSlug %>/items/<%= itemSlug %>/item.cg"
|
||||
|
||||
exports.Login = Login = {}
|
||||
Login.authorizeUrl = _.template "https://github.com/login/oauth/authorize" +
|
||||
"?client_id=<%= clientId %>&scope=public_repo&state=<%= state %>"
|
||||
|
||||
@@ -49,14 +49,17 @@ module.exports = class BaseController extends backbone.View
|
||||
|
||||
if not ($el.hasClass('hiding') or $el.hasClass('hidden'))
|
||||
logger.verbose => "#{this} is hiding \"#{$el.selector}\""
|
||||
@_onAnimationComplete $el, Duration.normal, =>
|
||||
$el.addClass 'hidden'
|
||||
$el.removeClass 'hiding'
|
||||
logger.verbose => "#{this} is finished hiding #{$el.selector}"
|
||||
@trigger Event.animate.hide.finish, this, $el
|
||||
|
||||
$el.addClass 'hiding'
|
||||
@trigger Event.animate.hide.start, this, $el
|
||||
@_onAnimationComplete $el, Duration.normal, => @hide $el
|
||||
return
|
||||
|
||||
if $el.hasClass('hiding') and not $el.hasClass('hidden')
|
||||
$el.removeClass 'hiding'
|
||||
$el.addClass 'hidden'
|
||||
logger.verbose => "#{this} is finished hiding #{$el.selector}"
|
||||
|
||||
@trigger Event.animate.hide.finish, this, $el
|
||||
|
||||
refresh: ->
|
||||
logger.verbose => "#{this} refreshing"
|
||||
@@ -86,21 +89,28 @@ module.exports = class BaseController extends backbone.View
|
||||
|
||||
show: ($el)->
|
||||
$el ?= @$el
|
||||
|
||||
if not $el.hasClass 'hideable'
|
||||
$el.addClass 'hideable'
|
||||
_.defer => @show $el
|
||||
return
|
||||
|
||||
if $el.hasClass('hidden') or $el.hasClass('hiding')
|
||||
if $el.hasClass 'hidden'
|
||||
logger.verbose => "#{this} is showing \"#{$el.selector}\""
|
||||
$el.removeClass 'hidden'
|
||||
$el.addClass 'hiding'
|
||||
@trigger Event.animate.show.start, this, $el
|
||||
_.defer => @show $el
|
||||
return
|
||||
|
||||
if $el.hasClass 'hiding'
|
||||
$el.removeClass 'hiding'
|
||||
@_onAnimationComplete $el, Duration.normal, => @show $el
|
||||
return
|
||||
|
||||
@_onAnimationComplete $el, Duration.normal, =>
|
||||
logger.verbose => "#{this} is finished showing #{$el.selector}"
|
||||
@trigger Event.animate.show.finish, this, $el
|
||||
|
||||
$el.addClass 'hiding'
|
||||
$el.removeClass 'hidden'
|
||||
_.defer =>
|
||||
$el.removeClass 'hiding'
|
||||
@trigger Event.animate.show.start, this, $el
|
||||
|
||||
unrender: ->
|
||||
@undelegateEvents()
|
||||
@$el.empty()
|
||||
@@ -155,7 +165,7 @@ module.exports = class BaseController extends backbone.View
|
||||
Object.defineProperties @prototype,
|
||||
model: {get:@prototype.getModel, set:@prototype.setModel}
|
||||
rendered: {get:@prototype.isRendered }
|
||||
user: {get:@prototype.user, set:@prototype.setUser}
|
||||
user: {get:@prototype.getUser, set:@prototype.setUser}
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
|
||||
@@ -16,8 +16,10 @@ MarkdownSectionController = require './markdown_section_controller'
|
||||
PageController = require './page_controller'
|
||||
VideoController = require './video_controller'
|
||||
_ = require 'underscore'
|
||||
w = require 'when'
|
||||
{Duration} = require '../constants'
|
||||
{Event} = require '../constants'
|
||||
{GitHub} = require '../constants'
|
||||
{Text} = require '../constants'
|
||||
{Url} = require '../constants'
|
||||
|
||||
@@ -26,6 +28,7 @@ _ = require 'underscore'
|
||||
module.exports = class ItemPageController extends PageController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.client? then throw new Error 'options.client is required'
|
||||
if not options.itemSlug? then throw new Error 'options.itemSlug is required'
|
||||
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
@@ -35,8 +38,11 @@ module.exports = class ItemPageController extends PageController
|
||||
|
||||
super options
|
||||
|
||||
@client = options.client
|
||||
@imageLoader = options.imageLoader
|
||||
@modPack = options.modPack
|
||||
|
||||
@_editingFile = null
|
||||
@_itemSlug = options.itemSlug
|
||||
|
||||
@modPack.on Event.change, => @tryRefresh()
|
||||
@@ -59,11 +65,16 @@ module.exports = class ItemPageController extends PageController
|
||||
@adsenseController = @addChild AdsenseController, '.view__adsense', model:'sidebar_skyscraper'
|
||||
|
||||
options = imageLoader:@imageLoader, modPack:@modPack, show:false
|
||||
@_descriptionController = @addChild MarkdownSectionController, '.section.description', options
|
||||
@_similarItemsController = @addChild ItemGroupController, '.view__item_group.similar', options
|
||||
@_usedAsToolToMakeController = @addChild ItemGroupController, '.view__item_group.usedAsToolToMake', options
|
||||
@_usedToMakeController = @addChild ItemGroupController, '.view__item_group.usedToMake', options
|
||||
|
||||
@_descriptionController = @addChild MarkdownSectionController, '.section.description',
|
||||
editable: true
|
||||
modPack: @modPack
|
||||
beginEditing: => @_beginEditingDescription()
|
||||
endEditing: => @_endEditingDescription()
|
||||
|
||||
@$byline = @$('.byline')
|
||||
@$bylineLink = @$('.byline a')
|
||||
@$descriptionSection = @$('.description')
|
||||
@@ -111,6 +122,10 @@ module.exports = class ItemPageController extends PageController
|
||||
|
||||
super
|
||||
|
||||
setUser: (user)->
|
||||
super user
|
||||
@_descriptionController.user = user if @_descriptionController?
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
@@ -118,10 +133,43 @@ module.exports = class ItemPageController extends PageController
|
||||
'click a.craftingPlan': 'routeLinkClick'
|
||||
'click .byline a': 'routeLinkClick'
|
||||
'click .markdown a': 'routeLinkClick'
|
||||
'click button': 'craftingPlanButtonClicked'
|
||||
'click button.craftingPlan': 'craftingPlanButtonClicked'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_beginEditingDescription: ->
|
||||
if not global.router.user?
|
||||
global.router.login()
|
||||
return w.reject new Error 'must be logged in to edit'
|
||||
|
||||
if not @model.item?
|
||||
return w.reject new Error 'must have an item'
|
||||
|
||||
@client.fetchFile path:GitHub.file.itemDescription modSlug:@_itemSlug.mod, itemSlug:@_itemSlug.item
|
||||
.then (response)=>
|
||||
@_editingFile = response.json.data
|
||||
if @_editingFile.content.length > 0
|
||||
@model.item.parse @_editingFile.content
|
||||
else
|
||||
@model.item.description = ''
|
||||
|
||||
@_descriptionController.model = @model.item.description
|
||||
|
||||
_endEditingDescription: ->
|
||||
oldDescription = @model.item.description
|
||||
@model.item.description = @_descriptionController.model
|
||||
|
||||
args =
|
||||
content: @model.item.unparse()
|
||||
message: "User-submitted text for #{@model.item.name}"
|
||||
path: GitHub.file.itemDescription modSlug:@_itemSlug.mod, itemSlug:@_itemSlug.item
|
||||
sha: @_editingFile.sha
|
||||
|
||||
@client.updateFile args
|
||||
.catch (e)=>
|
||||
@model.item.description = oldDescription
|
||||
throw e
|
||||
|
||||
_refreshByline: ->
|
||||
mod = @model.item?.modVersion?.mod
|
||||
if mod?.name?.length > 0
|
||||
@@ -136,6 +184,8 @@ module.exports = class ItemPageController extends PageController
|
||||
if @model.item?.description?.length > 0
|
||||
@_descriptionController.model = @model.item.description
|
||||
@show @$descriptionSection
|
||||
else if @user?
|
||||
@show @$descriptionSection
|
||||
else
|
||||
@hide @$descriptionSection
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ module.exports = class LoginPageController extends PageController
|
||||
attributes = response.json.data.user
|
||||
if attributes?
|
||||
router.user = new User attributes
|
||||
router.navigate Url.root(), trigger:true
|
||||
router.resumeAfterLogin()
|
||||
.catch (error)->
|
||||
logger.error "Failed to get access token: #{error}"
|
||||
@_accessTokenLookupFailed = true
|
||||
@@ -120,9 +120,9 @@ module.exports = class LoginPageController extends PageController
|
||||
@client.completeGitHubLogin code:code
|
||||
.then (response)=>
|
||||
router.user = new User response.json.user
|
||||
.catch (error)->
|
||||
.catch (error)=>
|
||||
@_accessTokenLookupFailed = true
|
||||
@refresh
|
||||
@refresh()
|
||||
.done()
|
||||
|
||||
_redirectToGitHub: ->
|
||||
|
||||
@@ -8,48 +8,153 @@ All rights reserved.
|
||||
BaseController = require './base_controller'
|
||||
convertMarkdown = require 'marked'
|
||||
_ = require 'underscore'
|
||||
{Event} = require '../constants'
|
||||
{Url} = require '../constants'
|
||||
w = require 'when'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class MarkdownSectionController extends BaseController
|
||||
|
||||
@State = State =
|
||||
appologizing: 'applogizing'
|
||||
confirming: 'confirming'
|
||||
creating: 'creating'
|
||||
editing: 'editing'
|
||||
previewing: 'previewing'
|
||||
viewing: 'viewing'
|
||||
waiting: 'waiting'
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
options.imageBase ?= ''
|
||||
options.model ?= ''
|
||||
options.title ?= 'Description'
|
||||
options.model ?= null
|
||||
options.templateName = 'markdown_section'
|
||||
@_state = State.waiting
|
||||
super options
|
||||
|
||||
@imageBase = options.imageBase
|
||||
@on Event.animate.hide.finish, => @_updateStateVisibility()
|
||||
@on Event.animate.show.finish, => @_updateStateVisibility()
|
||||
|
||||
@confirmactionMessage = options.confirmationMessage
|
||||
@confirmDuration = options.confirmationDuration ?= 5000
|
||||
@imageBase = options.imageBase ?= ''
|
||||
@modPack = options.modPack
|
||||
@title = options.title
|
||||
@title = options.title ?= 'Description'
|
||||
|
||||
# @_beginEditing must be a function returning a promise which resolves when all actions necessary prior to an
|
||||
# editing session have been completed (e.g., loading the latest content from a remote server). The promise must
|
||||
# reject if an error occured which should prevent editing.
|
||||
@_beginEditing = options.beginEditing
|
||||
|
||||
# @_endEditing must be a function returning a promise which resolves when the model of this controller has been
|
||||
# saved however is appropriate. The promise must reject if saving failed for some reason.
|
||||
@_endEditing = options.endEditing
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onCancelClicked: (event)->
|
||||
event.preventDefault()
|
||||
@model = @_originalModel
|
||||
@_resetToDefaultState()
|
||||
|
||||
onEditClicked: (event)->
|
||||
event.preventDefault()
|
||||
return unless @editable
|
||||
|
||||
@_originalModel = @model
|
||||
@state = State.waiting
|
||||
@_beginEditing()
|
||||
.then =>
|
||||
@state = State.editing
|
||||
.catch (e)=>
|
||||
logger.warning "cannot begin editing: #{e.stack}"
|
||||
@state = State.appologizing
|
||||
w(true).delay(@confirmDuration).then => @_resetToDefaultState()
|
||||
|
||||
onPreviewClicked: (event)->
|
||||
event.preventDefault()
|
||||
@state = State.previewing
|
||||
|
||||
onReturnClicked: (event)->
|
||||
event.preventDefault()
|
||||
@state = State.editing
|
||||
|
||||
onSaveClicked: (event)->
|
||||
event.preventDefault()
|
||||
|
||||
@state = State.waiting
|
||||
@_endEditing()
|
||||
.then =>
|
||||
@state = State.confirming
|
||||
.catch (e)=>
|
||||
logger.error "failed to end editing: #{e}"
|
||||
@state = State.appologizing
|
||||
.delay @confirmDuration
|
||||
.then =>
|
||||
@state = State.viewing
|
||||
|
||||
onTextChanged: (event)->
|
||||
event.preventDefault()
|
||||
@model = @$textarea.val()
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
isEditable: ->
|
||||
return _.isFunction(@_beginEditing) and _.isFunction(@_endEditing)
|
||||
|
||||
getState: ->
|
||||
return @_state
|
||||
|
||||
setState: (newState)->
|
||||
oldState = @_state
|
||||
return if oldState is newState
|
||||
@_state = newState
|
||||
|
||||
logger.verbose => "MarkdownSectionController.#{@cid} changed state from #{oldState} to #{newState}"
|
||||
@trigger Event.change + ':state', this, oldState, newState
|
||||
@_updateStateVisibility()
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
editable: {get:@prototype.isEditable}
|
||||
state: {get:@prototype.getState, set:@prototype.setState}
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@$title = @$('h2')
|
||||
@$errorText = @$('.error p')
|
||||
@$markdownPanel = @$('.markdown')
|
||||
@$sizer = @$('.sizer')
|
||||
@$textarea = @$('textarea')
|
||||
@$title = @$('h2')
|
||||
|
||||
@_resetToDefaultState()
|
||||
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
@$title.html @title
|
||||
|
||||
text = @model
|
||||
text = @_convertWikiLinks text
|
||||
text = @_convertImageLinks text
|
||||
|
||||
@$markdownPanel.html convertMarkdown text
|
||||
|
||||
@_updateSizer()
|
||||
@_updatePreview()
|
||||
super
|
||||
|
||||
onWillChangeModel: (oldModel, newModel)->
|
||||
result = super oldModel, newModel
|
||||
@$textarea.val newModel
|
||||
|
||||
return result
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'click .markdown a': 'routeLinkClick'
|
||||
'click button.cancel': 'onCancelClicked'
|
||||
'click button.edit': 'onEditClicked'
|
||||
'click button.preview': 'onPreviewClicked'
|
||||
'click button.return': 'onReturnClicked'
|
||||
'click button.save': 'onSaveClicked'
|
||||
'input textarea': 'onTextChanged'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
@@ -66,3 +171,81 @@ module.exports = class MarkdownSectionController extends BaseController
|
||||
result = "[#{name}](#{display.itemUrl})"
|
||||
|
||||
return result
|
||||
|
||||
_updateSizer: ->
|
||||
text = @model
|
||||
if @model?
|
||||
text = text.replace /\n/g, '<br>'
|
||||
|
||||
@$sizer.html text
|
||||
|
||||
_updatePreview: ->
|
||||
text = @model
|
||||
if @model?
|
||||
text = @_convertWikiLinks text
|
||||
text = @_convertImageLinks text
|
||||
text = convertMarkdown text
|
||||
|
||||
@$markdownPanel.html text
|
||||
|
||||
_resetToDefaultState: ->
|
||||
if @model?
|
||||
@state = State.viewing
|
||||
else
|
||||
if @editable?
|
||||
@state = State.creating
|
||||
else
|
||||
@state = State.waiting
|
||||
|
||||
|
||||
_updateStateVisibility: ->
|
||||
return if @_lastUpdatedState is @state
|
||||
@_lastUpdatedState = @state
|
||||
|
||||
elements =
|
||||
appologizingPanel: @$('.appologizing')
|
||||
buttonPanel: @$('.buttons')
|
||||
cancelButton: @$('button.cancel')
|
||||
confirmingPanel: @$('.confirming')
|
||||
creatingPanel: @$('.creating')
|
||||
editButton: @$('button.edit')
|
||||
editorPanel: @$('.editor')
|
||||
errorPanel: @$('.error')
|
||||
markdownPanel: @$markdownPanel
|
||||
previewButton: @$('button.preview')
|
||||
returnButton: @$('button.return')
|
||||
saveButton: @$('button.save')
|
||||
waitingPanel: @$('.waiting')
|
||||
|
||||
visible = {}
|
||||
errorText = ''
|
||||
|
||||
if @state is State.appologizing
|
||||
visible = appologizingPanel:true
|
||||
else if @state is State.confirming
|
||||
visible = confirmingPanel:true
|
||||
else if @state is State.creating
|
||||
visible = buttonPanel:true, creatingPanel:true, editButton:true
|
||||
else if @state is State.editing
|
||||
visible = buttonPanel:true, cancelButton:true, editorPanel:true, previewButton:true, saveButton:true
|
||||
else if @state is State.previewing
|
||||
visible = buttonPanel:true, errorPanel:true, markdownPanel:true, returnButton:true
|
||||
errorText = "remember: your changes aren't saved yet!"
|
||||
else if @state is State.viewing
|
||||
visible = markdownPanel:true
|
||||
if @editable then _.extend visible, {buttonPanel:true, editButton:true}
|
||||
else # assume any unknown state is the same as "waiting"
|
||||
visible = waitingPanel:true
|
||||
|
||||
toHide = ($el for name, $el of elements when not visible[name])
|
||||
toShow = ($el for name, $el of elements when visible[name])
|
||||
|
||||
if toHide.length > 0
|
||||
@hide $el for $el in toHide
|
||||
if toShow.length > 0
|
||||
@once Event.animate.hide.finish, =>
|
||||
@show $el for $el in toShow
|
||||
else if toShow.length > 0
|
||||
@show $el for $el in toShow
|
||||
|
||||
@$errorText.html errorText
|
||||
|
||||
@@ -36,9 +36,10 @@ backbone = require 'backbone'
|
||||
module.exports = class CraftingGuideRouter extends backbone.Router
|
||||
|
||||
constructor: (options={})->
|
||||
@_lastReported = null
|
||||
@_page = null
|
||||
@_pageControllers = {}
|
||||
@_lastReported = null
|
||||
@_postLoginUrl = Url.root
|
||||
@_user = null
|
||||
super options
|
||||
|
||||
@@ -80,6 +81,16 @@ module.exports = class CraftingGuideRouter extends backbone.Router
|
||||
logger.info "User is not logged in"
|
||||
.done()
|
||||
|
||||
login: ->
|
||||
@storage.store 'post-login-url', window.location.pathname
|
||||
@navigate Url.login(), trigger:true
|
||||
|
||||
resumeAfterLogin: ->
|
||||
url = @storage.load 'post-login-url'
|
||||
url ?= Url.root()
|
||||
@navigate url, trigger:true
|
||||
@storage.store 'post-login-url', null
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
getUser: ->
|
||||
|
||||
@@ -45,6 +45,11 @@ module.exports = class Item extends BaseModel
|
||||
return if this.name < that.name then -1 else +1
|
||||
return 0
|
||||
|
||||
unparse: ->
|
||||
ItemParser = require './item_parser' # to avoid require cycles
|
||||
@_parser ?= new ItemParser model:this
|
||||
return @_parser.unparse()
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
getIsCraftable: ->
|
||||
|
||||
@@ -17,17 +17,23 @@ module.exports = class ItemParserV1 extends CommandParserVersionBase
|
||||
@_buildItem rawData, model
|
||||
|
||||
_unparseModel: (builder, model)->
|
||||
builder.line 'schema: ', 1
|
||||
builder.line()
|
||||
|
||||
@_unparseItem builder, model
|
||||
|
||||
# Command Methods ##############################################################################
|
||||
|
||||
_command_description: (textParts...)->
|
||||
@_rawData.description ?= ''
|
||||
if not @_rawData.description?
|
||||
@_rawData.description = ''
|
||||
else
|
||||
@_rawData.description += '\n'
|
||||
@_rawData.description += textParts.join ', '
|
||||
|
||||
_command_officialUrl: (officialUrl)->
|
||||
if @_rawData.officialUrl? then throw new Error 'duplicate declaration of "officialUrl"'
|
||||
if officialUrl.length is 0 then throw new Error 'officialUrl cannot be empty'
|
||||
if not officialUrl? or (officialUrl.length is 0) then throw new Error 'officialUrl cannot be empty'
|
||||
@_rawData.officialUrl = officialUrl
|
||||
|
||||
_command_video: (youTubeId, nameParts...)->
|
||||
@@ -41,6 +47,26 @@ module.exports = class ItemParserV1 extends CommandParserVersionBase
|
||||
# Object Building Methods ######################################################################
|
||||
|
||||
_buildItem: (rawData, model)->
|
||||
model.description = rawData.description if rawData.description
|
||||
model.officialUrl = rawData.officialUrl if rawData.officialUrl
|
||||
model.videos = rawData.videos if rawData.videos
|
||||
model.description = rawData.description if rawData.description?
|
||||
model.officialUrl = rawData.officialUrl if rawData.officialUrl?
|
||||
model.videos = rawData.videos if rawData.videos?
|
||||
|
||||
# Un-parsing Methods ###########################################################################
|
||||
|
||||
_unparseItem: (builder, model)->
|
||||
if model.officialUrl?
|
||||
builder.line 'officialUrl: ', model.officialUrl
|
||||
builder.line()
|
||||
|
||||
if model.description?
|
||||
if model.description.indexOf('\n') isnt -1
|
||||
builder.line 'description: <<-END'
|
||||
builder.line model.description
|
||||
builder.line 'END'
|
||||
else
|
||||
builder.line 'description: ', model.description
|
||||
builder.line()
|
||||
|
||||
for video in model.videos
|
||||
builder.line 'video: ', video.youTubeId, ', ', video.name
|
||||
builder.line()
|
||||
|
||||
@@ -7,4 +7,24 @@
|
||||
|
||||
.view__markdown_section.section
|
||||
h2
|
||||
.panel.markdown
|
||||
.panel
|
||||
.waiting.hideable: img(src='/images/wait.gif')
|
||||
.markdown.hideable.hidden
|
||||
.editor.hideable.hidden
|
||||
textarea
|
||||
.sizer
|
||||
.creating.hideable.hidden
|
||||
p Please help by adding a description!
|
||||
.buttons.hideable.hidden
|
||||
.error.hideable.hidden: p
|
||||
button.return.hideable.hidden return
|
||||
button.cancel.hideable.hidden cancel
|
||||
button.preview.hideable.hidden preview
|
||||
button.save.hideable.hidden save
|
||||
button.edit.hideable edit
|
||||
.confirming.hideable.hidden
|
||||
p Thanks for your help!
|
||||
p Your changes will appear on the site in a few minutes.
|
||||
.appologizing.hideable.hidden
|
||||
p Sorry, but something went wrong.
|
||||
p Please try again after a few moments.
|
||||
|
||||
@@ -32,9 +32,9 @@ All rights reserved.
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.error { background: $color-error !important; }
|
||||
.error { background: $color-error; }
|
||||
|
||||
.error-new { background: $color-error-new !important; }
|
||||
.error-new { background: $color-error-new; }
|
||||
|
||||
.float-right {
|
||||
float: right;
|
||||
|
||||
@@ -6,7 +6,102 @@ All rights reserved.
|
||||
*/
|
||||
|
||||
.view__markdown_section {
|
||||
|
||||
.appologizing, .confirming {
|
||||
width: 100%;
|
||||
|
||||
p {
|
||||
width: 100%;
|
||||
|
||||
margin: 1em auto;
|
||||
text-align: center;
|
||||
|
||||
&:first-child {
|
||||
font-size: $font-size-x-large;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 4em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
position: relative; width: 100%;
|
||||
|
||||
text-align: right;
|
||||
|
||||
button {
|
||||
margin: 0.66em 0 0 0.66em;
|
||||
min-width: 6.66em;
|
||||
padding: 0.25em 0.5em;
|
||||
}
|
||||
|
||||
.error {
|
||||
|
||||
height: 100%;
|
||||
|
||||
background: none;
|
||||
display: inline-block;
|
||||
|
||||
p {
|
||||
color: $color-error-new;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.creating {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
color: $color-gray-light;
|
||||
font-size: $font-size-x-large;
|
||||
margin: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.editor {
|
||||
width: 100%;
|
||||
|
||||
position: relative;
|
||||
min-height: 10em;
|
||||
|
||||
textarea, .sizer {
|
||||
width: 100%;
|
||||
border: 1px solid $color-gray-light;
|
||||
font-family: $font-family-input;
|
||||
font-size: $font-size-normal;
|
||||
}
|
||||
|
||||
textarea {
|
||||
position: absolute; height: 100%; width: 100%;
|
||||
|
||||
background: $color-gray-faint;
|
||||
resize: none;
|
||||
z-index: $layer-normal;
|
||||
|
||||
&:focus {
|
||||
background: $color-background-panel;
|
||||
border: 1px solid $color-gray-medium;
|
||||
}
|
||||
}
|
||||
|
||||
.sizer {
|
||||
padding-bottom: 1.5em;
|
||||
visibility: hidden;
|
||||
z-index: $layer-normal - 1;
|
||||
}
|
||||
}
|
||||
|
||||
.waiting {
|
||||
position: relative; min-height: 20em; width: 100%;
|
||||
|
||||
img {
|
||||
position: absolute; top: 50%; left: 50%;
|
||||
@include transform(translate(-50%, -50%));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,6 @@ All rights reserved.
|
||||
position: absolute; bottom: 5em; height: 3.2em; width: 100%;
|
||||
|
||||
.login {
|
||||
display: none; // Just until there's a feature which needs it.
|
||||
|
||||
position: absolute; top: 50%; right: 2em;
|
||||
@include transform(translateY(-50%));
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
@@ -0,0 +1,100 @@
|
||||
###
|
||||
Crafting Guide - item_parser_v1.test.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
Item = require '../../src/coffee/models/item'
|
||||
ItemParserV1 = require '../../src/coffee/models/parser_versions/item_parser_v1'
|
||||
_ = require 'underscore'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
baseText = item = parser = null
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
describe 'item_parser_v1.coffee', ->
|
||||
|
||||
beforeEach ->
|
||||
item = new Item name:'alpha'
|
||||
parser = new ItemParserV1 model:item
|
||||
|
||||
describe 'officialUrl', ->
|
||||
|
||||
it 'may be omitted', ->
|
||||
parser.parse 'schema: 1\nvideo: youtubeid, Video Alpha\ndescription: Bravo, Charlie'
|
||||
expect(item.officialUrl).to.be.null
|
||||
|
||||
it 'is assigned properly when given', ->
|
||||
parser.parse 'schema: 1\nofficialUrl: http://testurl.com'
|
||||
item.officialUrl.should.equal 'http://testurl.com'
|
||||
|
||||
it 'does not allow duplicate declarations', ->
|
||||
func = -> parser.parse 'schema: 1\nofficialUrl: http://testurl.com\nofficialUrl: http://testurl2.com'
|
||||
expect(func).to.throw Error, 'duplicate'
|
||||
|
||||
it 'does not allow an empty value if given', ->
|
||||
func = -> parser.parse 'schema: 1\nofficialUrl:'
|
||||
expect(func).to.throw Error, 'empty'
|
||||
|
||||
describe 'description', ->
|
||||
|
||||
it 'may be omitted', ->
|
||||
parser.parse 'schema: 1\nvideo: youTubeId, Video Alpha\nofficialUrl: http://testurl.com'
|
||||
expect(item.description).to.be.null
|
||||
|
||||
it 'is assigned properly when given', ->
|
||||
parser.parse 'schema: 1\ndescription: Alpha Bravo Charlie'
|
||||
item.description.should.equal 'Alpha Bravo Charlie'
|
||||
|
||||
it 'can be a heredoc', ->
|
||||
parser.parse 'schema: 1\ndescription: <<-END\nAlpha\nBravo\nCharlie\nEND'
|
||||
item.description.should.equal 'Alpha\nBravo\nCharlie'
|
||||
|
||||
it 'concatenates multiple declarations', ->
|
||||
parser.parse 'schema: 1\ndescription: Alpha\ndescription: Bravo'
|
||||
item.description.should.equal 'Alpha\nBravo'
|
||||
|
||||
describe 'video', ->
|
||||
|
||||
it 'may be omitted', ->
|
||||
parser.parse 'schema: 1\nofficialUrl: http://testurl.com\ndescription: Alpha Bravo Charlie'
|
||||
item.videos.should.eql []
|
||||
|
||||
it 'is assigned properly when given', ->
|
||||
parser.parse 'schema: 1\nvideo: youtubeid, Alpha Bravo'
|
||||
item.videos[0].should.eql youTubeId:'youtubeid', name:'Alpha Bravo'
|
||||
item.videos.length.should.equal 1
|
||||
|
||||
it 'may be included multiple times', ->
|
||||
parser.parse 'schema: 1\nvideo: youtubeid1, Alpha\nvideo: youtubeid2, Bravo'
|
||||
item.videos[0].should.eql youTubeId:'youtubeid1', name:'Alpha'
|
||||
item.videos[1].should.eql youTubeId:'youtubeid2', name:'Bravo'
|
||||
item.videos.length.should.equal 2
|
||||
|
||||
it 'requires a YouTubeId and name', ->
|
||||
func = -> parser.parse 'schema: 1\nvideo: alpha'
|
||||
expect(func).to.throw Error, 'requires a name'
|
||||
|
||||
describe 'unparsing', ->
|
||||
|
||||
it 'can round-trip a fully described item', ->
|
||||
text = """
|
||||
schema: 1
|
||||
|
||||
officialUrl: http://testurl.com
|
||||
|
||||
description: <<-END
|
||||
Alpha
|
||||
Bravo
|
||||
END
|
||||
|
||||
video: youtubeid1, Alpha Bravo
|
||||
video: youtubeid2, Charlie Delta
|
||||
|
||||
|
||||
"""
|
||||
parser.parse text
|
||||
parser.unparse().should.equal text
|
||||
Reference in New Issue
Block a user