Show markdown description text on item detail page

* Update build process to *not* copy over prerendered pages during a
  regular build (only during a `dist` build)
* Add dependency on npm "markdown" package and include it in the
  scripts loaded in the default layout
* Update the Item model to be able to load from a *.cg file
* Add an ItemParser to process the `item.cg` file
* Update the general parser to be able to work with HereDoc-style
  text blocks
* Add a markdown-specific stylesheet
* Add a sample of the `item.cg` file format (Advanced Crafting Table)
This commit is contained in:
Andrew Miner
2015-03-08 22:24:15 -07:00
parent f92e98674a
commit ff17d5f9ba
21 changed files with 183 additions and 69 deletions
+9 -8
View File
@@ -56,14 +56,15 @@ ModelState.failed = 'failed'
exports.Text = Text = {}
Text.title = 'Crafting Guide for Minecraft | The Ultimate Step-by-Step Tutorial for Making Anything in Minecraft'
exports.Url = Url = {}
Url.crafting = _.template "/craft/<%= inventoryText %>"
Url.itemIcon = _.template "/browse/<%= modSlug %>/<%= itemSlug %>/icon.png"
Url.item = _.template "/browse/<%= modSlug %>/<%= itemSlug %>/"
Url.mod = _.template "/browse/<%= modSlug %>/"
Url.modData = _.template "/data/<%= modSlug %>/mod.cg"
Url.modIcon = _.template "/browse/<%= modSlug %>/icon.png"
Url.modVersion = _.template "/data/<%= modSlug %>/<%= modVersion %>/mod-version.cg"
exports.Url = Url = {}
Url.crafting = _.template "/craft/<%= inventoryText %>"
Url.item = _.template "/browse/<%= modSlug %>/<%= itemSlug %>/"
Url.itemData = _.template "/browse/<%= modSlug %>/<%= itemSlug %>/item.cg"
Url.itemIcon = _.template "/browse/<%= modSlug %>/<%= itemSlug %>/icon.png"
Url.mod = _.template "/browse/<%= modSlug %>/"
Url.modData = _.template "/data/<%= modSlug %>/mod.cg"
Url.modIcon = _.template "/browse/<%= modSlug %>/icon.png"
Url.modVersionData = _.template "/data/<%= modSlug %>/<%= modVersion %>/mod-version.cg"
exports.UrlParam = UrlParam = {}
UrlParam.quantity = 'count'
@@ -18,8 +18,8 @@ module.exports = class ItemController extends BaseController
options.templateName = 'item'
super options
@_imageLoader = options.imageLoader
@_modPack = options.modPack
@imageLoader = options.imageLoader
@modPack = options.modPack
# BaseController Overrides #####################################################################
@@ -30,9 +30,9 @@ module.exports = class ItemController extends BaseController
super
refresh: ->
display = @_modPack.findItemDisplay @model.slug
display = @modPack.findItemDisplay @model.slug
@_imageLoader.load display.iconUrl, @$icon
@imageLoader.load display.iconUrl, @$icon
@$name.html display.itemName
@$nameLink.attr 'href', display.itemUrl
@@ -76,7 +76,6 @@ module.exports = class ItemGroupController extends BaseController
delay += @_delayStep
else
controller.model = item
controller.refresh()
controllerIndex += 1
while @_itemControllers.length > controllerIndex
@@ -57,15 +57,17 @@ module.exports = class ItemPageController extends PageController
imageLoader: @imageLoader
modPack: @modPack
@$usedAsToolToMakeSection = @$('.usedAsToolToMake')
@$byline = @$('.byline')
@$bylineLink = @$('.byline a')
@$craftingPlanLink = @$('a.craftingPlan')
@$descriptionPanel = @$('.description .panel')
@$descriptionSection = @$('.description')
@$name = @$('h1.name')
@$recipeContainer = @$('.recipes .panel')
@$recipesSection = @$('.recipes')
@$similarSection = @$('.similar')
@$titleImage = @$('.titleImage img')
@$usedAsToolToMakeSection = @$('.usedAsToolToMake')
@$usedToMakeSection = @$('.usedToMake')
super
@@ -84,6 +86,7 @@ module.exports = class ItemPageController extends PageController
@$el.slideUp duration:Duration.normal
@_refreshByline()
@_refreshDescription()
@_refreshRecipes()
@_refreshSimilarItems()
@_refreshUsedAsToolToMake()
@@ -100,15 +103,6 @@ module.exports = class ItemPageController extends PageController
# Private Methods ##############################################################################
_refreshUsedAsToolToMake: ->
@_usedAsToolToMakeController.title = 'Used as Tool to Make'
@_usedAsToolToMakeController.model = @model.findToolForRecipes()
if @_usedAsToolToMakeController.model?
@$usedAsToolToMakeSection.slideDown duration:Duration.normal
else
@$usedAsToolToMakeSection.slideUp duration:Duration.normal
_refreshByline: ->
mod = @model.item?.modVersion?.mod
if mod?.name?.length > 0
@@ -118,14 +112,15 @@ module.exports = class ItemPageController extends PageController
else
@$byline.fadeOut duration:Duration.fast
_refreshUsedToMake: ->
@_usedToMakeController.title = 'Used to Make'
@_usedToMakeController.model = @model.findComponentInItems()
if @_usedToMakeController.model?
@$usedToMakeSection.slideDown duration:Duration.normal
_refreshDescription: ->
logger.debug "refreshing description"
logger.debug "item.description: #{@model?.item?.description}"
description = @model.compileDescription()
if description?
@$descriptionPanel.html description
@$descriptionSection.slideDown duration:Duration.normal
else
@$usedToMakeSection.slideUp duration:Duration.normal
@$descriptionSection.slideUp duration:Duration.normal
_refreshRecipes: ->
@_recipeControllers ?= []
@@ -165,10 +160,35 @@ module.exports = class ItemPageController extends PageController
else
@$similarSection.slideUp duration:Duration.normal
_resolveItemSlug: ->
item = @modPack.findItem @_itemSlug, includeDisabled:false
if item? and not ItemSlug.equal item.slug, @_itemSlug
router.navigate Url.item(modSlug:item.slug.mod, itemSlug:item.slug.item), trigger:true
return
_refreshUsedAsToolToMake: ->
@_usedAsToolToMakeController.title = 'Used as Tool to Make'
@model.item = item
@_usedAsToolToMakeController.model = @model.findToolForRecipes()
if @_usedAsToolToMakeController.model?
@$usedAsToolToMakeSection.slideDown duration:Duration.normal
else
@$usedAsToolToMakeSection.slideUp duration:Duration.normal
_refreshUsedToMake: ->
@_usedToMakeController.title = 'Used to Make'
@_usedToMakeController.model = @model.findComponentInItems()
if @_usedToMakeController.model?
@$usedToMakeSection.slideDown duration:Duration.normal
else
@$usedToMakeSection.slideUp duration:Duration.normal
_resolveItemSlug: ->
return if @model.item?
item = @modPack.findItem @_itemSlug, includeDisabled:false
if item?
if not ItemSlug.equal item.slug, @_itemSlug
router.navigate Url.item(modSlug:item.slug.mod, itemSlug:item.slug.item), trigger:true
return
@model.item = item
item.fetch()
item.on Event.sync, =>
logger.debug "item loaded"
@refresh()
+13
View File
@@ -10,6 +10,7 @@ BaseModel = require './base_model'
ItemSlug = require './item_slug'
Recipe = require './recipe'
StringBuilder = require './string_builder'
{Url} = require '../constants'
########################################################################################################################
@@ -57,6 +58,18 @@ module.exports = class Item extends BaseModel
Object.defineProperties @prototype,
isCraftable: {get:@prototype.getIsCraftable}
# Backbone.Model Overrides #####################################################################
parse: (text)->
ItemParser = require './item_parser' # to avoid require cycles
@_parser ?= new ItemParser model:this
@_parser.parse text
return null # prevent calling `set`
url: ->
return Url.itemData modSlug:@slug.mod, itemSlug:@slug.item
# Object Overrides #############################################################################
toString: ->
+4
View File
@@ -20,6 +20,10 @@ module.exports = class ItemPage extends BaseModel
# Property Methods #############################################################################
compileDescription: ->
return null unless @item?.description?
return markdown.parse @item.description
findComponentInItems: ->
return @_findRecipesMatching (recipe)=> recipe.requires @item.slug
+19
View File
@@ -0,0 +1,19 @@
###
Crafting Guide - item_parser.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
VersionedParserBase = require './versioned_parser_base'
ItemParserV1 = require './parser_versions/item_parser_v1'
########################################################################################################################
module.exports = class ItemParser extends VersionedParserBase
# VersionedParserBase Overrides ################################################################
_createParsers: (options)->
return result =
'1': new ItemParserV1 options
+1 -1
View File
@@ -171,7 +171,7 @@ module.exports = class ModVersion extends BaseModel
return null # prevent calling `set`
url: ->
return Url.modVersion modSlug:@modSlug, modVersion:@version
return Url.modVersionData modSlug:@modSlug, modVersion:@version
# Object Overrides #############################################################################
@@ -78,7 +78,7 @@ module.exports = class CommandParserVersionBase
line = line.trim()
return [] if line.length is 0
hereDoc = @_parseHereDoc line
[line, hereDoc] = @_parseHereDoc line
lineParts = (part.trim() for part in line.split(';'))
commands = []
@@ -89,8 +89,10 @@ module.exports = class CommandParserVersionBase
if not match? then throw new Error "Expected <command>: <args>, but found: \"#{linePart}\""
args = []
args = (s.trim() for s in match[2].split(',')) if match[2]?
args.push hereDoc if hereDoc
args = (s for s in match[2].split(',') when s.length > 0) if match[2]?
args = (s.trim() for s in args)
args = (s for s in args when s.length > 0)
args.push hereDoc if hereDoc?
commands.push name:match[1], args:args
return commands
@@ -107,9 +109,11 @@ module.exports = class CommandParserVersionBase
_parseHereDoc: (line)->
hereDocIndex = line.indexOf '<<-'
return null unless hereDocIndex isnt -1
return [line, null] unless hereDocIndex isnt -1
hereDocStopText = line[hereDocIndex+3...line.length]
line = line[0...hereDocIndex]
hereDocLines = []
while true
@_lineNumber += 1
@@ -120,11 +124,11 @@ module.exports = class CommandParserVersionBase
hereDocLines.push nextLine
shortestIndent = Number.MAX_VALUE
for line in hereDocLines
shortestIndent = Math.min line.match(/( *).*/)[1].length, shortestIndent
for hereDocLine in hereDocLines
shortestIndent = Math.min hereDocLine.match(/( *).*/)[1].length, shortestIndent
for i in [0...hereDocLines.length]
hereDocLines[i] = hereDocLines[i][shortestIndent..]
return null unless hereDocLines.length > 0
return hereDocLines.join '\n'
return [line, null] unless hereDocLines.length > 0
return [line, hereDocLines.join('\n')]
@@ -22,16 +22,17 @@ module.exports = class ItemParserV1 extends CommandParserVersionBase
# Command Methods ##############################################################################
_command_description: (textParts...)->
@_rawData.text ?= ''
@_rawData.text += textParts.join ', '
@_rawData.description ?= ''
@_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'
@_rawData.officialUrl = officialUrl
_command_video: (youTubeId, name)->
_command_video: (youTubeId, nameParts...)->
if not youTubeId?.length then throw new Error 'video declaration requires a YouTubeID'
name = nameParts.join ', '
if not name?.length then throw new Error 'video declaration requires a name'
@_rawData.videos ?= []
@@ -40,6 +41,6 @@ module.exports = class ItemParserV1 extends CommandParserVersionBase
# Object Building Methods ######################################################################
_buildItem: (rawData, model)->
item.description = rawData.description if rawData.description
item.officialUrl = rawData.officialUrl if rawData.officialUrl
item.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
@@ -21,25 +21,28 @@ describe 'command_parser_version_base.coffee', ->
it 'returns null for non-heredoc lines', ->
result = parser._parseHereDoc 'foobar: baz'
expect(result).to.be.null
expect(result[1]).to.be.null
it 'identifies the right text for a real heredoc', ->
parser._lines = ['command: <<-END', 'alpha', 'bravo', 'charlie', 'END', 'command1: arg2']
parser._lineNumber = 1
result = parser._parseHereDoc parser._lines[0]
result.should.equal 'alpha\nbravo\ncharlie'
result[0].should.equal 'command: '
result[1].should.equal 'alpha\nbravo\ncharlie'
it 'identifies an empty heredoc', ->
parser._lines = ['command: <<-END', 'END']
parser._lineNumber = 1
result = parser._parseHereDoc parser._lines[0]
expect(result).to.be.null
result[0].should.equal 'command: '
expect(result[1]).to.be.null
it 'trims smallest leading whitespace', ->
parser._lines = ['command: <<-END', ' alpha', ' bravo', ' charlie', 'END', 'command1: arg2']
parser._lineNumber = 1
result = parser._parseHereDoc parser._lines[0]
result.should.equal 'alpha\n bravo\ncharlie'
result[0].should.equal 'command: '
result[1].should.equal 'alpha\n bravo\ncharlie'
+1 -1
View File
@@ -103,7 +103,7 @@ var buf = [];
var jade_mixins = {};
var jade_interp;
buf.push("<div class=\"view__item_page\"><div class=\"sidebar\"><div class=\"titleImage\"><a><img/></a></div><a class=\"craftingPlan externalLink\"><p>See Crafting Plan</p></a><script async src=\"//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"></script>\n<!-- Sidebar Skyscraper -->\n<ins class=\"adsbygoogle\"\n style=\"display:inline-block;width:160px;height:600px\"\n data-ad-client=\"ca-pub-6593013914878730\"\n data-ad-slot=\"7613920409\"></ins>\n<script>\n(adsbygoogle = window.adsbygoogle || []).push({});\n</script>\n</div><div class=\"mainBody\"><h1 class=\"name\"></h1><div class=\"byline\"><p>from <a></a></p></div><div class=\"description\"><p></p></div><div class=\"recipes section\"><h2>Recipes</h2><div class=\"panel\"></div></div><div class=\"usedToMake\"><div class=\"view__item_group\"></div></div><div class=\"usedAsToolToMake\"><div class=\"view__item_group\"></div></div><div class=\"similar\"><div class=\"view__item_group\"></div></div><div class=\"plan\"></div></div></div>");;return buf.join("");
buf.push("<div class=\"view__item_page\"><div class=\"sidebar\"><div class=\"titleImage\"><a><img/></a></div><a class=\"craftingPlan externalLink\"><p>See Crafting Plan</p></a><script async src=\"//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"></script>\n<!-- Sidebar Skyscraper -->\n<ins class=\"adsbygoogle\"\n style=\"display:inline-block;width:160px;height:600px\"\n data-ad-client=\"ca-pub-6593013914878730\"\n data-ad-slot=\"7613920409\"></ins>\n<script>\n(adsbygoogle = window.adsbygoogle || []).push({});\n</script>\n</div><div class=\"mainBody\"><h1 class=\"name\"></h1><div class=\"byline\"><p>from <a></a></p></div><div class=\"description section\"><h2>Description</h2><div class=\"panel markdown\"></div></div><div class=\"recipes section\"><h2>Recipes</h2><div class=\"panel\"></div></div><div class=\"usedToMake\"><div class=\"view__item_group\"></div></div><div class=\"usedAsToolToMake\"><div class=\"view__item_group\"></div></div><div class=\"similar\"><div class=\"view__item_group\"></div></div><div class=\"plan\"></div></div></div>");;return buf.join("");
};
this["JST"]["minimal_recipe"] = function template(locals) {
+1
View File
@@ -34,4 +34,5 @@ html
script(src="/js/jquery.js")
script(src="/js/backbone.js")
script(src="/js/jade.js")
script(src="/js/markdown.js")
script(src="/js/when.js")
+4 -1
View File
@@ -15,7 +15,10 @@
.mainBody
h1.name
.byline: p from <a></a>
.description: p
.description.section
h2 Description
.panel.markdown
.recipes.section
h2 Recipes
+1
View File
@@ -86,6 +86,7 @@ $layer-dialog: 1000;
// Child Stylesheets ///////////////////////////////////////////////////////////////////////////////////////////////////
@import 'classes';
@import 'markdown';
@import 'tags';
@import 'views';
+22
View File
@@ -0,0 +1,22 @@
/*
Crafting Guide - markdown.scss
Copyright (C) 2015 by Redwood Labs
All rights reserved.
*/
.markdown {
p {
font-family: $font-family-normal;
font-size: $font-size-normal;
line-height: $font-size-normal + 0.25em;
}
img {
position: relative; left: 50%;
@include transform(translateX(-50%));
margin: 2em 0;
text-align: center;
}
}
-9
View File
@@ -31,15 +31,6 @@ All rights reserved.
}
}
.description {
margin-bottom: 4em;
p {
font-family: $font-family-normal;
font-size: $font-size-large;
}
}
.recipes {
display: none;