diff --git a/design/tutorial-scratchpad.graffle b/design/tutorial-scratchpad.graffle new file mode 100644 index 000000000..6edd479cb Binary files /dev/null and b/design/tutorial-scratchpad.graffle differ diff --git a/scripts/sitemap b/scripts/sitemap index 5986ae28e..6a7760f3b 100755 --- a/scripts/sitemap +++ b/scripts/sitemap @@ -16,6 +16,7 @@ fs = require 'fs' modSlugs = {} itemSlugs = {} +tutorialSlugs = [] base = 'http://crafting-guide.com' @@ -41,9 +42,15 @@ for modSlug, modData of DefaultMods modVersion.eachItem (item)-> itemSlugs[item.slug] = item.slug + for tutorial in mod.tutorials + tutorialSlugs.push mod:mod.slug, tutorial:tutorial.slug + for modSlug in _.values(modSlugs).sort() urls.push base + "/browse/#{modSlug}/" +for tutorialSlug in tutorialSlugs + urls.push base + "/browse/#{tutorialSlug.mod}/tutorials/#{tutorialSlug.tutorial}/" + for key in _.keys(itemSlugs).sort() itemSlug = itemSlugs[key] urls.push base + "/browse/#{itemSlug.mod}/#{itemSlug.item}/" diff --git a/src/coffee/constants.coffee b/src/coffee/constants.coffee index bb5824ec2..8ab9e87fa 100644 --- a/src/coffee/constants.coffee +++ b/src/coffee/constants.coffee @@ -69,6 +69,9 @@ 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" +Url.tutorial = _.template "/browse/<%= modSlug %>/tutorials/<%= tutorialSlug %>/" +Url.tutorialData = _.template "/data/<%= modSlug %>/tutorials/<%= tutorialSlug %>.cg" +Url.tutorialIcon = _.template "/browse/<%= modSlug %>/tutorials/<%= tutorialSlug %>/icon.png" exports.UrlParam = UrlParam = {} UrlParam.quantity = 'count' diff --git a/src/coffee/controllers/markdown_section_controller.coffee b/src/coffee/controllers/markdown_section_controller.coffee new file mode 100644 index 000000000..52c3139d6 --- /dev/null +++ b/src/coffee/controllers/markdown_section_controller.coffee @@ -0,0 +1,40 @@ +### +Crafting Guide - markdown_section_controller.coffee + +Copyright (c) 2015 by Redwood Labs +All rights reserved. +### + +BaseController = require './base_controller' + +######################################################################################################################## + +module.exports = class MarkdownSectionController extends BaseController + + constructor: (options={})-> + options.model ?= '' + options.title ?= 'Description' + options.templateName = 'markdown_section' + super options + + @title = options.title + + # BaseController Overrides ##################################################################### + + onDidRender: -> + @$title = @$('h2') + @$markdownPanel = @$('.markdown') + super + + refresh: -> + @$title.html @title + + @$markdownPanel.empty() + @$markdownPanel.html _.parseMarkdown @model + super + + # Backbone.View Overrides ###################################################################### + + events: -> + return _.extend super, + 'click .markdown': 'routeLinkClick' diff --git a/src/coffee/controllers/mod_page_controller.coffee b/src/coffee/controllers/mod_page_controller.coffee index 634723043..600ca93fc 100644 --- a/src/coffee/controllers/mod_page_controller.coffee +++ b/src/coffee/controllers/mod_page_controller.coffee @@ -11,6 +11,7 @@ ItemGroupController = require './item_group_controller' Mod = require '../models/mod' ModPack = require '../models/mod_pack' PageController = require './page_controller' +TutorialController = require './tutorial_controller' {Duration} = require '../constants' {Text} = require '../constants' {Url} = require '../constants' @@ -55,15 +56,17 @@ module.exports = class ModPageController extends PageController onDidRender: -> @adsenseController = @addChild AdsenseController, '.view__adsense', model:'sidebar_skyscraper' - @$name = @$('.name') - @$byline = @$('.byline p') - @$description = @$('.description p') - @$documentationLink = @$('.documentation') - @$downloadLink = @$('.download') - @$homePageLink = @$('.homePage') - @$groupContainer = @$('.itemGroups') - @$titleImage = @$('.titleImage img') - @$versionSelector = @$('select.version') + @$name = @$('.name') + @$byline = @$('.byline p') + @$description = @$('.description p') + @$documentationLink = @$('.documentation') + @$downloadLink = @$('.download') + @$homePageLink = @$('.homePage') + @$groupContainer = @$('.itemGroups') + @$titleImage = @$('.titleImage img') + @$tutorialsSection = @$('.tutorials') + @$tutorialsContainer = @$('.tutorials .panel') + @$versionSelector = @$('select.version') super @@ -83,6 +86,7 @@ module.exports = class ModPageController extends PageController @_refreshLink @$downloadLink, @model.downloadUrl @_refreshItemGroups() + @_refreshTutorials() @_refreshVersions() super @@ -128,7 +132,7 @@ module.exports = class ModPageController extends PageController while @_groupControllers.length > groupIndex + 1 controller = @_groupControllers.pop() - controller.$el.slideUp duration:Duration.normal, -> @remove() + controller.$el.slideUp duration:Duration.normal, complete:-> @remove() _refreshLink: ($link, url)-> if url? @@ -137,6 +141,32 @@ module.exports = class ModPageController extends PageController else $link.slideUp duration:Duration.normal + _refreshTutorials: -> + @_tutorialControllers ?= [] + tutorials = @model.tutorials + + if tutorials.length is 0 + @$tutorialsSection.addClass 'hidden' + else + @$tutorialsSection.removeClass 'hidden' + + index = 0 + for tutorial in tutorials + controller = @_tutorialControllers[index] + if not controller? + controller = new TutorialController model:tutorial + controller.render() + @$tutorialsContainer.append controller.$el + @_tutorialControllers.push controller + else + controller.model = model + + index += 1 + + while @_tutorialControllers.length > index + controller = @_tutorialControllers.pop() + controller.$el.slideUp duration:Duration.normal, complete:-> @remove() + _refreshVersions: -> @$versionSelector.empty() return unless @model? diff --git a/src/coffee/controllers/tutorial_controller.coffee b/src/coffee/controllers/tutorial_controller.coffee new file mode 100644 index 000000000..8c590d582 --- /dev/null +++ b/src/coffee/controllers/tutorial_controller.coffee @@ -0,0 +1,39 @@ +### +Crafting Guide - tutorial_controller.coffee + +Copyright (c) 2015 by Redwood Labs +All rights reserved. +### + +BaseController = require './base_controller' +{Url} = require '../constants' + +######################################################################################################################## + +module.exports = class TutorialController extends BaseController + + constructor: (options={})-> + if not options.model? then throw new Error 'options.model is required' + options.templateName = 'tutorial' + super options + + # BaseController Overrides ##################################################################### + + onDidRender: -> + @$icon = @$('img') + @$link = @$('a') + @$title = @$('p') + super + + refresh: -> + templateData = modSlug:@model.modSlug, tutorialSlug:@model.slug + @$icon.attr 'src', Url.tutorialIcon templateData + @$link.attr 'href', Url.tutorial templateData + @$title.html @model.name + super + + # Backbone.View Overrides ###################################################################### + + events: -> + return _.extend super, + 'click a': 'routeLinkClick' diff --git a/src/coffee/controllers/tutorial_page_controller.coffee b/src/coffee/controllers/tutorial_page_controller.coffee new file mode 100644 index 000000000..f56c5202e --- /dev/null +++ b/src/coffee/controllers/tutorial_page_controller.coffee @@ -0,0 +1,159 @@ +### +Crafting Guide - tutorial_page_controller.coffee + +Copyright (c) 2015 by Redwood Labs +All rights reserved. +### + +AdsenseController = require './adsense_controller' +BaseController = require './base_controller' +MarkdownSectionController = require './markdown_section_controller' +VideoController = require './video_controller' +{Duration} = require '../constants' +{Event} = require '../constants' +{Url} = require '../constants' + +######################################################################################################################## + +module.exports = class TutorialPageController extends BaseController + + constructor: (options={})-> + if not options.modSlug? then throw new Error 'options.modSlug is required' + if not options.tutorialSlug? then throw new Error 'options.tutorialSlug is required' + if not options.modPack? then throw new Error 'options.modPack is required' + + options.model ?= null + options.templateName = 'tutorial_page' + super options + + @modPack = options.modPack + @imageLoader = options.imageLoader + @modSlug = options.modSlug + @tutorialSlug = options.tutorialSlug + + @modPack.on Event.change, => @_resolveTutorial() + @_resolveTutorial() + + # BaseController Overrides ##################################################################### + + onDidRender: -> + @adsenseController = @addChild AdsenseController, '.view__adsense', model:'sidebar_skyscraper' + + @$byline = @$('.byline') + @$bylineLink = @$('.byline a') + @$name = @$('.name') + @$officialLink = @$('a.officialPage') + @$sectionsContainer = @$('.sections') + @$title = @$('h1.name') + @$titleImage = @$('.titleImage img') + @$videosSection = @$('.videos') + @$videosSectionTitle = @$('.videos h2') + @$videosSectionPanel = @$('.videos .panel') + super + + refresh: -> + if @model? and @model.isLoaded + @$el.removeClass 'hidden' + else + @$el.addClass 'hidden' + + @_refreshByline() + @_refreshOfficialUrl() + @_refreshSections() + @_refreshTitle() + @_refreshVideos() + super + + # Private Methods ############################################################################## + + _refreshByline: -> + if @model? + @$byline.removeClass 'hidden' + @$bylineLink.html @modPack.getMod(@modSlug).name + @$bylineLink.attr 'href', Url.mod modSlug:@modSlug + else + @$byline.addClass 'hidden' + + _refreshOfficialUrl: -> + if @model?.officialUrl? + @$officialLink.attr 'href', @model.officialUrl + @$officialLink.removeClass 'hidden' + else + @$officialLink.addClass 'hidden' + + _refreshTitle: -> + if @model? + @$title.html @model.name + @$titleImage.attr 'src', Url.tutorialIcon modSlug:@modSlug, tutorialSlug:@tutorialSlug + else + @$title.empty() + @$titleImage.removeAttr 'src' + + _refreshSections: -> + @_sectionControllers ?= [] + index = 0 + + if @model? + for section in @model.sections + controller = @_sectionControllers[index] + if not controller? + controller = new MarkdownSectionController title:section.title, model:section.content + controller.render() + @$sectionsContainer.append controller.$el + @_sectionControllers.push controller + else + controller.title = section.title + controller.model = section.model + controller.refresh() + + index += 1 + + while @_sectionControllers.length > index + controller = @_sectionController.pop() + controller.$el.fadeOut duration:Duration.normal, complete:-> @remove() + + _refreshVideos: -> + @_videoControllers ?= [] + index = 0 + + videos = @model?.videos or [] + if videos? and videos.length > 0 + @$videosSection.slideDown duration:Duration.normal + @$videosSectionTitle.html if videos.length is 1 then 'Video' else 'Videos' + + for video in videos + controller = @_videoControllers[index] + if not controller? + controller = new VideoController model:video + @_videoControllers.push controller + controller.render() + @$videosSectionPanel.append controller.$el + else + controller.model = video + index++ + else + @$videosSection.slideUp duration:Duration.normal + + while @_videoControllers.length > index + controller = @_videoControllers.pop() + controller.$el.slideUp duration:Duration.normal, complete:-> controller.$el.remove() + + _resolveTutorial: -> + return if @model? + + mod = @modPack.getMod @modSlug + if not mod? + logger.error => "cannot find the mod for: #{@modSlug}" + router.navigate '/', trigger:true + return + + if not mod.isLoaded + mod.fetch() + return + + @model = mod.getTutorial @tutorialSlug + if not @model? + logger.error => "mod #{@modSlug} doesn't have a tutorial for: #{@tutorialSlug}" + return + + @model.fetch() diff --git a/src/coffee/crafting_guide_router.coffee b/src/coffee/crafting_guide_router.coffee index ec32b5044..6a5f3e06d 100644 --- a/src/coffee/crafting_guide_router.coffee +++ b/src/coffee/crafting_guide_router.coffee @@ -16,6 +16,7 @@ ItemSlug = require './models/item_slug' Mod = require './models/mod' ModPack = require './models/mod_pack' ModPageController = require './controllers/mod_page_controller' +TutorialPageController = require './controllers/tutorial_page_controller' Storage = require './models/storage' UrlParams = require './url_params' {ProductionEnvs} = require './constants' @@ -64,13 +65,14 @@ module.exports = class CraftingGuideRouter extends Backbone.Router @_recordPageView() routes: - '(/)': 'route__home' - 'browse(/)': 'route__browse' - 'browse/:modSlug(/)': 'route__browseMod' - 'browse/:modSlug/:itemSlug(/)': 'route__browseModItem' - 'configure(/)': 'route__configure' - 'craft(/)': 'route__craft' - 'craft/:text': 'route__craft' + '(/)': 'route__home' + 'browse(/)': 'route__browse' + 'browse/:modSlug(/)': 'route__browseMod' + 'browse/:modSlug/:itemSlug(/)': 'route__browseModItem' + 'browse/:modSlug/tutorials/:tutorialSlug(/)': 'route__browseTutorial' + 'configure(/)': 'route__configure' + 'craft(/)': 'route__craft' + 'craft/:text': 'route__craft' 'item/:itemSlug': 'deprecated__item' 'crafting/(:text)': 'deprecated__crafting' @@ -100,6 +102,10 @@ module.exports = class CraftingGuideRouter extends Backbone.Router controller = new ItemPageController _.extend {itemSlug:slug}, @_defaultOptions @_setPage 'browseModItem', controller + route__browseTutorial: (modSlug, tutorialSlug)-> + controller = new TutorialPageController _.extend {modSlug:modSlug, tutorialSlug:tutorialSlug}, @_defaultOptions + @_setPage 'browseTutorial', controller + route__configure: -> @_setPage 'configure', new ConfigurePageController _.extend {}, @_defaultOptions diff --git a/src/coffee/models/item_page.coffee b/src/coffee/models/item_page.coffee index 4500a01b7..2638475a6 100644 --- a/src/coffee/models/item_page.coffee +++ b/src/coffee/models/item_page.coffee @@ -22,7 +22,7 @@ module.exports = class ItemPage extends BaseModel compileDescription: -> return null unless @item?.description? - description = markdown.parse @item.description, 'Maruku' + description = _.parseMarkdown @item.description return description findComponentInItems: -> diff --git a/src/coffee/models/mod.coffee b/src/coffee/models/mod.coffee index fa0edea5b..7c7715c9a 100644 --- a/src/coffee/models/mod.coffee +++ b/src/coffee/models/mod.coffee @@ -16,6 +16,7 @@ module.exports = class Mod extends BaseModel constructor: (attributes={}, options={})-> if not attributes.slug? then throw new Error 'attributes.slug is required' + attributes.author ?= '' attributes.description ?= '' attributes.documentationUrl ?= null @@ -23,11 +24,13 @@ module.exports = class Mod extends BaseModel attributes.homePageUrl ?= null attributes.modPack ?= null attributes.name ?= '' + super attributes, options @_activeModVersion = null @_activeVersion = null @_modVersions = [] + @_tutorials = [] Object.defineProperties this, 'activeModVersion': { get:-> @_activeModVersion } @@ -107,6 +110,31 @@ module.exports = class Mod extends BaseModel # Property Methods ############################################################################# + getActiveVersion: -> + return @_activeVersion + + setActiveVersion: (version)-> + return if version is @_activeVersion + + version ?= Mod.Version.None + if version is Mod.Version.Latest then version = _.last(@_modVersions).version + + if version is Mod.Version.None + @_activeVersion = version + @_activateModVersion null + + @trigger Event.change + ':activeVersion', this, @_activeVersion + @trigger Event.change, this + else + for modVersion in @_modVersions + if version is modVersion.version + @_activateModVersion modVersion + break + + @_activeVersion = version + @trigger Event.change + ':activeVersion', this, @_activeVersion + @trigger Event.change, this + addModVersion: (modVersion)-> return unless modVersion? return if @_modVersions.indexOf(modVersion) isnt -1 @@ -136,30 +164,22 @@ module.exports = class Mod extends BaseModel return null - getActiveVersion: -> - return @_activeVersion + addTutorial: (tutorial)-> + return unless tutorial? + if @getTutorial(tutorial.slug)? then throw new Error "duplicate tutorial: #{tutorial.name}" + @_tutorials.push tutorial + tutorial.modSlug = @slug - setActiveVersion: (version)-> - return if version is @_activeVersion + getAllTutorials: -> + return @_tutorials[..] - version ?= Mod.Version.None - if version is Mod.Version.Latest then version = _.last(@_modVersions).version + getTutorial: (tutorialSlug)-> + for tutorial in @_tutorials + return tutorial if tutorial.slug is tutorialSlug + return null - if version is Mod.Version.None - @_activeVersion = version - @_activateModVersion null - - @trigger Event.change + ':activeVersion', this, @_activeVersion - @trigger Event.change, this - else - for modVersion in @_modVersions - if version is modVersion.version - @_activateModVersion modVersion - break - - @_activeVersion = version - @trigger Event.change + ':activeVersion', this, @_activeVersion - @trigger Event.change, this + Object.defineProperties @prototype, + tutorials: { get:@prototype.getAllTutorials } # Backbone.View Overrides ###################################################################### diff --git a/src/coffee/models/parser_versions/command_parser_version_base.coffee b/src/coffee/models/parser_versions/command_parser_version_base.coffee index c9b29050d..f6a1efd32 100644 --- a/src/coffee/models/parser_versions/command_parser_version_base.coffee +++ b/src/coffee/models/parser_versions/command_parser_version_base.coffee @@ -125,6 +125,7 @@ module.exports = class CommandParserVersionBase shortestIndent = Number.MAX_VALUE for hereDocLine in hereDocLines + continue if hereDocLine.trim().length is 0 shortestIndent = Math.min hereDocLine.match(/( *).*/)[1].length, shortestIndent for i in [0...hereDocLines.length] diff --git a/src/coffee/models/parser_versions/mod_parser_v1.coffee b/src/coffee/models/parser_versions/mod_parser_v1.coffee index 3c2c25f50..f244fb301 100644 --- a/src/coffee/models/parser_versions/mod_parser_v1.coffee +++ b/src/coffee/models/parser_versions/mod_parser_v1.coffee @@ -8,6 +8,7 @@ All rights reserved. CommandParserVersionBase = require './command_parser_version_base' Mod = require '../mod' ModVersion = require '../mod_version' +Tutorial = require '../tutorial' ######################################################################################################################## @@ -47,17 +48,23 @@ module.exports = class ModParserV1 extends CommandParserVersionBase if downloadUrl.length is 0 then throw new Error 'downloadUrl cannot be empty (omit it instead)' @_rawData.downloadUrl = downloadUrl - _command_name: (name)-> - if @_rawData.name? then throw new Error 'duplicate declaration of "name"' - if name.length is 0 then throw new Error '"name" cannot be empty' - @_rawData.name = name - _command_homePageUrl: (homePageUrl='')-> if @_rawData.homePageUrl? then throw new Error 'duplicate declaration of "homePageUrl"' if homePageUrl.length is 0 then throw new Error 'homePageUrl cannot be empty' @_rawData.homePageUrl = homePageUrl + _command_name: (name)-> + if @_rawData.name? then throw new Error 'duplicate declaration of "name"' + if name.length is 0 then throw new Error '"name" cannot be empty' + @_rawData.name = name + + _command_tutorial: (nameParts...)-> + name = nameParts.join(', ').trim() + if name.length is 0 then throw new Error '"name" cannot be empty' + @_rawData.tutorialNames ?= [] + @_rawData.tutorialNames.push name + _command_version: (version='')-> if version.length is 0 then throw new Error 'version cannot be empty' @@ -78,5 +85,9 @@ module.exports = class ModParserV1 extends CommandParserVersionBase model.name = rawData.name model.homePageUrl = rawData.homePageUrl + if rawData.tutorialNames? + for tutorialName in rawData.tutorialNames + model.addTutorial new Tutorial name:tutorialName + for version in rawData.versions model.addModVersion new ModVersion modSlug:model.slug, version:version diff --git a/src/coffee/models/parser_versions/tutorial_parser_v1.coffee b/src/coffee/models/parser_versions/tutorial_parser_v1.coffee new file mode 100644 index 000000000..c5bf37df3 --- /dev/null +++ b/src/coffee/models/parser_versions/tutorial_parser_v1.coffee @@ -0,0 +1,65 @@ +### +Crafting Guide - tutorial_parser_v1.coffee + +Copyright (c) 2014-2015 by Redwood Labs +All rights reserved. +### + +CommandParserVersionBase = require './command_parser_version_base' +Tutorial = require '../tutorial' + +######################################################################################################################## + +module.exports = class TutorialParserV1 extends CommandParserVersionBase + + # CommandParserVersionBase Overrides ########################################################### + + _buildModel: (rawData, model)-> + @_buildTutorial rawData, model + + _unparseModel: (builder, model)-> + @_unparseTutorial builder, model + + # Command Methods ############################################################################## + + _command_content: (contentParts...)-> + if not @_rawData.currentSection? then throw new Error 'cannot declare "title" before "section"' + if @_rawData.currentSection.content? then throw new Error 'duplicate declaration of content' + content = contentParts.join(', ').trim() + if not content.length > 0 then throw new Error 'content cannot be empty' + + @_rawData.currentSection.content = content + + _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_section: (textParts...)-> + @_rawData.sections ?= [] + @_rawData.sections.push @_rawData.currentSection = {} + + _command_title: (titleParts...)-> + if not @_rawData.currentSection? then throw new Error 'cannot declare "title" before "section"' + if @_rawData.currentSection.title? then throw new Error 'duplicate declaration of title' + title = titleParts.join(', ').trim() + if not title.length > 0 then throw new Error 'title cannot be empty' + + @_rawData.currentSection.title = title + + _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 ?= [] + @_rawData.videos.push youTubeId:youTubeId, name:name + + # Object Building Methods ###################################################################### + + _buildTutorial: (rawData, model)-> + if not rawData.sections? then throw new Error 'the "section" declaration is required' + + model.officialUrl = rawData.officialUrl + model.videos = rawData.videos + model.sections = rawData.sections diff --git a/src/coffee/models/tutorial.coffee b/src/coffee/models/tutorial.coffee new file mode 100644 index 000000000..1ca03a6d5 --- /dev/null +++ b/src/coffee/models/tutorial.coffee @@ -0,0 +1,35 @@ +### +Crafting Guide - tutorial.coffee + +Copyright (c) 2015 by Redwood Labs +All rights reserved. +### + +BaseModel = require './base_model' +TutorialParser = require './tutorial_parser' +{Url} = require '../constants' + +######################################################################################################################## + +module.exports = class Tutorial extends BaseModel + + constructor: (attributes={}, options={})-> + if not attributes.name?.length > 0 then throw new Error "attributes.name cannot be empty" + attributes.modSlug ?= null + attributes.officialUrl ?= null + attributes.sections ?= [] + attributes.slug ?= _.slugify attributes.name + attributes.videos ?= [] + super attributes, options + + # Backbone.Model Overrides ##################################################################### + + parse: (text)-> + TutorialParser = require './tutorial_parser' # to avoid require cycles + @_parser ?= new TutorialParser model:this + @_parser.parse text + + return null # prevent calling `set` + + url: -> + return Url.tutorialData modSlug:@modSlug, tutorialSlug:@slug diff --git a/src/coffee/models/tutorial_parser.coffee b/src/coffee/models/tutorial_parser.coffee new file mode 100644 index 000000000..21d83127b --- /dev/null +++ b/src/coffee/models/tutorial_parser.coffee @@ -0,0 +1,19 @@ +### +Crafting Guide - tutorial_parser.coffee + +Copyright (c) 2014-2015 by Redwood Labs +All rights reserved. +### + +VersionedParserBase = require './versioned_parser_base' +TutorialParserV1 = require './parser_versions/tutorial_parser_v1' + +######################################################################################################################## + +module.exports = class TutorialParser extends VersionedParserBase + + # VersionedParserBase Overrides ################################################################ + + _createParsers: (options)-> + return result = + '1': new TutorialParserV1 options diff --git a/src/coffee/test/parser_versions/command_parser_version_base.test.coffee b/src/coffee/test/parser_versions/command_parser_version_base.test.coffee index 6cfe148a7..08d50bee5 100644 --- a/src/coffee/test/parser_versions/command_parser_version_base.test.coffee +++ b/src/coffee/test/parser_versions/command_parser_version_base.test.coffee @@ -40,9 +40,9 @@ describe 'command_parser_version_base.coffee', -> expect(result[1]).to.be.null it 'trims smallest leading whitespace', -> - parser._lines = ['command: <<-END', ' alpha', ' bravo', ' charlie', 'END', 'command1: arg2'] + parser._lines = ['command: <<-END', ' alpha', ' bravo', '', ' charlie', 'END', 'command1: arg2'] parser._lineNumber = 1 result = parser._parseHereDoc parser._lines[0] result[0].should.equal 'command: ' - result[1].should.equal 'alpha\n bravo\ncharlie' + result[1].should.equal 'alpha\n bravo\n\ncharlie' diff --git a/src/coffee/underscore_mixins.coffee b/src/coffee/underscore_mixins.coffee index 707227cca..ac789d648 100644 --- a/src/coffee/underscore_mixins.coffee +++ b/src/coffee/underscore_mixins.coffee @@ -1,5 +1,5 @@ ### -Crafting Guide - underscore.coffee +Crafting Guide - underscore_mixins.coffee Copyright (c) 2014-2015 by Redwood Labs All rights reserved. @@ -7,12 +7,15 @@ All rights reserved. _.mixin + parseMarkdown: (text)-> + return markdown.parse text, 'Maruku' + slugify: (text)-> return null unless text? result = text.toLowerCase() result = result.replace /[^a-zA-Z0-9_]/g, '_' - result = result.replace /__+/, '_' + result = result.replace /__+/g, '_' result = result.replace /^_/, '' result = result.replace /_$/, '' return result diff --git a/src/jade/templates/markdown_section.jade b/src/jade/templates/markdown_section.jade new file mode 100644 index 000000000..1e77bb761 --- /dev/null +++ b/src/jade/templates/markdown_section.jade @@ -0,0 +1,10 @@ +//- +//- Crafting Guide - markdown_section.jade +//- +//- Copyright (c) 2015 by Redwood Labs +//- All rights reserved. +//- + +.view__markdown_section.section + h2 + .panel.markdown diff --git a/src/jade/templates/mod_page.jade b/src/jade/templates/mod_page.jade index 3b950dce0..f0145fdc3 100644 --- a/src/jade/templates/mod_page.jade +++ b/src/jade/templates/mod_page.jade @@ -20,4 +20,8 @@ .byline: p .description: p + .tutorials.section + h2 Tutorials + .panel + .itemGroups diff --git a/src/jade/templates/tutorial.jade b/src/jade/templates/tutorial.jade new file mode 100644 index 000000000..2ab96ca96 --- /dev/null +++ b/src/jade/templates/tutorial.jade @@ -0,0 +1,11 @@ +//- +//- Crafting Guide - tutorial.jade +//- +//- Copyright (c) 2015 by Redwood Labs +//- All rights reserved. +//- + +.view__tutorial + a + img + .caption: p \ No newline at end of file diff --git a/src/jade/templates/tutorial_page.jade b/src/jade/templates/tutorial_page.jade new file mode 100644 index 000000000..d3076229c --- /dev/null +++ b/src/jade/templates/tutorial_page.jade @@ -0,0 +1,23 @@ +//- +//- Crafting Guide - tutorial_page.jade +//- +//- Copyright (c) 2015 by Redwood Labs +//- All rights reserved. +//- + +.view__tutorial_page + .sidebar + .titleImage: a: img + a.officialPage.externalLink(target="new"): p Offical Documentation + + .view__adsense + + .mainBody + h1.name + .byline: p from + + .sections + + .videos.section + h2 + .panel diff --git a/src/scss/classes.scss b/src/scss/classes.scss index 4ad3695ce..c797e935b 100644 --- a/src/scss/classes.scss +++ b/src/scss/classes.scss @@ -5,6 +5,16 @@ Copyright (C) 2015 by Redwood Labs All rights reserved. */ +.byline { + margin: 0.25em 0 2em 0; + + p { + font-family: $font-family-normal; + font-size: $font-size-normal; + font-style: italic; + } +} + .centered { text-align: center; } @@ -22,6 +32,20 @@ All rights reserved. .error-new { background: $color-error-new !important; } +.hidden { + max-height: 0; + opacity: 0; +} + +.mainBody { + position: relative; width: 81.25%; + + display: inline-block; + padding: 1em; + padding-top: 2em; + vertical-align: top; +} + .sidebar { position: relative; width: 18.75%; @@ -53,15 +77,6 @@ All rights reserved. } } -.mainBody { - position: relative; width: 81.25%; - - display: inline-block; - padding: 1em; - padding-top: 2em; - vertical-align: top; -} - .section { margin-top: 3em; @@ -86,4 +101,12 @@ All rights reserved. background: white; padding: 1em; } +} + +.videos { + display: none; + + .panel { + text-align: center; + } } \ No newline at end of file diff --git a/src/scss/markdown.scss b/src/scss/markdown.scss index 62e7763d9..1e89387a7 100644 --- a/src/scss/markdown.scss +++ b/src/scss/markdown.scss @@ -39,6 +39,19 @@ All rights reserved. } } + ol { + margin: 2em 2em 0 2em; + + li { + font-family: $font-family-normal; + font-size: $font-size-normal; + line-height: $font-size-normal + 0.15em; + list-style: decimal; + margin: 0.66em 0 0 2em; + } + } + + ul { margin: 2em 2em 0 2em; diff --git a/src/scss/tags.scss b/src/scss/tags.scss index 3d1665d0e..93fbc2efe 100644 --- a/src/scss/tags.scss +++ b/src/scss/tags.scss @@ -48,6 +48,12 @@ h1 { font-family: $font-family-header; font-size: $font-size-xx-large; } + + &.name { + font-family: $font-family-header; + font-size: $font-size-x-large; + margin: 0; + } } h2 { diff --git a/src/scss/templates/index.scss b/src/scss/templates/index.scss index b72f3f7c7..19bc6da1e 100644 --- a/src/scss/templates/index.scss +++ b/src/scss/templates/index.scss @@ -23,4 +23,5 @@ All rights reserved. @import 'mod_page'; @import 'mod_selector'; @import 'stack'; +@import 'tutorial'; @import 'video'; diff --git a/src/scss/templates/item_page.scss b/src/scss/templates/item_page.scss index 3358a9d92..2780a09d7 100644 --- a/src/scss/templates/item_page.scss +++ b/src/scss/templates/item_page.scss @@ -24,22 +24,6 @@ All rights reserved. } .mainBody { - h1.name { - font-family: $font-family-header; - font-size: $font-size-x-large; - margin: 0; - } - - .byline { - display: none; - margin: 0.25em 0 2em 0; - - p { - font-family: $font-family-normal; - font-size: $font-size-normal; - font-style: italic; - } - } .recipes { display: none; @@ -61,14 +45,6 @@ All rights reserved. .usedToMake { display: none; } - - .videos { - display: none; - - .panel { - text-align: center; - } - } } .view__item { diff --git a/src/scss/templates/mod_page.scss b/src/scss/templates/mod_page.scss index b29173afb..5d2670b72 100644 --- a/src/scss/templates/mod_page.scss +++ b/src/scss/templates/mod_page.scss @@ -23,22 +23,6 @@ All rights reserved. .mainBody { - .name { - font-family: $font-family-header; - font-size: $font-size-x-large; - margin: 0; - } - - .byline { - margin: 0.25em 0 2em 0; - - p { - font-family: $font-family-normal; - font-size: $font-size-normal; - font-style: italic; - } - } - .description { margin-bottom: 4em; diff --git a/src/scss/templates/tutorial.scss b/src/scss/templates/tutorial.scss new file mode 100644 index 000000000..2d4065e69 --- /dev/null +++ b/src/scss/templates/tutorial.scss @@ -0,0 +1,28 @@ +/* +Crafting Guide - tutorial.scss + +Copyright (C) 2015 by Redwood Labs +All rights reserved. +*/ + +.view__tutorial { + position: relative; + display: inline-block; + margin-left: 1em; + vertical-align: top; + + &:first-child { + margin-left: 0; + } + + .caption { + position: relative; width: 100%; + display: block; + + p { + margin-top: 0.5em; + font-weight: bold; + text-align: center; + } + } +} \ No newline at end of file diff --git a/static/browse/buildcraft/tutorials/gates_wires_and_chips/example_1_overview.png b/static/browse/buildcraft/tutorials/gates_wires_and_chips/example_1_overview.png new file mode 100644 index 000000000..6d7e9094c Binary files /dev/null and b/static/browse/buildcraft/tutorials/gates_wires_and_chips/example_1_overview.png differ diff --git a/static/browse/buildcraft/tutorials/gates_wires_and_chips/icon.png b/static/browse/buildcraft/tutorials/gates_wires_and_chips/icon.png new file mode 100644 index 000000000..4ee0debda Binary files /dev/null and b/static/browse/buildcraft/tutorials/gates_wires_and_chips/icon.png differ diff --git a/static/browse/buildcraft/tutorials/gates_wires_and_chips/index.html b/static/browse/buildcraft/tutorials/gates_wires_and_chips/index.html new file mode 100644 index 000000000..f1a9bdbc5 --- /dev/null +++ b/static/browse/buildcraft/tutorials/gates_wires_and_chips/index.html @@ -0,0 +1,107 @@ + + The Ultimate Minecraft Crafting Guide + + + + + + +

