expand_less

window.decko ||= {} #needed to run w/o *head. eg. jasmine

# $.extend decko,
# Can't get this to work yet. Intent was to tighten up head tag.
# initGoogleAnalytics: (key) ->
# window._gaq.push ['_setAccount', key]
# window._gaq.push ['_trackPageview']
#
# initfunc = ()->
# ga = document.createElement 'script'
# ga.type = 'text/javascript'
# ga.async = true
# ga.src = `('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'`
# s = document.getElementsByTagName('script')[0]
# s.parentNode.insertBefore ga, s
# initfunc()

$(window).ready ->
$('body').on 'click', '._stop_propagation', (event)->
event.stopPropagation()

$('body').on 'mouseenter', 'a[data-hover-text]', ->
text = $(this).text()
$(this).data("original-text", text)
$(this).text($(this).data("hover-text"))

$('body').on 'mouseleave', 'a[data-hover-text]', ->
$(this).text($(this).data("original-text"))

#decko_org mod (for now)
$('body').on 'click', '.shade-view h1', ->
toggleThis = $(this).slot().find('.shade-content').is ':hidden'
decko.toggleShade $(this).closest('.pointer-list').find('.shade-content:visible').parent()
if toggleThis
decko.toggleShade $(this).slot()

if firstShade = $('.shade-view h1')[0]
$(firstShade).trigger 'click'

