From 9d0ce8547afa51350fd693328ac8e526c3fbff65 Mon Sep 17 00:00:00 2001 From: Andrew Miner Date: Tue, 9 Jun 2015 15:41:04 -0700 Subject: [PATCH] Upload new/changed images to GitHub when saving MD --- src/coffee/constants.coffee | 8 +- .../controllers/item_page_controller.coffee | 35 +++-- .../markdown_image_controller.coffee | 5 +- .../markdown_image_list_controller.coffee | 5 +- .../markdown_section_controller.coffee | 14 +- src/coffee/main.coffee | 3 +- src/coffee/models/editable_file.coffee | 127 ++++++++++++++++++ src/coffee/models/markdown_image.coffee | 68 +--------- src/coffee/models/markdown_image_list.coffee | 8 +- 9 files changed, 173 insertions(+), 100 deletions(-) create mode 100644 src/coffee/models/editable_file.coffee diff --git a/src/coffee/constants.coffee b/src/coffee/constants.coffee index 9919113bc..a3ee1524f 100644 --- a/src/coffee/constants.coffee +++ b/src/coffee/constants.coffee @@ -88,9 +88,11 @@ 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.GitHub = GitHub = {} +GitHub.file = {} +GitHub.file.itemDescription = {} +GitHub.file.itemDescription.fileName = _.template "item.cg" +GitHub.file.itemDescription.path = _.template "/data/<%= modSlug %>/items/<%= itemSlug %>" exports.Login = Login = {} Login.authorizeUrl = _.template "https://github.com/login/oauth/authorize" + diff --git a/src/coffee/controllers/item_page_controller.coffee b/src/coffee/controllers/item_page_controller.coffee index c7c7212cf..7cb291222 100644 --- a/src/coffee/controllers/item_page_controller.coffee +++ b/src/coffee/controllers/item_page_controller.coffee @@ -6,6 +6,7 @@ All rights reserved. ### AdsenseController = require './adsense_controller' +EditableFile = require '../models/editable_file' FullRecipeController = require './full_recipe_controller' ImageLoader = require './image_loader' Item = require '../models/item' @@ -42,7 +43,7 @@ module.exports = class ItemPageController extends PageController @imageLoader = options.imageLoader @modPack = options.modPack - @_editingFile = null + @_descriptionFile = null @_itemSlug = options.itemSlug @modPack.on Event.change, => @tryRefresh() @@ -155,13 +156,16 @@ module.exports = class ItemPageController extends PageController 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 - @_editingFile.content = new Buffer(@_editingFile.content, 'base64').toString('utf8') + pathArgs = modSlug:@_itemSlug.mod, itemSlug:@_itemSlug.item + attributes = + fileName: GitHub.file.itemDescription.fileName pathArgs + path: GitHub.file.itemDescription.path pathArgs - if @_editingFile.content.length > 0 - @model.item.parse @_editingFile.content + @_descriptionFile = new EditableFile attributes, client:@client + @_descriptionFile.fetch() + .then => + if @_descriptionFile.encodedData.length > 0 + @model.item.parse @_descriptionFile.getDecodedData 'utf8' else @model.item.description = '' @@ -169,15 +173,18 @@ module.exports = class ItemPageController extends PageController _endEditingDescription: -> 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 + @_descriptionFile.setDecodedData @model.item.unparse() + promises.push @_descriptionFile.save commitMessage - args = - 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 + w.all promises .catch (e)=> @model.item.description = oldDescription throw e diff --git a/src/coffee/controllers/markdown_image_controller.coffee b/src/coffee/controllers/markdown_image_controller.coffee index 1ef83f1fe..9b1fdd2f7 100644 --- a/src/coffee/controllers/markdown_image_controller.coffee +++ b/src/coffee/controllers/markdown_image_controller.coffee @@ -46,10 +46,7 @@ module.exports = class MarkdownImageController extends BaseController mimeType = match[1] encoding = match[2] - @model.set - encodedData: encodedData - status: MarkdownImage.Status.updateable - + @model.encodedData = encodedData logger.info "loaded #{encodedData.length} bytes from #{file.name} as #{mimeType}" logger.info "starting to read local file: #{file.name}" diff --git a/src/coffee/controllers/markdown_image_list_controller.coffee b/src/coffee/controllers/markdown_image_list_controller.coffee index 45d3b0e7b..0cc71f18e 100644 --- a/src/coffee/controllers/markdown_image_list_controller.coffee +++ b/src/coffee/controllers/markdown_image_list_controller.coffee @@ -25,7 +25,7 @@ module.exports = class MarkdownImageListController extends BaseController # Public Methods ############################################################################### getImageUrlForFile: (fileName)-> - @model.getImageUrlForFile fileName + @model.getFile(fileName).imageUrl reset: -> @model.reset() @@ -68,7 +68,7 @@ module.exports = class MarkdownImageListController extends BaseController super refresh: -> - @model.loadImages() + @model.fetchImages() @_controllers ?= [] index = 0 @@ -95,6 +95,5 @@ module.exports = class MarkdownImageListController extends BaseController return if newValid is oldValid @_valid = newValid - logger.debug "setting mic.valid to #{@_valid}" @trigger Event.change + ':valid', this, oldValid, newValid @trigger Event.change diff --git a/src/coffee/controllers/markdown_section_controller.coffee b/src/coffee/controllers/markdown_section_controller.coffee index 322637394..ec121d29a 100644 --- a/src/coffee/controllers/markdown_section_controller.coffee +++ b/src/coffee/controllers/markdown_section_controller.coffee @@ -104,7 +104,8 @@ module.exports = class MarkdownSectionController extends BaseController .then => @state = State.confirming .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 .delay @confirmDuration .then => @@ -130,6 +131,9 @@ module.exports = class MarkdownSectionController extends BaseController @trigger Event.change + ':imageBase', this, oldImageBase, newImageBase @trigger Event.change, this + getImageFiles: -> + return @_imageListController.model.all + getState: -> return @_state @@ -143,9 +147,10 @@ module.exports = class MarkdownSectionController extends BaseController @_updateStateVisibility() Object.defineProperties @prototype, - editable: {get:@prototype.isEditable} - imageBase: {get:@prototype.getImageBase, set:@prototype.setImageBase} - state: {get:@prototype.getState, set:@prototype.setState} + editable: { get:@prototype.isEditable } + imageBase: { get:@prototype.getImageBase, set:@prototype.setImageBase } + imageFiles: { get:@prototype.getImageFiles } + state: { get:@prototype.getState, set:@prototype.setState } # BaseController Overrides ##################################################################### @@ -211,7 +216,6 @@ module.exports = class MarkdownSectionController extends BaseController _updateButtonStates: -> for $button in [ @$previewButton, @$saveButton ] - logger.debug "updating buttons based upon valid state: #{@_imageListController.valid}" if @_imageListController.valid $button.prop 'disabled', false else diff --git a/src/coffee/main.coffee b/src/coffee/main.coffee index 635252eb1..430b87d7e 100644 --- a/src/coffee/main.coffee +++ b/src/coffee/main.coffee @@ -23,9 +23,10 @@ if typeof(global) is 'undefined' window.global = window global = window.global +global.hostName = window.location.hostname global.logger = new Logger -switch window.location.hostname +switch global.hostName when 'prerender.crafting-guide.com' global.env = 'prerender' logger.level = Logger.FATAL diff --git a/src/coffee/models/editable_file.coffee b/src/coffee/models/editable_file.coffee new file mode 100644 index 000000000..45bfc69ed --- /dev/null +++ b/src/coffee/models/editable_file.coffee @@ -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 diff --git a/src/coffee/models/markdown_image.coffee b/src/coffee/models/markdown_image.coffee index b7ead70fe..27817d2b0 100644 --- a/src/coffee/models/markdown_image.coffee +++ b/src/coffee/models/markdown_image.coffee @@ -5,82 +5,20 @@ Copyright (c) 2015 by Redwood Labs All rights reserved. ### -BaseModel = require './base_model' -_ = require 'underscore' -{Event} = require '../constants' +_ = require 'underscore' +EditableFile = require './editable_file' ######################################################################################################################## -module.exports = class MarkdownImage extends BaseModel - - @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 +module.exports = class MarkdownImage extends EditableFile # Property Methods ############################################################################# - getFullPath: -> - return "#{@path}/#{@fileName}" - getImageUrl: -> if @mimeType? and @encodedData? return "data:#{@mimeType};base64,#{@encodedData}" else 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, - 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 '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 diff --git a/src/coffee/models/markdown_image_list.coffee b/src/coffee/models/markdown_image_list.coffee index f79d7f4d8..4f7aa9e9f 100644 --- a/src/coffee/models/markdown_image_list.coffee +++ b/src/coffee/models/markdown_image_list.coffee @@ -30,12 +30,10 @@ module.exports = class MarkdownImageList extends BaseModel # Public Methods ############################################################################### - getImageUrlForFile: (fileName)-> - result = @_images[fileName]?.imageUrl - result ?= "#{@imageBase}/#{fileName}" - return result + getFile: (fileName)-> + return @_images[fileName] - loadImages: -> + fetchImages: -> for fileName, image of @_images if image.status is MarkdownImage.Status.unknown image.fetch()