Sending failed. Please try again later.

Feedback

+
+
+
+

+

Crafting Guide for Minecraft

+

+
+
+ +
+

Gates, Wires, and Chips

What do gates do?

BuildCraft gates are devices which can be attached to a pipe to detect and interact with objects nearby. Gates +can read a wide variety of conditions: that a nearby chest is full, or that a Combustion +Engine is about to overheat. In reaction to these conditions, gates can +emit various kinds of signals (including redstone signals) to control other gates and/or machines.

An Example: Controlling Engines

Before diving into all the details, let's look at a realistic example of what you might do with gates. This is a +small power plant driving a quarry.

+ +

+ +

Here are the important pieces of the build:

+ +
  1. The on/off switch for the entire operation. Flipping the switch creates a redstone signal.
  2. A Basic Gate attached to a Structure Pipe reads the redstone signal and generates a red pipe wire signal.
  3. Red Pipe Wire follows the Cobblestone Kinesis Pipe, transmitting the signal throughout the rest of the build.
  4. Another Basic Gate receives the red pipe wire signal and converts it back to a redstone signal, thus turning +the Combustion Engines on.
  5. The red pipe wire signal is also received by Basic Gates next to the Redstone +Engines, turning them on as well, and causing both fuel and water to +flow into the engines.
  6. Finally, the power generated by the Combusion Engines flows into the Quarry.