# performance log mod
$('body').on 'click', '.open-slow-items', ->
panel = $(this).closest('.panel-group')
panel.find('.open-slow-items').removeClass('open-slow-items').addClass('close-slow-items')
panel.find('.toggle-fast-items').text("show .panel-collapse').collapse('show').find('a > span').addClass('show-fast-items')

$('body').on 'click', '.close-slow-items', ->
panel = $(this).closest('.panel-group')
panel.find('.close-slow-items').removeClass('close-slow-items').addClass('open-slow-items')
panel.find('.toggle-fast-items').text("hide .panel-collapse').collapse('hide').removeClass('show-fast-items')
panel.find('.duration-ok').show()

$('body').on 'click', '.toggle-fast-items', ->
panel = $(this).closest('.panel-group')
if $(this).text() == 'hide
$(this).removeClass('show-fast-items')
panel = $(this).closest('.panel-group')
panel.find('.duration-ok').show()
panel.find('.show-fast-items').removeClass('show-fast-items')
panel.find('.panel-collapse').collapse('show')
event.stopPropagation()

$.extend decko,
toggleShade: (shadeSlot) ->
shadeSlot.find('.shade-content').slideToggle 1000
shadeSlot.find('.glyphicon').toggleClass 'glyphicon-triangle-right glpyphicon-triangle-bottom'







$.extend decko,
initializeEditors: (range, map) ->
map = decko.editorInitFunctionMap unless map?
$.each map, (selector, fn) ->
$.each range.find(selector), ->
fn.call $(this)

editorContentFunctionMap: {}

editorInitFunctionMap:
'textarea': -> $(this).autosize()
'.file-upload': -> decko.upload_file(this)
'.etherpad-textarea': ->
$(this).closest('form')
.find('.edit-submit-button')
.attr('class', 'etherpad-submit-button')

addEditor: (selector, init, get_content) ->
decko.editorContentFunctionMap[selector] = get_content
decko.editorInitFunctionMap[selector] = init

jQuery.fn.extend
setContentFieldsFromMap: (map) ->
map = decko.editorContentFunctionMap unless map?
this_form = $(this)
$.each map, (selector, fn) ->
this_form.setContentFields(selector, fn)
setContentFields: (selector, fn) ->
$.each @find(selector), ->
$(this).setContentField(fn)
setContentField: (fn) ->
field = @closest('.card-editor').find('.d0-card-content')
init_val = field.val() # tinymce-jquery overrides val();
# that's why we're not using it.
new_val = fn.call this
field.val new_val
field.change() if init_val != new_val

$(window).ready ->
# decko.initializeEditors $('body > :not(.modal)')
setTimeout (-> decko.initializeEditors $('body > :not(.modal)')), 10
# dislike the timeout, but without this forms with multiple TinyMCE editors
# were failing to load properly
# I couldn't reproduce that problem described above -pk

$('body').on 'submit', '.card-form', ->
$(this).setContentFieldsFromMap()
$(this).find('.d0-card-content').attr('no-autosave','true')
true

setInterval (-> $('.card-form').setContentFieldsFromMap()), 20000





checkNameAfterTyping = null

$(window).ready ->
$('body').on 'click', '.renamer-updater', ->
$(this).closest('form').find('#card_update_referers').val 'true'

$('body').on 'submit', '.edit_name-view .card-form, .name_form-view .card-form', ->
confirmer = $(this).find '.alert'
if confirmer.is ':hidden'
if $(this).find('#referers').val() > 0
$(this).find('.renamer-updater').show()

confirmer.show 'blind'
false

$('body').on 'keyup', '.name-editor input', (event) ->
clearTimeout(checkNameAfterTyping) if checkNameAfterTyping
input = $(this)
if event.which == 13
checkName(input)
checkNameAfterTyping = null
else
checkNameAfterTyping = setTimeout ->
checkName(input)
checkNameAfterTyping = null
, 400

checkName = (box) ->
name = box.val()
decko.pingName name, (data)->
return null if box.val() != name # avert race conditions
status = data['status']
if status
ed = box.parent()
leg = box.closest('fieldset').find('legend')
msg = leg.find '.name-messages'
unless msg[0]
msg = $('')
leg.append msg?
ed.removeClass 'real-name virtual-name known-name'

# use id to avoid warning when renaming to name variant
slot_id = box.slot().data 'cardId'
if status != 'unknown' and !(slot_id && parseInt(slot_id) == data['id'])
ed.addClass status + '-name known-name'
qualifier = if status == 'virtual' then 'in virtual' else 'already in'
href = decko.path(data['url_key'])
msg.html "\"#{name}\" #{qualifier} use"
else
msg.html ''


jQuery.fn.extend
autosave: ->
slot = @slot()
return if @attr 'no-autosave'
multi = @closest '.form-group'
if multi[0]
return unless id = multi.data 'cardId'
reportee = ': ' + multi.data 'cardName'
else
id = slot.data 'cardId'
reportee = ''

return unless id

# might be better to put this href base in the html
submit_url = decko.path 'update/~' + id
form_data = $('#edit_card_'+id).serializeArray().reduce( ((obj, item) ->
obj[item.name] = item.value
return obj
), { 'draft' : 'true', 'success[view]' : 'blank'});
$.ajax submit_url, {
data : form_data,
type : 'POST'
}
##{ 'card[content]' : @val() },

$(window).ready ->
$('body').on 'change', '.autosave .d0-card-content', ->
content_field = $(this)
setTimeout ( -> content_field.autosave() ), 500

doubleClickActiveMap = { off: false, on: true, signed_in: decko.currentUserId }

doubleClickActive = () ->
doubleClickActiveMap[decko.doubleClick]
# else alert "illegal configuration: " + decko.doubleClick

doubleClickApplies = (el) ->
return false if ['.nodblclick', '.d0-card-header', '.card-editor'].some (klass) ->
el.closest(klass)[0]
# double click inactive inside header, editor, or tag with "nodblclick" class
!el.slot().find('.card-editor')[0]?


triggerDoubleClickEditingOn = (el)->
slot = el.slot()
edit_link = decko.slotEditLink(slot)

if edit_link
edit_link.click()
else
edit_view = decko.slotEditView(slot)
url = decko.path("~#{slot.data('cardId')}?view=#{edit_view}")
slot.reloadSlot url

$(window).ready ->
if doubleClickActive()
$('body').on 'dblclick', 'div', (_event) ->
if doubleClickApplies $(this)
triggerDoubleClickEditingOn $(this)
false # don't propagate up to next slot

wrapDeckoLayout = ->
$footer = $('body > footer').first()
$('body > article, body > aside').wrapAll("")
$('body > div > article, body > div > aside')
.wrapAll('')
if $footer
$('body').append $footer

wrapSidebarToggle = (toggle, flex) ->
"#{toggle}"

containerClass = ->
if $('body').hasClass('fluid') then "container-fluid" else "container"

toggleButton = (side) ->
icon_dir = if side == 'left' then 'right' else 'left'
"" +
"chevron_#{icon_dir}"

sidebarToggle = (side) ->
if side == "both"
wrapSidebarToggle(toggleButton("left") + toggleButton("right"), "flex-row justify-content-between")
else if side == "left"
wrapSidebarToggle(toggleButton("left"), "flex-row")
else
wrapSidebarToggle(toggleButton("right"), "flex-row-reverse")

singleSidebar = (side) ->
$article = $('body > article').first()
$aside = $('body > aside').first()
$article.addClass("col-xs-12 col-sm-9")
$aside.addClass(
"col-xs-6 col-sm-3 sidebar-offcanvas sidebar-offcanvas-#{side}"
)
if side == 'left'
$('body').append($aside).append($article)
else
$('body').append($article).append($aside)
wrapDeckoLayout()
$article.prepend(sidebarToggle(side))

doubleSidebar = ->
$article = $('body > article').first()
$asideLeft = $('body > aside').first()
$asideRight = $($('body > aside')[1])
$article.addClass("col-xs-12 col-sm-6")
sideClass = "col-xs-6 col-sm-3 sidebar-offcanvas"
$asideLeft.addClass("#{sideClass} sidebar-offcanvas-left")
$asideRight.addClass("#{sideClass} sidebar-offcanvas-right")
$('body').append($asideLeft).append($article).append($asideRight)
wrapDeckoLayout()
toggles = sidebarToggle('both')
$article.prepend(toggles)

$.fn.extend toggleText: (a, b) ->
@text(if @text() == b then a else b)

this
$(window).ready ->
switch
when $('body').hasClass('right-sidebar')
singleSidebar('right')
when $('body').hasClass('left-sidebar')
singleSidebar('left')
when $('body').hasClass('two-sidebar')
doubleSidebar()

$('[data-toggle="offcanvas-left"]').click ->
$('.row-offcanvas').removeClass('right-active').toggleClass('left-active')
$(this).find('i.material-icons')
.toggleText('chevron_left', 'chevron_right')
$('[data-toggle="offcanvas-right"]').click ->
$('.row-offcanvas').removeClass('left-active').toggleClass('right-active')
$(this).find('i.material-icons')
.toggleText('chevron_left', 'chevron_right')

$(window).ready ->
navbox = $('._navbox')
navbox.select2
placeholder: navbox.attr("placeholder")
escapeMarkup: (markup) ->
markup
minimumInputLength: 1
maximumSelectionSize: 1
ajax:
url: decko.path ':search.json'
data: (params) ->
query: { keyword: params.term }
view: "complete"
processResults: (data) ->
results: navboxize(data)
cache: true
templateResult: formatNavboxItem
templateSelection: formatNavboxSelectedItem
multiple: true
containerCssClass: 'select2-navbox-autocomplete'
dropdownCssClass: 'select2-navbox-dropdown'
width: "100%!important"

navbox.on "select2:select", (e) ->
navboxSelect(e)

formatNavboxItem = (i) ->
if i.loading
return i.text
'' + i.icon + '' +
'' + i.prefix + ': ' +
'' + i.label + ''

formatNavboxSelectedItem = (i) ->
unless i.icon
return i.text
'' + i.icon + '' +
'' + i.label + ''

navboxize = (results) ->
items = []
term = results.term
if results["search"]
# id is what the form sends
items.push navboxItem(prefix: "search", id: term, text: term)

$.each ['add', 'new'], (index, key) ->
if val = results[key]
items.push navboxItem(prefix: key, icon: "add", text: val[0], href: val[1])

$.each results['goto'], (index, val) ->
i = navboxItem(
prefix: "go to", id: index, icon: "arrow_forward",
text: val[0], href: val[1], label: val[2]
)
items.push i

items

navboxItem = (data) ->
data.id ||= data.prefix
data.icon ||= data.prefix
data.label ||= '' + data.text + ''
data

navboxSelect = (event) ->
item = event.params.data
if item.href
window.location = decko.path(item.href)
else
$(event.target).closest('form').submit()

$(event.target).attr('disabled', 'disabled')

$.extend decko,
upload_file: (fileupload) ->
# for file as a subcard in a form,
# excess parameters are included in the request which cause errors.
# only the file, type_id and attachment_card_name are needed
# attachment_card_name is the original card name,
# ex: card[subcards][+logo][image], card[file]
$(fileupload).on 'fileuploadsubmit', (e,data) ->
$_this = $(this)
card_name = $_this.siblings(".attachment_card_name:first").attr("name")
type_id = $_this.siblings("#attachment_type_id").val()
data.formData = {
"card[type_id]": type_id,
"attachment_upload": card_name
}
$_fileupload = $(fileupload)
if $_fileupload.closest("form").attr("action").indexOf("update") > -1
url = "card/update/"+$(fileupload).siblings("#file_card_name").val()
else
url = "card/create"
$(fileupload).fileupload(
url: decko.path(url),
dataType: 'html',
done: decko.doneFile,
add: decko.chooseFile,
progressall: decko.progressallFile
)#, forceIframeTransport: true )

chooseFile: (e, data) ->
data.form.find('button[type=submit]').attr('disabled',true)
editor = $(this).closest '.card-editor'
$('#progress').show()
editor.append ''
editor.append ''
data.submit()
editor.find('.choose-file').hide()
editor.find('.extra_upload_param').remove()

progressallFile: (e, data) ->
progress = parseInt(data.loaded / data.total * 100, 10)
$('#progress .progress-bar').css('width', progress + '%')

doneFile: (e, data) ->
editor = $(this).closest '.card-editor'
editor.find('.chosen-file').replaceWith data.result
data.form.find('button[type=submit]').attr('disabled',false)

$(window).ready ->
$('body').on 'click', '.cancel-upload', ->
editor = $(this).closest '.card-editor'
editor.find('.choose-file').show()
editor.find('.chosen-file').empty()
editor.find('.progress').show()
editor.find('#progress .progress-bar').css('width', '0%')
editor.find('#progress').hide()

$.extend decko,
# returns full path with slot parameters
slotPath: (path, slot)->
params = decko.slotData(slot)
decko.path(path) + ( (if path.match /\?/ then '&' else '?') + $.param(params) )

slotData: (slot) ->
xtra = {}
main = $('#main').children('.card-slot').data 'cardName'
xtra['main'] = main if main?
if slot
xtra['is_main'] = true if slot.isMain()
slotdata = slot.data 'slot'
decko.slotParams slotdata, xtra, 'slot' if slotdata?
xtra

slotEditView: (slot) ->
data = decko.slotData(slot)
switch data["slot[edit]"]
when "inline" then "edit_inline"
when "full" then "bridge"
else "edit"

slotEditLink: (slot) ->
edit_links =
slot.find(".edit-link").filter (i, el) ->
$(el).slot().data('slotId') == slot.data('slotId')

if edit_links[0] then $(edit_links[0]) else false

slotParams: (raw, processed, prefix)->
$.each raw, (key, value)->
cgiKey = prefix + '[' + snakeCase(key) + ']'
if key == 'items'
decko.slotParams value, processed, cgiKey
else
processed[cgiKey] = value

contentLoaded: (el, slotter)->
decko.initializeEditors(el)
notice = slotter.attr('notify-success')
if notice?
el.notify notice, "success"
el.triggerSlotReady(slotter)

slotReady: (func)->
$('document').ready ->
$('body').on 'slotReady', '.card-slot', (e, slotter) ->
e.stopPropagation()
if slotter?
func.call this, $(this), $(slotter)
else
func.call this, $(this)

slotDestroy: (func)->
$('document').ready ->
$('body').on 'slotDestroy', '.card-slot, ._modal-slot', (e) ->
e.stopPropagation()
func.call this, $(this)

jQuery.fn.extend
slot: (status="success", mode="replace") ->
if mode == "modal"
@modalSlot()
else
@selectSlot("slot-#{status}-selector") ||
@selectSlot("slot-selector") ||
@closest(".card-slot")

selectSlot: (selectorName) ->
if selector = @data(selectorName)
slot = @findSlot selector
slot && slot[0] && slot

isSlot: ->
$(this).hasClass "card-slot"

isMain: -> @slot().parent('#main')[0]

findSlot: (selector) ->
if selector == "modal-origin"
@findOriginSlot("modal")
else if selector == "overlay-origin"
@findOriginSlot("overlay")
else
target_slot = @closest(selector)
parent_slot = @closest '.card-slot'

# if slot-selector doesn't apply to a child, search in all parent slots and finally in the body
while target_slot.length == 0 and parent_slot.length > 0
target_slot = $(parent_slot).find(selector)
parent_slot = $(parent_slot).parent().closest '.card-slot'
if target_slot.length == 0
$(selector)
else
target_slot

# type can be "modal" or "overlay"
findOriginSlot: (type) ->
overlaySlot = @closest("[data-#{type}-origin-slot-id]")
origin_slot_id = overlaySlot.data("#{type}-origin-slot-id")
origin_slot = $("[data-slot-id=#{origin_slot_id}]")
if origin_slot[0]?
origin_slot
else
console.log "couldn't find origin with slot id #{origin_slot_id}"

reloadSlot: (url) ->
$slot = $(this)
if $slot.length > 1
$slot.each ->
$(this).reloadSlot url
return

$slot = $slot.slot() unless $slot.isSlot
return unless $slot[0]

unless url?
url = $slot.slotUrl()
$slot.addClass 'slotter'
$slot.attr 'href', url
$slot.data "url", url
this[0].href = url # that's where handleRemote gets the url from
# .attr(href, url) only works for anchors
$slot.data "remote", true
$.rails.handleRemote($slot)

clearSlot: () ->
@triggerSlotDestroy()
@empty()

slotUrl: ->
decko.slotPath "#{this.slotMark()}?view=#{@data("slot")["view"]}"

slotMark: ->
if @data('cardId') then "~#{@data('cardId')}" else @data("cardName")

setSlotContent: (val, mode, $slotter) ->
v = $(val)[0] && $(val) || val

if typeof(v) == "string"
# Needed to support "TEXT: result" pattern in success (eg deleting nested cards)
@slot("success", mode).replaceWith v
else
if v.hasClass("_overlay")
mode = "overlay"
else if v.hasClass("_modal")
mode = "modal"
@slot("success", mode).setSlotContentFromElement v, mode, $slotter
v

setSlotContentFromElement: (el, mode, $slotter) ->
if mode == "overlay"
@addOverlay(el, $slotter)
else if el.hasClass("_modal-slot") or mode == "modal"
el.showAsModal($slotter)
else
slot_id = @data("slot-id")
el.attr("data-slot-id", slot_id) if slot_id
@triggerSlotDestroy()
@replaceWith el
decko.contentLoaded(el, $slotter)

triggerSlotReady: (slotter) ->
@trigger "slotReady", slotter if @isSlot()
@find(".card-slot").trigger "slotReady", slotter

triggerSlotDestroy: () ->
@trigger "slotDestroy"

$(window).ready ->
$('body').on 'hidden.bs.modal', (_event) ->
decko.removeModal()

$('body').on "show.bs.modal", "._modal-slot", (event, slot) ->
link = $(event.relatedTarget)
addModalDialogClasses $(this), link
$(this).modal("handleUpdate")
decko.contentLoaded $(event.target), link

$('._modal-slot').each ->
openModalIfPresent $(this)
addModalDialogClasses $(this)

$('body').on 'click', '.submit-modal', ->
$(this).closest('.modal-content').find('form').submit()

openModalIfPresent = (mslot) ->
modal_content = mslot.find(".modal-content")
if modal_content.length > 0 && modal_content.html().length > 0
$("#main > .card-slot").registerAsOrigin("modal", mslot)
mslot.modal("show")

addModalDialogClasses = ($modal_slot, $link) ->
dialog = $modal_slot.find(".modal-dialog")
classes_from_link =
if $link? then $link.data("modal-class") else $modal_slot.data("modal-class")
if classes_from_link? and dialog?
dialog.addClass classes_from_link

jQuery.fn.extend {
showAsModal: ($slotter) ->
el = @modalify($slotter) if $slotter?
if $("body > ._modal-slot").is(":visible")
@addModal el, $slotter
else
if $("body > ._modal-slot")[0]
$("._modal-slot").trigger "slotDestroy"
$("body > ._modal-slot").replaceWith el
else
$("body").append el

$slotter.registerAsOrigin("modal", el)
el.modal("show", $slotter)

addModal: (el, $slotter) ->
if $slotter.data("slotter-mode") == "modal-replace"
dialog = el.find(".modal-dialog")
el.adoptModalOrigin()
$("._modal-slot").trigger "slotDestroy"
$("body > ._modal-slot > .modal-dialog").replaceWith(dialog)
decko.contentLoaded(dialog, $slotter)
else
decko.pushModal el
$slotter.registerAsOrigin("modal", el)
el.modal("show", $slotter)

adoptModalOrigin: () ->
origin_slot_id = $("body > ._modal-slot .card-slot[data-modal-origin-slot-id]")
.data("modal-origin-slot-id")
@find(".modal-body .card-slot").attr("data-modal-origin-slot-id", origin_slot_id)

modalSlot: ->
slot = $("#modal-container")
if slot.length > 0 then slot else decko.createModalSlot()

modalify: ($slotter) ->
if $slotter.data("modal-body")?
@find(".modal-body").append($slotter.data("modal-body"))

if @hasClass("_modal-slot")
this
else
modalSlot = $('', id: "modal-container", class: "modal fade _modal-slot")
modalSlot.append(
$('' , class: "modal-dialog").append(
$('', class: "modal-content").append(this)
)
)
modalSlot
}

$.extend decko,
createModalSlot: ->
slot = $('', id: "modal-container", class: "modal fade _modal-slot")
$("body").append(slot)
slot

removeModal: ->
if $("._modal-stack")[0]
decko.popModal()
else
$("._modal-slot").trigger "slotDestroy"
$(".modal-dialog").empty()

pushModal: (el) ->
mslot = $("body > ._modal-slot")
mslot.removeAttr("id")
mslot.removeClass("_modal-slot").addClass("_modal-stack").removeClass("modal").addClass("background-modal")
el.insertBefore mslot
$(".modal-backdrop").removeClass("show")

popModal: ->
$(".modal-backdrop").addClass("show")
$("body > ._modal-slot").trigger "slotDestroy"
$("body > ._modal-slot").remove()
modal = $($("._modal-stack")[0])
modal.addClass("_modal-slot").removeClass("_modal-stack").attr("id", "modal-container").addClass("modal").removeClass("background-modal")
$(document.body).addClass("modal-open")


jQuery.fn.extend
overlaySlot: ->
oslot = @closest(".card-slot._overlay")
return oslot if oslot[0]?
oslot = @closest(".overlay-container").find("._overlay")
oslot[0]? && $(oslot[0])

addOverlay: (overlay, $slotter) ->
if @parent().hasClass("overlay-container")
if $(overlay).hasClass("_stack-overlay")
@before overlay
else
$("._overlay-origin").removeClass("_overlay-origin")
@replaceOverlay(overlay)
else
#@find(".tinymce-textarea").each ->
# tinymce.remove("##{$(this).attr("id")}")
# #tinyMCE.execCommand('mceRemoveControl', false, $(this).attr("id"))
if @parent().hasClass("_overlay-container-placeholder")
@parent().addClass("overlay-container")
else
@wrapAll('')
@addClass("_bottomlay-slot")
@before overlay

$slotter.registerAsOrigin("overlay", overlay)
decko.contentLoaded(overlay, $slotter)

replaceOverlay: (overlay) ->
@overlaySlot().trigger "slotDestroy"
@overlaySlot().replaceWith overlay
$(".bridge-sidebar .tab-pane:not(.active) .bridge-pills > .nav-item > .nav-link.active").removeClass("active")

isInOverlay: ->
return @closest(".card-slot._overlay").length

removeOverlay: () ->
slot = @overlaySlot()
if slot
slot.removeOverlaySlot()

removeOverlaySlot: () ->
@trigger "slotDestroy"
if @siblings().length == 1
bottomlay = $(@siblings()[0])
if bottomlay.hasClass("_bottomlay-slot")
if bottomlay.parent().hasClass("_overlay-container-placeholder")
bottomlay.parent().removeClass("overlay-container")
else
bottomlay.unwrap()
bottomlay.removeClass("_bottomlay-slot").updateBridge(true, bottomlay)

#bottomlay.find(".tinymce-textarea").each ->
# tinymce.EditorManager.execCommand('mceAddControl',true, editor_id);
# decko.initTinyMCE($(this).attr("id"))

@remove()

jQuery.fn.extend {
updateRecaptchaToken: (event) ->
recaptcha = @find("input._recaptcha-token")

if !recaptcha[0]?
recaptcha.val "recaptcha-token-field-missing"
else if !grecaptcha?
recaptcha.val("grecaptcha-undefined")
else
$slotter = $(this)
event.stopPropagation() if event
grecaptcha.execute(recaptcha.data("site-key"), action: recaptcha.data("action"))
.then (token) ->
recaptcha.val(token)
recaptcha.addClass("_token-updated")
if event
$slotter.submit()
false
}

# There are three places that can control what happens after an ajax request
# 1. the element that triggered the request (eg. a link or a button)
# 2. the closest ".slotter" element
# (if the trigger itself isn't a slotter,
# a common example is that a form is a slotter but the form buttons aren't)
# 3. the slot returned by the request
#
# A slot is an element marked with "card-slot" class, a slotter has a "slotter" class.
# By the default, the closest slot of the slotter is replaced with the new slot that the
# request returned. The behavior can be changed with css classes and data attributes.
#
# To 1. The trigger element has only a few options to override the slotter.
# classes:
# "_close-modal-on-success"
# "_close-overlay-on-success"
# "_update-origin"
#
# To 2. The slotter is the standard way to define what happens with request result
# data:
# slot-selector
# a css selector that defines which slot will be replaced.
# You can also use "modal-origin" and "overlay-origin" to refer to the origin slots.
# slot-success-selector/slot-error-selector
# the same as slot-selector but only used
# for success case or error case, respectively
# update-foreign-slot
# a css selector to specify an additional slot that will be
# updated after the request.
# update-foreign-slot-url
# a url to fetch the new content for the additional slot
# if not given the slot is updated with the same card and view that used before
# update-origin
# if present then the slot from where the current modal or overlay slot was opened
# will be updated
# slotter-mode
# possible values are
# replace (default)
# replace the closest slot with new slot
# modal
# show new slot in modal; if there is already a modal then put it on top
# modal-replace
# replace existing modal
# overlay
# show new slot in overlay
# update-origin
# update closest slot of the slotter that opened the modal or overlay
# (assumes that the request was triggered from a modal or overlay)
# If you need the update origin together with another mode then use
# data-update-origin="true".
# silent-success
# do nothing
#
# classes:
# _close-overlay
# _close-modal
#
# To 3. Similar as 1, the slot has only overlay and modal options.
# classes:
# _modal
# show slot in modal
# _overlay
# show slot in overlay
#
#
$(window).ready ->
$('body').on 'ajax:success', '.slotter', (event, data, c, d) ->
$(this).slotterSuccess event, data

$('body').on 'ajax:error', '.slotter', (event, xhr) ->
$(this).showErrorResponse xhr.status, xhr.responseText

$('body').on 'click', 'button.slotter', (event)->
return false if !$.rails.allowAction $(this)
$.rails.handleRemote $(this)

$('body').on 'click', '._clickable.slotter', (event)->
$(this)[0].href = $(this).attr("href") # that's where rails.handleRemote
# expects the url
$.rails.handleRemote $(this)

$('body').on 'click', '[data-dismiss="overlay"]', (event) ->
$(this).findSlot(".card-slot._overlay").removeOverlay()

$('body').on 'click', '._close-overlay-on-success', (event) ->
$(this).closeOnSuccess("overlay")

$('body').on 'click', '._close-modal-on-success', (event) ->
$(this).closeOnSuccess("modal")

$('body').on 'click', '._close-on-success', (event) ->
$(this).closeOnSuccess()

$('body').on 'click', '._update-origin', (event) ->
$(this).closest('.slotter').data("slotter-mode", "update-origin")

$('body').on 'submit', 'form.slotter', (event)->
if (target = $(this).attr 'main-success') and $(this).isMain()
input = $(this).find '[name=success]'
if input and input.val() and !(input.val().match /^REDIRECT/)
input.val(
(if target == 'REDIRECT' then target + ': ' + input.val() else target)
)
if $(this).data('recaptcha') == 'on'
return $(this).handleRecaptchaBeforeSubmit(event)

$('body').on 'ajax:beforeSend', '.slotter', (event, xhr, opt)->
$(this).slotterBeforeSend(opt)

jQuery.fn.extend
slotterSuccess: (event, data) ->
unless @hasClass("slotter")
console.log "warning: slotterSuccess called on non-slotter element #{this}"
return

return if event.slotSuccessful

if @data("reload")
window.locacation.reload(true)

if @data("update-origin")
@updateOrigin()

if @data('original-slotter-mode')
@attr 'data-slotter-mode', @data('original-slotter-mode')

mode = @data("slotter-mode")
@showSuccessResponse data, mode

if @hasClass "_close-overlay"
@removeOverlay()
if @hasClass "_close-modal"
@closest('.modal').modal('hide')

# should scroll to top after clicking on new page
if @hasClass "card-paging-link"
slot_top_pos = @slot().offset().top
$("body").scrollTop slot_top_pos
if @data("update-foreign-slot")
$slot = @findSlot @data("update-foreign-slot")
reload_url = @data("update-foreign-slot-url")
$slot.reloadSlot reload_url

event.slotSuccessful = true

showSuccessResponse: (data, mode) ->
if mode == "silent-success"
return
else if mode == "update-origin"
@updateOrigin()
else if data.redirect
window.location = data.redirect
else if data.reload
window.location.reload(true)
else
@updateSlot data, mode

showErrorResponse: (status, result) ->
if status == 403 #permission denied
$(result).showAsModal $(this)
else if status == 900
$(result).showAsModal $(this)
else
@notify result, "error"

if status == 409 #edit conflict
@slot().find('.current_revision_id').val(
@slot().find('.new-current-revision-id').text()
)

updateOrigin: () ->
type = if @overlaySlot()
"overlay"
else if @closest("#modal-container")[0]
"modal"

return unless type?

origin = @findOriginSlot(type)
if origin && origin[0]?
origin.reloadSlot()

registerAsOrigin: (type, slot) ->
if slot.hasClass("_modal-slot")
slot = slot.find(".modal-body .card-slot")
slot.attr("data-#{type}-origin-slot-id", @closest(".card-slot").data("slot-id"))

updateSlot: (data, mode) ->
mode ||= "replace"
@setSlotContent data, mode, $(this)

# close modal or overlay
closeOnSuccess: (type) ->
slotter = @closest('.slotter')
if !type?
type = if @isInOverlay() then "overlay" else "modal"
slotter.addClass "_close-#{type}"

slotterBeforeSend: (opt) ->
return if opt.skip_before_send

# avoiding duplication. could be better test?
unless (opt.url.match(/home_view/) or @data("slotter-mode") == "modal")
opt.url = decko.slotPath opt.url, @slot()

if @is('form')
if data = @data 'file-data'
# NOTE - this entire solution is temporary.
@uploadWithBlueimp(data, opt)
false

uploadWithBlueimp: (data, opt) ->
input = @find '.file-upload'
if input[1]
@notify(
"Decko does not yet support multiple files in a single form.",
"error"
)
return false

widget = input.data 'blueimpFileupload' #jQuery UI widget

# browsers that can't do ajax uploads use iframe
unless widget._isXHRUpload(widget.options)
# can't do normal redirects.
@find('[name=success]').val('_self')
# iframe response not passed back;
# all responses treated as success. boo
opt.url += '&simulate_xhr=true'
# iframe is not xhr request,
# so would otherwise get full response with layout
iframeUploadFilter = (data)-> data.find('body').html()
opt.dataFilter = iframeUploadFilter
# gets rid of default html and body tags

args = $.extend opt, (widget._getAJAXSettings data), url: opt.url
# combines settings from decko's slotter and jQuery UI's upload widget
args.skip_before_send = true #avoid looping through this method again

$.ajax( args )

handleRecaptchaBeforeSubmit: (event) ->
recaptcha = @find("input._recaptcha-token")

if !recaptcha[0]?
# monkey error (bad form)
recaptcha.val "recaptcha-token-field-missing"
else if recaptcha.hasClass "_token-updated"
# recaptcha token is fine - continue submitting
recaptcha.removeClass "_token-updated"
else if !grecaptcha?
# shark error (probably recaptcha keys of pre v3 version)
recaptcha.val "grecaptcha-undefined"
else
@updateRecaptchaToken(event)
# this stops the submit here
# and submits again when the token is ready


decko.slotReady (slot, slotter) ->
slot.updateBridge(false, slotter)

links = slot.find('ul._auto-single-select > li.nav-item > a.nav-link')
if links.length == 1
$(links[0]).click()

jQuery.fn.extend
# overlayClosed=true means the bridge update was
# triggered by closing an overlay
updateBridge: (overlayClosed=false, slotter) ->
return unless @closest(".bridge").length > 0
if @data("breadcrumb")
@updateBreadcrumb()
else if slotter and $(slotter).data("breadcrumb")
$(slotter).updateBreadcrumb()

if overlayClosed
$(".bridge-pills > .nav-item > .nav-link.active").removeClass("active")

updateBreadcrumb: () ->
bc_item = $(".modal-header ._bridge-breadcrumb li:last-child")
bc_item.text(this.data("breadcrumb"))
bc_item.attr("class", "breadcrumb-item active #{this.data('breadcrumb-class')}")

$(window).ready ->
$('body').on "select2:select", "._close-rule-overlay-on-select", (event) ->
$(".overlay-container > ._overlay.card-slot.overlay_rule-view.RULE").removeOverlay()

$(document).ready ->
$('body').on 'click', 'button._nest-apply', () ->
nest.apply($(this).data("tinymce-id"), $(this).data("nest-start"), $(this).data("nest-size"))

window.nest ||= {}

$.extend nest,
openEditor: (tm, params) ->
params = nest.editParams(tm) unless params?

slot = $("##{tm.id}").closest(".card-slot")
card = if slot[0] then $(slot[0]).attr('data-card-name') else ":update"
nest.tmRequest(tm, card, "nest_editor", "modal_nest_editor", params)

openImageEditor: (tm) ->
slot = $("##{tm.id}").closest(".card-slot")
card_name = slot.data("card-name")
nest.sendTmRequest(tm, slot, "modal", card_name, "nest_image")

insertNest: (tm, nest) ->
tm.insertContent(nest)
params = nest.paramsStr(nest.offsetAfterInsert(tm, nest), nest)
nest.openEditor(tm, params)

tmRequest: (tm, card, overlay_view, modal_view, params) ->
slot = $(".bridge-sidebar > ._overlay-container-placeholder > .card-slot")

if slot[0]
view = overlay_view
mode = "overlay"
else
# FIXME get a slot
slot = $($(".card-slot")[0])
view = modal_view
mode = "modal"

nest.sendTmRequest(tm, slot, mode, card, view, params)

sendTmRequest: (tm, slot, mode, card, view, params) ->
slotter = $("##{tm.id}")
params = "" unless params?
url = "/#{card}?view=#{view}&tinymce_id=#{tm.id}#{params}"

$.ajax
url: url
type: 'GET'
success: (html) ->
slot.setSlotContent html, mode, slotter

editParams: (tm) ->
sel = tm.selection.getSel()
return nest.paramsStr(0) unless sel? and sel.anchorNode?

text = sel.anchorNode.data
return nest.paramsStr(sel.anchorOffset) unless text

offset = sel.anchorOffset
before = text.substr(0, offset)
after = text.substr(offset)
index = {
before: {
close: before.lastIndexOf("}}")
open: before.lastIndexOf("{{")
},
after: {
close: after.indexOf("}}")
open: after.indexOf("{{")
}
}
if index.before.open > index.before.close &&
index.after.close != -1 &&
(index.after.open == -1 || index.after.close
params = ""
if start?
params += "&nest_start=#{start}"
if name? and name.length > 0
params += "&edit_nest=#{encodeURIComponent(name)}"

params

apply: (tinymce_id, nest_start, nest_size) ->
content = $("._nest-preview").val()
editor = tinymce.get(tinymce_id)
if nest_start?
nest.replaceNest(editor, nest_start, nest_size, content)
else
editor.insertContent content
offset = nest.offsetAfterInsert(editor, content)
$('button._nest-apply').attr("data-nest-start", offset)

$('button._nest-apply').attr("data-nest-size", content.length)

offsetAfterInsert: (editor, content) ->
offset = editor.selection.getSel().anchorOffset
offset - content.length

replaceNest: (editor, nest_start, nest_size, content) ->
sel = editor.selection.getSel()
if sel? and sel.anchorNode? and sel.anchorNode.data?
text = sel.anchorNode.data
nest_size = 0 unless nest_size?
text = "#{text.substr(0, nest_start)}#{content}#{text.substr(nest_start + nest_size)}"
sel.anchorNode.data = text
else
editor.insertContent content

updatePreview: (new_val) ->
new_val = "{{#{nest.name()}|#{nest.options()}}}" unless new_val?
$("._nest-preview").val new_val





$(document).ready ->
$('body').on 'keyup', 'input._nest-option-value', () ->
nest.updatePreview()

$('body').on "select2:select", "._nest-option-name", () ->
nest.toggleOptionName($(this).closest("._options-select"), $(this).val(), true)
nest.updatePreview()

$('body').on "select2:selecting", "._nest-option-name", () ->
nest.toggleOptionName($(this).closest("._options-select"), $(this).val(), false)

$('body').on "select2:select", "._nest-option-name._new-row", () ->
$(this).closest(".input-group").find(".input-group-prepend").removeClass("d-none")
row = $(this).closest("._nest-option-row")
row.find("._nest-option-value").removeAttr("disabled")
template = row.parent().find("._nest-option-row._template")
$(this).removeClass("_new-row")
nest.addRow(template)

$('body').on "click", "._configure-items-button", () ->
nest.addItemsOptions($(this))

$('body').on 'click', 'button._nest-delete-option', () ->
nest.removeRow $(this).closest("._nest-option-row")

$.extend nest,
showTemplate: (elem) ->
elem.removeClass("_template") #.removeClass("_#{name}-template").addClass("_#{name}")

addRow: (template) ->
select_tag = template.find("select")
select_tag.select2("destroy")
select_tag.removeAttr("data-select2-id")
double = template.clone()
#double = template.cloneSelect2(true, true)
decko.initSelect2(select_tag)
nest.showTemplate template
template.after(double)
decko.initSelect2(double.find("select"))

removeRow: (row) ->
name = row.find("._nest-option-name").val()
nest.toggleOptionName(row.closest("._options-select"), name,false)
row.remove()
nest.updatePreview()

addItemsOptions: (button) ->
container = button.closest("._configure-items")
next = container.cloneSelect2(true)
title = button.text()
button.replaceWith($("#{title.substr(9)}"))
nest.showTemplate container.find("._options-select._template")
next.find("._configure-items-button").text(title.replace("items", "subitems"))
container.after(next)
nest.updatePreview()

options: () ->
options = []
for ele in $("._options-select:not(._template")
options.push nest.extractOptions($(ele))

level_options = options.map (opts) ->
nest.toNestSyntax(opts)
level_options.join "|"

# extract options for one item level
extractOptions: (ele) ->
options = {}
nest.addOption(options, $(row)) for row in ele.find("._nest-option-row:not(.template)")
options

addOption: (options, row) ->
val = row.find("._nest-option-value").val()
return unless val? && val.length > 0

name = row.find("._nest-option-name").val()
if options[name]?
options[name].push val
else
options[name] = [val]

toggleOptionName: (container, name, active) ->
return true if name == "show" || name == "hide"
for sel in container.find("._nest-option-name")
if $(sel).val() != name
$(sel).find("option[value=#{name}]").attr "disabled", active
# $(sel).find("option[value=#{val}]").removeAttr "disabled"
decko.initSelect2($(sel))

toNestSyntax: (opts) ->
str = []
str.push "#{name}: #{values.join ', '}" for name, values of opts
str.join "; "

nestNameTimeout = null

$(document).ready ->
$('body').on 'click', '._nest-field-toggle', () ->
if $(this).is(':checked')
nest.addPlus()
else
nest.removePlus()

$('body').on 'input', 'input._nest-name', (event) ->
nest.nameChanged()

unless event.which == 13
clearTimeout(nestNameTimeout) if nestNameTimeout
nestNameTimeout = setTimeout nest.updateRulesTab, 700

$('body').on 'keydown', 'input._nest-name', (event) ->
if event.which == 13
clearTimeout(nestNameTimeout) if nestNameTimeout
nest.updateRulesTab()

$.extend nest,
name: () ->
nest.evalFieldOption $('input._nest-name').val()

nameChanged: () ->
new_val = $("._nest-preview").val().replace(/^\{\{[^}|]*/, "{{" + nest.name())
nest.updatePreview new_val

evalFieldOption: (name) ->
if nest.isField() then "+#{name}" else name

isField: ->
$('._nest-field-toggle').is(":checked")

addPlus: () ->
new_val = $("._nest-preview").val().replace(/^\{\{\+?/, "{{+")
nest.updatePreview new_val
$(".input-group.hide-prefix").removeClass("hide-prefix").addClass("show-prefix")

removePlus: () ->
new_val = $("._nest-preview").val().replace(/^\{\{\+?/, "{{")
nest.updatePreview new_val
$(".input-group.show-prefix").removeClass("show-prefix").addClass("hide-prefix")

rulesTabSlot: () ->
$("._nest-editor .tab-pane-rule > .card-slot")

emptyNameAlert: (show) ->
if show
$("._empty-nest-name-alert").removeClass("d-none")
else
$("._empty-nest-name-alert:not(.d-none)").addClass("d-none")

updateRulesTab: () ->
name = $("input._nest-name").val()
$rulesTab = nest.rulesTabSlot()

if name? && name.length > 0
url = decko.path "#{nest.setNameForRules()}?view=nest_rules"
nest.emptyNameAlert(false)
$rulesTab.reloadSlot url
else
$rulesTab.clearSlot()
nest.emptyNameAlert(true)

# set in the sense of card set
setNameForRules: () ->
input = $('input._nest-name')
nest_name = input.val()
if nest.isField()
if input.attr("data-left-type")
"#{input.attr("data-left-type")}+#{nest_name}+*type plus right"
else
"#{nest_name}+*right"
else
return "#{nest_name}+*self"



decko.slotReady (slot) ->
$('[data-toggle="popover"]').popover(html: true)

$('.colorpicker-component').colorpicker()

submitAfterTyping = null

$(window).ready ->
$('body').on 'show.bs.tab', 'a.load[data-toggle="tab"][data-url]', (e) ->
tab_id = $(e.target).attr('href')
url = $(e.target).data('url')
$(e.target).removeClass('load')
$.ajax
url: url
type: 'GET'
success: (html) ->
$(tab_id).append(html)
decko.contentLoaded($(tab_id), $(this))

$('body').on "input", "._submit-after-typing", (event) ->
form = $(event.target).closest('form')
form.slot().find(".autosubmit-success-notification").remove()
clearTimeout(submitAfterTyping) if submitAfterTyping
submitAfterTyping = setTimeout ->
$(event.target).closest('form').submit()
submitAfterTyping = null
, 1000

$('body').on "keydown", "._submit-after-typing", (event) ->
if event.which == 13
clearTimeout(submitAfterTyping) if submitAfterTyping
submitAfterTyping = null
$(event.target).closest('form').submit()
false

$('body').on "change", "._submit-on-change", (event) ->
$(event.target).closest('form').submit()
false

$('body').on "change", "._edit-item", (event) ->
cb = $(event.target)
if cb.is(":checked")
cb.attr("name", "add_item")
else
cb.attr("name", "drop_item")

$(event.target).closest('form').submit()
false




$.extend decko,
# returns absolute path (starting with a slash)
# if rawPath is complete url, this returns the complete url
# if rawPath is relative (no slash), this adds relative root
path: (rawPath) ->
if rawPath.match /^\/|:\/\//
rawPath
else
decko.rootUrl + rawPath

pingName: (name, success)->
$.getJSON decko.path(''), format: 'json', view: 'status', 'card[name]': name, success

jQuery.fn.extend {
notify: (message, status) ->
slot = @slot(status)
notice = slot.find '.card-notice'
unless notice[0]
notice = $('')
form = slot.find('.card-form')
if form[0]
$(form[0]).append notice
else
slot.append notice
notice.html message
notice.show 'blind'

report: (message) ->
report = @slot().find '.card-report'
return false unless report[0]
report.hide()
report.html message
report.show 'drop', 750
setTimeout (->report.hide 'drop', 750), 3000
}

#~~~~~ ( EVENTS )

$(window).ready ->
$.ajaxSetup cache: false

$('body').on 'click', '.submitter', ->
$(this).closest('form').submit()

$('body').on 'click', 'button.redirecter', ->
window.location = $(this).attr('href')

$('body').on "change", '.live-type-field', ->
$this = $(this)

setSlotMode($this)
$this.data 'params', $(this).closest('form').serialize()
$this.data 'url', $(this).attr 'href'

$('body').on 'change', '.edit-type-field', ->
$(this).closest('form').submit()

$('body').on 'mouseenter', '[hover_content]', ->
$(this).attr 'hover_restore', $(this).html()
$(this).html $(this).attr( 'hover_content' )
$('body').on 'mouseleave', '[hover_content]', ->
$(this).html $(this).attr( 'hover_restore' )

$('body').on 'click', '.render-error-link', (event) ->
msg = $(this).closest('.render-error').find '.render-error-message'
msg.show()
# msg.dialog()
event.preventDefault()

decko.slotReady (slot) ->
slot.find('card-view-placeholder').each ->
$place = $(this)
return if $place.data("loading")

$place.data "loading", true
$.get $place.data("url"), (data, _status) ->
$place.replaceWith data

# important: this prevents jquery-mobile from taking over everything
# $( document ).on "mobileinit", ->
# $.extend $.mobile , {
# #autoInitializePage: false
# #ajaxEnabled: false
# }

setSlotMode = ($el, mode=null) ->
$slotter = $el.closest(".slotter")
if $slotter.length && $slotter.attr('data-slotter-mode')
$slotter.attr 'data-original-slotter-mode', $slotter.attr('slotter-mode')
$slotter.attr 'data-slotter-mode', mode

snakeCase = (str)->
str.replace /([a-z])([A-Z])/g, (match) -> match[0] + '_' +
match[1].toLowerCase()

warn = (stuff) -> console.log stuff if console?





$(window).ready ->
$('body').on 'click', '.btn-item', ->
$(this).find('i').html('hourglass_full')

$('body').on 'mouseenter', '.btn-item-delete', ->
$(this).find('i').html('remove')
$(this).addClass("btn-danger").removeClass("btn-primary")

$('body').on 'mouseleave', '.btn-item-delete', ->
$(this).find('i').html('check')
$(this).addClass("btn-primary").removeClass("btn-danger")

$('body').on 'click', '.follow-updater', ->
$(this).closest('form').find('#card_update_all_users').val 'true'

$('body').on 'submit', '.edit-view.SELF-Xfollow_default .card-form', ->
confirmer = $(this).find '.confirm_update_all-view'
if confirmer.is ':hidden'
$(this).find('.follow-updater').show()

confirmer.show 'blind'
false

decko.isTouchDevice = ->
if 'ontouchstart' of window or window.DocumentTouch and
document instanceof DocumentTouch
return true
else
return detectMobileBrowser()

# source for this method: detectmobilebrowsers.com
detectMobileBrowser = (userAgent) ->
userAgent = navigator.userAgent or navigator.vendor or window.opera
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(userAgent) or /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(userAgent.substr(0, 4))

decko.slotReady (slot) ->
if decko.isTouchDevice()
slot.find('._show-on-hover').removeClass('_show-on-hover')

$(window).ready ->
$('body').on 'show.bs.popover', '._card-menu-popover', () ->
$(this).closest(".card-menu._show-on-hover")
.removeClass("_show-on-hover")
.addClass("_show-on-hover-disabled")

$('body').on 'hide.bs.popover', '._card-menu-popover', () ->
$(this).closest(".card-menu._show-on-hover-disabled")
.removeClass("_show-on-hover-disabled")
.addClass("_show-on-hover")

decko.slotReady (slot) ->
slot.find('._disappear').delay(5000).animate(
height: 0, 1000, -> $(this).hide())

if slot.hasClass("_refresh-timer")
setTimeout(
-> slot.reloadSlot(slot.data("refresh-url")),
2000
)



# filter object that manages dynamic sorting and filtering

# el can be any element inside widget
decko.filter = (el) ->
closest_widget = $(el).closest "._filter-widget"
@widget =
if closest_widget.length
closest_widget
else
$(el).closest("._filtered-content").find "._filter-widget"

@activeContainer = @widget.find "._filter-container"
@dropdown = @widget.find "._add-filter-dropdown"
@dropdownItems = @widget.find "._filter-category-select"
@form = @widget.find "._filter-form"
@quickFilter = @widget.find "._quick-filter"

@showWithStatus = (status) ->
f = this
$.each (@dropdownItems), ->
item = $(this)
if item.data status
f.activate item.data("category")

@reset = () ->
@clear()
@dropdownItems.show()
@showWithStatus "default"

@clear = () ->
@activeContainer.find(".input-group").remove()

@activate = (category, value) ->
@activateField category, value
@hideOption category

@showOption = (category) ->
@dropdown.show()
@option(category).show()

@hideOption = (category) ->
@option(category).hide()
@dropdown.hide() if @dropdownItems.length
@activeContainer.find "._filter-input"

@option = (category) ->
@dropdownItems.filter("[data-category='#{category}']")

@findPrototype = (category) ->
@widget.find "._filter-input-field-prototypes ._filter-input-#{category}"

@activateField = (category, value) ->
field = @findPrototype(category).clone()
@fieldValue field, value
@dropdown.before field
@initField field
field.find("input, select").first().focus()

@fieldValue = (field, value) ->
if typeof(value) == "object"
@compoundFieldValue field, value
else
@simpleFieldValue field, value

@simpleFieldValue = (field, value) ->
input = field.find("input, select")
input.val value if value

@compoundFieldValue = (field, vals) ->
for key of vals
input = field.find "#filter_value_" + key
input.val vals[key]

@removeField = (category)->
@activeField(category).remove()
@showOption category

@initField = (field) ->
@initSelectField field
decko.initAutoCardPlete field.find("input")
# only has effect if there is a data-options-card value

@initSelectField = (field) ->
field.find("select").select2(
containerCssClass: ":all:"
width: "auto"
dropdownAutoWidth: "true"
)

@activeField = (category) ->
@activeContainer.find("._filter-input-#{category}")

@isActive = (category) ->
@activeField(category).length

@restrict = (data) ->
@clear()
for key of data
@activateField key, data[key]
@update()

@addRestrictions = (hash) ->
for category of hash
@removeField category
@activate category, hash[category]
@update()

# triggers update
@setInputVal = (field, value) ->
select = field.find "select"
if select.length
@setSelect2Val select, value
else
@setTextInputVal field.find("input"), value

# this triggers change, which updates form
# if we just use simple "val", the display doesn't update correctly
@setSelect2Val = (select, value) ->
value = [value] if select.attr("multiple") && !Array.isArray(value)
select.select2 "val", value

@setTextInputVal = (input, value) ->
input.val value
@update()

@updateLastVals = ()->
@activeFields().find("input, select").each ()->
$(this).data "lastVal", $(this).val()

@updateUrlBar = () ->
return if @widget.closest('._noFilterUrlUpdates')[0]
window.history.pushState "filter", "filter", '?' + @form.serialize()

@update = ()->
@updateLastVals()
@updateQuickLinks()
@form.submit()
@updateUrlBar()

@updateQuickLinks = ()->
widget = this
links = @quickFilter.find "a"
links.addClass "active"
links.each ->
link = $(this)
opts = link.data "filter"
for key of opts
widget.deactivateQuickLink link, key, opts[key]

@deactivateQuickLink = (link, key, value) ->
sel = "._filter-input-#{key}"
$.map [@form.find("#{sel} input, #{sel} select").val()], (arr) ->
link.removeClass "active" if $.inArray(value, arr) > -1

@updateIfChanged = ()->
@update() if @changedSinceLastVal()

@changedSinceLastVal = () ->
changed = false
@activeFields().find("input, select").each ()->
changed = true if $(this).val() != $(this).data("lastVal")
changed

this

decko.slotReady (slot) ->
slot.find("._filter-widget").each ->
if slot[0] == $(this).slot()[0]
filter = new decko.filter this
filter.showWithStatus "active"
filter.updateLastVals()
filter.updateQuickLinks()

$(window).ready ->
filterFor = (el) ->
new decko.filter el

# sometimes this element shows up as changed and breaks the filter.
weirdoSelect2FilterBreaker = (el) ->
$(el).hasClass "select2-search__field"

filterableData = (filterable) ->
f = $(filterable)
f.data("filter") || f.find("._filterable").data("filter")

targetFilter = (filterable) ->
selector = $(filterable).closest("._filtering").data("filter-selector")
filterFor (selector || this)

# Add Filter
$("body").on "click", "._filter-category-select", (e) ->
e.preventDefault()
# e.stopPropagation()
filterFor(this).activate $(this).data("category")

# Update filter results based on filter value changes
onchangers = "._filter-input input:not(.simple-text), " +
"._filter-input select, ._filter-sort"
$("body").on "change", onchangers, ->
return if weirdoSelect2FilterBreaker this
filterFor(this).update()

# update filter result after typing in text box
keyupTimeout = null
$("body").on "keyup", "._filter-input input.simple-text", ->
clearTimeout keyupTimeout
filter = filterFor this
keyupTimeout = setTimeout ( -> filter.updateIfChanged() ), 333

# remove filter
$("body").on "click", "._delete-filter-input", ->
filter = filterFor this
filter.removeField $(this).closest("._filter-input").data("category")
filter.update()

# reset all filters
$('body').on 'click', '._reset-filter', () ->
f = filterFor(this)
f.reset()
f.update()

$('body').on 'click', '._filtering ._filterable', (e) ->
f = targetFilter this
if f.widget.length
f.restrict filterableData(this)
e.preventDefault()
e.stopPropagation()

$('body').on 'click', '._quick-filter a, ._filter-link', (e) ->
f = filterFor this
f.addRestrictions $(this).data("filter")
e.preventDefault()

# FILTERED LIST / ITEMS INTERFACE
# (fancy pointer ui)

$(window).ready ->
# add all selected items
$("body").on "click", "._filter-items ._add-selected", ->
btn = $(this)
content = newFilteredListContent btn
btn.attr "href", addSelectedButtonUrl(btn, content)

# select all visible filtered items
$("body").on "click", "._select-all", ->
filterBox($(this)).find("._unselected ._search-checkbox-item input").each ->
selectFilteredItem $(this)
$(this).prop "checked", false
updateAfterSelection $(this)

# deselect all selected items
$("body").on "click", "._deselect-all", ->
filterBox($(this)).find("._selected ._search-checkbox-item input").each ->
$(this).slot().remove()
$(this).prop "checked", true
updateAfterSelection $(this)

$("body").on "click", "._filter-items ._unselected ._search-checkbox-item input", ->
selectFilteredItem $(this)
updateAfterSelection $(this)

$("body").on "click", "._filter-items ._selected ._search-checkbox-item input", ->
bin = selectedBin $(this)
$(this).slot().remove()
updateAfterSelection bin

$('body').on 'click', '._filtered-list-item-delete', ->
$(this).closest('li').remove()

# TODO: make this object oriented!

newFilteredListContent = (el) ->
$.map(prefilteredIds(el).concat(selectedIds el), (id) -> "~" + id).join "\n"

addSelectedButtonUrl = (btn, content) ->
view = btn.slot().data("slot")["view"]
card_args = { content: content, type: "Pointer" }
query = { assign: true, view: view, card: card_args }
path_base = btn.attr("href") + "&" + $.param(query)
decko.slotPath path_base, btn.slot()

updateAfterSelection = (el) ->
trackSelectedIds el
f = new decko.filter(filterBox(el).find('._filter-widget'))
f.update()
updateSelectedCount el
updateUnselectedCount el

updateSelectedCount = (el) ->
count = selectedBin(el).children().length
filterBox(el).find("._selected-items").html count
deselectAllLink(el).attr "disabled", count == 0
if count > 0
addSelectedButton(el).removeClass("disabled")
else
addSelectedButton(el).addClass("disabled")

updateSelectedSectionVisibility el, count > 0

updateSelectedSectionVisibility = (el, items_present) ->
box = filterBox el
selected_items = box.find "._selected-item-list"
help_text = box.find "._filter-help"
if items_present
selected_items.show()
help_text.hide()
else
selected_items.hide()
help_text.show()

updateUnselectedCount = (el) ->
box = filterBox(el)
count = box.find("._search-checkbox-list").children().length
box.find("._unselected-items").html count
box.find("._select-all").attr "disabled", count > 0

selectFilteredItem = (checkbox) ->
checkbox.prop "checked", true
selectedBin(checkbox).append checkbox.slot()

selectedBin = (el) ->
filterBox(el).find "._selected-bin"

filterBox = (el) ->
el.closest "._filter-items"

# this button contains the data about the form that opened the filter-items interface.
# the itemSelector
addSelectedButton = (el) ->
filterBox(el).find("._add-selected")

deselectAllLink = (el) ->
filterBox(el).find("._deselect-all")

selectedIds = (el) ->
selectedData el, "cardId"

prefilteredIds = (el) ->
prefilteredData el, "cardId"

prefilteredNames = (el) ->
prefilteredData el, "cardName"

prefilteredData = (el, field) ->
btn = addSelectedButton el
selector = btn.data "itemSelector"
arrayFromField btn.slot().find(selector), field

selectedNames = (el) ->
selectedData el, "cardName"

selectedData = (el, field) ->
arrayFromField selectedBin(el).children(), field

arrayFromField = (rows, field) ->
rows.map( -> $(this).data field ).toArray()

trackSelectedIds = (el) ->
ids = prefilteredIds(el).concat selectedIds(el)
box = filterBox el
box.find("._not-ids").val ids.toString()