Upload new/changed images to GitHub when saving MD

This commit is contained in:
Andrew Miner
2015-06-09 15:41:04 -07:00
parent 4e39b9bb0c
commit 9d0ce8547a
9 changed files with 173 additions and 100 deletions
+5 -3
View File
@@ -88,9 +88,11 @@ Key.Escape = 27
Key.UpArrow = 38 Key.UpArrow = 38
Key.DownArrow = 40 Key.DownArrow = 40
exports.GitHub = GitHub = {} exports.GitHub = GitHub = {}
GitHub.file = {} GitHub.file = {}
GitHub.file.itemDescription = _.template "/data/<%= modSlug %>/items/<%= itemSlug %>/item.cg" GitHub.file.itemDescription = {}
GitHub.file.itemDescription.fileName = _.template "item.cg"
GitHub.file.itemDescription.path = _.template "/data/<%= modSlug %>/items/<%= itemSlug %>"
exports.Login = Login = {} exports.Login = Login = {}
Login.authorizeUrl = _.template "https://github.com/login/oauth/authorize" + Login.authorizeUrl = _.template "https://github.com/login/oauth/authorize" +
@@ -6,6 +6,7 @@ All rights reserved.
### ###
AdsenseController = require './adsense_controller' AdsenseController = require './adsense_controller'
EditableFile = require '../models/editable_file'
FullRecipeController = require './full_recipe_controller' FullRecipeController = require './full_recipe_controller'
ImageLoader = require './image_loader' ImageLoader = require './image_loader'
Item = require '../models/item' Item = require '../models/item'
@@ -42,7 +43,7 @@ module.exports = class ItemPageController extends PageController
@imageLoader = options.imageLoader @imageLoader = options.imageLoader
@modPack = options.modPack @modPack = options.modPack
@_editingFile = null @_descriptionFile = null
@_itemSlug = options.itemSlug @_itemSlug = options.itemSlug
@modPack.on Event.change, => @tryRefresh() @modPack.on Event.change, => @tryRefresh()
@@ -155,13 +156,16 @@ module.exports = class ItemPageController extends PageController
if not @model.item? if not @model.item?
return w.reject new Error 'must have an item' return w.reject new Error 'must have an item'
@client.fetchFile path:GitHub.file.itemDescription modSlug:@_itemSlug.mod, itemSlug:@_itemSlug.item pathArgs = modSlug:@_itemSlug.mod, itemSlug:@_itemSlug.item
.then (response)=> attributes =
@_editingFile = response.json.data fileName: GitHub.file.itemDescription.fileName pathArgs
@_editingFile.content = new Buffer(@_editingFile.content, 'base64').toString('utf8') path: GitHub.file.itemDescription.path pathArgs
if @_editingFile.content.length > 0 @_descriptionFile = new EditableFile attributes, client:@client
@model.item.parse @_editingFile.content @_descriptionFile.fetch()
.then =>
if @_descriptionFile.encodedData.length > 0
@model.item.parse @_descriptionFile.getDecodedData 'utf8'
else else
@model.item.description = '' @model.item.description = ''
@@ -169,15 +173,18 @@ module.exports = class ItemPageController extends PageController
_endEditingDescription: -> _endEditingDescription: ->
oldDescription = @model.item.description oldDescription = @model.item.description
promises = []
commitMessage = "User-submitted image for #{@model.item.name} from #{global.hostName}"
for imageFile in @_descriptionController.imageFiles
promises.push imageFile.save commitMessage
commitMessage = "User-submitted text for #{@model.item.name} from #{global.hostName}"
@model.item.description = @_descriptionController.model @model.item.description = @_descriptionController.model
@_descriptionFile.setDecodedData @model.item.unparse()
promises.push @_descriptionFile.save commitMessage
args = w.all promises
content: new Buffer(@model.item.unparse(), 'utf8').toString('base64')
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)=> .catch (e)=>
@model.item.description = oldDescription @model.item.description = oldDescription
throw e throw e
@@ -46,10 +46,7 @@ module.exports = class MarkdownImageController extends BaseController
mimeType = match[1] mimeType = match[1]
encoding = match[2] encoding = match[2]
@model.set @model.encodedData = encodedData
encodedData: encodedData
status: MarkdownImage.Status.updateable
logger.info "loaded #{encodedData.length} bytes from #{file.name} as #{mimeType}" logger.info "loaded #{encodedData.length} bytes from #{file.name} as #{mimeType}"
logger.info "starting to read local file: #{file.name}" logger.info "starting to read local file: #{file.name}"
@@ -25,7 +25,7 @@ module.exports = class MarkdownImageListController extends BaseController
# Public Methods ############################################################################### # Public Methods ###############################################################################
getImageUrlForFile: (fileName)-> getImageUrlForFile: (fileName)->
@model.getImageUrlForFile fileName @model.getFile(fileName).imageUrl
reset: -> reset: ->
@model.reset() @model.reset()
@@ -68,7 +68,7 @@ module.exports = class MarkdownImageListController extends BaseController
super super
refresh: -> refresh: ->
@model.loadImages() @model.fetchImages()
@_controllers ?= [] @_controllers ?= []
index = 0 index = 0
@@ -95,6 +95,5 @@ module.exports = class MarkdownImageListController extends BaseController
return if newValid is oldValid return if newValid is oldValid
@_valid = newValid @_valid = newValid
logger.debug "setting mic.valid to #{@_valid}"
@trigger Event.change + ':valid', this, oldValid, newValid @trigger Event.change + ':valid', this, oldValid, newValid
@trigger Event.change @trigger Event.change
@@ -104,7 +104,8 @@ module.exports = class MarkdownSectionController extends BaseController
.then => .then =>
@state = State.confirming @state = State.confirming
.catch (e)=> .catch (e)=>
logger.error "failed to end editing: #{e}" message = if e.stack? then e.stack else e
logger.error "failed to end editing: #{message}"
@state = State.appologizing @state = State.appologizing
.delay @confirmDuration .delay @confirmDuration
.then => .then =>
@@ -130,6 +131,9 @@ module.exports = class MarkdownSectionController extends BaseController
@trigger Event.change + ':imageBase', this, oldImageBase, newImageBase @trigger Event.change + ':imageBase', this, oldImageBase, newImageBase
@trigger Event.change, this @trigger Event.change, this
getImageFiles: ->
return @_imageListController.model.all
getState: -> getState: ->
return @_state return @_state
@@ -143,9 +147,10 @@ module.exports = class MarkdownSectionController extends BaseController
@_updateStateVisibility() @_updateStateVisibility()
Object.defineProperties @prototype, Object.defineProperties @prototype,
editable: {get:@prototype.isEditable} editable: { get:@prototype.isEditable }
imageBase: {get:@prototype.getImageBase, set:@prototype.setImageBase} imageBase: { get:@prototype.getImageBase, set:@prototype.setImageBase }
state: {get:@prototype.getState, set:@prototype.setState} imageFiles: { get:@prototype.getImageFiles }
state: { get:@prototype.getState, set:@prototype.setState }
# BaseController Overrides ##################################################################### # BaseController Overrides #####################################################################
@@ -211,7 +216,6 @@ module.exports = class MarkdownSectionController extends BaseController
_updateButtonStates: -> _updateButtonStates: ->
for $button in [ @$previewButton, @$saveButton ] for $button in [ @$previewButton, @$saveButton ]
logger.debug "updating buttons based upon valid state: #{@_imageListController.valid}"
if @_imageListController.valid if @_imageListController.valid
$button.prop 'disabled', false $button.prop 'disabled', false
else else
+2 -1
View File
@@ -23,9 +23,10 @@ if typeof(global) is 'undefined'
window.global = window window.global = window
global = window.global global = window.global
global.hostName = window.location.hostname
global.logger = new Logger global.logger = new Logger
switch window.location.hostname switch global.hostName
when 'prerender.crafting-guide.com' when 'prerender.crafting-guide.com'
global.env = 'prerender' global.env = 'prerender'
logger.level = Logger.FATAL logger.level = Logger.FATAL
+127
View File
@@ -0,0 +1,127 @@
###
Crafting Guide - editable_file.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
{Event} = require '../constants'
w = require 'when'
########################################################################################################################
module.exports = class EditableFile extends BaseModel
@MimeTypes = MimeTypes = [
{ pattern:/\.cg$/i, text:'text/plain' }
{ pattern:/\.png$/i, text:'image/png' }
{ pattern:/\.gif$/i, text:'image/gif' }
{ pattern:/\.jpg$/i, text:'image/jpeg' }
{ pattern:/\.jpeg$/i, text:'image/jpeg' }
]
@Status = Status = {
'unknown': 'unknown'
'checking': 'checking'
'empty': 'empty'
'clean': 'clean'
'dirty': 'dirty'
}
constructor: (attributes={}, options={})->
attributes.fileName ?= ''
attributes.path ?= ''
attributes.sha ?= null
attributes.status ?= Status.unknown
super
@client = options.client
@_encodedData = null
@on Event.change + ':fileName', => @_mimeType = null
# Public Methods ###############################################################################
getDecodedData: (targetEncoding='utf8')->
return new Buffer(@encodedData, 'base64').toString(targetEncoding)
setDecodedData: (text, sourceEncoding='utf8')->
@encodedData = new Buffer(text, sourceEncoding).toString('base64')
# Property Methods #############################################################################
getDataAsText: ->
if not @_dataAsText
@_dataAsText = new Buffer(@encodedData, 'base64').toString('utf8')
return @_dataAsText
getEncodedData: ->
return @_encodedData
setEncodedData: (newEncodedData)->
oldEncodedData = @_encodedData
return if newEncodedData is oldEncodedData
@_encodedData = newEncodedData
@_updateStatusForNewData()
@trigger Event.change + ':encodedData', this, oldEncodedData, newEncodedData
@trigger Event.change, this
getFullPath: ->
return "#{@path}/#{@fileName}"
getMimeType: ->
if not @_mimeType?
for mimeType in MimeTypes
if mimeType.pattern.test @fileName
@_mimeType = mimeType.text
break
return @_mimeType
isValid: ->
return @status in [ Status.clean, Status.dirty ]
Object.defineProperties @prototype,
encodedData: { get:@prototype.getEncodedData, set:@prototype.setEncodedData }
fullPath: { get:@prototype.getFullPath }
imageUrl: { get:@prototype.getImageUrl }
mimeType: { get:@prototype.getMimeType }
valid: { get:@prototype.isValid }
# BaseModel Overrides ##########################################################################
fetch: ->
if not @client? then throw new Error 'EditableFile must be given a client to fetch with'
@status = Status.checking
@client.fetchFile path:@fullPath
.then (response)=>
data = response.json.data
if data.sha?
@encodedData = data.content
@sha = data.sha
else
@status = Status.empty
return this
save: (commitMessage)->
return w(true) if @status is Status.unchanged
return w(true) unless @encodedData?
if not @valid then return w.reject new Error "cannot save with an invalid status: #{@status}"
commitMessage ?= "User-submitted update to #{@fileName} from #{global.hostName}"
@client.updateFile content:@encodedData, message:commitMessage, path:@fullPath, sha:@sha
# Private Methods ##############################################################################
_updateStatusForNewData: ->
switch @status
when Status.unknown then @status = Status.dirty
when Status.checking then @status = Status.clean
when Status.empty then @status = Status.dirty
when Status.clean then @status = Status.dirty
+3 -65
View File
@@ -5,82 +5,20 @@ Copyright (c) 2015 by Redwood Labs
All rights reserved. All rights reserved.
### ###
BaseModel = require './base_model' _ = require 'underscore'
_ = require 'underscore' EditableFile = require './editable_file'
{Event} = require '../constants'
######################################################################################################################## ########################################################################################################################
module.exports = class MarkdownImage extends BaseModel module.exports = class MarkdownImage extends EditableFile
@MimeTypes = MimeTypes = [
{pattern:/\.png$/i, text:'image/png'}
{pattern:/\.gif$/i, text:'image/gif'}
{pattern:/\.jpg$/i, text:'image/jpeg'}
{pattern:/\.jpeg$/i, text:'image/jpeg'}
]
@Status = Status = {
'unknown': 'unknown'
'checking': 'checking'
'empty': 'empty'
'creatable': 'creatable'
'unchanged': 'unchanged'
'updateable': 'updateable'
}
constructor: (attributes={}, options={})->
attributes.encodedData ?= null
attributes.fileName ?= ''
attributes.path ?= ''
attributes.sha ?= null
attributes.status ?= Status.unknown
super
@client = options.client
@on Event.change + ':fileName', => @_mimeType = null
# Property Methods ############################################################################# # Property Methods #############################################################################
getFullPath: ->
return "#{@path}/#{@fileName}"
getImageUrl: -> getImageUrl: ->
if @mimeType? and @encodedData? if @mimeType? and @encodedData?
return "data:#{@mimeType};base64,#{@encodedData}" return "data:#{@mimeType};base64,#{@encodedData}"
else else
return @fullPath return @fullPath
getMimeType: ->
if not @_mimeType?
for mimeType in MimeTypes
if mimeType.pattern.test @fileName
@_mimeType = mimeType.text
break
return @_mimeType
isValid: ->
return @status in [ Status.creatable, Status.unchanged, Status.updateable ]
Object.defineProperties @prototype, Object.defineProperties @prototype,
fullPath: {get:@prototype.getFullPath}
imageUrl: {get:@prototype.getImageUrl} imageUrl: {get:@prototype.getImageUrl}
mimeType: {get:@prototype.getMimeType}
valid: {get:@prototype.isValid}
# BaseModel Overrides ##########################################################################
fetch: ->
if not @client? then throw new Error 'MarkdownImage must be given a client to fetch with'
@status = Status.checking
@client.fetchFile path:@fullPath
.then (response)=>
data = response.json.data
if data.sha?
@set encodedData:data.content, sha:data.sha, status:Status.unchanged
else
@status = Status.empty
+3 -5
View File
@@ -30,12 +30,10 @@ module.exports = class MarkdownImageList extends BaseModel
# Public Methods ############################################################################### # Public Methods ###############################################################################
getImageUrlForFile: (fileName)-> getFile: (fileName)->
result = @_images[fileName]?.imageUrl return @_images[fileName]
result ?= "#{@imageBase}/#{fileName}"
return result
loadImages: -> fetchImages: ->
for fileName, image of @_images for fileName, image of @_images
if image.status is MarkdownImage.Status.unknown if image.status is MarkdownImage.Status.unknown
image.fetch() image.fetch()