From 89a7f7a9d36ed5723366d1cd72a8006241ac31e0 Mon Sep 17 00:00:00 2001 From: Andrew Miner Date: Fri, 20 Mar 2015 12:22:01 -0700 Subject: [PATCH] Initial implementation of tutorial pages --- src/coffee/constants.coffee | 3 + .../markdown_section_controller.coffee | 40 ++++++ .../controllers/mod_page_controller.coffee | 50 +++++-- .../controllers/tutorial_controller.coffee | 39 ++++++ .../tutorial_page_controller.coffee | 127 ++++++++++++++++++ src/coffee/crafting_guide_router.coffee | 20 ++- src/coffee/models/item_page.coffee | 2 +- src/coffee/models/mod.coffee | 62 ++++++--- .../parser_versions/mod_parser_v1.coffee | 21 ++- .../parser_versions/tutorial_parser_v1.coffee | 65 +++++++++ src/coffee/models/tutorial.coffee | 35 +++++ src/coffee/models/tutorial_parser.coffee | 19 +++ src/coffee/underscore_mixins.coffee | 5 +- src/jade/templates/markdown_section.jade | 10 ++ src/jade/templates/mod_page.jade | 4 + src/jade/templates/tutorial.jade | 11 ++ src/jade/templates/tutorial_page.jade | 19 +++ src/scss/classes.scss | 35 +++-- src/scss/tags.scss | 6 + src/scss/templates/index.scss | 1 + src/scss/templates/item_page.scss | 16 --- src/scss/templates/mod_page.scss | 16 --- src/scss/templates/tutorial.scss | 28 ++++ .../buildcraft/tutorials/gates/icon.png | Bin 0 -> 25455 bytes static/data/buildcraft/mod.cg | 2 + static/data/buildcraft/tutorials/gates.cg | 7 + 26 files changed, 556 insertions(+), 87 deletions(-) create mode 100644 src/coffee/controllers/markdown_section_controller.coffee create mode 100644 src/coffee/controllers/tutorial_controller.coffee create mode 100644 src/coffee/controllers/tutorial_page_controller.coffee create mode 100644 src/coffee/models/parser_versions/tutorial_parser_v1.coffee create mode 100644 src/coffee/models/tutorial.coffee create mode 100644 src/coffee/models/tutorial_parser.coffee create mode 100644 src/jade/templates/markdown_section.jade create mode 100644 src/jade/templates/tutorial.jade create mode 100644 src/jade/templates/tutorial_page.jade create mode 100644 src/scss/templates/tutorial.scss create mode 100644 static/browse/buildcraft/tutorials/gates/icon.png create mode 100644 static/data/buildcraft/tutorials/gates.cg diff --git a/src/coffee/constants.coffee b/src/coffee/constants.coffee index 5ee7fa62b..4824efb3b 100644 --- a/src/coffee/constants.coffee +++ b/src/coffee/constants.coffee @@ -68,6 +68,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..45a9f03ff --- /dev/null +++ b/src/coffee/controllers/tutorial_page_controller.coffee @@ -0,0 +1,127 @@ +### +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' +{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') + super + + refresh: -> + if @model? and @model.isLoaded + @$el.removeClass 'hidden' + else + @$el.addClass 'hidden' + + @_refreshByline() + @_refreshOfficialUrl() + @_refreshSections() + @_refreshTitle() + 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() + + _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/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/underscore_mixins.coffee b/src/coffee/underscore_mixins.coffee index 707227cca..a4d9e0c9b 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,6 +7,9 @@ All rights reserved. _.mixin + parseMarkdown: (text)-> + return markdown.parse text, 'Maruku' + slugify: (text)-> return null unless text? 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..4029c040d --- /dev/null +++ b/src/jade/templates/tutorial_page.jade @@ -0,0 +1,19 @@ +//- +//- 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 diff --git a/src/scss/classes.scss b/src/scss/classes.scss index 4ad3695ce..7879fb392 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,4 @@ All rights reserved. background: white; padding: 1em; } -} \ No newline at end of file +} 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..137385cf1 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; 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/icon.png b/static/browse/buildcraft/tutorials/gates/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4ee0debdaee43f9bfbe20ac20d61f64c7cf03925 GIT binary patch literal 25455 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