241 lines
8.2 KiB
CoffeeScript
241 lines
8.2 KiB
CoffeeScript
###
|
|
Crafting Guide - markdown_section_controller.coffee
|
|
|
|
Copyright (c) 2015 by Redwood Labs
|
|
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'
|
|
editing: 'editing'
|
|
previewing: 'previewing'
|
|
viewing: 'viewing'
|
|
waiting: 'waiting'
|
|
|
|
constructor: (options={})->
|
|
@_state = State.waiting
|
|
|
|
if not options.modPack? then throw new Error 'options.modPack is required'
|
|
options.model ?= null
|
|
options.templateName = 'markdown_section'
|
|
super options
|
|
|
|
@confirmactionMessage = options.confirmationMessage
|
|
@confirmDuration = options.confirmationDuration ?= 5000
|
|
@imageBase = options.imageBase ?= ''
|
|
@modPack = options.modPack
|
|
@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
|
|
@state = State.viewing
|
|
|
|
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 => @state = State.viewing
|
|
|
|
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: ->
|
|
@$errorText = @$('.error p')
|
|
@$markdownPanel = @$('.markdown')
|
|
@$sizer = @$('.sizer')
|
|
@$textarea = @$('textarea')
|
|
@$title = @$('h2')
|
|
|
|
super
|
|
|
|
refresh: ->
|
|
@$title.html @title
|
|
|
|
@_updateSizer()
|
|
@_updatePreview()
|
|
@_updateStateVisibility()
|
|
super
|
|
|
|
onWillChangeModel: (oldModel, newModel)->
|
|
result = super oldModel, newModel
|
|
|
|
if @state is State.waiting and newModel?
|
|
@state = State.viewing
|
|
else if not newModel?
|
|
@state = State.waiting
|
|
|
|
@$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 ##############################################################################
|
|
|
|
_convertImageLinks: (text)->
|
|
text.replace /\!\[([^\]]*)\]\(([^\)]*)\)/g, (match, altText, fileName)=>
|
|
return ""
|
|
|
|
_convertWikiLinks: (text)->
|
|
text.replace /\[\[([^\]]*)\]\]/g, (match, name)=>
|
|
result = match
|
|
item = @modPack.findItemByName name
|
|
if item?
|
|
display = @modPack.findItemDisplay item.slug
|
|
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
|
|
|
|
_updateStateVisibility: ->
|
|
return if @_lastUpdatedState is @state
|
|
@_lastUpdatedState = @state
|
|
|
|
elements =
|
|
appologizingPanel: @$('.appologizing')
|
|
buttonPanel: @$('.buttons')
|
|
cancelButton: @$('button.cancel')
|
|
confirmingPanel: @$('.confirming')
|
|
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.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
|