MaxLength: 140,
PatternUsername: /^[0-9a-zA-Z\-_.]*$/,
HTTP20x30x: [200, 201, 202, 203, 204, 205, 206, 300, 301, 302, 303, 304, 305, 306, 307],
- NoticeFormMaster: null // to be cloned from the one at top
},
/**
}
},
+ V: { // Variables
+ },
+
+ /**
+ * list of callbacks, categorized into _callbacks['event_name'] = [ callback_function_1, callback_function_2 ]
+ *
+ * @access private
+ */
+ _callbacks: {},
+
/**
* Map of localized message strings exported to script from the PHP
* side via Action::getScriptMessages().
* @access private
*/
FormNoticeEnhancements: function (form) {
- if (jQuery.data(form[0], 'ElementData') === undefined) {
+ if ($.data(form[0], 'ElementData') === undefined) {
var MaxLength = form.find('.count').text();
if (MaxLength === undefined) {
MaxLength = SN.C.I.MaxLength;
}
- jQuery.data(form[0], 'ElementData', {MaxLength: MaxLength});
+ $.data(form[0], 'ElementData', {MaxLength: MaxLength});
SN.U.Counter(form);
NDT.on('cut', delayedUpdate)
.on('paste', delayedUpdate);
} else {
- form.find('.count').text(jQuery.data(form[0], 'ElementData').MaxLength);
+ form.find('.count').text($.data(form[0], 'ElementData').MaxLength);
}
},
Counter: function (form) {
SN.C.I.FormNoticeCurrent = form;
- var MaxLength = jQuery.data(form[0], 'ElementData').MaxLength;
+ var MaxLength = $.data(form[0], 'ElementData').MaxLength;
if (MaxLength <= 0) {
return;
return url;
},
+ FormNoticeUniqueID: function (form) {
+ var oldId = form.attr('id');
+ var newId = 'form_notice_' + Math.floor(Math.random()*999999999);
+ var attrs = ['name', 'for', 'id'];
+ for (var key in attrs) {
+ if (form.attr(attrs[key]) === undefined) {
+ continue;
+ }
+ form.attr(attrs[key], form.attr(attrs[key]).replace(oldId, newId));
+ }
+ for (var key in attrs) {
+ form.find("[" + attrs[key] + "*='" + oldId + "']").each(function () {
+ if ($(this).attr(attrs[key]) === undefined) {
+ return; // since we're inside the each(function () { ... });
+ }
+ var newAttr = $(this).attr(attrs[key]).replace(oldId, newId);
+ $(this).attr(attrs[key], newAttr);
+ });
+ }
+ },
+
/**
* Grabs form data and submits it asynchronously, with 'ajax=1'
* parameter added to the rest.
// Make sure we don't have a mixed HTTP/HTTPS submission...
form.attr('action', SN.U.RewriteAjaxAction(form.attr('action')));
- /**
- * Show a response feedback bit under the new-notice dialog.
- *
- * @param {String} cls: CSS class name to use ('error' or 'success')
- * @param {String} text
- * @access private
- */
- var showFeedback = function (cls, text) {
- form.append(
- $('<p class="form_response"></p>')
- .addClass(cls)
- .text(text)
- );
- };
-
/**
* Hide the previous response feedback, if any.
*/
removeFeedback();
if (textStatus == 'timeout') {
// @fixme i18n
- showFeedback('error', 'Sorry! We had trouble sending your notice. The servers are overloaded. Please try again, and contact the site administrator if this problem persists.');
+ SN.U.showFeedback(form, 'error', 'Sorry! We had trouble sending your notice. The servers are overloaded. Please try again, and contact the site administrator if this problem persists.');
} else {
var response = SN.U.GetResponseXML(xhr);
if ($('.' + SN.C.S.Error, response).length > 0) {
form.append(document._importNode($('.' + SN.C.S.Error, response)[0], true));
} else {
- if (parseInt(xhr.status) === 0 || jQuery.inArray(parseInt(xhr.status), SN.C.I.HTTP20x30x) >= 0) {
+ if (parseInt(xhr.status) === 0 || $.inArray(parseInt(xhr.status), SN.C.I.HTTP20x30x) >= 0) {
form
.resetForm()
.find('.attach-status').remove();
SN.U.FormNoticeEnhancements(form);
} else {
// @fixme i18n
- showFeedback('error', '(Sorry! We had trouble sending your notice (' + xhr.status + ' ' + xhr.statusText + '). Please report the problem to the site administrator if this happens again.');
+ SN.U.showFeedback(form, 'error', '(Sorry! We had trouble sending your notice (' + xhr.status + ' ' + xhr.statusText + '). Please report the problem to the site administrator if this happens again.');
}
}
}
removeFeedback();
var errorResult = $('#' + SN.C.S.Error, data);
if (errorResult.length > 0) {
- showFeedback('error', errorResult.text());
+ SN.U.showFeedback(form, 'error', errorResult.text());
} else {
- var commandResult = $('#' + SN.C.S.CommandResult, data);
- if (commandResult.length > 0) {
- showFeedback('success', commandResult.text());
- } else {
- // New notice post was successful. If on our timeline, show it!
- var notice = document._importNode($('li', data)[0], true);
- var notices = $('#notices_primary .notices:first');
- var replyItem = form.closest('li.notice-reply');
-
- if (replyItem.length > 0) {
- // If this is an inline reply, remove the form...
- var list = form.closest('.threaded-replies');
- var placeholder = list.find('.notice-reply-placeholder');
- replyItem.remove();
-
- var id = $(notice).attr('id');
- if ($('#' + id).length == 0) {
- $(notice).insertBefore(placeholder);
- } // else Realtime came through before us...
-
- // ...and show the placeholder form.
- placeholder.show();
- } else if (notices.length > 0 && SN.U.belongsOnTimeline(notice)) {
- // Not a reply. If on our timeline, show it at the top!
-
- if ($('#' + notice.id).length === 0) {
- var notice_irt_value = form.find('[name=inreplyto]').val();
- var notice_irt = '#notices_primary #notice-' + notice_irt_value;
- if ($('body')[0].id == 'conversation') {
- if (notice_irt_value.length > 0 && $(notice_irt + ' .notices').length < 1) {
- $(notice_irt).append('<ul class="notices"></ul>');
- }
- $($(notice_irt + ' .notices')[0]).append(notice);
- } else {
- notices.prepend(notice);
- }
- $('#' + notice.id)
- .css({display: 'none'})
- .fadeIn(2500);
- SN.U.NoticeWithAttachment($('#' + notice.id));
- SN.U.switchInputFormTab(null);
- }
- } else {
- // Not on a timeline that this belongs on?
- // Just show a success message.
- // @fixme inline
- showFeedback('success', $('title', data).text());
- }
- }
- form.resetForm();
- form.find('[name=inreplyto]').val('');
- form.find('.attach-status').remove();
- SN.U.FormNoticeEnhancements(form);
+ SN.E.ajaxNoticePosted(form, data, textStatus);
}
},
complete: function (xhr, textStatus) {
}
},
+ /**
+ * Setup function -- DOES NOT trigger actions immediately.
+ *
+ * Sets up event handlers on all visible notice's option <a> elements
+ * with the "popup" class so they behave as expected with AJAX.
+ *
+ * (without javascript the link goes to a page that expects you to verify
+ * the action through a form)
+ *
+ * @access private
+ */
+ NoticeOptionsAjax: function () {
+ $(document).on('click', '.notice-options > a.popup', function (e) {
+ e.preventDefault();
+ var noticeEl = $(this).closest('.notice');
+ $.ajax({
+ url: $(this).attr('href'),
+ data: {ajax: 1},
+ success: function (data, textStatus, xhr) {
+ SN.U.NoticeOptionPopup(data, noticeEl);
+ },
+ });
+ return false;
+ });
+ },
+
+ NoticeOptionPopup: function (data, noticeEl) {
+ title = $('head > title', data).text();
+ body = $('body', data).html();
+ dialog = $(body).dialog({
+ height: "auto",
+ width: "auto",
+ modal: true,
+ resizable: true,
+ title: title,
+ });
+ },
+
/**
* Setup function -- DOES NOT trigger actions immediately.
*
NoticeInlineReplyTrigger: function (notice, initialText) {
// Find the notice we're replying to...
var id = $($('.notice_id', notice)[0]).text();
- var replyForm, placeholder;
+ var replyForm;
var parentNotice = notice;
var stripForm = true; // strip a couple things out of reply forms that are inline
- // Find the threaded replies view we'll be adding to...
- var list = notice.closest('.notices');
- if (list.closest('.old-school').length) {
- // We're replying to an old-school conversation thread;
- // use the old-style ping into the top form.
- SN.U.switchInputFormTab("status");
- replyForm = $('#input_form_status').find('form');
- stripForm = false;
- } else if (list.hasClass('threaded-replies')) {
- // We're replying to a reply; use reply form on the end of this list.
- // We'll add our form at the end of this; grab the root notice.
- parentNotice = list.closest('.notice');
-
- // See if the form's already open...
- replyForm = $('.notice-reply-form', list);
- } else {
- // We're replying to a parent notice; pull its threaded list
- // and we'll add on the end of it. Will add if needed.
- list = $('ul.threaded-replies', notice);
- if (list.length == 0) {
- SN.U.NoticeInlineReplyPlaceholder(notice);
- list = $('ul.threaded-replies', notice);
- } else {
- placeholder = $('li.notice-reply-placeholder', notice);
- if (placeholder.length == 0) {
- SN.U.NoticeInlineReplyPlaceholder(notice);
- }
- }
-
- // See if the form's already open...
- replyForm = $('.notice-reply-form', list);
+ var list = notice.find('.threaded-replies');
+ if (list.length == 0) {
+ list = notice.closest('.threaded-replies');
+ }
+ if (list.length == 0) {
+ list = $('<ul class="notices threaded-replies xoxo"></ul>');
+ notice.append(list);
+ list = notice.find('.threaded-replies');
}
var nextStep = function () {
replyForm.find('label[for=notice_to]').hide();
replyForm.find('label[for=notice_private]').hide();
}
+ replyItem.show();
// Set focus...
var text = replyForm.find('textarea');
text[0].setSelectionRange(len, len);
}
};
- if (replyForm.length > 0) {
- // Update the existing form...
- nextStep();
- } else {
- // Hide the placeholder...
- placeholder = list.find('li.notice-reply-placeholder').hide();
-
- // Create the reply form entry at the end
- var replyItem = $('li.notice-reply', list);
- if (replyItem.length == 0) {
- replyItem = $('<li class="notice-reply"></li>');
- var intermediateStep = function (formMaster) {
- var formEl = document._importNode(formMaster, true);
- replyItem.append(formEl);
- list.append(replyItem); // *after* the placeholder
-
- var form = $(formEl);
- replyForm = form;
- SN.Init.NoticeFormSetup(form);
-
- nextStep();
- };
- if (SN.C.I.NoticeFormMaster) {
- // We've already saved a master copy of the form.
- // Clone it in!
- intermediateStep(SN.C.I.NoticeFormMaster);
- } else {
- // Fetch a fresh copy of the notice form over AJAX.
- // Warning: this can have a delay, which looks bad.
- // @fixme this fallback may or may not work
- var url = $('#form_notice').attr('action');
- $.get(url, {ajax: 1}, function (data, textStatus, xhr) {
- intermediateStep($('form', data)[0]);
- });
- }
- }
+ // Create the reply form entry
+ var replyItem = $('li.notice-reply', list);
+ if (replyItem.length == 0) {
+ replyItem = $('<li class="notice-reply"></li>');
}
- },
-
- NoticeInlineReplyPlaceholder: function (notice) {
- var list = notice.find('ul.threaded-replies');
- if (list.length == 0) {
- list = $('<ul class="notices threaded-replies xoxo"></ul>');
- notice.append(list);
- list = notice.find('ul.threaded-replies');
+ replyForm = replyItem.children('form');
+ if (replyForm.length == 0) {
+ // Let's try another trick to avoid fetching by URL
+ var noticeForm = $('#input_form_status > form');
+ if (noticeForm.length == 0) {
+ // No notice form found on the page, so let's just
+ // fetch a fresh copy of the notice form over AJAX.
+ $.ajax({
+ url: SN.V.urlNewNotice,
+ data: {ajax: 1, inreplyto: id},
+ success: function (data, textStatus, xhr) {
+ var formEl = document._importNode($('form', data)[0], true);
+ replyForm = $(formEl);
+ replyItem.append(replyForm);
+ list.append(replyItem);
+
+ SN.Init.NoticeFormSetup(replyForm);
+ nextStep();
+ },
+ });
+ // We do everything relevant in 'success' above
+ return;
+ }
+ replyForm = noticeForm.clone();
+ SN.Init.NoticeFormSetup(replyForm);
+ replyItem.append(replyForm);
+ list.append(replyItem);
}
- var placeholder = $('<li class="notice-reply-placeholder">' +
- '<input class="placeholder" />' +
- '</li>');
- placeholder.find('input')
- .val(SN.msg('reply_placeholder'));
- list.append(placeholder);
+ // replyForm is set, we're not fetching by URL...
+ // Next setp is to configure in-reply-to etc.
+ nextStep();
},
/**
* Setup function -- DOES NOT apply immediately.
*
- * Sets up event handlers for inline reply mini-form placeholders.
* Uses 'on' rather than 'live' or 'bind', so applies to future as well as present items.
*/
NoticeInlineReplySetup: function () {
- $('li.notice-reply-placeholder input')
- .on('focus', function () {
- var notice = $(this).closest('li.notice');
- SN.U.NoticeInlineReplyTrigger(notice);
- return false;
- });
+ // Expand conversation links
$(document).on('click', 'li.notice-reply-comments a', function () {
var url = $(this).attr('href');
var area = $(this).closest('.threaded-replies');
- $.get(url, {ajax: 1}, function (data, textStatus, xhr) {
- var replies = $('.threaded-replies', data);
- if (replies.length) {
- area.replaceWith(document._importNode(replies[0], true));
- }
+ $.ajax({
+ url: url,
+ data: {ajax: 1},
+ success: function (data, textStatus, xhr) {
+ var replies = $('.threaded-replies', data);
+ if (replies.length) {
+ area.replaceWith(document._importNode(replies[0], true));
+ }
+ },
});
return false;
});
form
.addClass('dialogbox')
- .append('<button class="close">×</button>')
+ .append('<button class="close" title="' + SN.msg('popup_close_button') + '">×</button>')
.closest('.notice-options')
.addClass('opaque');
function removeNoticeDataGeo(error) {
label
- .attr('title', jQuery.trim(label.text()))
+ .attr('title', $.trim(label.text()))
.removeClass('checked');
form.find('[name=lat]').val('');
label.attr('title', label.text());
check.change(function () {
- if (check.prop('checked') === true || $.cookie(SN.C.S.NoticeDataGeoCookie) === null) {
+ if (check.prop('checked') === true || $.cookie(SN.C.S.NoticeDataGeoCookie) === undefined) {
label
.attr('title', NoticeDataGeo_text.ShareDisable)
.addClass('checked');
- if ($.cookie(SN.C.S.NoticeDataGeoCookie) === null || $.cookie(SN.C.S.NoticeDataGeoCookie) == 'disabled') {
+ if ($.cookie(SN.C.S.NoticeDataGeoCookie) === undefined || $.cookie(SN.C.S.NoticeDataGeoCookie) == 'disabled') {
if (navigator.geolocation) {
SN.U.NoticeGeoStatus(form, 'Requesting location from browser...');
navigator.geolocation.getCurrentPosition(
* @fixme what is this?
*/
Delete: function () {
- $.cookie(SN.C.S.StatusNetInstance, null);
+ $.removeCookie(SN.C.S.StatusNetInstance);
}
},
*
* @param {String} tag
*/
- switchInputFormTab: function (tag, setFocus=true) {
+ switchInputFormTab: function (tag, setFocus) {
+ if (typeof setFocus === 'undefined') { setFocus = true; }
// The one that's current isn't current anymore
$('.input_form_nav_tab.current').removeClass('current');
if (tag != null) {
var extended = $(selector);
extended.removeClass('extended_menu');
return void(0);
+ },
+
+ /**
+ * Show a response feedback bit under a form.
+ *
+ * @param {Element} form: the new-notice form usually
+ * @param {String} cls: CSS class name to use ('error' or 'success')
+ * @param {String} text
+ * @access public
+ */
+ showFeedback: function (form, cls, text) {
+ form.append(
+ $('<p class="form_response"></p>')
+ .addClass(cls)
+ .text(text)
+ );
+ },
+
+ addCallback: function (ename, callback) {
+ // initialize to array if it's undefined
+ if (typeof SN._callbacks[ename] === 'undefined') {
+ SN._callbacks[ename] = [];
+ }
+ SN._callbacks[ename].push(callback);
+ },
+
+ runCallbacks: function (ename, data) {
+ if (typeof SN._callbacks[ename] === 'undefined') {
+ return;
+ }
+ for (cbname in SN._callbacks[ename]) {
+ SN._callbacks[ename][cbname](data);
+ }
}
},
+ E: { /* Events */
+ /* SN.E.ajaxNoticePosted, called when a notice has been posted successfully via an AJAX form
+ @param form the originating form element
+ @param data data from success() callback
+ @param textStatus textStatus from success() callback
+ */
+ ajaxNoticePosted: function (form, data, textStatus) {
+ var commandResult = $('#' + SN.C.S.CommandResult, data);
+ if (commandResult.length > 0) {
+ SN.U.showFeedback(form, 'success', commandResult.text());
+ } else {
+ // New notice post was successful. If on our timeline, show it!
+ var notice = document._importNode($('li', data)[0], true);
+ var notices = $('#notices_primary .notices:first');
+ var replyItem = form.closest('li.notice-reply');
+
+ if (replyItem.length > 0) {
+ // If this is an inline reply, remove the form...
+ var list = form.closest('.threaded-replies');
+
+ var id = $(notice).attr('id');
+ if ($('#' + id).length == 0) {
+ $(notice).insertBefore(replyItem);
+ } // else Realtime came through before us...
+
+ replyItem.remove();
+
+ } else if (notices.length > 0 && SN.U.belongsOnTimeline(notice)) {
+ // Not a reply. If on our timeline, show it at the top!
+
+ if ($('#' + notice.id).length === 0) {
+ var notice_irt_value = form.find('[name=inreplyto]').val();
+ var notice_irt = '#notices_primary #notice-' + notice_irt_value;
+ if ($('body')[0].id == 'conversation') {
+ if (notice_irt_value.length > 0 && $(notice_irt + ' .notices').length < 1) {
+ $(notice_irt).append('<ul class="notices"></ul>');
+ }
+ $($(notice_irt + ' .notices')[0]).append(notice);
+ } else {
+ notices.prepend(notice);
+ }
+ $('#' + notice.id)
+ .css({display: 'none'})
+ .fadeIn(2500);
+ SN.U.NoticeWithAttachment($('#' + notice.id));
+ SN.U.switchInputFormTab(null);
+ }
+ } else {
+ // Not on a timeline that this belongs on?
+ // Just show a success message.
+ // @fixme inline
+ SN.U.showFeedback(form, 'success', $('title', data).text());
+ }
+ }
+ form.resetForm();
+ form.find('[name=inreplyto]').val('');
+ form.find('.attach-status').remove();
+ SN.U.FormNoticeEnhancements(form);
+
+ SN.U.runCallbacks('notice_posted', {"notice": notice});
+ },
+ },
+
+
Init: {
/**
* If user is logged in, run setup code for the new notice form:
// Only close if there's been no edit.
if (cur == '' || cur == textarea.data('initialText')) {
var parentNotice = replyItem.closest('li.notice');
- replyItem.remove();
+ replyItem.hide();
parentNotice.find('li.notice-reply-placeholder').show();
}
}
});
}
});
-
- // Infield labels for notice form inputs.
- $('.input_forms fieldset fieldset label').inFieldLabels({ fadeOpacity:0 });
}
},
* @param {jQuery} form
*/
NoticeFormSetup: function (form) {
- if (!form.data('NoticeFormSetup')) {
- SN.U.NoticeLocationAttach(form);
- SN.U.FormNoticeXHR(form);
- SN.U.FormNoticeEnhancements(form);
- SN.U.NoticeDataAttach(form);
- form.data('NoticeFormSetup', true);
+ if (form.data('NoticeFormSetup')) {
+ return false;
}
+ SN.U.NoticeLocationAttach(form);
+ SN.U.FormNoticeUniqueID(form);
+ SN.U.FormNoticeXHR(form);
+ SN.U.FormNoticeEnhancements(form);
+ SN.U.NoticeDataAttach(form);
+ form.data('NoticeFormSetup', true);
},
/**
*/
Notices: function () {
if ($('body.user_in').length > 0) {
- var masterForm = $('.form_notice:first');
- if (masterForm.length > 0) {
- SN.C.I.NoticeFormMaster = document._importNode(masterForm[0], true);
- }
SN.U.NoticeRepeat();
SN.U.NoticeReply();
SN.U.NoticeInlineReplySetup();
+ SN.U.NoticeOptionsAjax();
}
SN.U.NoticeAttachments();
});
},
- /**
- * Called when a people tag edit box is shown in the interface
- *
- * - loads the jQuery UI autocomplete plugin
- * - sets event handlers for tag completion
- *
- */
- PeopletagAutocomplete: function (txtBox) {
- var split = function (val) {
- return val.split( /\s+/ );
- }
- var extractLast = function (term) {
- return split(term).pop();
- }
-
- // don't navigate away from the field on tab when selecting an item
- txtBox.on( "keydown", function ( event ) {
- if ( event.keyCode === $.ui.keyCode.TAB &&
- $(this).data( "autocomplete" ).menu.active ) {
- event.preventDefault();
- }
- }).autocomplete({
- minLength: 0,
- source: function (request, response) {
- // delegate back to autocomplete, but extract the last term
- response($.ui.autocomplete.filter(
- SN.C.PtagACData, extractLast(request.term)));
- },
- focus: function () {
- return false;
- },
- select: function (event, ui) {
- var terms = split(this.value);
- terms.pop();
- terms.push(ui.item.value);
- terms.push("");
- this.value = terms.join(" ");
- return false;
- }
- }).data('autocomplete')._renderItem = function (ul, item) {
- // FIXME: with jQuery UI you cannot have it highlight the match
- var _l = '<a class="ptag-ac-line-tag">' + item.tag
- + ' <em class="privacy_mode">' + item.mode + '</em>'
- + '<span class="freq">' + item.freq + '</span></a>'
-
- return $("<li/>")
- .addClass('mode-' + item.mode)
- .addClass('ptag-ac-line')
- .data("item.autocomplete", item)
- .append(_l)
- .appendTo(ul);
- }
- },
-
/**
* Run setup for the ajax people tags editor
*
}
SN.C.PtagACData = data;
- SN.Init.PeopletagAutocomplete(form.find('#tags'));
}
});