!function($, window, document, _undefined)
{
"use strict";
XF.Message = XF.Message || {};
XF.Message.insertMessages = function(dataHtml, $container, ascending, onInsert)
{
XF.setupHtmlInsert(dataHtml, function($html, container, onComplete)
{
var $noMessages = $container.find('.js-replyNoMessages');
if ($noMessages.length)
{
$noMessages.xfFadeUp();
}
$html.each(function()
{
if (!this.tagName)
{
return;
}
XF.Message.insertMessage($(this), $container, ascending);
});
if (onInsert)
{
onInsert($html);
}
});
};
XF.Message.insertMessage = function($message, $container, ascending)
{
var $firstChild = $container.children().first();
$message.hide();
if ($firstChild.is('form') && !ascending)
{
$message.insertAfter($firstChild);
}
else if (!ascending)
{
$container.prepend($message);
}
else
{
$container.append($message);
}
$message.xfFadeDown();
XF.activate($message);
};
// ################################## MESSAGE LOADER HANDLER ###########################################
XF.MessageLoaderClick = XF.Event.newHandler({
eventNameSpace: 'XFMessageLoaderClick',
options: {
href: null,
messagesContainer: '< .js-replyNewMessageContainer',
selfContainer: '.message',
ascending: true
},
loading: false,
init: function()
{
if (!this.options.href)
{
this.options.href = this.$target.attr('href');
if (!this.options.href)
{
console.error('Must be initialized with a data-href or href attribute.');
}
}
},
click: function(e)
{
e.preventDefault();
if (this.loading)
{
return;
}
var self = this;
XF.ajax('GET', this.options.href, {}, XF.proxy(this, 'loaded'))
.always(function() { self.loading = false; });
},
loaded: function(data)
{
if (!data.html)
{
return;
}
var $container = XF.findRelativeIf(this.options.messagesContainer, this.$target);
XF.Message.insertMessages(data.html, $container, this.options.ascending);
var $selfMessage = this.$target.closest(this.options.selfContainer);
$selfMessage.xfFadeUp(null, function()
{
$selfMessage.remove();
});
if (data.lastDate)
{
$('.js-quickReply input[name="last_date"]').val(data.lastDate);
}
}
});
// ################################## QUICK EDIT HANDLER ###########################################
XF.QuickEditClick = XF.Event.newHandler({
eventNameSpace: 'XFQuickEdit',
options: {
editorTarget: null,
editContainer: '.js-editContainer',
href: null,
noInlineMod: 0
},
$editorTarget: null,
$editForm: null,
href: null,
loading: false,
init: function()
{
var edTarget = this.options.editorTarget;
if (!edTarget)
{
console.error('No quick edit editorTarget specified');
return;
}
this.$editorTarget = XF.findRelativeIf(edTarget, this.$target);
if (!this.$editorTarget.length)
{
console.error('No quick edit target found');
return;
}
this.href = this.options.href || this.$target.attr('href');
if (!this.href)
{
console.error('No edit URL specified.');
}
},
click: function(e)
{
if (!this.$editorTarget || !this.href)
{
return;
}
e.preventDefault();
if (this.loading)
{
return;
}
this.loading = true;
var data = {};
if (this.options.noInlineMod)
{
data['_xfNoInlineMod'] = true;
}
XF.ajax('GET', this.href, data, XF.proxy(this, 'handleAjax'), { skipDefaultSuccessError: true });
},
handleAjax: function(data)
{
var $editorTarget = this.$editorTarget,
self = this;
if (data.errors)
{
this.loading = false;
XF.alert(data.errors);
return;
}
XF.setupHtmlInsert(data.html, function($html, container)
{
$html.hide().insertAfter($editorTarget);
XF.activate($html);
self.$editForm = $html;
$html.on('ajax-submit:response', XF.proxy(self, 'editSubmit'));
$html.find('.js-cancelButton').on('click', XF.proxy(self, 'cancelClick'));
var $hidden = $html.find('input[type=hidden]').first();
$hidden.after('<input type="hidden" name="_xfInlineEdit" value="1" />');
$editorTarget.xfFadeUp(null, function()
{
$editorTarget.parent().addClass('is-editing');
$html.xfFadeDown(XF.config.speed.normal, function()
{
$html.trigger('quick-edit:shown');
var $editContainer = $html.find(self.options.editContainer);
if ($editContainer.length && !XF.isElementVisible($editContainer))
{
$editContainer.get(0).scrollIntoView(true);
}
self.loading = false;
});
});
$html.trigger('quick-edit:show');
});
},
editSubmit: function(e, data)
{
if (data.errors || data.exception)
{
return;
}
e.preventDefault();
if (data.message)
{
XF.flashMessage(data.message, 3000);
}
var $editorTarget = this.$editorTarget,
self = this;
XF.setupHtmlInsert(data.html, function($html, container, onComplete)
{
var target = self.options.editorTarget;
target = target.replace(/<|\|/g, '').replace(/#[a-zA-Z0-9_-]+\s*/, '');
var $message = $html.find(target);
$message.hide();
$editorTarget.replaceWith($message);
self.$editorTarget = $message;
XF.activate($message);
self.stopEditing(false, function()
{
$message.xfFadeDown();
self.$editForm.trigger('quickedit:editcomplete', data);
});
});
},
cancelClick: function(e)
{
this.stopEditing(true);
},
stopEditing: function(showMessage, onComplete)
{
var $editorTarget = this.$editorTarget,
$editForm = this.$editForm,
self = this;
var finish = function()
{
$editorTarget.parent().removeClass('is-editing');
if (showMessage)
{
$editorTarget.xfFadeDown();
}
if (onComplete)
{
onComplete();
}
$editForm.remove();
self.$editForm = null;
};
if ($editForm)
{
$editForm.xfFadeUp(null, finish);
}
else
{
finish();
}
}
});
// ################################## QUOTE HANDLER ###########################################
XF.QuoteClick = XF.Event.newHandler({
eventNameSpace: 'XFQuoteClick',
options: {
quoteHref: null,
editor: '.js-quickReply .js-editor'
},
init: function()
{
if (!this.options.quoteHref)
{
console.error('Must be initialized with a data-quote-href attribute.');
}
},
click: function(e)
{
var $editor = XF.findRelativeIf(this.options.editor, this.$target);
$editor.closest('form').trigger('preview:hide', [this]);
var $qr = $editor.closest('.js-quickReply');
if (!$editor.length || !$qr.length)
{
return;
}
e.preventDefault();
var href = this.options.quoteHref,
$selectToQuote = $(e.target).parents('.tooltip--selectToQuote'),
quoteHtml = XF.unparseBbCode($selectToQuote.data('quote-html'));
XF.ajax('POST', href, { quoteHtml: quoteHtml }, XF.proxy(this, 'handleAjax'), {skipDefaultSuccess: true});
$(e.target).trigger('s2q:click');
window.scrollTo({ top: $qr.offset().top - XF.getStickyHeaderOffset() });
XF.focusEditor($editor);
},
handleAjax: function(data)
{
var $editor = XF.findRelativeIf(this.options.editor, this.$target);
XF.insertIntoEditor($editor, data.quoteHtml, data.quote);
}
});
// ################################## SOLUTION EDIT HANDLER ###########################################
XF.SolutionEditClick = XF.extend(XF.SwitchClick, {
applyResponseActions: function(data)
{
this.applyActionsTo(this.$target, data);
},
applyActionsTo: function($target, data)
{
var match,
replaceId;
if (data.switchKey)
{
match = data.switchKey.match(/^replaced:(\d+)$/);
if (match)
{
replaceId = parseInt(match[1], 10);
data.switchKey = 'marked'; // mark this post as the solution
}
}
XF.handleSwitchResponse($target, data, this.options.redirect);
// TODO: the selectors below this could do with being more flexible
var $message = $target.closest('.message');
if (data.switchKey == 'marked')
{
$message.addClass('message--solution');
}
else if (data.switchKey == 'removed')
{
$message.removeClass('message--solution');
var originalTopPos = $message.offset().top,
originalScrollTop = $(document).scrollTop();
$('#js-solutionHighlightBlock').remove();
var diff = $message.offset().top - originalTopPos;
if (diff)
{
$(document).scrollTop(originalScrollTop + diff);
}
}
if (replaceId)
{
var $replacedControl = $('#js-post-' + replaceId + ' .js-solutionControl');
if ($replacedControl.length)
{
this.applyActionsTo($replacedControl, { switchKey: 'removed' });
}
}
}
});
// ################################## MULTI QUOTE HANDLER ###########################################
XF.MultiQuote = XF.Element.newHandler({
options: {
href: '',
messageSelector: '',
addMessage: '',
removeMessage: '',
storageKey: ''
},
mqStorage: null,
mqOverlay: null,
removing: false,
quoting: false,
init: function()
{
this.initButton();
this.initControls();
var self = this;
XF.CrossTab.on('mqChange', function(data)
{
if (data.storageKey !== self.options.storageKey)
{
return;
}
var messageId = data.messageId;
switch (data.action)
{
case 'added':
self.selectMqControl(messageId);
break;
case 'removed':
self.deselectMqControl(messageId);
break;
case 'refresh':
// the code below will handle this
break;
}
self.refreshMqData();
self.updateButtonState();
});
},
initButton: function()
{
this.mqStorage = XF.LocalStorage.getJson(this.options.storageKey);
if (this.hasQuotesStored())
{
this.$target.show();
}
this.$target.on('click', XF.proxy(this, 'buttonClick'));
},
buttonClick: function(e)
{
e.preventDefault();
if (!this.options.href)
{
console.error('Multi-quote button must have a data-href attribute set to display selected quotes');
return false;
}
XF.ajax('post', this.options.href, {
quotes: XF.LocalStorage.get(this.options.storageKey)
}, XF.proxy(this, 'loadOverlay'));
},
loadOverlay: function(data)
{
if (data.html)
{
var self = this;
XF.setupHtmlInsert(data.html, function ($html, container)
{
var $overlay = XF.getOverlayHtml({
html: $html,
title: container.h1 || container.title
});
$overlay.find('.js-removeMessage').on('click', XF.proxy(self, 'removeMessage'));
$overlay.find('.js-quoteMessages').on('click', XF.proxy(self, 'quoteMessages'));
self.mqOverlay = XF.showOverlay($overlay);
});
}
},
removeMessage: function(e)
{
e.preventDefault();
if (this.removing)
{
return;
}
this.removing = true;
var $item = $(e.target).closest('.nestable-item'),
messageId = $item.data('id'),
overlay = this.mqOverlay;
this.removeFromMultiQuote(messageId);
$item.xfFadeUp(XF.config.speed.fast, function()
{
$item.remove();
});
if (!this.hasQuotesStored())
{
overlay.hide();
}
this.removing = false;
},
quoteMessages: function(e)
{
e.preventDefault();
if (this.quoting)
{
return;
}
this.quoting = true;
var overlay = this.mqOverlay,
$overlay = overlay.getOverlay(),
toInsert = $.parseJSON($overlay.find('input[name="message_ids"]').val()),
multiQuotes = this.mqStorage,
t = this;
for (var i in toInsert)
{
if (!toInsert.hasOwnProperty(i) || !toInsert[i].hasOwnProperty('id'))
{
continue;
}
var id = toInsert[i]['id'],
parts = id.split('-'),
messageId = parts[0],
key = parts[1];
if (!this.isValidQuote(multiQuotes[messageId], key))
{
continue;
}
var value = multiQuotes[messageId][key];
if (value !== true)
{
value = XF.unparseBbCode(value);
}
toInsert[i]['value'] = value;
}
overlay.hide();
XF.ajax('post', this.options.href, {
insert: toInsert,
quotes: XF.LocalStorage.get(this.options.storageKey)
}, XF.proxy(this, 'insertMessages')).always(function()
{
t.quoting = false;
});
},
isValidQuote: function(quote, key)
{
if (quote != undefined)
{
if (quote.hasOwnProperty(key))
{
if (quote[key] === true || typeof quote[key] == 'string')
{
return true;
}
}
}
return false;
},
insertMessages: function(data)
{
var $editor = XF.findRelativeIf('< form | .js-editor', this.$target).first();
if (!$editor.length)
{
$editor = $('.js-editor').parent();
}
$.each(data, function(i, quoteObj)
{
if (!quoteObj.hasOwnProperty('quote') || !quoteObj.hasOwnProperty('quoteHtml'))
{
return true;
}
if (i > 0)
{
// we want an extra line break between inserted quotes
quoteObj.quoteHtml = '<p></p>' + quoteObj.quoteHtml;
quoteObj.quote = "\n" + quoteObj.quote;
}
XF.insertIntoEditor($editor, quoteObj.quoteHtml, quoteObj.quote);
});
for (var messageId in this.mqStorage)
{
this.removeFromMultiQuote(messageId);
}
},
initControls: function()
{
var messages = '.tooltip--selectToQuote, ' + this.options.messageSelector,
$controls = $(messages).find('.js-multiQuote');
$(document).on('click', messages, XF.proxy(this, 'controlClick'));
var self = this;
$controls.each(function()
{
var $control = $(this),
messageId = $control.data('messageId');
if (self.mqStorage.hasOwnProperty(messageId))
{
$control.addClass('is-selected');
$control.data('mqAction', 'remove');
}
});
},
controlClick: function(e)
{
if (!$(e.target).is('.js-multiQuote'))
{
return;
}
e.preventDefault();
var $target = $(e.target),
action = $target.data('mqAction'),
messageId = $target.data('messageId');
switch (action)
{
case 'add':
this.addToMultiQuote(messageId);
XF.flashMessage(this.options.addMessage, 3000);
break;
case 'remove':
this.removeFromMultiQuote(messageId);
XF.flashMessage(this.options.removeMessage, 3000);
break;
}
$(e.target).trigger('s2q:click');
},
addToMultiQuote: function(messageId)
{
var $mqControl = $('.js-multiQuote[data-message-id="' + messageId + '"]'),
$selectToQuote = $mqControl.parents('.tooltip--selectToQuote'),
quoteHtml = XF.unparseBbCode($selectToQuote.data('quote-html'));
this.refreshMqData();
if (!this.hasQuotesStored())
{
this.mqStorage = {};
this.mqStorage[messageId] = [];
}
else
{
if (!this.mqStorage[messageId])
{
this.mqStorage[messageId] = [];
}
}
if ($selectToQuote.length)
{
this.mqStorage[messageId].push(quoteHtml);
}
else
{
this.mqStorage[messageId].push(true); // true == quoting the full message
}
this.updateMultiQuote();
this.selectMqControl(messageId);
this.triggerCrossTabEvent('added', messageId);
},
removeFromMultiQuote: function(messageId)
{
var quoteInfo = String(messageId).match(/^(\d+)-(\d+)$/);
this.refreshMqData();
if (quoteInfo)
{
messageId = quoteInfo[1];
delete this.mqStorage[messageId][quoteInfo[2]];
if (!this.getQuoteStoreCount(this.mqStorage[messageId]))
{
delete this.mqStorage[messageId];
}
}
else
{
delete this.mqStorage[messageId];
}
this.updateMultiQuote();
if (!this.mqStorage[messageId])
{
this.deselectMqControl(messageId);
this.triggerCrossTabEvent('removed', messageId);
}
},
selectMqControl: function(messageId)
{
var $mqControl = $('.js-multiQuote[data-message-id="' + messageId + '"]');
if ($mqControl.length)
{
$mqControl.addClass('is-selected');
$mqControl.data('mqAction', 'remove');
}
},
deselectMqControl: function(messageId)
{
var $mqControl = $('.js-multiQuote[data-message-id="' + messageId + '"]');
if ($mqControl.length)
{
$mqControl.removeClass('is-selected');
$mqControl.data('mqAction', 'add');
}
},
getQuoteStoreCount: function(quoteStore)
{
var length = 0;
for (var i in quoteStore)
{
if (quoteStore.hasOwnProperty(i))
{
if (quoteStore[i] == true || typeof quoteStore[i] == 'string')
{
length ++;
}
}
}
return length;
},
updateMultiQuote: function()
{
XF.LocalStorage.setJson(this.options.storageKey, this.mqStorage, true);
this.updateButtonState();
},
updateButtonState: function()
{
if (!this.hasQuotesStored())
{
this.$target.hide();
}
else
{
this.$target.show();
}
},
refreshMqData: function()
{
this.mqStorage = XF.LocalStorage.getJson(this.options.storageKey);
},
hasQuotesStored: function()
{
return this.mqStorage && !$.isEmptyObject(this.mqStorage);
},
triggerCrossTabEvent: function(action, messageId, data)
{
data = data || {};
data.storageKey = this.options.storageKey;
data.action = action;
data.messageId = messageId;
XF.CrossTab.trigger('mqChange', data);
}
});
// ################################## SELECT TO QUOTE HANDLER ###########################################
XF.SelectToQuote = XF.Element.newHandler({
options: {
messageSelector: ''
},
$quickReply: null,
timeout: null,
processing: false,
triggerEvent: null,
isMouseDown: false,
tooltip: null,
tooltipId: null,
init: function()
{
if (!window.getSelection)
{
return;
}
if (!this.options.messageSelector)
{
console.error('No messageSelector');
return;
}
this.$quickReply = $('.js-quickReply .js-editor').parent();
if (!this.$quickReply.length)
{
return;
}
this.$target.on('mousedown pointerdown', XF.proxy(this, 'mouseDown'));
this.$target.on('mouseup pointerup', XF.proxy(this, 'mouseUp'));
$(document).on('selectionchange', XF.proxy(this, 'selectionChange'));
},
mouseDown: function(e)
{
// store event so we can detect later if it originates from touch
this.triggerEvent = e;
if (e.type == 'mousedown')
{
this.isMouseDown = true;
}
},
mouseUp: function()
{
this.isMouseDown = false;
this.trigger();
},
selectionChange: function()
{
if (!this.isMouseDown)
{
this.trigger();
}
},
trigger: function()
{
if (!this.timeout && !this.processing)
{
this.timeout = setTimeout(XF.proxy(this, 'handleSelection'), 100);
}
},
handleSelection: function()
{
this.processing = true;
this.timeout = null;
var selection = window.getSelection(),
$selectionContainer = this.getValidSelectionContainer(selection);
if ($selectionContainer)
{
this.showQuoteButton($selectionContainer, selection);
}
else
{
this.hideQuoteButton();
}
var self = this;
setTimeout(function()
{
self.processing = false;
}, 0);
},
getValidSelectionContainer: function(selection)
{
if (selection.isCollapsed || !selection.rangeCount)
{
return null;
}
var range = selection.getRangeAt(0);
this.adjustRange(range);
if (!$.trim(range.toString()).length)
{
if (!range.cloneContents().querySelectorAll('img').length)
{
return null;
}
}
var $container = $(range.commonAncestorContainer).closest('.js-selectToQuote');
if (!$container.length)
{
return null;
}
if (!$container.closest(this.$target).length)
{
return null;
}
var $message = $container.closest(this.options.messageSelector);
if (!$message.find('.actionBar-action[data-xf-click="quote"]').length)
{
return null;
}
if ($(range.startContainer).closest('.bbCodeBlock--quote, .js-noSelectToQuote').length
|| $(range.endContainer).closest('.bbCodeBlock--quote, .js-noSelectToQuote').length)
{
return null;
}
return $container;
},
adjustRange: function(range)
{
var changed = false,
isQuote = false,
end = range.endContainer,
$end = $(end);
if (range.endOffset == 0)
{
if (end.nodeType == 3 && !end.previousSibling)
{
// text node with nothing before it, move up
$end = $end.parent();
}
isQuote = ($end.closest('.bbCodeBlock--quote').length > 0);
}
if (isQuote)
{
var $quote = $end.closest('.bbCodeBlock--quote');
if ($quote.length)
{
range.setEndBefore($quote[0]);
changed = true;
}
}
if (changed)
{
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
},
showQuoteButton: function($selectionContainer, selection)
{
var id = $selectionContainer.xfUniqueId();
if (!this.tooltip || this.tooltipId !== id)
{
this.hideQuoteButton();
this.createButton($selectionContainer, id);
}
var $tooltip = this.tooltip.getTooltip();
$tooltip.data('quote-html', this.getSelectionHtml(selection));
var offset = this.getButtonPositionMarker(selection),
touchTriggered = false;
if (this.triggerEvent)
{
touchTriggered = XF.isEventTouchTriggered(this.triggerEvent);
}
// if touch triggered then the browser may have large selection handles which might
// obscure our tooltip - if we detect touch then try to offset that
if (touchTriggered)
{
offset.top += 10;
}
this.tooltip.setPositioner([offset.left, offset.top]);
if (this.tooltip.isShown())
{
this.tooltip.reposition();
}
else
{
this.tooltip.show();
}
$tooltip.addClass('tooltip--selectToQuote');
},
getButtonPositionMarker: function(selection)
{
// get absolute position of end of selection - or maybe focusNode
// and position the quote button immediately next to the highlight
var $el, range, offset, height, bounds;
$el = $('<span />').text('\u200B');
range = selection.getRangeAt(0).cloneRange();
bounds = range.getBoundingClientRect ? range.getBoundingClientRect() : null;
range.collapse(false);
range.insertNode($el[0]);
var changed,
moves = 0;
do
{
changed = false;
moves++;
if ($el[0].parentNode && $el[0].parentNode.className == 'js-selectToQuoteEnd')
{
// highlight after the marker to ensure that triple click works
$el.insertBefore($el[0].parentNode);
changed = true;
}
if ($el[0].previousSibling && $el[0].previousSibling.nodeType == 3 && $.trim($el[0].previousSibling.textContent).length == 0)
{
// highlight after an empty text block
$el.insertBefore($el[0].previousSibling);
changed = true;
}
if ($el[0].parentNode && $el[0].parentNode.tagName == 'LI' && !$el[0].previousSibling)
{
// highlight at the beginning of a list item, move to previous item if possible
var li = $el[0].parentNode;
if ($(li).prev('li').length)
{
// move to inside the last li
$el.appendTo($(li).prev('li'));
changed = true;
}
else if (li.parentNode)
{
// first list item, move before the list
$el.insertBefore(li.parentNode);
changed = true;
}
}
if ($el[0].parentNode && !$el[0].previousSibling && $.inArray($el[0].parentNode.tagName, ['DIV', 'BLOCKQUOTE', 'PRE']) != -1)
{
$el.insertBefore($el[0].parentNode);
changed = true;
}
if ($el[0].previousSibling && $.inArray($el[0].previousSibling.tagName, ['OL', 'UL']) != -1)
{
// immediately after a list, position at end of last LI
$($el[0].previousSibling).find('li').last().append($el);
changed = true;
}
if ($el[0].previousSibling && $.inArray($el[0].previousSibling.tagName, ['DIV', 'BLOCKQUOTE', 'PRE']) != -1)
{
// highlight immediately after a block causes weird positioning
$el.appendTo($el[0].previousSibling);
changed = true;
}
if ($el[0].previousSibling && $el[0].previousSibling.tagName == 'BR')
{
// highlight immediately after a line break causes weird positioning
$el.insertBefore($el[0].previousSibling);
changed = true;
}
}
while (changed && moves < 5);
offset = $el.offset();
height = $el.height();
// if we're in a scrollable element, find the right edge of that element and don't position beyond it
$el.parentsUntil('body').each(function()
{
var $parent = $(this), left, right;
switch ($parent.css('overflow-x'))
{
case 'hidden':
case 'scroll':
case 'auto':
left = $parent.offset().left;
right = left + $parent.outerWidth();
if (offset.left < left)
{
offset.left = left;
}
if (right < offset.left)
{
offset.left = right;
}
}
});
var $parent = $el.parent();
$el.remove();
if (!XF.browser.msie) // IE loses the selection from this
{
$parent[0].normalize(); // recombine text nodes for accurate text rendering
}
if (bounds && !XF.isRtl())
{
if (offset.left - bounds.left > 32)
{
offset.left -= 16;
}
}
offset.top += height;
return offset;
},
createButton: function($selectionContainer, id)
{
var $message = $selectionContainer.closest(this.options.messageSelector),
$tooltip = $('<span />');
var $mqButton = $message.find('.actionBar-action.js-multiQuote').clone();
if ($mqButton.length)
{
$mqButton
.attr('title', '')
.removeClass('is-selected')
.data('mqAction', 'add')
.css({
marginLeft: 0,
background: 'transparent'
})
.on('s2q:click', XF.proxy(this, 'buttonClicked'));
$tooltip.append($mqButton);
$tooltip.append(document.createTextNode(' | '));
}
var $quoteButton = $message.find('.actionBar-action[data-xf-click="quote"]')
.attr('title', '')
.clone()
.css({
marginLeft: 0
})
.on('s2q:click', XF.proxy(this, 'buttonClicked'));
$tooltip.append($quoteButton);
this.tooltip = new XF.TooltipElement($tooltip, {
html: true,
placement: 'bottom'
});
this.tooltipId = id;
},
buttonClicked: function()
{
var s = window.getSelection();
if (!s.isCollapsed)
{
s.collapse(s.getRangeAt(0).commonAncestorContainer, 0);
this.hideQuoteButton();
}
},
hideQuoteButton: function()
{
var tooltip = this.tooltip;
if (tooltip)
{
tooltip.destroy();
this.tooltip = null;
}
},
getSelectionHtml: function(selection)
{
var el = document.createElement('div'),
i, len, contents;
for (i = 0, len = selection.rangeCount; i < len; i++)
{
contents = selection.getRangeAt(i).cloneContents();
this.groupIncompleteTableSegment(contents, 'td, th', 'tr', 'TR');
this.groupIncompleteTableSegment(contents, 'tr', 'table, tbody, thead, tfoot', 'TABLE');
this.groupIncompleteTableSegment(contents, 'tbody, thead, tfoot', 'table', 'TABLE');
el.appendChild(contents);
}
return this.prepareSelectionHtml(el.innerHTML);
},
groupIncompleteTableSegment: function(fragment, segmentMatch, parentMatch, containerNodeName)
{
var matches = fragment.querySelectorAll(segmentMatch),
i, node, entries, container, j;
for (i = 0; i < matches.length; i++)
{
node = matches[i];
if (!$(node.parentNode).is(parentMatch))
{
entries = [node];
while (node = node.nextSibling)
{
if ($(node).is(segmentMatch))
{
entries.push(node);
}
else
{
break;
}
}
container = document.createElement(containerNodeName);
entries[0].parentNode.insertBefore(container, entries[0]);
for (j = 0; j < entries.length; j++)
{
container.appendChild(entries[j]);
}
}
}
},
prepareSelectionHtml: function(html)
{
return XF.adjustHtmlForRte(html);
}
});
// ################################## QUICK REPLY HANDLER ###########################################
XF.QuickReply = XF.Element.newHandler({
options: {
messageContainer: '',
ascending: true,
submitHide: null
},
init: function()
{
this.$target.on('ajax-submit:before', XF.proxy(this, 'beforeSubmit'));
this.$target.on('ajax-submit:response', XF.proxy(this, 'afterSubmit'));
this.$target.on('draft:complete', XF.proxy(this, 'onDraft'));
},
beforeSubmit: function(e, config)
{
var $button = config.submitButton;
if ($button && $button.attr('name') == 'more_options')
{
e.preventDefault();
}
},
afterSubmit: function(e, data)
{
if (data.errors || data.exception)
{
return;
}
e.preventDefault();
if (data.redirect)
{
XF.redirect(data.redirect);
return;
}
this.$target.find('input[name="last_date"]').val(data.lastDate);
this.getMessagesContainer().find('.js-newMessagesIndicator').remove();
this.insertMessages(data.html);
XF.clearEditorContent(this.$target);
var editor = XF.getEditorInContainer(this.$target);
if (editor && XF.Editor && editor instanceof XF.Editor)
{
editor.blur();
}
var $target = this.$target,
options = this.options;
$target.trigger('preview:hide', [this]);
$target.trigger('attachment-manager:reset');
if (options.submitHide)
{
var $submitHide = XF.findRelativeIf(options.submitHide, this.$target);
$submitHide.hide();
}
},
insertMessages: function(dataHtml)
{
XF.Message.insertMessages(
dataHtml,
this.getMessagesContainer(),
this.options.ascending,
function($messages)
{
var $message = $messages.first();
if ($message && $message.length)
{
var dims = $message.dimensions(),
windowTop = $(window).scrollTop(),
windowBottom = windowTop + $(window).height();
if (dims.top < windowTop + 50 || dims.top > windowBottom)
{
XF.smoothScroll(Math.max(0, dims.top - 60), false, 200);
}
}
}
);
},
getMessagesContainer: function()
{
var containerOption = this.options.messageContainer;
if (containerOption)
{
return XF.findRelativeIf(containerOption, this.$target).first();
}
else
{
return $('.js-replyNewMessageContainer').first();
}
},
onDraft: function(e, data)
{
if (data.hasNew && data.html)
{
if (data.lastDate && data.lastDate > 0)
{
var $lastDate = $('.js-quickReply input[name="last_date"]');
if ($lastDate.length && parseInt($lastDate.val(), 10) > data.lastDate)
{
return;
}
}
if (this.getMessagesContainer().find('.js-newMessagesIndicator').length)
{
return;
}
// structured like a message
this.insertMessages(data.html);
}
}
});
// ################################## GUEST CAPTCHA HANDLER ###########################################
XF.GuestCaptcha = XF.Element.newHandler({
options: {
url: 'index.php?misc/captcha&with_row=1',
target: '.js-captchaContainer',
skip: '[name=more_options]'
},
$captchaContainer: null,
initialized: false,
init: function()
{
var $form = this.$target;
this.$captchaContainer = $form.find(this.options.target);
if (!this.$captchaContainer.length)
{
return;
}
$form.on('focusin', XF.proxy(this, 'initializeCaptcha'));
$form.on('submit ajax-submit:before', XF.proxy(this, 'submit'));
},
initializeCaptcha: function(e)
{
var $activeElement = $(document.activeElement);
if (this.initialized || $activeElement.is(this.options.skip))
{
return;
}
var rowType = this.$captchaContainer.data('row-type') || '';
XF.ajax('get',
XF.canonicalizeUrl(this.options.url),
{ row_type: rowType },
XF.proxy(this, 'showCaptcha')
);
this.initialized = true;
},
showCaptcha: function(data)
{
var self = this;
XF.setupHtmlInsert(data.html, function ($html, container, onComplete)
{
$html.replaceAll(self.$captchaContainer);
onComplete();
});
},
submit: function(e)
{
if (!this.initialized)
{
var $activeElement = $(document.activeElement);
if (!$activeElement.is(this.options.skip))
{
e.preventDefault();
return false;
}
}
}
});
// ################################## POST EDIT HANDLER ######################
XF.PostEdit = XF.Element.newHandler({
init: function()
{
this.$target.on('quickedit:editcomplete', XF.proxy(this, 'editComplete'));
},
editComplete: function(e, data)
{
var self = this;
XF.setupHtmlInsert(data.html, function($html, container, onComplete)
{
var threadChanges = data.threadChanges || {};
if (threadChanges.title)
{
$('h1.p-title-value').html(container.h1);
$('title').html(container.title);
// This effectively runs twice, but we do need the title to be correct if updating this way.
if (XF.config.visitorCounts['title_count'] && data.visitor)
{
XF.pageTitleCache = container.title;
XF.pageTitleCounterUpdate(data.visitor.total_unread);
}
}
if (threadChanges.customFields)
{
var $newThreadStatusField = $html.closest('.js-threadStatusField'),
$threadStatusField = XF.findRelativeIf('< .block--messages | .js-threadStatusField', self.$target);
if ($newThreadStatusField.length && $threadStatusField.length)
{
$threadStatusField.xfFadeUp(XF.config.speed.fast, function()
{
$threadStatusField.replaceWith($newThreadStatusField).xfFadeDown(XF.config.speed.fast);
});
}
}
else
{
$html.find('.js-threadStatusField').remove();
}
});
}
});
XF.Event.register('click', 'message-loader', 'XF.MessageLoaderClick');
XF.Event.register('click', 'quick-edit', 'XF.QuickEditClick');
XF.Event.register('click', 'quote', 'XF.QuoteClick');
XF.Event.register('click', 'solution-edit', 'XF.SolutionEditClick');
XF.Element.register('multi-quote', 'XF.MultiQuote');
XF.Element.register('select-to-quote', 'XF.SelectToQuote');
XF.Element.register('quick-reply', 'XF.QuickReply');
XF.Element.register('guest-captcha', 'XF.GuestCaptcha');
XF.Element.register('post-edit', 'XF.PostEdit');
}
(jQuery, window, document);