R_CLEAN_QUERY = /[^\w\s]/g
Template
(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]/g
Regular expression used to validate template keys.
R_VALID_KEY = /^[A-Z0-9]*\.[A-Z0-9]*$/i
Regular expression used to validate keyboard shortcut inputs.
R_VALID_SHORTCUT = /[A-Z0-9]/i
Regular expression used to identify whitespace.
R_WHITESPACE = /\s+/
Copy of template being actively modified.
activeTemplate = null
Easily accessible reference to the extension controller as well as it’s Icon
class.
{ext} = chrome.extension.getBackgroundPage()
{Icon} = ext
Indicate whether or not the user feedback feature has been added to the page.
feedbackAdded = no
Templates matching the current search query.
searchResults = null
Bind 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, value
Update 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 loadDeveloperTools
Bind 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 loadLogger
Create 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.name
Update 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 option
Ensure 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 loadLoggerSaveEvents
Bind 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 value
Update the notification section of the options page with the current settings.
loadNotifications = ->
log.trace()
{enabled} = store.get 'notifications'
$('#notifications').prop 'checked', enabled
do loadNotificationSaveEvents
Bind 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 enabled
Bind 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 value
Update 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 loadTemplateExportEvents
Load the template data into the table.
do loadTemplateRows
Create 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.key
Add 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.title
Add 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.content
Finally, 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.title
Bind 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 resetWizard
Support 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: 0
Ensure 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.templates
Reset the filtered templates and the search input when the reset button is clicked.
$('#template_search :reset').on 'click', ->
$('#template_search :text').val ''
do searchTemplates
Search 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 closeWizard
Clear all existing validation errors when the wizard is closed.
$('#template_wizard').on 'hide', ->
error.hide() for error in validationErrors
Validate 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 deriveTemplate
Clear all existing validation errors before validating the derived template data.
error.hide() for error in validationErrors
validationErrors = validateTemplate template
if validationErrors.length
Show all validation errors that were found.
error.show() for error in validationErrors
else
Update the active template with the derived data before persisting it in localStorage
.
saveTemplate $.extend activeTemplate, template
Ensure 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 closeWizard
Enable/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 null
Ensure 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 = null
Prompt 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 predefinedCount
Show 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.length
Begin 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(), yes
Briefly 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 = null
Simulate 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').length
Select 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', no
Deselect 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', yes
Read 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 result
Read the file safely as plain text and allow the event handlers to do the rest of the work.
reader.readAsText file
Complete 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.length
Ensure 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()
try
Attempt 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.key
Show 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', no
Load 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.length
Retrieve 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 templates
Apply pagination and other UI fanciness to the populated table and it’s new rows.
paginate templates if pagination
activateTooltips table
do activateDraggables
do activateModifications
else
Show 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 activateSelections
Update 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 loadToolbarSaveEvents
Bind 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 value
Update 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.
true
YOURLS 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 loadUrlShortenerSaveEvents
Update 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 success
This 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
else
Since 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.title
Apply 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.title
Update 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 template
No 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 updateToolbarTemplates
Enable/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.length
Enable/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.templates
Reorder 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 updateToolbarTemplates
Update 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 isNew
Simply add new templates to the end.
template.index = templates.length
template.key = utils.keyGen()
templates.push template
else
Replace 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
template
Update 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.usage
Ensure that certain fields of read-only templates are protected.
unless existing.readOnly
existing.content = template.content
Only allow valid titles.
existing.title = template.title if 0 < template.title.length <= 32
Only allow existing images or None.
if template.image is '' or Icon.exists template.image
existing.image = template.image
Only 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
existing
Update 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 opt
ValidationError
allows easy management and display of validation error messages that are
associated with specific fields.
class ValidationError extends utils.Class
Create 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 @key
ValidationWarning
allows easy management and display of validation warning messages that are
associated with specific fields.
class ValidationWarning extends ValidationError
Create 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 key
Indicate whether or not the specified keyboard shortcut
is valid for use by a template.
isShortcutValid = (shortcut) ->
log.trace()
R_VALID_SHORTCUT.test shortcut
Indicate 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.usage
Validate 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...', template
Title 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
errors
Message
allows simple management and display of alert messages.
class Message extends utils.Class
Create 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 Message
Create 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 Message
Create 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 Message
Create 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.usage
Only allow existing images.
newTemplate.image = template.image if Icon.exists template.image
Only allow valid keyboard shortcuts.
newTemplate.shortcut = template.shortcut if isShortcutValid template.shortcut
Only allow valid titles.
newTemplate.title = template.title if 0 < template.title.length <= 32
log.debug 'Following template was created...', newTemplate
newTemplate
Activate 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, toIndex
Return false
to ensure default behaviour is prevented.
false
Activate 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 refreshSelectButtons
Refresh 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 refreshSelectButtons
Activate tooltip effects, optionally only within a specific context.
activateTooltips = (selector) ->
log.trace()
base = $ selector or document
Reset 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 null
Clear 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.version
Remove 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 data
Create 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
data
Create 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
template
Add the user feedback feature to the page.
feedback = ->
log.trace()
Only load and configure the feedback widget once.
return if feedbackAdded
Create 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, script
Configure 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 = yes
Retrieve all currently selected templates.
getSelectedTemplates = ->
log.trace()
keys = []
$('#templates tbody :checkbox:checked').each ->
keys.push $(this).val()
_.filter ext.templates, (template) ->
template.key in keys
Open 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 > 0
The results span across multiple pages so we need to show the pagination UI.
children = pagination.find 'ul li'
pages = Math.ceil templates.length / limit
Refresh 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], no
Ensure 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()
else
Hide 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 keys
Template 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
else
Template 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.key
Couldn’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.key
Now 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
data
Update 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 getSelectedTemplates
Update the visual state of buttons
.
updateButton = (buttons, disabled, titleKey) ->
buttons[if disabled then 'addClass' else 'removeClass'] 'disabled'
buttons.attr 'data-original-title', i18n.get titleKey
Export 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.length
Doesn’t matter as no templates have been selected.
updateButton $('#disable_btn, #enable_btn'), yes, 'opt_template_none_selected_title'
else if allEnabled or allDisabled
Templates 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'
else
Templates 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 refreshResetButton
Set the current context of the template wizard.
setContext = (template = {}) ->
log.trace()
activeTemplate = $.extend {}, template
do resetWizard
options = window.options = new class Options extends utils.Class
Initialize 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 feedback
Begin 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.version
Ensure 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 0
Reflect 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.
false
Bind 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 load
Ensure 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 activateTooltips
Retrieve 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 pos
Initialize options
when the DOM is ready.
utils.ready -> options.init()