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
+19 -3
View File
@@ -22,6 +22,7 @@ module.exports = (grunt)->
extensions: ['.coffee']
files:
'./dist/js/main.js': ['./src/coffee/main.coffee']
test:
options:
transform: ['coffeeify']
@@ -30,6 +31,15 @@ module.exports = (grunt)->
extensions: ['.coffee']
files:
'./dist/js/test.js': ['./src/coffee/test/test.coffee']
markdown:
options:
browserifyOptions:
debug: false
standalone: 'markdown'
files:
'./dist/js/markdown.js': ['./node_modules/markdown/lib/index.js']
when:
options:
browserifyOptions:
@@ -102,6 +112,12 @@ module.exports = (grunt)->
src: './static/'
dest: './dist/'
recursive: true
non_html:
options:
src: './static/'
dest: './dist/'
exclude: '*.html'
recursive: true
sass:
build:
@@ -134,7 +150,7 @@ module.exports = (grunt)->
tasks: ['browserify:main']
jade:
files: ['./src/**/*.jade']
tasks: ['jade:pages', 'jade:templates']
tasks: ['jade:pages', 'jade:templates', 'browserify:main']
sass:
files: ['./src/**/*.scss']
tasks: ['sass', 'copy:styles']
@@ -144,8 +160,8 @@ module.exports = (grunt)->
grunt.registerTask 'default', 'build'
grunt.registerTask 'build', [ 'rsync', 'copy', 'sass:build', 'jade', 'browserify', 'exorcise' ]
grunt.registerTask 'build', [ 'rsync:non_html', 'copy', 'sass:build', 'jade', 'browserify', 'exorcise' ]
grunt.registerTask 'dist', ['clean', 'build', 'copy:index_prerender', 'sass:dist', 'uglify']
grunt.registerTask 'dist', ['clean', 'build', 'rsync:static', 'copy:index_prerender', 'sass:dist', 'uglify']
grunt.registerTask 'clean-watch', ['clean', 'build', 'watch']
+1
View File
@@ -36,6 +36,7 @@
"chai": "^1.10.0",
"jade": "^1.8.2",
"jquery": "^2.1.3",
"markdown": "^0.5.0",
"mocha": "^2.0.1",
"sinon": "^1.12.2",
"sinon-chai": "^2.6.0",
+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;
Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

@@ -0,0 +1,15 @@
schema: 1
officialUrl: http://www.mod-buildcraft.com/wiki/doku.php?id=advanced_crafting_table
video: Z-Kl3IRjxE, Tutorial
description:<<-END
The advanced crafting table is an improved version of the basic automatic crafting table. It contains an inventory
for storing the input and an inventory for storing the outputs of the crafting process, making it potentially more
easy and seamless to create large automatic crafting systems. The advanced crafting table requires 500 MJ to craft
any recipe.
![GUI](./gui.png)
END