From 16848772a340c44a5057c808dbf9acb37fe3eb1e Mon Sep 17 00:00:00 2001 From: Andrew Miner Date: Wed, 11 May 2016 21:39:43 -0700 Subject: [PATCH] Add GoogleAnalytics event tracking --- src/client/client.coffee | 17 +++-- src/client/site/base_controller.coffee | 21 ++++-- .../inventory/inventory_controller.coffee | 10 ++- .../item_selector_controller.coffee | 8 +-- .../markdown_section_controller.coffee | 5 ++ .../site/common/stack/stack_controller.coffee | 3 + .../craft_page/craft_page_controller.coffee | 4 ++ .../craftsman_working_controller.coffee | 1 + .../craft_page/step/step_controller.coffee | 6 ++ src/client/site/feedback/feedback.jade | 4 +- .../site/feedback/feedback_controller.coffee | 4 ++ .../site/header/header_controller.coffee | 14 +++- .../item_page/item_page_controller.coffee | 1 + .../multiblock_viewer_controller.coffee | 3 + .../login_page/login_page_controller.coffee | 5 +- src/client/site/mod_page/mod_page.jade | 6 +- .../site/mod_page/mod_page_controller.coffee | 6 +- src/client/site/router.coffee | 11 +-- src/client/site/url_params.coffee | 2 +- src/client/tracker.coffee | 67 +++++++++++++++++++ src/common/constants.coffee | 14 ++++ 21 files changed, 176 insertions(+), 36 deletions(-) create mode 100644 src/client/tracker.coffee diff --git a/src/client/client.coffee b/src/client/client.coffee index 63a431cb7..71dbd4dbd 100644 --- a/src/client/client.coffee +++ b/src/client/client.coffee @@ -18,15 +18,18 @@ global.π = Math.PI global.ε = 0.0001 global.w = require 'when' -global.Backbone = Backbone = require 'backbone' -Backbone.$ = $ - {Logger} = require 'crafting-guide-common' global.logger = new Logger +Tracker = require './tracker' +global.tracker = new Tracker + marked = require 'marked' marked.setOptions sanitize:true +global.Backbone = Backbone = require 'backbone' +Backbone.$ = $ + ######################################################################################################################## global.hostName = window.location.hostname @@ -44,9 +47,10 @@ switch global.hostName logger.level = Logger.VERBOSE apiBaseUrl = 'http://api-staging.crafting-guide.com' when 'crafting-guide.com' - global.env = 'production' - logger.level = Logger.INFO - apiBaseUrl = 'http://api.crafting-guide.com' + global.env = 'production' + logger.level = Logger.INFO + tracker.enabled = true + apiBaseUrl = 'http://api.crafting-guide.com' else throw new Error "cannot determine the environment of: #{window.location.hostname}" @@ -75,4 +79,5 @@ site.loadDefaultModPack() site.loadCurrentUser() Backbone.history.start pushState:true +tracker.trackPageView() logger.info -> "CraftingGuide is ready" diff --git a/src/client/site/base_controller.coffee b/src/client/site/base_controller.coffee index 1981c7e38..aa23f96c7 100644 --- a/src/client/site/base_controller.coffee +++ b/src/client/site/base_controller.coffee @@ -49,15 +49,26 @@ module.exports = class BaseController extends Backbone.View @off() routeLinkClick: (event)-> - event.preventDefault() - href = $(event.target).attr 'href' - href ?= $(event.currentTarget).attr 'href' - logger.info "Re-routing link to internal navigation: #{href}" + $link = $(event.target) + if not $link.attr('href')? + $link = $(event.currentTarget) + + href = $link.attr 'href' + target = $link.attr 'target' + + logger.info -> "Re-routing link to internal navigation: #{href}" if href? and href.match /^http/ - window.location.href = href + tracker.trackEvent c.tracking.category.navigate, 'external-link', href + .then -> window.location.href = href + return false + else if target? + tracker.trackEvent c.tracking.category.navigate, 'external-link', href + return true else + tracker.trackEvent c.tracking.category.navigate, 'internal-link', href @router.navigate href, trigger:true + return false show: ($el)-> $el ?= @$el diff --git a/src/client/site/common/inventory/inventory_controller.coffee b/src/client/site/common/inventory/inventory_controller.coffee index 8d86f3aba..22e0efc8b 100644 --- a/src/client/site/common/inventory/inventory_controller.coffee +++ b/src/client/site/common/inventory/inventory_controller.coffee @@ -40,6 +40,7 @@ module.exports = class InventoryController extends BaseController @_isAcceptable = options.isAcceptable ?= null @_secondButtonType = options.secondButtonType ?= null @_shouldEnableButton = options.shouldEnableButton ?= (model, button)-> true + @_trackingContext = options.trackingContext ?= null options.templateName = 'common/inventory' super options @@ -54,20 +55,26 @@ module.exports = class InventoryController extends BaseController return unless @model? return if @$clearButton.hasClass 'disabled' + tracker.trackEvent @_trackingContext, 'clear' @model.clear() @trigger c.event.clear, this @trigger c.event.change, this onFirstButtonClicked: (stackController)-> + tracker.trackEvent @_trackingContext, 'remove-from', stackController?.model?.itemSlug @trigger c.event.button.first, this, stackController?.model?.itemSlug onItemSelectorButtonClicked: -> return unless @model? + tracker.trackEvent @_trackingContext, 'launch-add-to' @_selector.launch() .then (itemSlug)=> - return unless itemSlug? + if not itemSlug? + tracker.trackEvent @_trackingContext, 'cancel-add-to' + return + tracker.trackEvent @_trackingContext, 'complete-add-to', itemSlug @model.add itemSlug, 1 @trigger c.event.add, this, itemSlug @trigger c.event.change, this @@ -136,6 +143,7 @@ module.exports = class InventoryController extends BaseController router: @_router secondButtonType: @_secondButtonType shouldEnableButton: @_shouldEnableButton + trackingContext: @_trackingContext controller.on c.event.change, => @trigger c.event.change, this controller.on c.event.button.first, (c)=> @onFirstButtonClicked(c) controller.on c.event.button.second, (c)=> @onSecondButtonClicked(c) diff --git a/src/client/site/common/item_selector/item_selector_controller.coffee b/src/client/site/common/item_selector/item_selector_controller.coffee index c3f03ccd4..ed108ba08 100644 --- a/src/client/site/common/item_selector/item_selector_controller.coffee +++ b/src/client/site/common/item_selector/item_selector_controller.coffee @@ -29,10 +29,6 @@ module.exports = class ItemSelectorController extends BaseController # Public Methods ############################################################################### launch: (hint='')-> - if @_session - @_session.resolve null - @_session = null - @model.hint = hint if @rendered then @refresh() @@ -141,6 +137,10 @@ module.exports = class ItemSelectorController extends BaseController @$popup.detach() @model.hint = '' + if @_session + @_session.resolve null + @_session = null + _disableWindowScrolling: -> @_windowScrollPosition = $(window).scrollTop() $('body').addClass('scrollDisabled').css('margin-top', -@_windowScrollPosition) diff --git a/src/client/site/common/markdown_section/markdown_section_controller.coffee b/src/client/site/common/markdown_section/markdown_section_controller.coffee index 8b2f1850c..c9c9c159a 100644 --- a/src/client/site/common/markdown_section/markdown_section_controller.coffee +++ b/src/client/site/common/markdown_section/markdown_section_controller.coffee @@ -60,6 +60,7 @@ module.exports = class MarkdownSectionController extends BaseController # Event Methods ################################################################################ onCancelClicked: (event)-> + tracker.trackEvent c.tracking.category.markdown, 'cancel' @model = @_originalModel @resetToDefaultState() @@ -70,6 +71,7 @@ module.exports = class MarkdownSectionController extends BaseController onEditClicked: (event)-> return false unless @editable + tracker.trackEvent c.tracking.category.markdown, 'edit' @_originalModel = @model @state = @State.waiting @_beginEditing() @@ -87,15 +89,18 @@ module.exports = class MarkdownSectionController extends BaseController return false onPreviewClicked: (event)-> + tracker.trackEvent c.tracking.category.markdown, 'preview' @_updatePreview() @state = @State.previewing return false onReturnClicked: (event)-> + tracker.trackEvent c.tracking.category.markdown, 'return' @state = @State.editing return false onSaveClicked: (event)-> + tracker.trackEvent c.tracking.category.markdown, 'save' @state = @State.waiting @_endEditing() .then => diff --git a/src/client/site/common/stack/stack_controller.coffee b/src/client/site/common/stack/stack_controller.coffee index 20a108f3a..74b6cf105 100644 --- a/src/client/site/common/stack/stack_controller.coffee +++ b/src/client/site/common/stack/stack_controller.coffee @@ -36,6 +36,7 @@ module.exports = class StackController extends BaseController @_modPack = options.modPack @_secondButtonType = options.secondButtonType ?= null @_shouldEnableButton = options.shouldEnableButton ?= (model, button)-> true + @_trackingContext = options.trackingContext ?= null @_modPack.on c.event.change, => @tryRefresh() @@ -70,6 +71,8 @@ module.exports = class StackController extends BaseController @trigger c.event.change + ':quantity', this, oldQuantity, newQuantity @trigger c.event.change, this + tracker.trackEvent @_trackingContext, 'update-quantity', @model.itemSlug, @model.quantity + onQuantityFieldChanged: -> return unless @_editable diff --git a/src/client/site/craft_page/craft_page_controller.coffee b/src/client/site/craft_page/craft_page_controller.coffee index 6aebfa35e..47a9dc634 100644 --- a/src/client/site/craft_page/craft_page_controller.coffee +++ b/src/client/site/craft_page/craft_page_controller.coffee @@ -59,6 +59,7 @@ module.exports = class CraftPageController extends PageController $el = $el.parent() if inventoryText? + tracker.trackEvent c.tracking.category.craft, 'add-sample', inventoryText sampleInventory = new SimpleInventory sampleInventory.parse inventoryText @model.craftsman.want.addInventory sampleInventory @@ -91,6 +92,7 @@ module.exports = class CraftPageController extends PageController modPack: @_modPack model: @model.craftsman.want router: @_router + trackingContext: c.tracking.category.craftWant @_wantInventoryController.on c.event.button.first, (c, s)=> @onRemoveFromWant(s) @_wantInventoryController.on c.event.change, (c)=> @onWantInventoryChanged() @@ -100,6 +102,7 @@ module.exports = class CraftPageController extends PageController modPack: @_modPack model: @model.craftsman.have router: @_router + trackingContext: c.tracking.category.craftHave @_haveInventoryController.on c.event.button.first, (controller, itemSlug)=> @onRemoveFromHaveInventory itemSlug @@ -110,6 +113,7 @@ module.exports = class CraftPageController extends PageController modPack: @_modPack model: null router: @_router + trackingContext: c.tracking.category.craftNeed @_needInventoryController.on c.event.button.first, (c, s)=> @onMoveNeedToHave(s) @_workingSectionController = @addChild CraftsmanWorkingController, '.view__craftsman_working', diff --git a/src/client/site/craft_page/craftsman_working/craftsman_working_controller.coffee b/src/client/site/craft_page/craftsman_working/craftsman_working_controller.coffee index 08bc905af..36498fb1c 100644 --- a/src/client/site/craft_page/craftsman_working/craftsman_working_controller.coffee +++ b/src/client/site/craft_page/craftsman_working/craftsman_working_controller.coffee @@ -24,6 +24,7 @@ module.exports = class CraftsmanWorkingController extends BaseController # Event Methods ################################################################################ onButtonClicked: -> + tracker.trackEvent c.tracking.category.craft, 'start' @trigger c.event.click @model.reset() @model.work() diff --git a/src/client/site/craft_page/step/step_controller.coffee b/src/client/site/craft_page/step/step_controller.coffee index de3eb1c9b..4015c6e06 100644 --- a/src/client/site/craft_page/step/step_controller.coffee +++ b/src/client/site/craft_page/step/step_controller.coffee @@ -41,8 +41,13 @@ module.exports = class StepController extends BaseController onCompleteButtonClicked: (event)-> return if @$completeButton.hasClass 'disabled' + tracker.trackEvent c.tracking.category.craft, 'mark-complete', null, @model.number @onComplete this + onShowToolPlan: (event)-> + tracker.trackEvent c.tracking.category.navigate, 'show-tool-plan', @$toolButton.attr 'target' + return true + # BaseController Overrides ##################################################################### onDidRender: -> @@ -90,6 +95,7 @@ module.exports = class StepController extends BaseController events: -> return _.extend super, 'click .button.complete': 'onCompleteButtonClicked' + 'click a.button.tool': 'onShowToolPlan' # Private Methods ############################################################################## diff --git a/src/client/site/feedback/feedback.jade b/src/client/site/feedback/feedback.jade index 3e2264bf6..93f10d0c3 100644 --- a/src/client/site/feedback/feedback.jade +++ b/src/client/site/feedback/feedback.jade @@ -7,8 +7,8 @@ .view__feedback .form-content - input(name="name", placeholder="name (optional)") - input(name="email", placeholder="email (optional)") + input(name="name", placeholder="name") + input(name="email", placeholder="email") label(name="comment") Comment: textarea(name="comment") diff --git a/src/client/site/feedback/feedback_controller.coffee b/src/client/site/feedback/feedback_controller.coffee index 1bb8d8210..0157099e4 100644 --- a/src/client/site/feedback/feedback_controller.coffee +++ b/src/client/site/feedback/feedback_controller.coffee @@ -22,6 +22,7 @@ module.exports = class FeedbackController extends BaseController # Public Methods ############################################################################### enterFeedback: (message)-> + tracker.trackEvent c.tracking.category.feedback, 'ask-a-question' promise = w(true) if not @isOpen then promise = @onToggle() promise.then => @$commentField.val message @@ -38,6 +39,7 @@ module.exports = class FeedbackController extends BaseController email: @$emailField.val() comment: @$commentField.val() + tracker.trackEvent c.tracking.category.feedback, 'send' @model.send('feedback', message) .then => @onToggle() @@ -58,6 +60,7 @@ module.exports = class FeedbackController extends BaseController @$screen.off 'click' if @isOpen + tracker.trackEvent c.tracking.category.feedback, 'toggle-closed' @$screen.css display:'none' @$page.removeClass 'blur' @$el.animate {left:-@_closeWidth}, duration:c.duration.fast, complete:=> @@ -65,6 +68,7 @@ module.exports = class FeedbackController extends BaseController @$('input, textarea').blur() resolve() else + tracker.trackEvent c.tracking.category.feedback, 'toggle-open' @$page.addClass 'blur' @$screen.on 'click', => @onToggle() @$screen.css display:'block' diff --git a/src/client/site/header/header_controller.coffee b/src/client/site/header/header_controller.coffee index 8404b1e67..f08fad3e5 100644 --- a/src/client/site/header/header_controller.coffee +++ b/src/client/site/header/header_controller.coffee @@ -26,29 +26,38 @@ module.exports = class HeaderController extends BaseController # Event Methods ################################################################################ onCraft: -> + tracker.trackEvent c.tracking.category.navigate, 'click-button', 'craft' @router.navigate '/craft', trigger:true return false onBrowse: -> + tracker.trackEvent c.tracking.category.navigate, 'click-button', 'browse' @router.navigate '/browse', trigger:true return false onLogin: -> if global.site.user? + tracker.trackEvent c.tracking.category.account, 'logout' global.site.logout() else + tracker.trackEvent c.tracking.category.navigate, 'click-button', 'login' @router.navigate '/login', trigger:true return false onNews: -> + tracker.trackEvent c.tracking.category.navigate, 'click-button', 'news' @router.navigate '/news', trigger:true return false onSearch: (event, hint='')-> + tracker.trackEvent c.tracking.category.search, 'launch' @_selector.launch hint .then (itemSlug)=> - return unless itemSlug? + if not itemSlug? + tracker.trackEvent c.tracking.category.search, 'cancel', itemSlug + return + tracker.trackEvent c.tracking.category.search, 'complete', itemSlug itemDisplay = @_modPack.findItemDisplay itemSlug @router.navigate itemDisplay.itemUrl, trigger:true return false @@ -121,7 +130,8 @@ module.exports = class HeaderController extends BaseController @hide @$breadcrumbs _refreshExtraNav: -> - extraNavContent = @model?.controller?.getExtraNav() + return unless _.isFunction @model?.controller?.getExtraNav + extraNavContent = @model.controller.getExtraNav() if extraNavContent? @$extraNav.empty() diff --git a/src/client/site/item_page/item_page_controller.coffee b/src/client/site/item_page/item_page_controller.coffee index ab3114551..f78e95ed0 100644 --- a/src/client/site/item_page/item_page_controller.coffee +++ b/src/client/site/item_page/item_page_controller.coffee @@ -50,6 +50,7 @@ module.exports = class ItemPageController extends PageController # Event Methods ################################################################################ craftingPlanButtonClicked: -> + tracker.trackEvent c.tracking.category.craft, 'view-crafting-plan', @model.item.slug display = @_modPack.findItemDisplay @model.item.slug @_router.navigate display.craftingUrl, trigger:true return false diff --git a/src/client/site/item_page/multiblock_viewer/multiblock_viewer_controller.coffee b/src/client/site/item_page/multiblock_viewer/multiblock_viewer_controller.coffee index 15cb18154..600a925d8 100644 --- a/src/client/site/item_page/multiblock_viewer/multiblock_viewer_controller.coffee +++ b/src/client/site/item_page/multiblock_viewer/multiblock_viewer_controller.coffee @@ -29,11 +29,13 @@ module.exports = class MultiblockViewerController extends BaseController onBackClicked: -> return if @$backButton.hasClass 'disabled' + tracker.trackEvent c.tracking.category.multiblock, 'back' @multiblockController.goBackLayer() @refresh() onNextClicked: -> return if @$nextButton.hasClass 'disabled' + tracker.trackEvent c.tracking.category.multiblock, 'next' @multiblockController.goNextLayer() @refresh() @@ -41,6 +43,7 @@ module.exports = class MultiblockViewerController extends BaseController if itemDisplay? @$captionText.html itemDisplay.itemName @_imageLoader.load itemDisplay.iconUrl, @$captionIcon + tracker.trackEvent c.tracking.category.multiblock, 'hover', itemDisplay.slug else @$captionText.html ' ' @$captionIcon.attr 'src', '/images/empty.png' diff --git a/src/client/site/login_page/login_page_controller.coffee b/src/client/site/login_page/login_page_controller.coffee index 25d3da52d..1dc802f69 100644 --- a/src/client/site/login_page/login_page_controller.coffee +++ b/src/client/site/login_page/login_page_controller.coffee @@ -36,15 +36,18 @@ module.exports = class LoginPageController extends PageController # Event Methods ################################################################################ onExpandReadMore: (event)-> + tracker.trackEvent c.tracking.category.account, 'click-read-more' @$readMoreLink.css display: 'none' @$readMoreContainer.css 'max-height':@$readMoreContent.height() return false onLoginButtonClicked: (event)-> - @_redirectToGitHub() + tracker.trackEvent c.tracking.category.account, 'login' + .then => @_redirectToGitHub() return false onLogoutButtonClicked: (event)-> + tracker.trackEvent c.tracking.category.account, 'logout' global.site.logout() return false diff --git a/src/client/site/mod_page/mod_page.jade b/src/client/site/mod_page/mod_page.jade index 86827c604..7faf1a0ea 100644 --- a/src/client/site/mod_page/mod_page.jade +++ b/src/client/site/mod_page/mod_page.jade @@ -27,9 +27,9 @@ and recipes from this mod. .right p Official Links: - a.homePage project page - a.documentation documentation - a.download download + a.homePage(target="home-page") project page + a.documentation(target="documentation") documentation + a.download(target="download") download section.tutorials h2 Tutorials diff --git a/src/client/site/mod_page/mod_page_controller.coffee b/src/client/site/mod_page/mod_page_controller.coffee index 8c6c00a07..d8010e54b 100644 --- a/src/client/site/mod_page/mod_page_controller.coffee +++ b/src/client/site/mod_page/mod_page_controller.coffee @@ -30,14 +30,17 @@ module.exports = class ModPageController extends BaseController onToggleEnabled: -> if @model.activeVersion is Mod.Version.None + tracker.trackEvent c.tracking.category.modPack, 'toggle-on', @model.slug @model.activeVersion = Mod.Version.Latest else + tracker.trackEvent c.tracking.category.modPack, 'toggle-off', @model.slug @model.activeVersion = Mod.Version.None return false onVersionChanged: -> @model.activeVersion = @$versionSelector.val() + tracker.trackEvent c.tracking.category.modPack, 'select-version', "#{@model.slug}@#{@model.activeVersion}" return false # Property Methods ############################################################################# @@ -94,7 +97,8 @@ module.exports = class ModPageController extends BaseController events: -> return _.extend super, 'change .version select': 'onVersionChanged' - 'click .modpack': 'onToggleEnabled' + 'click .modpack': 'onToggleEnabled' + 'click .about .right a': 'routeLinkClick' # Private Methods ############################################################################## diff --git a/src/client/site/router.coffee b/src/client/site/router.coffee index cb04b7c76..c2b4eca18 100644 --- a/src/client/site/router.coffee +++ b/src/client/site/router.coffee @@ -31,7 +31,7 @@ module.exports = class Router extends Backbone.Router navigate: -> super - @_recordPageView() + tracker.trackPageView() routes: '': 'route__home' @@ -161,12 +161,3 @@ module.exports = class Router extends Backbone.Router _makeOptions: (options={})-> return _.extend options, @_siteController.controllerOptions - - _recordPageView: -> - pathname = window.location.pathname - - if global.env is 'production' and ga? - logger.info -> "Recording GA page view: #{pathname}" - ga 'send', 'pageview', pathname - else - logger.info -> "Suppressing GA page view: #{pathname}" diff --git a/src/client/site/url_params.coffee b/src/client/site/url_params.coffee index 68f5c4e73..3b798889e 100644 --- a/src/client/site/url_params.coffee +++ b/src/client/site/url_params.coffee @@ -1,5 +1,5 @@ # -# Crafting Guide - storage.coffee +# Crafting Guide - url_params.coffee # # Copyright © 2014-2016 by Redwood Labs # All rights reserved. diff --git a/src/client/tracker.coffee b/src/client/tracker.coffee new file mode 100644 index 000000000..f67681d62 --- /dev/null +++ b/src/client/tracker.coffee @@ -0,0 +1,67 @@ +# +# Crafting Guide - tracker.coffee +# +# Copyright © 2014-2016 by Redwood Labs +# All rights reserved. +# + +######################################################################################################################## + +module.exports = class Tracker + + @::TIMEOUT = 5000 + + constructor: -> + @_enabled = false + + # Public Methods ############################################################################### + + trackEvent: (category, action, label='', value=0)-> + return unless category? + @_trackEvent category, action, label, value + + trackOutboundEvent: (category, action, label='', value=0)-> + return unless category? + @_trackEvent category, action, label, value + + trackPageView: (pathname)-> + promise = w.promise (resolve, reject)=> + pathname ?= window.location.pathname + if @_enabled and ga? + logger.info -> "Recording GA page view: #{pathname}" + ga 'send', 'pageview', pathname, hitCallback: -> resolve true + else + logger.info -> "Suppressing GA page view: #{pathname}" + resolve true + + promise.timeout @TIMEOUT + .catch (error)-> + logger.error "Failed to send GA event!" + + # Property Methods ############################################################################# + + Object.defineProperties @prototype, + + enabled: + get: -> return @_enabled + + set: (newEnabled)-> + @_enabled = !! newEnabled + + # Private Methods ############################################################################## + + _trackEvent: (category, action, label='', value=0, options={})-> + promise = w.promise (resolve, reject)=> + options.hitCallback = -> + resolve true + + if @_enabled and ga? + logger.info -> "Recording GA event: #{category}, #{action}, '#{label}', #{value}" + ga 'send', 'event', category, action, label, value + else + logger.info -> "Suppressing GA event: #{category}, #{action}, '#{label}', #{value}" + resolve true + + promise.timeout @TIMEOUT + .catch (error)-> + logger.error -> "Failed to send GA event!: #{error}" diff --git a/src/common/constants.coffee b/src/common/constants.coffee index 97070cc99..f58bd9292 100644 --- a/src/common/constants.coffee +++ b/src/common/constants.coffee @@ -156,6 +156,20 @@ text.title = _.template 'Minecraft Crafting Guide' text.titleWithText = _.template '<%= text %> | Minecraft Crafting Guide' text.tutorialDescription = _.template 'Learn about <%= name %> in <%= mod %> and use the interactive crafting planner to get ready for your build!' +exports.tracking = tracking = {} +tracking.category = {} +tracking.category.account = 'account' +tracking.category.craft = 'craft' +tracking.category.craftHave = 'craft-have' +tracking.category.craftNeed = 'craft-need' +tracking.category.craftWant = 'craft-want' +tracking.category.feedback = 'feedback' +tracking.category.markdown = 'markdown' +tracking.category.modPack = 'mod-pack' +tracking.category.multiblock = 'multiblock' +tracking.category.navigate = 'navigate' +tracking.category.search = 'search' + exports.url = url = {} url.crafting = _.template "/craft/<%= inventoryText %>" url.item = _.template "/browse/<%= modSlug %>/<%= itemSlug %>/"