Video

Buildcraft Gates Tutorial, Part 1

+ +
+ + + + + + + + + + + + + diff --git a/static/data/buildcraft/mod.cg b/static/data/buildcraft/mod.cg index 078e9ef32..fe966fe3a 100644 --- a/static/data/buildcraft/mod.cg +++ b/static/data/buildcraft/mod.cg @@ -8,3 +8,5 @@ documentationUrl: http://www.mod-buildcraft.com/wiki/doku.php downloadUrl: http://www.mod-buildcraft.com/download/ version: 6.2.6 + +tutorial: Gates, Wires, and Chips diff --git a/static/data/buildcraft/tutorials/gates_wires_and_chips.cg b/static/data/buildcraft/tutorials/gates_wires_and_chips.cg new file mode 100644 index 000000000..e5a5a88d1 --- /dev/null +++ b/static/data/buildcraft/tutorials/gates_wires_and_chips.cg @@ -0,0 +1,35 @@ +schema: 1 + +section: + title: What do gates do? + content: <<-END + BuildCraft gates are devices which can be attached to a pipe to detect and interact with objects nearby. Gates + can read a wide variety of conditions: that a nearby chest is full, or that a [Combustion + Engine](/browse/buildcraft/combustion_engine) is about to overheat. In reaction to these conditions, gates can + emit various kinds of signals (including redstone signals) to control other gates and/or machines. + END + +section: + title: An Example: Controlling Engines + content: <<-END + Before diving into all the details, let's look at a realistic example of what you might do with gates. This is a + small power plant driving a quarry. + + ![](./example_1_overview.png) + + Here are the important pieces of the build: + + 1. The on/off switch for the entire operation. Flipping the switch creates a redstone signal. + 2. A [Basic Gate](/browse/buildcraft/basic_gate/) attached to a [Structure Pipe] + (/browse/buildcraft/structure_pipe) reads the redstone signal and generates a red pipe wire signal. + 3. [Red Pipe Wire](/browse/buildcraft/red_pipe_wire) follows the [Cobblestone Kinesis Pipe] + (/browse/buildcraft/cobblestone_kinesis_pipe), transmitting the signal throughout the rest of the build. + 4. Another Basic Gate receives the red pipe wire signal and converts it back to a redstone signal, thus turning + the [Combustion Engine](/browse/buildcraft/combustion_engine)s on. + 5. The red pipe wire signal is also received by Basic Gates next to the [Redstone + Engine](/browse/buildcraft/redstone_engine)s, turning them on as well, and causing both fuel and water to + flow into the engines. + 6. Finally, the power generated by the Combusion Engines flows into the [Quarry](/browse/buildcraft/quarry/). + END + +video: J6-VUApZGQs, Buildcraft Gates Tutorial, Part 1 diff --git a/static/sitemap.txt b/static/sitemap.txt index d377d7415..c2f2986d5 100644 --- a/static/sitemap.txt +++ b/static/sitemap.txt @@ -13,6 +13,7 @@ http://crafting-guide.com/browse/minecraft/ http://crafting-guide.com/browse/railcraft/ http://crafting-guide.com/browse/thermal_dynamics/ http://crafting-guide.com/browse/thermal_expansion/ +http://crafting-guide.com/browse/buildcraft/tutorials/gates_wires_and_chips/ http://crafting-guide.com/browse/applied_energistics_2/128_spatial_component/ http://crafting-guide.com/browse/applied_energistics_2/128_spatial_storage_cell/ http://crafting-guide.com/browse/applied_energistics_2/16_spatial_component/