R_CLEAN_QUERY = /[^\w\s]/gTemplate
(c) 2014 Alasdair Mercer
Freely distributable under the MIT license:
http://template-extension.org/license
Regular expression used to sanitize search queries.
R_CLEAN_QUERY = /[^\w\s]/gRegular expression used to validate template keys.
R_VALID_KEY = /^[A-Z0-9]*\.[A-Z0-9]*$/iRegular expression used to validate keyboard shortcut inputs.
R_VALID_SHORTCUT = /[A-Z0-9]/iRegular expression used to identify whitespace.
R_WHITESPACE = /\s+/Copy of template being actively modified.
activeTemplate = nullEasily accessible reference to the extension controller as well as it’s Icon class.
{ext} = chrome.extension.getBackgroundPage()
{Icon} = extIndicate whether or not the user feedback feature has been added to the page.
feedbackAdded = noTemplates matching the current search query.
searchResults = nullBind an event of the specified type to the elements included by selector that, when
triggered, modifies the underlying option with the value returned by evaluate.
bindSaveEvent = (selector, type, option, evaluate, callback) ->
log.trace()
$(selector).on type, ->
$this = $ this
key = ''
value = null
store.modify option, (data) ->
key = $this.attr('id').match(new RegExp("^#{option}(\\S*)"))[1]
key = key[0].toLowerCase() + key[1..]
data[key] = value = evaluate.call $this, key
callback? $this, key, valueUpdate the options page with the values from the current settings.
load = ->
log.trace()
links = store.get 'links'
markdown = store.get 'markdown'
menu = store.get 'menu'
shortcuts = store.get 'shortcuts'
$('#analytics').prop 'checked', store.get 'analytics'
$('#linksTarget').prop 'checked', links.target
$('#linksTitle').prop 'checked', links.title
$('#markdownInline').prop 'checked', markdown.inline
$('#menuEnabled').prop 'checked', menu.enabled
$('#menuOptions').prop 'checked', menu.options
$('#menuPaste').prop 'checked', menu.paste
$('#shortcutsEnabled').prop 'checked', shortcuts.enabled
$('#shortcutsPaste').prop 'checked', shortcuts.paste
do loadControlEvents
do loadSaveEvents
do loadImages
do loadTemplates
do loadNotifications
do loadToolbar
do loadUrlShorteners
do loadDeveloperToolsBind the event handlers required for controlling general changes.
loadControlEvents = ->
log.trace()
$('#menuEnabled').on('change', ->
groups = $('#menuOptions').parents('.control-group').first()
groups = groups.add $('#menuPaste').parents('.control-group').first()
if $(this).is ':checked' then groups.slideDown() else groups.slideUp()
).trigger 'change'
$('#shortcutsEnabled').on('change', ->
groups = $('#shortcutsPaste').parents('.control-group').first()
if $(this).is ':checked' then groups.slideDown() else groups.slideUp()
).trigger 'change'Update the developer tools section of the options page with the current settings.
loadDeveloperTools = ->
log.trace()
do loadLoggerCreate an option element for each available template image.
Each element is inserted in to the select element containing template images on the options
page.
loadImages = ->
log.trace()
images = $ '#template_image'Add an option for when no icon is to be associated with the template.
images.append $ '<option/>',
text: new Icon().message
value: ''
images.append $ '<option/>',
disabled: 'disabled'
text: '---------------'Add options for each of the available template images.
for icon in ext.config.icons.current
images.append $ '<option/>',
text: icon.message
value: icon.nameUpdate the icon preview initially and whenever the user changes the selection.
images.on('change', ->
selectedIcon = Icon.get $(this).find('option:selected').val(), yes
$('#template_image_preview').attr 'class', selectedIcon.style
).trigger 'change'Update the logging section of the options page with the current settings.
loadLogger = ->
log.trace()
logger = store.get 'logger'
$('#loggerEnabled').prop 'checked', logger.enabled
loggerLevel = $ '#loggerLevel'
loggerLevel.find('option').remove()Add options for each of the available levels of logging.
for level in log.LEVELS
option = $ '<option/>',
text: i18n.get "opt_logger_level_#{level.name}_text"
value: level.value
option.prop 'selected', level.value is logger.level
loggerLevel.append optionEnsure debug level is selected if configuration currently matches none.
unless loggerLevel.find('option[selected]').length
loggerLevel.find("option[value='#{log.DEBUG}']").prop 'selected', yes
do loadLoggerSaveEventsBind the event handlers required for persisting logging changes.
loadLoggerSaveEvents = ->
log.trace()
bindSaveEvent '#loggerEnabled, #loggerLevel', 'change', 'logger', (key) ->
value = if key is 'level' then @val() else @is ':checked'
log.debug "Changing logging #{key} to '#{value}'"
value
, (jel, key, value) ->
chrome.extension.getBackgroundPage().log.config = log.config = store.get 'logger'
analytics.track 'Logging', 'Changed', utils.capitalize(key), Number valueUpdate the notification section of the options page with the current settings.
loadNotifications = ->
log.trace()
{enabled} = store.get 'notifications'
$('#notifications').prop 'checked', enabled
do loadNotificationSaveEventsBind the event handlers required for persisting notification changes.
loadNotificationSaveEvents = ->
log.trace()
$('#notifications').on 'change', ->
enabled = $(this).is ':checked'
log.debug "Changing notifications to '#{enabled}'"
store.modify 'notifications', (notifications) ->
notifications.enabled = enabled
analytics.track 'Notifications', 'Changed', 'Enabled', Number enabledBind the event handlers required for persisting general changes.
loadSaveEvents = ->
log.trace()
$('#analytics').on 'change', ->
enabled = $(this).is ':checked'
log.debug "Changing analytics to '#{enabled}'"
if enabled
store.set 'analytics', yes
chrome.extension.getBackgroundPage().analytics.add()
analytics.add()
analytics.track 'General', 'Changed', 'Analytics', 1
else
analytics.track 'General', 'Changed', 'Analytics', 0
analytics.remove()
chrome.extension.getBackgroundPage().analytics.remove()
store.set 'analytics', no
bindSaveEvent '#linksTarget, #linksTitle', 'change', 'links', (key) ->
value = @is ':checked'
log.debug "Changing links #{key} to '#{value}'"
value
, (jel, key, value) ->
analytics.track 'Links', 'Changed', utils.capitalize(key), Number value
bindSaveEvent '#markdownInline', 'change', 'markdown', (key) ->
value = @is ':checked'
log.debug "Changing markdown #{key} to '#{value}'"
value
, (jel, key, value) ->
analytics.track 'Markdown', 'Changed', utils.capitalize(key), Number value
bindSaveEvent '#menuEnabled, #menuOptions, #menuPaste', 'change', 'menu', (key) ->
value = @is ':checked'
log.debug "Changing context menu #{key} to '#{value}'"
value
, (jel, key, value) ->
ext.updateContextMenu()
analytics.track 'Context Menu', 'Changed', utils.capitalize(key), Number value
bindSaveEvent '#shortcutsEnabled, #shortcutsPaste', 'change', 'shortcuts', (key) ->
value = @is ':checked'
log.debug "Changing keyboard shortcuts #{key} to '#{value}'"
value
, (jel, key, value) ->
ext.updateToolbar() if key is 'enabled'
analytics.track 'Keyboard Shortcuts', 'Changed', utils.capitalize(key), Number valueUpdate the templates section of the options page with the current settings.
loadTemplates = ->
log.trace()Load all of the event handlers required for managing the templates.
do loadTemplateControlEvents
do loadTemplateImportEvents
do loadTemplateExportEventsLoad the template data into the table.
do loadTemplateRowsCreate a tr element representing the template provided.
The element returned should then be inserted in to the table that is displaying the templates.
loadTemplate = (template, modifiers) ->
log.trace()
modifiers ?= ext.modifiers()
log.debug 'Creating a row for the following template...', template
row = $ '<tr/>', draggable: yes
alignCenter = css: 'text-align': 'center'Add a column containing a checkbox allowing the user to select multiple templates for bulk operations.
row.append $('<td/>', alignCenter).append $ '<input/>',
title: i18n.get 'opt_select_box'
type: 'checkbox'
value: template.keyAdd a column to display the key details to allow the user to easily identify the template.
row.append $('<td/>').append $ '<span/>',
html: """<i class="#{Icon.get(template.image, yes).style}"></i> #{template.title}"""
title: i18n.get 'opt_template_modify_title', template.titleAdd a column to indicate the keyboard shortcut, if specified.
row.append $ '<td/>',
class: 'shortcut'
html: if template.shortcut then "#{modifiers}#{template.shortcut}" else ' 'Add a column to clearly indicate whether the template is enabled.
row.append $('<td/>', alignCenter).append $ '<i/>',
class: "icon-#{if template.enabled then 'ok' else 'remove'}"Add a column containing the template content which will be truncated if they overflow. Hovering over the content will display the full version in within a tooltip.
row.append $('<td/>').append $ '<span/>',
text: template.content
title: template.contentFinally, add a column to provide visual clues that drag & drop functionality is available for reordering the templates.
row.append $('<td/>').append $ '<span/>',
class: 'muted'
text: '::::'
title: i18n.get 'opt_template_move_title', template.titleBind the event handlers required for controlling the templates.
loadTemplateControlEvents = ->
log.trace()Ensure saved templates are properly validated before being persisted.
validationErrors = []Ensure template wizard is closed if/when tabify links are clicked within it.
$('#template_wizard [tabify], #template_cancel_btn').on 'click', ->
do closeWizard
$('#template_reset_btn').on 'click', ->
error.hide() for error in validationErrors
do resetWizardSupport search functionality for templates.
filter = $ '#template_filter'
filter.find('option').remove()
for limit in ext.config.options.limits
filter.append $ '<option/>', text: limit
filter.append $ '<option/>',
disabled: 'disabled'
text: '-----'
filter.append $ '<option/>',
text: i18n.get 'opt_show_all_text'
value: 0Ensure that the best default limit is selected initially.
store.init 'options_limit', parseInt $('#template_filter').val()
limit = store.get 'options_limit'
$('#template_filter option').each ->
$this = $ this
$this.prop 'selected', limit is parseInt $this.val()Update the visible templates whenever the display limit is changed.
$('#template_filter').on 'change', ->
store.set 'options_limit', parseInt $(this).val()
loadTemplateRows searchResults ? ext.templatesReset the filtered templates and the search input when the reset button is clicked.
$('#template_search :reset').on 'click', ->
$('#template_search :text').val ''
do searchTemplatesSearch for all of the templates that match the input query.
$('#template_controls').on 'submit', ->
searchTemplates $('#template_search :text').val()Ensure user confirms deletion of template.
$('#template_delete_btn').on 'click', ->
$this = $ this
return if $this.hasClass 'disabled'
$this.hide()
$('#template_confirm_delete').css 'display', 'inline-block'
$('#template_undo_delete_btn').on 'click', ->
$('#template_confirm_delete').hide()
$('#template_delete_btn').show()
$('#template_confirm_delete_btn').on 'click', ->
$('#template_confirm_delete').hide()
$('#template_delete_btn').show()
deleteTemplates [activeTemplate]
do closeWizardClear all existing validation errors when the wizard is closed.
$('#template_wizard').on 'hide', ->
error.hide() for error in validationErrorsValidate and persist the template data when the save button is clicked.
$('#template_save_btn').on 'click', ->Derive the template data from the input fields.
template = do deriveTemplateClear all existing validation errors before validating the derived template data.
error.hide() for error in validationErrors
validationErrors = validateTemplate template
if validationErrors.lengthShow all validation errors that were found.
error.show() for error in validationErrors
elseUpdate the active template with the derived data before persisting it in localStorage.
saveTemplate $.extend activeTemplate, templateEnsure any search criteria is cleared before closing the wizard and updating the templates displayed in the table.
$('#template_search :reset').hide()
$('#template_search :text').val ''
do closeWizardEnable/disable all selected templates when the corresponding buttons are clicked.
$('#disable_btn, #enable_btn').on 'click', ->
$this = $ this
return if $this.hasClass 'disabled'
enableTemplates do getSelectedTemplates, $this.is '#enable_btn'Open the template wizard without any context when creating a new template.
$('#add_btn').on 'click', ->
return if $(this).hasClass 'disabled'
openWizard nullEnsure confirmation is granted before deleting any templates and that no predefined templates are deleted at all.
selectedTemplates = []Clear all selected templates from memory when the wizard is closed.
$('#delete_wizard').on 'hide', ->
selectedTemplates = []
$('#delete_items li').remove()Ensure that only a single deletion warning can be displayed at any one time.
warningMsg = nullPrompt the user to confirm removal of the selected template(s).
$('#delete_btn').on 'click', ->
return if $(this).hasClass 'disabled'
warningMsg?.hide()
deleteItems = $ '#delete_items'
predefinedTemplates = $ '<ul/>'
selectedTemplates = do getSelectedTemplates
deleteItems.find('li').remove()Create list items for each selected template and allocate them accordingly.
for template in selectedTemplates
list = if template.readOnly then predefinedTemplates else deleteItems
list.append $ '<li/>', text: template.title
predefinedCount = predefinedTemplates.find('li').length
if predefinedCountShow warning message to inform the user that they have attempted to delete predefined template(s).
div = $ '<div/>'
if predefinedCount is 1
div.append $ '<p/>', html: i18n.get 'opt_template_delete_predefined_error_1'
div.append predefinedTemplates
div.append $ '<p/>', html: i18n.get 'opt_template_delete_predefined_error_2'
else
div.append $ '<p/>', html: i18n.get 'opt_template_delete_multiple_predefined_error_1'
div.append predefinedTemplates
div.append $ '<p/>', html: i18n.get 'opt_template_delete_multiple_predefined_error_2'
warningMsg = new WarningMessage yes
warningMsg.message = div.html()
warningMsg.show()
else if selectedTemplates.lengthBegin the removal process by showing the wizard.
$('#delete_wizard').modal 'show'Cancel the template removal process and hide the wizard.
$('#delete_cancel_btn, #delete_no_btn').on 'click', ->
$('#delete_wizard').modal 'hide'Complete the template removal process and hide the wizard.
$('#delete_yes_btn').on 'click', ->
deleteTemplates selectedTemplates
$('#delete_wizard').modal 'hide'Bind the event handlers required for exporting templates.
loadTemplateExportEvents = ->
log.trace()Simulate an alert closing without actually removing it from the DOM.
$('#export_error .close').on 'click', ->
$(this).next().html(' ').parent().hide()Ensure that the export wizard cleans up after itself when it’s closed.
$('#export_wizard').on 'hide', ->
$('#export_content').val ''
$('#export_save_btn').attr 'href', '#'
$('#export_error').find('span').html(' ').end().hide()Show the export wizard and create the exported data based on the selected templates.
$('#export_btn').on 'click', ->
return if $(this).hasClass 'disabled'
selectedTemplates = do getSelectedTemplates
if selectedTemplates.length
log.info 'Launching export wizard'
str = createExport selectedTemplates
$('#export_content').val str
blob = new Blob [str], type: 'application/json'
URL = window.URL or window.webkitURL
$('#export_save_btn').attr 'href', URL.createObjectURL blob
$('#export_wizard').modal 'show'Cancel the export process and hide the wizard.
$('#export_close_btn').on 'click', ->
$('#export_wizard').modal 'hide'Copy the JSON string into the system clipboard.
$('#export_copy_btn').on 'click', ->
ext.copy $('#export_content').val(), yesBriefly change the copy button’s text to indicate that the contents were copied.
$(this).text(i18n.get 'opt_export_wizard_copy_alt_button').delay(800).queue ->
$(this).text(i18n.get 'opt_export_wizard_copy_button').dequeue()Bind the event handlers required for importing templates.
loadTemplateImportEvents = ->
log.trace()
data = nullSimulate an alert closing without actually removing it from the DOM.
$('#import_error .close').on 'click', ->
$(this).next().html(' ').parent().hide()Ensure that the export wizard cleans up after itself when it’s closed.
$('#import_wizard').on 'hide', ->
data = null
$('#import_final_btn').prop 'disabled', yes
$('#import_wizard_stage2, #import_back_btn, #import_final_btn').hide()
$('#import_wizard_stage1, #import_continue_btn').show()
$('#import_content, #import_file_btn').val ''
$('#import_file_btn').val ''
$('#import_error').find('span').html(' ').end().hide()Restore the previous view in the import process when the back button is clicked.
$('#import_back_btn').on 'click', ->
log.info 'Going back to previous import stage'
$('#import_wizard_stage2, #import_back_btn, #import_final_btn').hide()
$('#import_wizard_stage1, #import_continue_btn').show()Show the import wizard to prompt the user to input/load the data to be imported.
$('#import_btn').on 'click', ->
return if $(this).hasClass 'disabled'
log.info 'Launching import wizard'
$('#import_wizard').modal 'show'Enable/disable the finalize button depending on whether any templates are currently selected.
$('#import_items').on 'change', ->
$('#import_final_btn').prop 'disabled', not $(this).find(':selected').lengthSelect all of the detected templates in the list.
$('#import_select_all_btn').on 'click', ->
$('#import_items option').prop('selected', yes).parent().focus()
$('#import_final_btn').prop 'disabled', noDeselect all of the detected templates in the list.
$('#import_deselect_all_btn').on 'click', ->
$('#import_items option').prop('selected', no).parent().focus()
$('#import_final_btn').prop 'disabled', yesRead the contents of the loaded file in to the text area and perform simple error handling.
$('#import_file_btn').on 'change', (e) ->
file = e.target.files[0]
reader = new FileReader()Gracefully handle any file system errors that occur.
reader.onerror = (err) ->
message = i18n.get switch err.code
when FileError.NOT_FOUND_ERR then 'error_file_not_found'
when FileError.ABORT_ERR then 'error_file_aborted'
else 'error_file_default'
log.warn 'The following error was caught when loading the imported file...', err
$('#import_error').find('span').text(message).end().show()Load the contents of the file and insert them into the text area.
reader.onload = (e) ->
result = e.target.result
log.debug 'The following contents were read from the file...', result
$('#import_error').find('span').html(' ').end().hide()
$('#import_content').val resultRead the file safely as plain text and allow the event handlers to do the rest of the work.
reader.readAsText fileComplete the import process once the data has been successfully imported.
$('#import_final_btn').on 'click', ->
return unless data?Merge all selected imported templates into the existing templates.
for template in data.templates
continue unless $("#import_items option[value='#{template.key}']").is ':selected'
matched = no
for existing, index in ext.templates when existing.key is template.key
matched = yes
template.index = index
ext.templates[index] = template
unless matched
template.index = ext.templates.length
ext.templates.push template
store.set 'templates', ext.templates
ext.updateTemplates()
do loadTemplateRows
do updateToolbarTemplates
analytics.track 'Templates', 'Imported', data.version, data.templates.lengthEnsure any search criteria is cleared before closing the wizard.
$('#import_wizard').modal 'hide'
$('#template_search :reset').hide()
$('#template_search :text').val ''Cancel the import process and hide the wizard.
$('#import_close_btn').on 'click', ->
$('#import_wizard').modal 'hide'Paste the contents of the system clipboard in to the textarea.
$('#import_paste_btn').on 'click', ->
$('#import_file_btn').val ''
$('#import_content').val ext.paste()
$(this).text(i18n.get 'opt_import_wizard_paste_alt_button').delay(800).queue ->
$(this).text(i18n.get 'opt_import_wizard_paste_button').dequeue()Read the imported data and attempt to extract any valid templates and list the changes to user for them to check and finalize.
$('#import_continue_btn').on 'click', ->
$this = $(this).prop 'disabled', yes
data = null
list = $ '#import_items'
str = $('#import_content').val()
$('#import_error').find('span').html(' ').end().hide()
list.find('option').remove()
tryAttempt to parse and read the imported contents.
data = readImport createImport str
if data.templates.length
$('#import_count').text data.templates.length
for template in data.templates
list.append $ '<option/>',
text: template.title
value: template.keyShow the final stage of the import wizard and allow the user to select which, if any, detected templates they would like to import.
$('#import_final_btn').prop 'disabled', yes
$('#import_wizard_stage1, #import_continue_btn').hide()
$('#import_wizard_stage2, #import_back_btn, #import_final_btn').show()
catch error
log.warn 'The following error occurred when parsing the imported data', error
$('#import_error').find('span').text(error).end().show()
$this.prop 'disabled', noLoad the templates into the table to be displayed to the user.
Optionally, pagination can be disabled but this should only really be used internally by the
pagination process.
loadTemplateRows = (templates = ext.templates, pagination = yes) ->
log.trace()
table = $ '#templates'Best to start from a clean slate so remove all existing rows.
table.find('tbody tr').remove()
if templates.lengthRetrieve the keyboard shortcut modifier(s) for current operating system once for a slight performance boost.
modifiers = ext.modifiers()Create and insert rows representing each template.
table.append loadTemplate template, modifiers for template in templatesApply pagination and other UI fanciness to the populated table and it’s new rows.
paginate templates if pagination
activateTooltips table
do activateDraggables
do activateModifications
elseShow a single row to indicate that no templates were found.
table.find('tbody').append $('<tr/>').append $ '<td/>',
colspan: table.find('thead th').length
html: i18n.get 'opt_no_templates_found_text'Uncheck the “Select All” checkbox before (re-)binding the click events for all “Select” checkboxes.
table.find('thead :checkbox').prop 'checked', no
do activateSelectionsUpdate the toolbar behaviour section of the options page with the current settings.
loadToolbar = ->
log.trace()
{close, popup, options} = store.get 'toolbar'
if popup then $('#toolbarPopupYes').addClass 'active'
else $('#toolbarPopupNo').addClass 'active'
$('#toolbarClose').prop 'checked', close
$('#toolbarOptions').prop 'checked', options
do updateToolbarTemplates
do loadToolbarControlEvents
do loadToolbarSaveEventsBind the event handlers required for controlling toolbar behaviour changes.
loadToolbarControlEvents = ->
log.trace()Bind a click event to listen for changes to the button selection.
$('#toolbarPopup .btn').on('click', ->
$(".#{$(this).attr 'id'}").show().siblings().hide()
).filter('.active').trigger 'click'Bind the event handlers required for persisting toolbar behaviour changes.
loadToolbarSaveEvents = ->
log.trace()
$('#toolbarPopup .btn').on 'click', ->
popup = not $('#toolbarPopupYes').hasClass 'active'
store.modify 'toolbar', (toolbar) -> toolbar.popup = popup
ext.updateToolbar()
log.debug "Toolbar popup enabled: #{popup}"
analytics.track 'Toolbars', 'Changed', 'Behaviour', Number popup
bindSaveEvent '#toolbarClose, #toolbarKey, #toolbarOptions', 'change', 'toolbar', (key) ->
value = if key is 'key' then @val() else @is ':checked'
log.debug "Changing toolbar #{key} to '#{value}'"
value
, (jel, key, value) ->
ext.updateToolbar()
analytics.track 'Toolbars', 'Change', utils.capitalize(key), if key is 'key' then Number valueUpdate the URL shorteners section of the options page with the current settings.
loadUrlShorteners = ->
log.trace()Ensure that the active URL shortener service is checked.
$('input[name="enabled_shortener"]').each ->
$this = $ this
$this.prop 'checked', store.get "#{$this.attr 'id'}.enabled"Return true to break the iteration.
trueYOURLS settings require special preparation since they’re unique.
yourls = store.get 'yourls'
$('#yourlsAuthentication' + (switch yourls.authentication
when 'advanced' then 'Advanced'
when 'basic' then 'Basic'
else 'None'
)).addClass 'active'
$('#yourlsPassword').val yourls.password
$('#yourlsSignature').val yourls.signature
$('#yourlsUrl').val yourls.url
$('#yourlsUsername').val yourls.username
do loadUrlShortenerAccounts
do loadUrlShortenerControlEvents
do loadUrlShortenerSaveEventsUpdate the accounts in the URL shorteners section of the options pages with current state of their OAuth objects.
loadUrlShortenerAccounts = ->
log.trace()Retrieve all URL shortener services that use OAuth and iterate over the results.
shorteners = _.filter ext.shorteners, (shortener) ->
shortener.oauth?
for shortener in shorteners
do (shortener) ->
button = $ "##{shortener.name}Account"Bind the event handler required for logging in and out of accounts and then reflect the current login state in the button.
button.on 'click', ->
$this = $(this).blur()
if $this.data('oauth') isnt 'true'Application isn’t yet authorized to use the service on the user’s behalf, so the user must want to do this now. Good for them!
$this.tooltip 'hide'
log.debug "Attempting to authorize #{shortener.name}"Attempt to authorize this application with the service on behalf of the user.
This steps asynchronous as it may require user confirmation (i.e. open a new tab and
redirect them to the OAuth permissions page - after authenticating themselves).
shortener.oauth.authorize ->
log.debug "Authorization response for #{shortener.name}...", arguments
success = shortener.oauth.hasAccessToken()
if successThis application has been successfully authorized so we’ll update the button to reflect this new state.
$this.addClass('btn-danger').removeClass 'btn-success'
$this.data 'oauth', 'true'
$this.html i18n.get 'opt_url_shortener_logout_button'
analytics.track 'Shorteners', 'Login', shortener.title, Number success
elseSince this application is already authorized to use the service the user must want to undo this, on this end at least.
log.debug "Removing authorization for #{shortener.name}"
shortener.oauth.clearAccessToken()bitly requires some extra clearing.
if $this.attr('id') is 'bitlyAccount'
shortener.oauth.clear 'apiKey'
shortener.oauth.clear 'login'Update the button to show that the user has logged out of the service.
$this.addClass('btn-success').removeClass 'btn-danger'
$this.data 'oauth', 'false'
$this.html i18n.get 'opt_url_shortener_login_button'
analytics.track 'Shorteners', 'Logout', shortener.titleApply a visual indication of the service’s current authentication state to the button.
if shortener.oauth.hasAccessToken()
button.addClass('btn-danger').removeClass 'btn-success'
button.data 'oauth', 'true'
button.html i18n.get 'opt_url_shortener_logout_button'
else
button.addClass('btn-success').removeClass 'btn-danger'
button.data 'oauth', 'false'
button.html i18n.get 'opt_url_shortener_login_button'Bind the event handlers required for controlling URL shortener configuration changes.
loadUrlShortenerControlEvents = ->
log.trace()Bind a click event to listen for changes to the button selection.
$('#yourlsAuthentication button').on('click', ->
$(".#{$(this).attr 'id'}").show().siblings().hide()
).filter('.active').trigger 'click'Bind the event handlers required for persisting URL shortener configuration changes.
loadUrlShortenerSaveEvents = ->
log.trace()Update each URL shortener whenever the user switches between them.
$('input[name="enabled_shortener"]').on 'change', ->
store.modify 'bitly', 'googl', 'yourls', (data, key) ->
enabled = data.enabled = $("##{key}").is ':checked'
if enabled
shortener = _.findWhere ext.shorteners, name: key
log.debug "Enabling #{shortener.title} URL shortener"
analytics.track 'Shorteners', 'Enabled', shortener.titleUpdate the YOURLS authentication mechanism when the user switches modes.
$('#yourlsAuthentication button').on 'click', ->
$this = $ this
auth = $this.attr('id').match(/yourlsAuthentication(.*)/)[1]
store.modify 'yourls', (yourls) ->
yourls.authentication = if auth is 'None' then '' else auth.toLowerCase()
log.debug "YOURLS authentication changed: #{auth}"
analytics.track 'Shorteners', 'Changed', 'YOURLS Authentication', $this.index()All YOURLS fields should be trimmed before being persisted when changed.
yourlsFields = '#yourlsPassword, #yourlsSignature, #yourlsUrl, #yourlsUsername'
bindSaveEvent yourlsFields, 'input', 'yourls', ->
@val().trim()Delete all of the templates provided.
deleteTemplates = (templates) ->
log.trace()
keys = []
list = []Ensure that no predefined templates are removed.
for template in templates when not template.readOnly
keys.push template.key
list.push templateNo user-created templates were provided so go no further.
return unless keys.length
retain = []Retain only templates whose keys have not been specified for removal.
for template, i in ext.templates when template.key not in keys
template.index = retain.length
retain.push template
store.set 'templates', retain
ext.updateTemplates()
if keys.length > 1
log.debug "Deleted #{keys.length} templates"
analytics.track 'Templates', 'Deleted', "Count[#{keys.length}]"
else
template = list[0]
log.debug "Deleted #{template.title} template"
analytics.track 'Templates', 'Deleted', template.title
loadTemplateRows searchResults ? ext.templates
do updateToolbarTemplatesEnable/disable all of the templates provided.
enableTemplates = (templates, enable = yes) ->
log.trace()
keys = _.pluck templates, 'key'No templates were provided so we can stop here.
return unless keys.lengthEnable/disable only templates whose keys were specified.
template.enabled = enable for template in ext.templates when template.key in keys
store.set 'templates', ext.templates
ext.updateTemplates()
action = if enable then 'Enabled' else 'Disabled'
if keys.length > 1
log.debug "#{action} #{keys.length} templates"
analytics.track 'Templates', action, "Count[#{keys.length}]"
else
template = templates[0]
log.debug "#{action} #{template.title} template"
analytics.track 'Templates', action, template.title
loadTemplateRows searchResults ? ext.templatesReorder the templates after a drag and drop swap by updating their indices and sorting them
accordingly.
These changes are then persisted and should be reflected throughout the extension.
reorderTemplates = (fromIndex, toIndex) ->
log.trace()
if fromIndex? and toIndex?
ext.templates[fromIndex].index = toIndex
ext.templates[toIndex].index = fromIndex
store.set 'templates', ext.templates
ext.updateTemplates()
do updateToolbarTemplatesUpdate and persist the template provided.
Any required validation should be performed perior to calling this method.
saveTemplate = (template) ->
log.trace()
log.debug 'Saving the following template...', template
isNew = not template.key?
templates = store.get 'templates'
if isNewSimply add new templates to the end.
template.index = templates.length
template.key = utils.keyGen()
templates.push template
elseReplace the existing template with the same key as the updated template.
for temp, i in templates when temp.key is template.key
templates[i] = template
break
store.set 'templates', templates
ext.updateTemplates()
do loadTemplateRows
do updateToolbarTemplates
action = if isNew then 'Added' else 'Saved'
analytics.track 'Templates', action, template.title
templateUpdate the existing template with information extracted from the imported template provided.template should not be altered in any way and the properties of existing should only be
changed if the replacement values are valid.
Protected properties will only be updated if existing is not read-only and the replacement
value is valid.
updateImportedTemplate = (template, existing) ->
log.trace()
log.debug 'Updating an existing template with the following imported data...', template
existing.enabled = template.enabled
existing.usage = template.usageEnsure that certain fields of read-only templates are protected.
unless existing.readOnly
existing.content = template.contentOnly allow valid titles.
existing.title = template.title if 0 < template.title.length <= 32Only allow existing images or None.
if template.image is '' or Icon.exists template.image
existing.image = template.imageOnly allow valid keyboard shortcuts or empty.
if template.shortcut is '' or isShortcutValid template.shortcut
existing.shortcut = template.shortcut
log.debug 'Updated the following template...', existing
existingUpdate the selection of templates in the toolbar behaviour section to reflect those available in the templates section.
updateToolbarTemplates = ->
log.trace()
toolbarKey = store.get 'toolbar.key'
toolbarTemplates = $ '#toolbarKey'
lastSelectedTemplate = toolbarTemplates.find 'option:selected'
lastSelectedTemplateKey = if lastSelectedTemplate.length then lastSelectedTemplate.val() else ''
toolbarTemplates.find('option').remove()
for template in ext.templates
opt = $ '<option/>',
text: template.title
value: template.key
opt.prop 'selected', template.key in [lastSelectedTemplateKey, toolbarKey]
toolbarTemplates.append optValidationError allows easy management and display of validation error messages that are
associated with specific fields.
class ValidationError extends utils.ClassCreate a new instance of ValidationError.
constructor: (@id, @key) ->
@className = 'error'Hide any validation messages currently displayed for the associated field.
hide: ->
field = $ "##{@id}"
field.parents('.control-group:first').removeClass @className
field.parents('.controls:first').find('.help-block').remove()Display the validation message for the associated field.
show: ->
field = $ "##{@id}"
field.parents('.control-group:first').addClass @className
field.parents('.controls:first').append $ '<span/>',
class: 'help-block'
html: i18n.get @keyValidationWarning allows easy management and display of validation warning messages that are
associated with specific fields.
class ValidationWarning extends ValidationErrorCreate a new instance of ValidationWarning.
constructor: (@id, @key) ->
@className = 'warning'Indicate whether or not the specified key is valid.
isKeyValid = (key) ->
log.trace()
R_VALID_KEY.test keyIndicate whether or not the specified keyboard shortcut is valid for use by a template.
isShortcutValid = (shortcut) ->
log.trace()
R_VALID_SHORTCUT.test shortcutIndicate whether or not template contains the required fields of the correct types.
Perform a soft validation without validating the values themselves, instead only their
existence.
validateImportedTemplate = (template) ->
log.trace()
log.debug 'Validating property types of the following imported template...', template
'object' is typeof template and
'string' is typeof template.content and
'boolean' is typeof template.enabled and
'string' is typeof template.image and
'string' is typeof template.key and
'string' is typeof template.shortcut and
'string' is typeof template.title and
'number' is typeof template.usageValidate the template and return any validation errors/warnings that were encountered.
Any validation errors that are encountered will be returned as a result.
validateTemplate = (template) ->
log.trace()
isNew = not template.key?
errors = []
log.debug 'Validating the following template...', templateTitle is required for all user-created templates.
if not template.readOnly and not template.title
errors.push new ValidationError 'template_title', 'opt_template_title_invalid'Keyboard shortcuts must be valid when used.
if template.shortcut and not isShortcutValid template.shortcut
errors.push new ValidationError 'template_shortcut', 'opt_template_shortcut_invalid'
log.debug 'Following validation errors were found...', errors
errorsMessage allows simple management and display of alert messages.
class Message extends utils.ClassCreate a new instance of Message.
constructor: (@block) ->
@className = 'alert-info'
@headerKey = 'opt_message_header'Hide this Message if it has previously been displayed.
hide: ->
@alert?.alert 'close'Display this Message at the top of the current tab.
show: ->
@header = i18n.get @headerKey if @headerKey and not @header?
@message = i18n.get @messageKey if @messageKey and not @message?Create the alert based on this Message.
@alert = $ '<div/>', class: "alert #{@className}"
@alert.addClass 'alert-block' if @block
@alert.append $ '<button/>',
class: 'close'
'data-dismiss': 'alert'
html: '×'
type: 'button'
if @header
@alert.append $ "<#{if @block then 'h4' else 'strong'}/>", html: @header
if @message
@alert.append if @block then @message else " #{@message}"
@alert.prependTo $ $('#navigation li.active a').attr 'tabify'ErrorMessage allows simple management and display of error messages.
class ErrorMessage extends MessageCreate a new instance of ErrorMessage.
constructor: (@block) ->
@className = 'alert-error'
@headerKey = 'opt_message_error_header'SuccessMessage allows simple management and display of success messages.
class SuccessMessage extends MessageCreate a new instance of SuccessMessage.
constructor: (@block) ->
@className = 'alert-success'
@headerKey = 'opt_message_success_header'WarningMessage allows simple management and display of warning messages.
class WarningMessage extends MessageCreate a new instance of WarningMessage.
constructor: (@block) ->
@className = ''
@headerKey = 'opt_message_warning_header'Create a template from the information from the imported template provided.template is not altered in any way and the new template is only created if template has a
valid key.
Other than the key, any invalid fields will not be copied to the new template which will instead
use the preferred default value for those fields.
addImportedTemplate = (template) ->
log.trace()
log.debug 'Creating a new template with the following details...', template
if isKeyValid template.key
newTemplate =
content: template.content
enabled: template.enabled
image: ''
key: template.key
readOnly: no
shortcut: ''
title: i18n.get 'untitled'
usage: template.usageOnly allow existing images.
newTemplate.image = template.image if Icon.exists template.imageOnly allow valid keyboard shortcuts.
newTemplate.shortcut = template.shortcut if isShortcutValid template.shortcutOnly allow valid titles.
newTemplate.title = template.title if 0 < template.title.length <= 32
log.debug 'Following template was created...', newTemplate
newTemplateActivate drag and drop functionality for reordering templates.
The activation is done cleanly, unbinding any associated events that have been previously bound.
activateDraggables = ->
log.trace()
table = $ '#templates'
dragSource = null
draggables = table.find '[draggable]'Remove all previous bound DnD events.
draggables.off 'dragstart dragend dragenter dragover drop'
draggables.on 'dragstart', (e) ->
$this = $ this
dragSource = this
table.removeClass 'table-hover'
$this.addClass 'dnd-moving'
$this.find('[data-original-title]').tooltip 'hide'
e.originalEvent.dataTransfer.effectAllowed = 'move'
e.originalEvent.dataTransfer.setData 'text/html', $this.html()
draggables.on 'dragend', (e) ->
dragSource = null
draggables.removeClass 'dnd-moving dnd-over'
table.addClass 'table-hover'
draggables.on 'dragenter', (e) ->
$this = $ this
draggables.not($this).removeClass 'dnd-over'
$this.addClass 'dnd-over'
draggables.on 'dragover', (e) ->
e.originalEvent.dataTransfer.dropEffect = 'move'
e.preventDefault()Return false to ensure default behaviour is prevented.
false
draggables.on 'drop', (e) ->
$dragSource = $ dragSource
e.stopPropagation()
if dragSource isnt this
$this = $ this
$dragSource.html $this.html()
$this.html e.originalEvent.dataTransfer.getData 'text/html'
activateTooltips table
do activateModifications
do activateSelections
fromIndex = $dragSource.index()
toIndex = $this.index()
if searchResults?
fromIndex = searchResults[fromIndex].index
toIndex = searchResults[toIndex].index
reorderTemplates fromIndex, toIndexReturn false to ensure default behaviour is prevented.
falseActivate functionality to open template wizard when a row is clicked.
The activation is done cleanly, unbinding any associated events that have been previously bound.
activateModifications = ->
log.trace()
$('#templates tbody tr td:not(:first-child)').off('click').on 'click', ->
key = $(this).parents('tr:first').find(':checkbox').val()
openWizard _.findWhere ext.templates, {key}Activate select all/one functionality on the templates table.
The activation is done cleanly, unbinding any associated events that have been previously bound.
activateSelections = ->
log.trace()
table = $ '#templates'
selectBoxes = table.find 'tbody :checkbox'Refresh bulk operation buttons when individual row select boxes are checked/unchecked.
selectBoxes.off('change').on 'change', ->
$this = $ this
messageKey = 'opt_select_box'
messageKey += '_checked' if $this.is ':checked'
$this.attr 'data-original-title', i18n.get messageKey
do refreshSelectButtonsRefresh bulk operation buttons when select all boxes are checked/unchecked, while also copying their state to all individual row select boxes.
table.find('thead :checkbox').off('change').on 'change', ->
$this = $ this
checked = $this.is ':checked'
messageKey = 'opt_select_all_box'
messageKey += '_checked' if checked
$this.attr 'data-original-title', i18n.get messageKey
selectBoxes.prop 'checked', checked
do refreshSelectButtons
do refreshSelectButtonsActivate tooltip effects, optionally only within a specific context.
activateTooltips = (selector) ->
log.trace()
base = $ selector or documentReset all previously treated tooltips.
base.find('[data-original-title]').each ->
$this = $ this
$this.tooltip 'destroy'
$this.attr 'title', $this.attr 'data-original-title'
$this.removeAttr 'data-original-title'Apply tooltips to all relevant elements.
base.find('[title]').each ->
$this = $ this
placement = $this.attr 'data-placement'
placement = if placement? then utils.trimToLower placement else 'top'
$this.tooltip {container: $this.attr('data-container') ? 'body', placement}Convenient shorthand for setting the current context to null.
clearContext = ->
log.trace()
setContext nullClear the current context and close the template wizard.
closeWizard = ->
log.trace()
do clearContext
$('#template_wizard').modal 'hide'Create a JSON string to export the specified templates.
createExport = (templates) ->
log.trace()
log.debug 'Creating an export string for the following templates...', templates
data =
templates: templates[..]
version: ext.versionRemove any temporary context menu ID’s from the templates to be exported.
delete template.menuId for template in data.templates
if data.templates.length
analytics.track 'Templates', 'Exported', ext.version, data.templates.length
log.debug 'Following export data has been created...', data
JSON.stringify dataCreate a JSON object from the imported string specified.
createImport = (str) ->
log.trace()
log.debug 'Parsing the following import string...', str
try data = JSON.parse str catch error then throw i18n.get 'error_import_data'
if not _.isArray(data.templates) or _.isEmpty(data.templates) or not _.isString data.version
throw i18n.get 'error_import_invalid'
log.debug 'Following data was created from the string...', data
dataCreate a template based on the current context and the information derived from the wizard fields.
deriveTemplate = ->
log.trace()
readOnly = activeTemplate.readOnly ? no
template =
content: if readOnly then activeTemplate.content else $('#template_content').val()
enabled: $('#template_enabled').is ':checked'
image: $('#template_image').val()
index: activeTemplate.index ? ext.templates.length
key: activeTemplate.key
readOnly: readOnly
shortcut: utils.trimToUpper $('#template_shortcut').val()
title: if readOnly then activeTemplate.title else $('#template_title').val().trim()
usage: activeTemplate.usage ? 0
log.debug 'Following template was derived...', template
templateAdd the user feedback feature to the page.
feedback = ->
log.trace()Only load and configure the feedback widget once.
return if feedbackAddedCreate a script element to load the UserVoice widget.
uv = document.createElement 'script'
uv.async = 'async'
uv.src = "https://widget.uservoice.com/#{ext.config.options.userVoice.id}.js"Insert the script element into the DOM.
script = document.getElementsByTagName('script')[0]
script.parentNode.insertBefore uv, scriptConfigure the widget as it’s loading.
UserVoice = window.UserVoice or= []
UserVoice.push [
'showTab'
'classic_widget'
{
mode: 'full'
primary_color: '#333'
link_color: '#08c'
default_mode: 'feedback'
forum_id: ext.config.options.userVoice.forum
tab_label: i18n.get 'opt_feedback_button'
tab_color: '#333'
tab_position: 'middle-left'
tab_inverted: yes
}
]Ensure that the widget isn’t loaded again.
feedbackAdded = yesRetrieve all currently selected templates.
getSelectedTemplates = ->
log.trace()
keys = []
$('#templates tbody :checkbox:checked').each ->
keys.push $(this).val()
_.filter ext.templates, (template) ->
template.key in keysOpen the template wizard after optionally setting the current context, if one is specified.
openWizard = (template) ->
log.trace()
setContext template if arguments.length
$('#template_wizard').modal 'show'Update the pagination UI for the specified templates.
paginate = (templates) ->
log.trace()
limit = parseInt $('#template_filter').val()
pagination = $ '#pagination'
if templates.length > limit > 0The results span across multiple pages so we need to show the pagination UI.
children = pagination.find 'ul li'
pages = Math.ceil templates.length / limitRefresh the pagination link states based on the new page.
refreshPagination = (page = 1) ->Select and display the desired templates subset.
start = limit * (page - 1)
end = start + limit
loadTemplateRows templates[start...end], noEnsure that the previous link is only enabled when a previous page exists.
pagination.find('ul li:first-child').each ->
if page is 1
$(this).addClass 'disabled'
else
$(this).removeClass 'disabled'Ensure only the active page is highlighted.
pagination.find('ul li:not(:first-child, :last-child)').each ->
$this = $ this
if page is parseInt $this.text()
$this.addClass 'active'
else
$this.removeClass 'active'Ensure that the next link is only enabled when a next page exists.
pagination.find('ul li:last-child').each ->
$this = $ this
if page is pages
$this.addClass 'disabled'
else
$this.removeClass 'disabled'Create and insert the pagination links.
if pages isnt children.length - 2
children.remove()
list = pagination.find 'ul'
list.append $('<li/>').append $ '<a>«</a>'
list.append $('<li/>').append $ "<a>#{page}</a>" for page in [1..pages]
list.append $('<li/>').append $ '<a>»</a>'Bind event handlers to manage navigating pages.
pagination.find('ul li').off 'click'
pagination.find('ul li:first-child').on 'click', ->
unless $(this).hasClass 'disabled'
refreshPagination pagination.find('ul li.active').index() - 1
pagination.find('ul li:not(:first-child, :last-child)').on 'click', ->
$this = $ this
refreshPagination $this.index() unless $this.hasClass 'active'
pagination.find('ul li:last-child').on 'click', ->
unless $(this).hasClass 'disabled'
refreshPagination pagination.find('ul li.active').index() + 1
do refreshPagination
pagination.show()
elseHide the pagination and remove all links as the results fit on a single page.
pagination.hide().find('ul li').remove()Read the imported data created by createImport and extract all of the imported templates that
appear to be valid.
When overwriting an existing template, only the properties with valid values will be transferred
with the exception of protected properties (i.e. on read-only templates).
When creating a new template, any invalid properties will be replaced with their default values.
readImport = (importData) ->
log.trace()
log.debug 'Importing the following data...', importData
data = templates: []
keys = []
storedKeys = _.pluck ext.templates, 'key'
for template in importData.templates
existing = {}
if importData.version < '1.0.0'Ensure templates imported from previous versions are valid for v1.0.0+.
template.key = ext.getKeyForName template.name
template.usage = 0
template.image = if template.image > 0 then Icon.fromLegacy(template.image - 1)?.name or ''
else ''
else if importData.version < '1.1.0'Ensure templates imported from previous versions are valid for v1.1.0+.
template.image = Icon.fromLegacy(template.image)?.name or ''
if validateImportedTemplate template
if template.key not in storedKeys and template.key not in keysTemplate doesn’t exist and hasn’t already been imported so handle it now.
template = addImportedTemplate template
if template
template.index = storedKeys.length + keys.length
data.templates.push template
keys.push template.key
elseTemplate has already been imported so let’s attempt to update the previously imported template.
for imported, i in data.templates when imported.key is template.key
existing = updateImportedTemplate template, imported
data.templates[i] = existing
break
unless existing.keyCouldn’t find a previously imported template with that key so user must already have the template stored. Try to locate the stored template and create a clone of it.
existing = $.extend yes, {}, _.findWhere ext.templates, key: template.keyNow attempt to update the derived template.
if existing and not _.isEmpty existing
existing = updateImportedTemplate template, existing
data.templates.push existing
keys.push existing.key
log.debug 'Following data was derived from that imported...', data
dataUpdate the state of the reset button depending on the current search input.
refreshResetButton = ->
log.trace()
container = $ '#template_search'
resetBtn = container.find ':reset'
if container.find(':text').val()
container.addClass 'input-prepend'
resetBtn.show()
else
resetBtn.hide()
container.removeClass 'input-prepend'Update the state of certain buttons depending on whether any select boxes have been checked.
refreshSelectButtons = ->
log.trace()
templates = do getSelectedTemplatesUpdate the visual state of buttons.
updateButton = (buttons, disabled, titleKey) ->
buttons[if disabled then 'addClass' else 'removeClass'] 'disabled'
buttons.attr 'data-original-title', i18n.get titleKeyExport button can simply be enabled when any templates have been selected.
updateButton $('#export_btn'), not templates.length, if templates.length
'opt_export_button_title'
else
'opt_template_none_selected_title'Determine whether all selections share the same enabled state and if any are predefined.
allEnabled = yes
allDisabled = yes
hasPredefined = no
for template in templates
allEnabled = no unless template.enabled
allDisabled = no if template.enabled
hasPredefined = yes if template.readOnly
unless templates.lengthDoesn’t matter as no templates have been selected.
updateButton $('#disable_btn, #enable_btn'), yes, 'opt_template_none_selected_title'
else if allEnabled or allDisabledTemplates are either all enabled or disabled so only enable the relevant button.
updateButton $('#disable_btn'), allDisabled, if allDisabled
'opt_template_disabled_selected_title'
else
'opt_disable_button_title'
updateButton $('#enable_btn'), allEnabled, if allEnabled
'opt_template_enabled_selected_title'
else
'opt_enable_button_title'
elseTemplates have a mixed enabled state so enable both buttons.
updateButton $('#enable_btn'), no, 'opt_enable_button_title'
updateButton $('#disable_btn'), no, 'opt_disable_button_title'Delete button should only be enabled when only user-created templates have been selected.
unless templates.length
updateButton $('#delete_btn'), yes, 'opt_template_none_selected_title'
else if hasPredefined
updateButton $('#delete_btn'), yes, 'opt_template_no_predefined_delete_title'
else
updateButton $('#delete_btn'), no, 'opt_delete_button_title'Reset the wizard field values based on the current context.
resetWizard = ->
log.trace()
activeTemplate ?= {}Display an appropriate header in the wizard depending on whether the active template is new.
$('#template_wizard .modal-header h3').html if activeTemplate.key?
i18n.get 'opt_template_modify_title', activeTemplate.title
else
i18n.get 'opt_template_new_header'Assign template values to their respective fields, falling back on simple defaults.
$('#template_enabled').prop 'checked', activeTemplate.enabled ? yes
$('#template_content').val activeTemplate.content or ''
$('#template_shortcut').val activeTemplate.shortcut or ''
$('#template_title').val activeTemplate.title or ''Update the fields and controls to reflect selected option.
imgOpt = $ "#template_image option[value='#{activeTemplate.image}']"
if imgOpt.length then imgOpt.prop 'selected', yes
else $('#template_image option:first-child').prop 'selected', yes
$('#template_image').trigger 'change'Disable appropriate fields for predefined templates.
$('#template_content, #template_title').prop 'disabled', !!activeTemplate.readOnly
$('#template_delete_btn').each ->
$this = $ this
$this[if activeTemplate.readOnly then 'addClass' else 'removeClass'] 'disabled'
$this.attr 'data-original-title', i18n.get if activeTemplate.readOnly
'opt_template_no_predefined_delete_title'
else
'opt_wizard_delete_button_title'
if activeTemplate.key? then $this.show() else $this.hide()Search the templates for the specified query and filter those displayed.
searchTemplates = (query = '') ->
log.trace()
keywords = query.replace(R_CLEAN_QUERY, '').split R_WHITESPACE
if keywords.length
expression = ///
#{(keyword for keyword in keywords when keyword).join '|'}
///i
searchResults = _.filter ext.templates, (template) ->
expression.test "#{template.content} #{template.title}"
else
searchResults = null
loadTemplateRows searchResults ? ext.templates
do refreshResetButtonSet the current context of the template wizard.
setContext = (template = {}) ->
log.trace()
activeTemplate = $.extend {}, template
do resetWizard
options = window.options = new class Options extends utils.ClassInitialize the options page.
This will involve inserting and configuring the UI elements as well as loading the current
settings.
init: ->
log.trace()
log.info 'Initializing the options page'Add support for analytics if the user hasn’t opted out.
analytics.add() if store.get 'analytics'Add the user feedback feature to the page.
do feedbackBegin initialization.
i18n.init
bitlyAccount: opt_url_shortener_account_title: i18n.get 'shortener_bitly'
googlAccount: opt_url_shortener_account_title: i18n.get 'shortener_googl'
version_definition: opt_guide_standard_version_text: ext.versionEnsure the current year is displayed throughout, where appropriate.
$('.year-repl').html "#{new Date().getFullYear()}"Bind tab selection event to all tabs.
initialTabChange = yes
$('a[tabify]').on 'click', ->
target = $(this).attr 'tabify'
nav = $ "#navigation a[tabify='#{target}']"
parent = nav.parent 'li'
unless parent.hasClass 'active'
parent.addClass('active').siblings().removeClass 'active'
$(target).show().siblings('.tab').hide()
id = nav.attr 'id'
store.set 'options_active_tab', id
unless initialTabChange
id = utils.capitalize id.match(/(\S*)_nav$/)[1]
log.debug "Changing tab to #{id}"
analytics.track 'Tabs', 'Changed', id
initialTabChange = no
$(document.body).scrollTop 0Reflect the previously persisted tab initially.
store.init 'options_active_tab', 'general_nav'
optionsActiveTab = store.get 'options_active_tab'
$("##{optionsActiveTab}").trigger 'click'
log.debug "Initially displaying tab for #{optionsActiveTab}"Bind basic Developer Tools wizard events to their corresponding elements.
$('#tools_nav').on 'click', ->
$('#tools_wizard').modal 'show'
$('.tools_close_btn').on 'click', ->
$('#tools_wizard').modal 'hide'Ensure that form submissions don’t reload the page.
$('form:not([target="_blank"])').on 'submit', ->Return false to ensure default behaviour is prevented.
falseBind analytical tracking events to key footer buttons and links.
$('footer a[href*="template-extension.org"]').on 'click', ->
analytics.track 'Footer', 'Clicked', 'Homepage'Setup and configure the donation button in the footer.
$('#donation input[name="hosted_button_id"]').val ext.config.options.payPal
$('#donation').on 'submit', ->
analytics.track 'Footer', 'Clicked', 'Donate'Load the current option values.
do loadEnsure first enabled input field in modals get focus when opened.
$('.modal').on 'shown', ->
$(this).find(':input:enabled:first').focus()Show OS-specific keyboard shortcut modifiers in the template wizard.
$('#template_shortcut_modifier').html ext.modifiers()Initialize all popovers, tooltips and go-to links.
$('[popover]').each ->
$this = $ this
placement = $this.attr 'data-placement'
placement = if placement? then utils.trimToLower placement else 'right'
trigger = $this.attr 'data-trigger'
trigger = if trigger? then utils.trimToLower trigger else 'hover'
$this.popover {
placement
trigger
html: yes
content: ->
i18n.get $this.attr 'popover'
}
if trigger is 'manual'
$this.on 'click', ->
$this.popover 'toggle'
do activateTooltipsRetrieve the initial height of the navigation bar.
navHeight = $('.navbar').height()Offset the screen by the initial height of the navigation bar when a goto link is clicked so that the relevant content isn’t hidden.
$('[data-goto]').on 'click', ->
goto = $ $(this).attr 'data-goto'
pos = goto.position()?.top or 0
pos -= navHeight if pos and pos >= navHeight
log.debug "Relocating view to include '#{goto.selector}' at #{pos}"
$(window).scrollTop posInitialize options when the DOM is ready.
utils.ready -> options.init()