You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3005 lines
105 KiB
3005 lines
105 KiB
// interface_common.js |
|
// 2013 Lucas Leal, Miguel Freitas |
|
// |
|
// Common interface functions to all pages, modal manipulation, button manipulation etc |
|
// Profile, mentions and hashtag modal |
|
// Post actions: submit, count characters |
|
|
|
var twister = { |
|
URIs: {}, // shortened URIs are cached here after fetching |
|
torrentIds: {}, // auto-download torrentIds |
|
focus: {}, // focused elements are counted here |
|
html: { |
|
detached: $('<div>'), // here elements go to detach themself |
|
blanka: $('<a rel="nofollow noreferrer" target="_blank">') // to open stuff in new tab, see routeOnClick() |
|
}, |
|
tmpl: { // templates pointers are stored here |
|
root: $('<div>') // templates should be detached from DOM and attached here; use extractTemplate() |
|
}, |
|
modal: {}, |
|
res: {}, // reses for various reqs are cached here |
|
var: { |
|
dateFormatter: { |
|
format: function (req) { |
|
return req.toString().replace(/GMT.*/g, ''); |
|
} |
|
}, |
|
localAccounts: [], |
|
updatesCheckClient: {} |
|
} |
|
}; |
|
var window_scrollY = 0; |
|
var _watchHashChangeRelaxDontDoIt = window.location.hash === '' ? true : false; |
|
|
|
// FIXME so looks like it's wrapper over $; it's here to select and manipulate detached elements too |
|
// and actually I'm talking about 'so called \'detached\'' elements which appended to twister.html.detached |
|
// we may just append twister.html.detached to document instead and remove this weird shit (or I need to |
|
// improve my google skills to find native jQuery way to dig through all detached elemets with one query) |
|
function getElem(req, searchInTmpl) { |
|
var elem = $(req); |
|
var h = twister.html.detached.find(req); |
|
|
|
for (var i = 0; i < h.length; i++) |
|
elem[elem.length++] = h[i]; |
|
|
|
if (searchInTmpl) { |
|
h = twister.tmpl.root.find(req); |
|
for (var i = 0; i < h.length; i++) |
|
elem[elem.length++] = h[i]; |
|
} |
|
|
|
return elem; |
|
} |
|
|
|
function openModal(modal) { |
|
if (!modal.classBase) { |
|
modal.classBase = '.modal-wrapper'; |
|
|
|
window_scrollY = window.pageYOffset; |
|
$('body').css('overflow', 'hidden'); |
|
} |
|
|
|
if (modal.classBase !== '.prompt-wrapper') |
|
closeModal($(modal.classBase + ':not(#templates *)'), true); |
|
|
|
modal.self = $('#templates ' + modal.classBase).clone(true) |
|
.addClass(modal.classAdd); |
|
|
|
if (modal.removeBlackout) |
|
modal.self.find('.modal-blackout').remove(); |
|
|
|
if (modal.title) |
|
modal.self.find('.modal-header h3').html(modal.title); |
|
if (modal.content) |
|
modal.content = modal.self.find('.modal-content') |
|
.append(modal.content); |
|
else |
|
modal.content = modal.self.find('.modal-content'); |
|
|
|
if (modal.warn && modal.warn.name && modal.warn.text) { |
|
var elem = twister.tmpl.modalComponentWarn.clone(true) |
|
.attr('data-warn-name', modal.warn.name) |
|
.toggle(!$.Options.get('skipWarn' + modal.warn.name)) |
|
; |
|
fillElemWithTxt(elem.find('.text'), modal.warn.text, {markout: 'apply'}); |
|
elem.find('.options .never-again + span').text(polyglot.t('do_not_show_it_again')); |
|
elem.insertBefore(modal.content); |
|
} |
|
|
|
modal.self.appendTo('body').fadeIn('fast'); // FIXME maybe it's better to append it to some container inside body |
|
|
|
if (modal.classBase === '.modal-wrapper') { |
|
twister.modal[window.location.hash] = modal; |
|
modal.self.attr('data-modal-id', window.location.hash); |
|
|
|
modal.drapper = $('<div>').appendTo(twister.html.detached); // here modal goes instead detaching |
|
|
|
modal.content.outerHeight(modal.self.height() - modal.self.find('.modal-header').outerHeight() |
|
- modal.self.find('.inline-warn').outerHeight() |
|
* (modal.warn && !$.Options.get('skipWarn' + modal.warn.name) ? 1 : 0)); |
|
|
|
var windowHeight = $(window).height(); |
|
if (modal.self.outerHeight() > windowHeight) { |
|
modal.content.outerHeight(modal.content.outerHeight() - modal.self.outerHeight() + windowHeight); |
|
modal.self.outerHeight(windowHeight); |
|
modal.self.css('margin-top', - windowHeight / 2); |
|
} |
|
} |
|
|
|
return modal; |
|
} |
|
|
|
function closeModal(req, switchMode) { |
|
if (typeof req === 'undefined') |
|
var elem = $('.modal-wrapper:not(#templates *)'); // select active modal(s) |
|
else if (req.jquery) |
|
var elem = req; |
|
else if (req.target) |
|
var elem = getElem(req.target); // getElem() to search in minimized too |
|
else if (typeof req === 'string' || req.outerHTML) |
|
var elem = getElem(req); |
|
|
|
if (!elem || !elem.length) |
|
return; |
|
|
|
// we close all modals which are containing element(s) |
|
elem.closest('.modal-wrapper:not(.closed)').addClass('closed') |
|
.fadeOut(switchMode ? 10 : 'fast', function () { |
|
var i = this.getAttribute('data-modal-id'); |
|
|
|
if (twister.modal[i].minimized) |
|
twister.modal[i].btnResume.fadeOut('fast', function () {this.remove();}); |
|
else |
|
this.remove(); // if it's minimized it will be removed with twister.modal[i].drapper |
|
|
|
if (typeof twister.modal[i].onClose === 'function') |
|
twister.modal[i].onClose(twister.modal[i].onCloseReq); |
|
|
|
twister.modal[i].drapper.remove(); |
|
twister.modal[i] = undefined; |
|
} |
|
); |
|
|
|
if (!switchMode) { |
|
if (window.location.hash !== '') { |
|
_watchHashChangeRelaxDontDoIt = true; |
|
window.location.hash = '#'; |
|
} |
|
window.scroll(window.pageXOffset, window_scrollY); |
|
$('body').css({ |
|
'overflow': 'auto', |
|
'margin-right': '0' |
|
}); |
|
} |
|
} |
|
|
|
function closePrompt(req) { |
|
if (typeof req === 'undefined') |
|
var elem = $('.prompt-wrapper:not(#templates *)'); |
|
else if (req.jquery) |
|
var elem = req; |
|
else if (req.target) |
|
var elem = $(req.target); |
|
else if (typeof req === 'string' || req.outerHTML) |
|
var elem = $(req); |
|
|
|
if (!elem || !elem.length) |
|
return; |
|
|
|
if (typeof req.stopPropagation === 'function') { |
|
req.preventDefault(); |
|
req.stopPropagation(); |
|
req = req.data; |
|
} |
|
|
|
// we close all prompts which are containing element(s) |
|
elem.closest('.prompt-wrapper:not(.closed)').addClass('closed') |
|
.fadeOut('fast', function() {this.remove();}); |
|
|
|
if (req && typeof req.cbFunc === 'function') // FIXME maybe bind to ^ prompt fadeout function |
|
req.cbFunc(req.cbReq); |
|
} |
|
|
|
function minimizeModal(modal, switchMode) { |
|
|
|
function minimize(modal, scroll) { |
|
var i = modal.attr('data-modal-id'); |
|
|
|
modal.appendTo(twister.modal[i].drapper); |
|
|
|
twister.modal[i].minimized = true; |
|
twister.modal[i].scroll = scroll; |
|
twister.modal[i].btnResume = $('<li>' + modal.find('.modal-header h3').text() + '</li>') |
|
.on('click', {hashString: window.location.hash}, function (event) { |
|
if (!event.button) // only if left mouse (button is 0) or elem.click() (button is undefined) |
|
resumeModal(event); |
|
}) |
|
.on('mouseup', {route: window.location.hash, blankOnly: true}, routeOnClick) |
|
.appendTo($('#modals-minimized')) |
|
; |
|
} |
|
|
|
if (modal.is('.closed')) return; |
|
|
|
var scroll; // MUST be setted before modal.detach(), modal.fadeOut() and so on |
|
if (modal.is('.profile-modal')) { |
|
if (modal.find('.profile-card').attr('data-screen-name')[0] === '*') |
|
scroll = { |
|
targetSelector: '.modal-content .members', |
|
top: modal.find('.modal-content .members').scrollTop() |
|
}; |
|
else |
|
scroll = { |
|
targetSelector: '.modal-content .postboard-posts', |
|
top: modal.find('.modal-content .postboard-posts').scrollTop() |
|
}; |
|
} else |
|
scroll = { |
|
targetSelector: '.modal-content', |
|
top: modal.find('.modal-content').scrollTop() |
|
}; |
|
|
|
if (switchMode) |
|
minimize(modal, scroll); |
|
else |
|
modal.fadeOut('fast', function () { |
|
minimize(modal, scroll); |
|
|
|
_watchHashChangeRelaxDontDoIt = true; |
|
window.location.hash = '#'; |
|
window.scroll(window.pageXOffset, window_scrollY); |
|
$('body').css({ |
|
'overflow': 'auto', |
|
'margin-right': '0' |
|
}); |
|
}); |
|
} |
|
|
|
function resumeModal(event) { |
|
$(event.target).fadeOut('fast', function () {this.remove();}); |
|
|
|
var modalActive = $('.modal-wrapper:not(#templates *)').not('.closed'); |
|
if (modalActive.length) |
|
minimizeModal(modalActive, true); |
|
else { |
|
window_scrollY = window.pageYOffset; |
|
$('body').css('overflow', 'hidden'); |
|
} |
|
|
|
var modal = twister.modal[event.data.hashString]; |
|
if (modal.self.not('.closed') && modal.minimized) { |
|
modal.minimized = false; |
|
modal.btnResume = undefined; |
|
if (window.location.hash !== event.data.hashString) { |
|
_watchHashChangeRelaxDontDoIt = true; |
|
window.location.hash = event.data.hashString; |
|
} |
|
modal.self.prependTo('body').fadeIn('fast', function () { |
|
// TODO also need reset modal height here maybe and then compute new scroll |
|
if (modal.scroll) |
|
modal.self.find($(modal.scroll.targetSelector).scrollTop(modal.scroll.top)); |
|
|
|
if (typeof modal.onResume === 'function') |
|
modal.onResume(modal.onResumeReq); |
|
}); |
|
} |
|
} |
|
|
|
function focusModalWithElement(elem, cbFunc, cbReq) { |
|
if (elem.jquery ? elem.is('html *') : $(elem).is('html *')) { |
|
if (typeof cbFunc === 'function') |
|
cbFunc(cbReq); |
|
return true; |
|
} |
|
|
|
var i = getHashOfMinimizedModalWithElem(elem); |
|
if (i) { |
|
if (typeof i === 'object') i = i[0]; // several modals, but only one may be active currently |
|
twister.modal[i].onResume = cbFunc; |
|
twister.modal[i].onResumeReq = cbReq; |
|
twister.modal[i].btnResume.click(); |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
function getHashOfMinimizedModalWithElem(req) { |
|
var hashes = []; |
|
|
|
for (var i in twister.modal) |
|
if (twister.modal[i] && twister.modal[i].minimized && twister.modal[i].drapper.find(req).length) |
|
hashes[hashes.length++] = i; |
|
|
|
return hashes.length > 1 ? hashes : hashes[0]; |
|
} |
|
|
|
function isModalWithElemExists(elem) { |
|
if (elem.jquery ? elem.is('html *') : $(elem).is('html *')) |
|
return true; |
|
else |
|
return getHashOfMinimizedModalWithElem(elem) ? true : false; |
|
} |
|
|
|
function confirmPopup(req) { |
|
if (!req) return; |
|
|
|
if (typeof req.stopPropagation === 'function') { |
|
req.preventDefault(); |
|
req.stopPropagation(); |
|
if (req.data) |
|
req = req.data; |
|
else |
|
return; |
|
} |
|
|
|
var modal = openModal({ |
|
classBase: '.prompt-wrapper', |
|
classAdd: req.classAdd ? 'confirm-popup ' + req.classAdd : 'confirm-popup', |
|
content: $('#confirm-popup-template').children().clone(true), |
|
removeBlackout: !req.addBlackout, |
|
title: req.txtTitle |
|
}); |
|
|
|
if (req.txtMessage) { |
|
if (req.txtMessage.polyglot) |
|
req.txtMessage = polyglot.t(req.txtMessage.polyglot, req.txtMessage.polyglotReq); |
|
fillElemWithTxt(modal.content.find('.message'), req.txtMessage, {markout: 'apply'}); |
|
} |
|
|
|
var btn = modal.content.find('.confirm'); |
|
if (req.removeConfirm) |
|
btn.remove(); |
|
else { |
|
if (req.txtConfirm) |
|
btn.text(req.txtConfirm); |
|
else |
|
btn.text(polyglot.t('Confirm')); |
|
|
|
if (req.cbConfirm) |
|
btn.on('click', {cbFunc: req.cbConfirm, cbReq: req.cbConfirmReq}, closePrompt); |
|
else |
|
btn.on('click', closePrompt); |
|
} |
|
var btn = modal.content.find('.cancel'); |
|
if (req.removeCancel) |
|
btn.remove(); |
|
else { |
|
if (req.txtCancel) |
|
btn.text(req.txtCancel); |
|
else |
|
btn.text(polyglot.t('Cancel')); |
|
|
|
if (req.cbCancel) |
|
btn.on('click', {cbFunc: req.cbCancel, cbReq: req.cbCancelReq}, closePrompt); |
|
else |
|
btn.on('click', closePrompt); |
|
} |
|
var btn = modal.self.find('.prompt-close'); |
|
if (req.removeClose) |
|
btn.remove(); |
|
else { |
|
if (req.cbClose) { |
|
if (typeof req.cbClose === 'string') |
|
if (req.cbClose === 'cbConfirm') { |
|
req.cbClose = req.cbConfirm; |
|
req.cbCloseReq = req.cbConfirmReq; |
|
} else if (req.cbClose === 'cbCancel') { |
|
req.cbClose = req.cbCancel; |
|
req.cbCloseReq = req.cbCancelReq; |
|
} |
|
|
|
btn.on('click', {cbFunc: req.cbClose, cbReq: req.cbCloseReq}, closePrompt); |
|
} |
|
} |
|
|
|
return modal; |
|
} |
|
|
|
function alertPopup(req) { |
|
if (!req) return; |
|
|
|
if (typeof req.stopPropagation === 'function') { |
|
if (typeof req.data !== 'object') return; |
|
if (!req.data.txtConfirm) |
|
req.data.txtConfirm = polyglot.t('btn_ok'); |
|
req.data.removeCancel = true; |
|
} else { |
|
if (!req.txtConfirm) |
|
req.txtConfirm = polyglot.t('btn_ok'); |
|
req.removeCancel = true; |
|
} |
|
|
|
return confirmPopup(req); |
|
} |
|
|
|
function checkNetworkStatusAndAskRedirect(cbFunc, cbReq) { |
|
networkUpdate(function(req) { |
|
if (!twisterdConnectedAndUptodate) { |
|
confirmPopup({ |
|
txtMessage: polyglot.t('confirm_switch_to_network', {page: '/network.html'}), |
|
cbConfirm: $.MAL.goNetwork |
|
}); |
|
} else { |
|
if (req.cbFunc) |
|
req.cbFunc(req.cbReq); |
|
} |
|
}, {cbFunc: cbFunc, cbReq: cbReq}); |
|
} |
|
|
|
function timeGmtToText(t) { |
|
if (t == 0) return '-'; |
|
var d = new Date(0); |
|
d.setUTCSeconds(t); |
|
return twister.var.dateFormatter.format(d); |
|
} |
|
|
|
function setupTimeGmtToText(lang) { |
|
if (typeof window.Intl !== 'object' || typeof window.Intl.DateTimeFormat !== 'function') |
|
return; |
|
|
|
twister.var.dateFormatter = new Intl.DateTimeFormat( |
|
knownLanguages.indexOf(lang) !== -1 ? lang : undefined, |
|
{ |
|
hour12: false, |
|
weekday: 'short', |
|
year: 'numeric', |
|
month: 'short', |
|
day: 'numeric', |
|
hour: 'numeric', |
|
minute: 'numeric', |
|
second: 'numeric' |
|
} |
|
); |
|
} |
|
|
|
function timeSincePost(t) { |
|
var d = new Date(0); |
|
d.setUTCSeconds(t); |
|
var now = new Date(); |
|
var t_delta = Math.ceil((now - d) / 1000); |
|
var expression; |
|
if (t_delta < 60) |
|
expression = polyglot.t('seconds', t_delta); |
|
else if (t_delta < 3600) // 60 * 60 |
|
expression = polyglot.t('minutes', Math.floor(t_delta / 60)); |
|
else if (t_delta < 86400) // 24 * 60 * 60 |
|
expression = polyglot.t('hours', Math.floor(t_delta / 3600)); // 60 * 60 |
|
else |
|
expression = polyglot.t('days', Math.floor(t_delta / 86400)); // 24 * 60 * 60 |
|
|
|
return polyglot.t('time_ago', {time: expression}); |
|
} |
|
|
|
function openModalLogin() { |
|
var modal = openModal({ |
|
classAdd: 'login-modal', |
|
content: twister.tmpl.loginMC.clone(true), |
|
title: polyglot.t('twister login') |
|
}); |
|
} |
|
|
|
function handleClickAccountLoginLogin(event) { |
|
loginToAccount($(event.target).closest('.module').find('select.local-usernames').val()); |
|
} |
|
|
|
function handleInputAccountCreateSetReq(event) { |
|
var container = $(event.target).closest('.module'); |
|
|
|
container.find('.availability').text(''); |
|
$.MAL.enableButton(container.find('.check')); |
|
$.MAL.disableButton(container.find('.create')); |
|
} |
|
|
|
function handleClickAccountCreateCheckReq(event) { |
|
var container = $(event.target).closest('.module'); |
|
var peerAliasElem = container.find('.alias'); |
|
var peerAlias = peerAliasElem.val().toLowerCase(); |
|
var availField = container.find('.availability'); |
|
|
|
peerAliasElem.val(peerAlias); |
|
$.MAL.disableButton(container.find('.check')); |
|
|
|
if (!peerAlias.length) |
|
return; |
|
if (peerAlias.length > 16) { |
|
availField.text(polyglot.t('Must be 16 characters or less.')); |
|
return; |
|
} |
|
|
|
// check for non-alphabetic characters and space |
|
if (peerAlias.search(/[^a-z0-9_]/) !== -1) { |
|
availField.text(polyglot.t('Only alphanumeric and underscore allowed.')); |
|
return; |
|
} |
|
|
|
availField.text(polyglot.t('Checking...')); |
|
|
|
dumpPubkey(peerAlias, |
|
function(req, ret) { |
|
if (ret) { |
|
req.container.find('.availability').text(polyglot.t('Not available')); |
|
} else { |
|
req.container.find('.availability').text(polyglot.t('Available')); |
|
$.MAL.enableButton(req.container.find('.create')); |
|
} |
|
}, {container: container} |
|
); |
|
} |
|
|
|
function handleClickAccountCreateCreate(event) { |
|
var container = $(event.target).closest('.module'); |
|
var peerAlias = container.find('.alias').val().toLowerCase(); |
|
|
|
if (twister.var.localAccounts.indexOf(peerAlias) < 0) { |
|
createAccount(peerAlias); |
|
} else { |
|
// user exists in wallet but transaction not sent |
|
dumpPrivkey(peerAlias, |
|
function (req, ret) { |
|
$.MAL.processCreateAccount(req.peerAlias, ret); |
|
}, {peerAlias: peerAlias} |
|
); |
|
} |
|
} |
|
|
|
function handleInputAccountImportSetReq(event) { |
|
var container = $(event.target).closest('.module'); |
|
|
|
if (container.find('.secret-key').val().length === 52 |
|
&& container.find('.alias').val().toLowerCase().length) |
|
$.MAL.enableButton(container.find('.import')); |
|
else |
|
$.MAL.disableButton(container.find('.import')); |
|
} |
|
|
|
function handleClickAccountImportImport(event) { |
|
var container = $(event.target).closest('.module'); |
|
|
|
importAccount(container.find('.alias').val().toLowerCase(), container.find('.secret-key').val()) |
|
} |
|
|
|
function openGroupProfileModalWithNameHandler(groupAlias) { |
|
var modal = openModal({ |
|
classAdd: 'profile-modal', |
|
content: $('#group-profile-modal-template').children().clone(true), |
|
title: polyglot.t('users_profile', {username: '<span>' + groupAlias + '</span>'}) |
|
}); |
|
|
|
modal.content.find('.profile-card').attr('data-screen-name', groupAlias); |
|
|
|
groupMsgGetGroupInfo(groupAlias, |
|
function(req, ret) { |
|
if (ret) { |
|
req.modal.content.find('.profile-bio .group-description') |
|
.val(ret.description) |
|
.attr('val-origin', ret.description) |
|
; |
|
|
|
if (ret.members.indexOf(defaultScreenName) !== -1) |
|
req.modal.content.find('.group-messages-control').children('button').attr('disabled', false); |
|
|
|
var membersList = req.modal.content.find('.members'); |
|
var memberTemplate = $('#group-profile-member-template').children(); |
|
for (var i = 0; i < ret.members.length; i++) { |
|
var item = memberTemplate.clone(true).appendTo(membersList); |
|
|
|
item.find('.twister-user-info').attr('data-screen-name', ret.members[i]); |
|
item.find('.twister-user-name').attr('href', $.MAL.userUrl(ret.members[i])); |
|
|
|
getAvatar(ret.members[i], item.find('.twister-user-photo')); |
|
getFullname(ret.members[i], item.find('.twister-user-full')); |
|
getBioToElem(ret.members[i], item.find('.bio')); |
|
} |
|
|
|
elemFitNextIntoParentHeight(req.modal.content.find('.profile-card')); |
|
} |
|
}, {modal: modal} |
|
); |
|
|
|
elemFitNextIntoParentHeight(modal.content.find('.profile-card')); |
|
} |
|
|
|
function openUserProfileModalWithNameHandler(peerAlias) { |
|
var content = $('#profile-modal-template').children().clone(true); |
|
|
|
updateProfileData(content, peerAlias); |
|
// FIXME following ctc could be part of updateProfileData() when mobile will be ready for this |
|
content.find('.tox-ctc').attr('title', polyglot.t('Copy to clipboard')); |
|
content.find('.bitmessage-ctc').attr('title', polyglot.t('Copy to clipboard')); |
|
|
|
content.find('.open-followers').on('mouseup', {route: '#followers?user=' + peerAlias}, routeOnClick); |
|
|
|
var modal = openModal({ |
|
classAdd: 'profile-modal', |
|
content: content, |
|
title: polyglot.t('users_profile', {username: peerAlias}) |
|
}); |
|
|
|
toggleFollowButton({ |
|
button: modal.content.find('.profile-card-buttons .follow'), |
|
peerAlias: peerAlias, |
|
toggleUnfollow: followingUsers.indexOf(peerAlias) !== -1 ? true : false |
|
}); |
|
|
|
elemFitNextIntoParentHeight(modal.content.find('.profile-card')); |
|
|
|
var postboard = modal.content.find('.postboard'); |
|
postboard.find('ol').outerHeight(postboard.actual('height') |
|
- postboard.find('h2').actual('outerHeight', {includeMargin: true})); |
|
} |
|
|
|
function openHashtagModalFromSearchHandler(hashtag) { |
|
var modal = openModal({ |
|
classAdd: 'hashtag-modal', |
|
content: $('#hashtag-modal-template').children().clone(true), |
|
title: '#' + hashtag |
|
}); |
|
|
|
var req = queryStart(modal.content.find('.postboard-posts'), hashtag, 'hashtag'); |
|
modal.content.find('.postboard-news').on('click', {req: req}, handleClickDisplayPendingTwists); |
|
} |
|
|
|
function handleClickDisplayPendingTwists(event) { |
|
if (!event || !event.data || !event.data.req) |
|
return; |
|
|
|
$(event.target).hide(); |
|
|
|
queryPendingDraw(event.data.req); |
|
|
|
if (typeof event.data.cbFunc === 'function') |
|
event.data.cbFunc(event.data.cbReq); |
|
} |
|
|
|
function openFavsModal(event) { |
|
if (event && typeof event.stopPropagation === 'function') { |
|
event.preventDefault(); |
|
event.stopPropagation(); |
|
} |
|
|
|
var userInfo = $(this).closest('[data-screen-name]'); |
|
var peerAlias = ''; |
|
if (userInfo.length) |
|
peerAlias = userInfo.attr('data-screen-name'); |
|
else if (defaultScreenName) |
|
peerAlias = defaultScreenName; |
|
else { |
|
alertPopup({ |
|
//txtTitle: polyglot.t(''), add some title (not 'error', please) or just KISS |
|
txtMessage: polyglot.t('No favs here because you are not logged in.') |
|
}); |
|
return; |
|
} |
|
|
|
window.location.hash = '#favs?user=' + peerAlias; |
|
} |
|
|
|
function openFavsModalHandler(peerAlias) { |
|
var modal = openModal({ |
|
classAdd: 'hashtag-modal', |
|
content: $('#hashtag-modal-template').children().clone(true), |
|
title: polyglot.t('users_favs', {username: peerAlias}) |
|
}); |
|
|
|
var req = queryStart(modal.content.find('.postboard-posts'), peerAlias, 'fav'); |
|
modal.content.find('.postboard-news').on('click', {req: req}, handleClickDisplayPendingTwists); |
|
} |
|
|
|
function openMentionsModal(event) { |
|
if (event && typeof event.stopPropagation === 'function') { |
|
event.preventDefault(); |
|
event.stopPropagation(); |
|
} |
|
|
|
var userInfo = $(this).closest('[data-screen-name]'); |
|
if (userInfo.length) |
|
var peerAlias = userInfo.attr('data-screen-name'); |
|
else if (defaultScreenName) |
|
var peerAlias = defaultScreenName; |
|
else { |
|
alertPopup({ |
|
//txtTitle: polyglot.t(''), add some title (not 'error', please) or just KISS |
|
txtMessage: polyglot.t('No one can mention you because you are not logged in.') |
|
}); |
|
return; |
|
} |
|
|
|
window.location.hash = '#mentions?user=' + peerAlias; |
|
} |
|
|
|
function openMentionsModalHandler(peerAlias) { |
|
var modal = openModal({ |
|
classAdd: 'hashtag-modal', |
|
content: $('#hashtag-modal-template').children().clone(true), |
|
title: polyglot.t('users_mentions', {username: peerAlias}) |
|
}); |
|
|
|
var req = queryStart(modal.content.find('.postboard-posts'), peerAlias, 'mention'); |
|
modal.content.find('.postboard-news').on('click', {req: req}, handleClickDisplayPendingTwists); |
|
|
|
if (peerAlias === defaultScreenName) |
|
modal.content.on('scroll', handleMentionsModalScroll); |
|
} |
|
|
|
function openFollowersModal(peerAlias) { |
|
var followers, title, warn; |
|
|
|
if (!peerAlias || peerAlias === defaultScreenName) { |
|
if (!defaultScreenName) { |
|
alertPopup({ |
|
//txtTitle: polyglot.t(''), add some title (not 'error', please) or just KISS |
|
txtMessage: polyglot.t('You don\'t have any followers because you are not logged in.') |
|
}); |
|
history.back(); |
|
return; |
|
} |
|
title = polyglot.t('Followers'); |
|
followers = twisterFollowingO.knownFollowers.slice(); |
|
warn = { |
|
name: 'FollowersNotAll', |
|
text: '* ' + polyglot.t('warn_followers_not_all') |
|
}; |
|
} else { |
|
title = polyglot.t('Followers_of', {alias: peerAlias}); |
|
followers = whoFollows(peerAlias); |
|
warn = { |
|
name: 'FollowersNotAllOf', |
|
text: polyglot.t('warn_followers_not_all_of', {alias: peerAlias}) |
|
}; |
|
} |
|
|
|
var modal = openModal({ |
|
classAdd: 'followers-modal', |
|
content: twister.tmpl.followersList.clone(true), |
|
title: title, |
|
warn: warn |
|
}); |
|
|
|
appendFollowersToElem(modal.content.find('ol'), followers); |
|
} |
|
|
|
function appendFollowersToElem(list, followers) { |
|
for (var i = 0; i < followers.length; i++) |
|
addPeerToFollowersList(list, followers[i]); |
|
|
|
$.MAL.listLoaded(list); |
|
} |
|
|
|
function addPeerToFollowersList(list, peerAlias, isCheckNeeded) { |
|
if (typeof list !== 'object' || !list.length) |
|
return; |
|
if (isCheckNeeded && list.find('li[data-peer-alias="' + peerAlias + '"]').length) |
|
return; |
|
|
|
var item = twister.tmpl.followersPeer.clone(true).attr('data-peer-alias', peerAlias); |
|
|
|
item.find('.alias').text('@' + peerAlias); |
|
item.find('.alias, .avatar, .name').on('mouseup', {route: $.MAL.userUrl(peerAlias)}, routeOnClick); |
|
getAvatar(peerAlias, item.find('.avatar img')); |
|
getFullname(peerAlias, item.find('.name')); |
|
getBioToElem(peerAlias, item.find('.bio')); |
|
|
|
item.prependTo(list); |
|
} |
|
|
|
function openFollowingModal(peerAlias) { |
|
if (!peerAlias || peerAlias === defaultScreenName) { |
|
if (!defaultScreenName) { |
|
alertPopup({ |
|
//txtTitle: polyglot.t(''), add some title (not 'error', please) or just KISS |
|
txtMessage: polyglot.t('You are not following anyone because you are not logged in.') |
|
}); |
|
history.back(); |
|
return; |
|
} |
|
|
|
var modal = openModal({ |
|
classAdd: 'following-own-modal', |
|
content: twister.tmpl.followingList.clone(true), |
|
title: polyglot.t('Following') |
|
}); |
|
appendFollowingToElem(modal.content.find('.following-list')); |
|
requestSwarmProgress(); |
|
} else { |
|
var modal = openModal({ |
|
classAdd: 'following-modal', |
|
content: $('#following-modal-template').children().clone(true), |
|
title: polyglot.t('followed_by', {username: peerAlias}) |
|
}); |
|
modal.content.find('.following-screen-name b').text(peerAlias); |
|
loadFollowingIntoList(peerAlias, modal.content.find('ol')); |
|
} |
|
} |
|
|
|
function appendFollowingToElem(list) { |
|
if (followingEmptyOrMyself()) |
|
$.MAL.warnFollowingNotAny(closeModal, list); |
|
else |
|
for (var i = 0; i < followingUsers.length; i++) |
|
addPeerToFollowingList(list, followingUsers[i]); |
|
|
|
$.MAL.listLoaded(list); |
|
} |
|
|
|
function addPeerToFollowingList(list, peerAlias) { |
|
var item = twister.tmpl.followingPeer.clone(true).attr('data-peer-alias', peerAlias); |
|
|
|
item.find('.mini-profile-info').attr('data-screen-name', peerAlias) |
|
item.find('.following-screen-name').text(peerAlias); |
|
item.find('a.open-profile-modal').attr('href', $.MAL.userUrl(peerAlias)); |
|
item.find('.direct-messages-with-user').text(polyglot.t('send_DM')) |
|
.on('mouseup', {route: $.MAL.dmchatUrl(peerAlias)}, routeOnClick); |
|
item.find('.mentions-from-user').text(polyglot.t('display_mentions')) |
|
.on('mouseup', {route: $.MAL.mentionsUrl(peerAlias)}, routeOnClick); |
|
getAvatar(peerAlias, item.find('.mini-profile-photo')); |
|
getFullname(peerAlias, item.find('.mini-profile-name')); |
|
getStatusTime(peerAlias, item.find('.latest-activity .time')); |
|
|
|
if (peerAlias === defaultScreenName) |
|
item.find('.following-config').hide(); |
|
|
|
toggleFollowButton({ |
|
button: item.find('.follow'), |
|
peerAlias: peerAlias, |
|
toggleUnfollow: true |
|
}); |
|
var elem = item.find('.public-following').on('click', followingListPublicCheckbox); |
|
if (isPublicFollowing(peerAlias)) |
|
elem.text(polyglot.t('Public')); |
|
else |
|
elem.text(polyglot.t('Private')).addClass('private'); |
|
|
|
item.prependTo(list); |
|
} |
|
|
|
function fillWhoToFollowModal(list, hlist, start) { |
|
for (var i = 0; i < followingUsers.length && list.length < start + 20; i++) { |
|
if (typeof twisterFollowingO.followingsFollowings[followingUsers[i]] !== 'undefined') { |
|
for (var j = 0; j < twisterFollowingO.followingsFollowings[followingUsers[i]].following.length && list.length < start + 25; j++) { |
|
var utf = twisterFollowingO.followingsFollowings[followingUsers[i]].following[j]; |
|
if (followingUsers.indexOf(utf) < 0 && list.indexOf(utf) < 0) { |
|
list.push(utf); |
|
|
|
processWhoToFollowSuggestion(hlist, utf, followingUsers[i]); |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (i >= followingUsers.length - 1) |
|
return false; |
|
|
|
// returns true, if there are more... |
|
return true; |
|
} |
|
|
|
function openWhoToFollowModal() { |
|
var modal = openModal({ |
|
classAdd: 'who-to-follow-modal', |
|
title: polyglot.t('Who to Follow') |
|
}); |
|
|
|
var tmplist = []; |
|
var hlist = $('<ol class="follow-suggestions"></ol>') |
|
.appendTo(modal.content); |
|
|
|
modal.content.on('scroll', function() { |
|
if (modal.content.scrollTop() >= hlist.height() - modal.content.height() - 20) { |
|
if (!fillWhoToFollowModal(tmplist, modal.self, tmplist.length)) |
|
modal.content.off('scroll'); |
|
} |
|
}); |
|
|
|
fillWhoToFollowModal(tmplist, modal.self, 0); |
|
} |
|
|
|
function openNewUsersModal() { |
|
var modal = openModal({ |
|
classAdd: 'new-users-modal', |
|
title: polyglot.t('New Users'), |
|
onClose: function() { |
|
NewUserSearch.isNewUserModalOpen = false; |
|
} |
|
}); |
|
|
|
var hlist = $('<ol class="follow-suggestions"></ol>') |
|
.appendTo(modal.content); |
|
var count = 15; |
|
|
|
modal.content.on('scroll', function() { |
|
if (modal.content.scrollTop() >= hlist.height() - modal.content.height() - 20) { |
|
if (newUsers.getLastNUsers(5, count, modal.self)) |
|
count += 5; |
|
} |
|
}); |
|
|
|
NewUserSearch.isNewUserModalOpen = true; |
|
newUsers.getLastNUsers(15, 0, modal.self); |
|
} |
|
|
|
function openModalUriShortener() |
|
{ |
|
var modal = openModal({ |
|
classAdd: 'uri-shortener-modal', |
|
content: twister.tmpl.uriShortenerMC.clone(true), |
|
title: polyglot.t('URI_shortener') |
|
}); |
|
|
|
modal.content.find('.uri-shortener-control .shorten-uri').text(polyglot.t('shorten_URI')); |
|
modal.content.find('.uri-shortener-control .clear-cache').text(polyglot.t('clear_cache')); |
|
|
|
var urisList = modal.content.find('.uris-list'); |
|
//var i = 0; |
|
for (var short in twister.URIs) { |
|
//i++; |
|
var long = twister.URIs[short] instanceof Array ? twister.URIs[short][0] : twister.URIs[short]; |
|
var item = twister.tmpl.uriShortenerUrisListItem.clone(true); |
|
item.find('.short').text(short); |
|
item.find('.long').text(long).attr('href', long); |
|
item.appendTo(urisList); |
|
} |
|
//i + URIs are cached |
|
} |
|
|
|
function newConversationModal(peerAlias, resource) { |
|
var content = $('#hashtag-modal-template').children().clone(true); |
|
|
|
requestPost(content.find('.postboard-posts'), peerAlias, resource, |
|
function(args) { |
|
var postboard = args.content.find('.postboard-posts'); |
|
var postLi = postboard.children().first() |
|
.css('display', 'none'); |
|
getTopPostOfConversation(postLi, null, postboard); |
|
}, {content: content} |
|
); |
|
|
|
return content; |
|
} |
|
|
|
function addToCommonDMsList(list, targetAlias, message) { |
|
var item = twister.tmpl.commonDMsListItem.clone(true) |
|
.attr('data-screen-name', targetAlias) |
|
.attr('data-last_id', message.id) |
|
.attr('data-time', message.time) |
|
; |
|
|
|
item.find('.post-info-tag').text('@' + targetAlias); |
|
item.find('.post-info-name').attr('href', $.MAL.userUrl(targetAlias)); |
|
fillElemWithTxt(item.find('.post-text'), message.text); |
|
item.find('.post-info-time') |
|
.attr('title', timeSincePost(message.time)) |
|
.find('span:last') |
|
.text(timeGmtToText(message.time)) |
|
; |
|
if (targetAlias[0] === '*') { |
|
getAvatar(message.from, item.find('.post-photo img')); // it's impossible yet to get group avatar |
|
getGroupChatName(targetAlias, item.find('a.post-info-name')); |
|
} else { |
|
getAvatar(targetAlias, item.find('.post-photo img')); |
|
getFullname(targetAlias, item.find('a.post-info-name')); |
|
} |
|
|
|
if (twister.DMs[targetAlias].lengthNew > 0) |
|
item.addClass('new') |
|
.find('.messages-qtd').text(twister.DMs[targetAlias].lengthNew).show(); |
|
|
|
var items = list.children(); |
|
for (var i = 0; i < items.length; i++) { |
|
var elem = items.eq(i); |
|
var time = elem.attr('data-time'); |
|
if (typeof time === 'undefined' || message.time > parseInt(time)) { |
|
elem.before(item); |
|
break; |
|
} |
|
} |
|
if (i === items.length) // equals to !item.parent().length |
|
list.append(item); |
|
} |
|
|
|
function handleClickOpenProfileModal(event) { |
|
event.data = {route: $(this).attr('href')}; |
|
routeOnClick(event); |
|
} |
|
|
|
function handleClickOpenConversation(event) { |
|
var elem = $(event.target).closest(event.data.feeder); |
|
if (!elem.length) { |
|
muteEvent(event, true); |
|
return; |
|
} |
|
|
|
var post = { |
|
writer: elem.attr('data-screen-name'), |
|
id: elem.attr('data-id') |
|
}; |
|
if (!post.writer || !post.id) { |
|
muteEvent(event, true); |
|
return; |
|
} |
|
|
|
event.data.route = '#conversation?post=' + post.writer + ':post' + post.id; |
|
routeOnClick(event); |
|
} |
|
|
|
function openConversationModal(peerAlias, resource) { |
|
openModal({ |
|
classAdd: 'conversation-modal', |
|
content: newConversationModal(peerAlias, resource), |
|
title: polyglot.t('conversation_title', {username: peerAlias}) |
|
}); |
|
} |
|
|
|
function openRequestShortURIForm(event) { |
|
if (event) muteEvent(event); |
|
|
|
if (!defaultScreenName) { |
|
alertPopup({ |
|
//txtTitle: polyglot.t(''), add some title (not 'error', please) or just KISS |
|
txtMessage: 'You can\'t shorten links because you are not logged in.' |
|
}); |
|
return; |
|
} |
|
if (parseInt(twisterVersion) < 93500) { |
|
alertPopup({ |
|
//txtTitle: polyglot.t(''), add some title (not 'error', please) or just KISS |
|
txtMessage: 'You can\'t shorten links —\n' |
|
+ polyglot.t('daemon_is_obsolete', {versionReq: '0.9.35'}) |
|
}); |
|
return; |
|
} |
|
|
|
var uri = prompt(polyglot.t('shorten_URI_enter_link')); // FIXME replace native prompt |
|
|
|
if (event && event.data && typeof event.data.cbFunc === 'function') |
|
newShortURI(uri, event.data.cbFunc, event.data.cbReq); |
|
else |
|
newShortURI(uri, showURIPair); |
|
} |
|
|
|
function showURIPair(uriLong, uriShort) { // FIXME req |
|
if (uriShort) |
|
alertPopup({ |
|
txtTitle: polyglot.t('URI_shortener'), |
|
txtMessage: uriLong + ' — `' + uriShort + '`' |
|
}); |
|
else |
|
showURIShortenerErrorRPC(uriShort); |
|
} |
|
|
|
function showURIShortenerErrorRPC(ret) { |
|
alertPopup({ |
|
txtTitle: polyglot.t('URI_shortener'), |
|
txtMessage: 'something went wrong. RPC error message:\n' + (ret && ret.message ? ret.message : ret) |
|
}); |
|
} |
|
|
|
function fillElemWithTxt(elem, txt, htmlFormatMsgOpt) { |
|
var formatted = htmlFormatMsg(txt, htmlFormatMsgOpt); |
|
|
|
elem.html(formatted.html); |
|
elem.find('a').each(function (i, elem) { |
|
var href = elem.getAttribute('href'); |
|
if (elem.classList.contains('link-shortened')) { |
|
$(elem).on('click mouseup', {href: href}, |
|
function (event) { |
|
muteEvent(event, true); |
|
fetchShortenedURI(event.data.href); |
|
} |
|
); |
|
fetchShortenedURI(href); |
|
} else if (href && href[0] === '#') |
|
$(elem) |
|
.on('click', {preventDefault: true}, muteEvent) |
|
.on('mouseup', {route: href}, routeOnClick) |
|
; |
|
else |
|
$(elem).on('click mouseup', muteEvent); |
|
}); |
|
|
|
return formatted; |
|
} |
|
|
|
function fetchShortenedURI(req, attemptCount) { |
|
if (twister.URIs[req]) { |
|
applyShortenedURI(req, twister.URIs[req]); |
|
return; |
|
} |
|
|
|
decodeShortURI(req, |
|
function (req, ret) { |
|
if (ret) { |
|
twister.URIs[req.shortURI] = ret; |
|
$.localStorage.set('twistaURIs', twister.URIs); |
|
applyShortenedURI(req.shortURI, ret); |
|
} else { |
|
console.warn('can\'t fetch URI "' + req.shortURI + '": null response'); |
|
if ((req.attemptCount ? ++req.attemptCount : req.attemptCount = 1) < 3) // < $.Options.decodeShortURITriesMax |
|
fetchShortenedURI(req.shortURI, req.attemptCount); |
|
} |
|
}, {shortURI: req, attemptCount: attemptCount} |
|
); |
|
} |
|
|
|
function applyShortenedURI(short, uriAndMimetype) { |
|
var long = (uriAndMimetype instanceof Array) ? uriAndMimetype[0] : uriAndMimetype; |
|
var mimetype = (uriAndMimetype instanceof Array) ? uriAndMimetype[1] : undefined; |
|
var elems = getElem('.link-shortened[href="' + short + '"]'); |
|
|
|
if (isUriSuspicious(long)) { |
|
elems.replaceWith( |
|
'…<br><b><i>' + polyglot.t('busted_oh') + '</i> ' |
|
+ polyglot.t('busted_avowal') + ':</b><br><samp>' |
|
+ long |
|
.replace(/&(?!lt;|gt;)/g, '&') |
|
.replace(/"/g, '"') |
|
.replace(/'/g, ''') |
|
+ '</samp><br>…<br>' |
|
); |
|
return; |
|
} |
|
|
|
elems |
|
.attr('href', long) |
|
.removeClass('link-shortened') |
|
.off('click mouseup') |
|
.on('click mouseup', muteEvent) |
|
; |
|
|
|
var cropped = (/*$.Options.cropLongURIs &&*/ long.length > 23) ? long.slice(0, 23) + '…' : undefined; |
|
for (var i = 0; i < elems.length; i++) { |
|
if (elems[i].text === short) // there may be some other text, possibly formatted, so we check it |
|
if (cropped) |
|
$(elems[i]) |
|
.text(cropped) |
|
.on('mouseover', {uri: long}, function (event) {event.target.text = event.data.uri;}) |
|
.on('mouseout', {uri: cropped}, function (event) {event.target.text = event.data.uri;}) |
|
; |
|
else |
|
elems[i].text = long; |
|
|
|
if (long.lastIndexOf('magnet:?xt=urn:btih:') === 0) { |
|
var previewContainer = $(elems[i]).parents(".post-data").find(".preview-container"); |
|
var fromUser = $(elems[i]).parents(".post-data").attr("data-screen-name"); |
|
var isMedia = mimetype !== undefined && |
|
(mimetype.lastIndexOf('video') === 0 || |
|
mimetype.lastIndexOf('image') === 0 || |
|
mimetype.lastIndexOf('audio') === 0); |
|
if ($.Options.WebTorrent.val === 'enable') { |
|
if ($.Options.WebTorrentAutoDownload.val === 'enable' && |
|
followingUsers.indexOf(fromUser) !==-1) { |
|
if(!(long in twister.torrentIds)) { |
|
twister.torrentIds[long] = true; |
|
$.localStorage.set('torrentIds', twister.torrentIds); |
|
} |
|
startTorrentDownloadAndPreview(long, previewContainer, isMedia); |
|
} else { |
|
// webtorrent enabled but no auto-download. provide a link to start manually. |
|
var startTorrentLink = $('<a href="#">Start WebTorrent download of this media</a>'); |
|
startTorrentLink.on('click', function(event) { |
|
event.stopPropagation(); |
|
startTorrentDownloadAndPreview(long, previewContainer, isMedia) |
|
}); |
|
previewContainer.append(startTorrentLink); |
|
} |
|
} else { |
|
var enableWebTorrentWarning = $('<span>' + |
|
polyglot.t('Enable WebTorrent support in options page to display this content') + |
|
'</span>'); |
|
previewContainer.append(enableWebTorrentWarning); |
|
} |
|
} |
|
} |
|
} |
|
|
|
function startTorrentDownloadAndPreview(torrentId, previewContainer, isMedia) { |
|
if (typeof WebTorrentClient !== 'undefined') { |
|
_startTorrentDownloadAndPreview(torrentId, previewContainer, isMedia); |
|
} else { |
|
// delay execution until WebTorrent is loaded/initialized |
|
setTimeout(_startTorrentDownloadAndPreview,1000,torrentId, previewContainer, isMedia) |
|
} |
|
} |
|
|
|
function _startTorrentDownloadAndPreview(torrentId, previewContainer, isMedia) { |
|
var torrent = WebTorrentClient.get(torrentId); |
|
if( torrent === null ) |
|
torrent = WebTorrentClient.add(torrentId); |
|
|
|
previewContainer.empty(); |
|
var speedStatus = $('<span class="post-text"/>'); |
|
previewContainer.append(speedStatus); |
|
function updateSpeed () { |
|
var progress = (100 * torrent.progress).toFixed(1) |
|
speedStatus[0].innerHTML = |
|
'<b>Peers:</b> ' + torrent.numPeers + ' ' + |
|
'<b>Progress:</b> ' + progress + '% ' + |
|
'<b>Download:</b> ' + torrent.downloadSpeed + '/s ' + |
|
'<b>Upload:</b> ' + torrent.uploadSpeed + '/s'; |
|
} |
|
setInterval(updateSpeed, 1000); |
|
updateSpeed(); |
|
|
|
if( torrent.files.length ) { |
|
webtorrentFilePreview(torrent.files[0], previewContainer, isMedia) |
|
} else { |
|
torrent.on('metadata', function () { |
|
webtorrentFilePreview(torrent.files[0], previewContainer, isMedia) |
|
}); |
|
} |
|
} |
|
|
|
function webtorrentFilePreview(file, previewContainer, isMedia) { |
|
if (!isMedia) { |
|
// try guessing by filename extension |
|
isMedia = /^[^?]+\.(?:jpe?g|gif|png|mp4|webm|mp3|ogg|wav|)$/i.test(file.name) |
|
} |
|
|
|
if (isMedia) { |
|
var imagePreview = $('<div class="image-preview" />'); |
|
previewContainer.append(imagePreview); |
|
file.appendTo(imagePreview[0], function (err, elem) { |
|
if ('pause' in elem) { |
|
elem.pause(); |
|
} |
|
}); |
|
var $vid = imagePreview.find("video"); |
|
$vid.removeAttr("autoplay"); |
|
$vid.on('click mouseup', muteEvent); |
|
} else { |
|
file.getBlobURL(function (err, url) { |
|
if (err) return console.error(err) |
|
var blobLink = $('<a href="' + url + '" download="'+file.name+'">' + |
|
'Download ' + file.name + '</a>'); |
|
previewContainer.append(blobLink); |
|
}) |
|
} |
|
} |
|
|
|
function routeOnClick(event) { |
|
|
|
function routeNewTab(event) { |
|
if (event.target.href) { |
|
// we can't prevent hyperlink navigation on middle button in Firefox via event.preventDefault(), see https://bugzilla.mozilla.org/show_bug.cgi?id=1249970 |
|
// so we need to delete href to make element not hyperlink and add it again after 200 ms |
|
setTimeout((function () {this.elem.setAttribute('href', this.href);}) |
|
.bind({elem: event.target, href: event.target.getAttribute('href')}), 200); |
|
event.target.removeAttribute('href'); |
|
} |
|
twister.html.blanka.attr('href', event.data.route)[0].click(); // opens .route in new tab |
|
} |
|
|
|
if (!event || !event.data || !event.data.route) |
|
return; |
|
|
|
if (event.button === 0 && window.getSelection().toString() !== '') |
|
return; |
|
|
|
event.stopPropagation(); |
|
event.preventDefault(); |
|
|
|
if (event.button === 0 && !event.data.blankOnly) // left mouse button |
|
window.location = event.data.route; // closes modal(s) in watchHashChange() and opens .route |
|
else if (event.button === 1) // middle mouse button |
|
if (event.data.blankOnly || event.metaKey || event.ctrlKey) |
|
routeNewTab(event); |
|
else { |
|
var modal = $(event.target).closest('.modal-wrapper:not(.closed)'); |
|
if (modal.length && window.location.hash !== event.data.route) { |
|
minimizeModal(modal, true); // yep, we minimize current modal before .route opening |
|
window.location.hash = event.data.route; |
|
} else |
|
routeNewTab(event); |
|
} |
|
} |
|
|
|
function watchHashChange(event) { |
|
if (typeof event !== 'undefined') { |
|
var prevurlsplit = event.oldURL.split('#'); |
|
var prevhashstring = prevurlsplit[1]; |
|
|
|
// FIXME need to move back button handling to special function and call it in openModal() and resumeModal() |
|
var notFirstModalView = (prevhashstring !== undefined && prevhashstring.length > 0); |
|
var notNavigatedBackToFirstModalView = (window.history.state == null || |
|
(window.history.state != null && window.history.state.showCloseButton !== false)); |
|
|
|
if (notFirstModalView && notNavigatedBackToFirstModalView) { |
|
$('.modal-back').css('display', 'inline'); |
|
} else { |
|
window.history.replaceState({showCloseButton: false}, '', window.location.pathname + window.location.hash); |
|
$('.modal-back').css('display', 'none'); |
|
} |
|
} |
|
|
|
if (_watchHashChangeRelaxDontDoIt) |
|
_watchHashChangeRelaxDontDoIt = false; |
|
else |
|
loadModalFromHash(); |
|
} |
|
|
|
function loadModalFromHash() { |
|
var i = window.location.hash; |
|
if (twister.modal[i] && twister.modal[i].minimized) { |
|
// need to close active modal(s) before btnResume.click() or it will be minimized in resumeModal() |
|
// e.g. for case when you click on profile link in some modal having this profile's modal minimized already |
|
closeModal(undefined, true); |
|
twister.modal[i].btnResume.click(); |
|
return; |
|
} |
|
|
|
var hashstring = decodeURIComponent(window.location.hash); |
|
if (hashstring === '') { |
|
closeModal(); // close active modal(s) |
|
return; |
|
} |
|
var hashdata = hashstring.split(':'); |
|
|
|
// FIXME rework hash scheme from '#following?user=twister' to something like '#/@twister/following' |
|
if (hashdata[0] !== '#web+twister') |
|
hashdata = hashstring.match(/(hashtag|profile|mentions|directmessages|followers|following|conversation|favs)\?(?:group|user|hashtag|post)=(.+)/); |
|
|
|
if (hashdata && hashdata[1] !== undefined && hashdata[2] !== undefined) { |
|
if (hashdata[1] === 'profile') |
|
if (hashdata[2][0] === '*') |
|
openGroupProfileModalWithNameHandler(hashdata[2]); |
|
else |
|
openUserProfileModalWithNameHandler(hashdata[2]); |
|
|
|
else if (hashdata[1] === 'hashtag') |
|
openHashtagModalFromSearchHandler(hashdata[2]); |
|
else if (hashdata[1] === 'mentions') |
|
openMentionsModalHandler(hashdata[2]); |
|
else if (hashdata[1] === 'directmessages') { |
|
if (hashdata[2][0] === '*') |
|
openGroupMessagesModal(hashdata[2]); |
|
else |
|
openDmWithUserModal(hashdata[2]); |
|
|
|
} else if (hashdata[1] === 'followers') |
|
openFollowersModal(hashdata[2]); |
|
else if (hashdata[1] === 'following') |
|
openFollowingModal(hashdata[2]); |
|
else if (hashdata[1] === 'conversation') { |
|
splithashdata2 = hashdata[2].split(':'); |
|
openConversationModal(splithashdata2[0], splithashdata2[1]); |
|
} |
|
else if (hashdata[1] === 'favs') |
|
openFavsModalHandler(hashdata[2]); |
|
} else if (hashstring === '#directmessages') |
|
openCommonDMsModal(); |
|
else if (hashstring === '#followers') |
|
openFollowersModal(); |
|
else if (hashstring === '#following') |
|
openFollowingModal(); |
|
else if (hashstring === '#groupmessages') |
|
openGroupMessagesModal(); |
|
else if (hashstring === '#groupmessages+newgroup') |
|
openGroupMessagesNewGroupModal(); |
|
else if (hashstring === '#groupmessages+joingroup') |
|
openGroupMessagesJoinGroupModal(); |
|
else if (hashstring === '#/login') |
|
openModalLogin(); |
|
else if (hashstring === '#whotofollow') |
|
openWhoToFollowModal(); |
|
else if (hashstring === '#/uri-shortener') |
|
openModalUriShortener(); |
|
else if (hashstring === '#newusers') |
|
openNewUsersModal(); |
|
} |
|
|
|
function initHashWatching() { |
|
// register custom protocol handler |
|
if (window.navigator && window.navigator.registerProtocolHandler && |
|
!_getResourceFromStorage('twister_protocol_registered')) { |
|
window.navigator.registerProtocolHandler( |
|
'web+twister', |
|
window.location.protocol + '//' + window.location.host + '/home.html#%s', |
|
'Twister' |
|
); |
|
_putResourceIntoStorage('twister_protocol_registered', true); |
|
} |
|
|
|
// register hash spy and launch it once |
|
window.addEventListener('hashchange', watchHashChange, false); |
|
setTimeout(watchHashChange, 1000); |
|
} |
|
|
|
function reTwistPopup(event, post, textArea) { |
|
event.stopPropagation(); |
|
|
|
if (!defaultScreenName) { |
|
alertPopup({ |
|
//txtTitle: polyglot.t(''), add some title (not 'error', please) or just KISS |
|
txtMessage: polyglot.t('You have to log in to retransmit messages.') |
|
}); |
|
return; |
|
} |
|
|
|
if (typeof post === 'undefined') |
|
post = $.evalJSON($(event.target).closest('.post-data').attr('data-userpost')); |
|
|
|
var modal = openModal({ |
|
classBase: '.prompt-wrapper', |
|
classAdd: 'reTwist', |
|
title: polyglot.t('retransmit_this') |
|
}); |
|
|
|
modal.content |
|
.append(postToElem(post, '')) |
|
.append($('#reTwist-modal-template').children().clone(true)) |
|
; |
|
|
|
modal.content.find('.switch-mode') |
|
.text(polyglot.t('Switch to Reply')) |
|
.on('click', {post: post}, |
|
function(event) { |
|
var textArea = $(event.target).closest('form').find('textarea').detach(); |
|
closePrompt(event.target); |
|
replyInitPopup(event, event.data.post, textArea); |
|
} |
|
) |
|
; |
|
|
|
var replyArea = modal.content.find('.post-area .post-area-new'); |
|
if (typeof textArea === 'undefined') { |
|
textArea = replyArea.find('textarea'); |
|
var textAreaPostInline = modal.content.find('.post .post-area-new textarea'); |
|
$.each(['placeholder', 'data-reply-to'], function(i, attribute) { |
|
textArea.attr(attribute, textAreaPostInline.attr(attribute)); |
|
}); |
|
} else { |
|
replyArea.find('textarea').replaceWith(textArea); |
|
if (textArea.val()) { |
|
textArea.focus(); |
|
replyArea.addClass('open'); |
|
} |
|
} |
|
replyArea.find('.post-submit').addClass('with-reference'); |
|
} |
|
|
|
function favPopup(event, post, textArea) { |
|
event.stopPropagation(); |
|
|
|
if (!defaultScreenName) { |
|
alertPopup({ |
|
txtMessage: polyglot.t('You have to log in to favorite messages.') |
|
}); |
|
return; |
|
} |
|
|
|
if (typeof post === 'undefined') |
|
post = $.evalJSON($(event.target).closest('.post-data').attr('data-userpost')); |
|
|
|
var modal = openModal({ |
|
classBase: '.prompt-wrapper', |
|
classAdd: 'fav-this', |
|
title: polyglot.t('fav_this') |
|
}); |
|
|
|
modal.content |
|
.append(postToElem(post, '')) |
|
.append($('#fav-modal-template').children().clone(true)) |
|
; |
|
/* |
|
//TODO: favs can be also commented |
|
var replyArea = modal.content.find('.post-area .post-area-new'); |
|
if (typeof textArea === 'undefined') { |
|
textArea = replyArea.find('textarea'); |
|
var textAreaPostInline = modal.content.find('.post .post-area-new textarea'); |
|
$.each(['placeholder', 'data-reply-to'], function(i, attribute) { |
|
textArea.attr(attribute, textAreaPostInline.attr(attribute)); |
|
}); |
|
} else { |
|
replyArea.find('textarea').replaceWith(textArea); |
|
if (textArea.val()) { |
|
textArea.focus(); |
|
replyArea.addClass('open'); |
|
} |
|
} |
|
replyArea.find('.post-submit').addClass('with-reference'); |
|
*/ |
|
} |
|
|
|
// Expande Área do Novo post |
|
function replyInitPopup(e, post, textArea) { |
|
var modal = openModal({ |
|
classBase: '.prompt-wrapper', |
|
classAdd: 'reply', |
|
title: polyglot.t('reply_to', {fullname: '<span class="fullname">'+post.userpost.n+'</span>'}) |
|
}); |
|
|
|
getFullname(post.userpost.n, modal.self.find('h3 .fullname')); |
|
|
|
modal.content |
|
.append(postToElem(post, '')) |
|
.append($('#reply-modal-template').children().clone(true)) |
|
; |
|
|
|
modal.content.find('.switch-mode') |
|
.text(polyglot.t('Switch to Retransmit')) |
|
.on('click', {post: post}, |
|
function(event) { |
|
var textArea = $(event.target).closest('form').find('textarea').detach(); |
|
closePrompt(event.target); |
|
reTwistPopup(event, event.data.post, textArea); |
|
} |
|
) |
|
; |
|
|
|
var replyArea = modal.content.find('.post-area .post-area-new').addClass('open'); |
|
if (typeof textArea === 'undefined') { |
|
textArea = replyArea.find('textarea'); |
|
var textAreaPostInline = modal.content.find('.post .post-area-new textarea'); |
|
$.each(['placeholder', 'data-reply-to'], function(i, attribute) { |
|
textArea.attr(attribute, textAreaPostInline.attr(attribute)); |
|
}); |
|
} else { |
|
replyArea.find('textarea').replaceWith(textArea); |
|
} |
|
composeNewPost(e, replyArea); |
|
} |
|
|
|
// abre o menu dropdown de configurações |
|
function dropDownMenu() { |
|
$('.config-menu').slideToggle('fast'); |
|
} |
|
|
|
// fecha o config menu ao clicar em qualquer lugar da tela |
|
function closeThis() { |
|
$(this).slideUp('fast'); |
|
} |
|
|
|
function muteEvent(event, preventDefault) { |
|
event.stopPropagation(); |
|
if (preventDefault || (event.data && event.data.preventDefault)) |
|
event.preventDefault(); |
|
} |
|
|
|
function toggleFollowButton(req) { |
|
if (!req || !req.peerAlias) |
|
return; |
|
|
|
if (req.toggleUnfollow) { |
|
if (!req.button || !req.button.jquery) |
|
req.button = getElem('[data-screen-name="' + req.peerAlias + '"]').find('.follow'); |
|
req.button |
|
.text(polyglot.t('Unfollow')) |
|
.removeClass('follow') |
|
.addClass('unfollow') |
|
.off('click') |
|
.on('click', {peerAlias: req.peerAlias}, clickUnfollow) |
|
; |
|
} else { |
|
if (!req.button || !req.button.jquery) |
|
req.button = getElem('[data-screen-name="' + req.peerAlias + '"]').find('.unfollow'); |
|
req.button |
|
.text(polyglot.t('Follow')) |
|
.removeClass('unfollow') |
|
.addClass('follow') |
|
.off('click') |
|
.on('click', {peerAlias: req.peerAlias}, clickFollow) |
|
; |
|
} |
|
} |
|
|
|
function clickFollow(event) { |
|
event.preventDefault(); |
|
event.stopPropagation(); |
|
|
|
if (!defaultScreenName) { |
|
alertPopup({ |
|
//txtTitle: polyglot.t(''), add some title (not 'error', please) or just KISS |
|
txtMessage: polyglot.t('You have to log in to follow users.') |
|
}); |
|
return; |
|
} |
|
|
|
var peerAlias = (event.data && event.data.peerAlias) ? event.data.peerAlias |
|
: $(event.target).closest('[data-screen-name]').attr('data-screen-name'); |
|
var content = $('#following-config-modal-template').children().clone(true); |
|
|
|
content.closest('.following-config-modal-content').attr('data-screen-name', peerAlias); |
|
fillElemWithTxt(content.find('.following-config-method-message'), |
|
polyglot.t('select_way_to_follow_@', {alias: peerAlias}), {markout: 'apply'}); |
|
content.find('.following-screen-name b').text(peerAlias); |
|
|
|
openModal({ |
|
classBase: '.prompt-wrapper', // FIXME it will be modal with advanced following set up in future |
|
classAdd: 'following-config-modal', |
|
content: content, |
|
title: polyglot.t('Following config') |
|
}); |
|
} |
|
|
|
function clickUnfollow(event) { |
|
event.preventDefault(); |
|
event.stopPropagation(); |
|
|
|
var peerAlias = (event.data && event.data.peerAlias) ? event.data.peerAlias |
|
: $(event.target).closest('[data-screen-name]').attr('data-screen-name'); |
|
|
|
confirmPopup({ |
|
txtMessage: polyglot.t('confirm_unfollow_@', {alias: peerAlias}), |
|
cbConfirm: function (peerAlias) { |
|
unfollow(peerAlias, |
|
function(req) { |
|
$('.mini-profile .following-count').text(followingUsers.length - 1); |
|
$('.wrapper .postboard .post').each(function() { |
|
var elem = $(this); |
|
if ((elem.find('[data-screen-name="' + req.peerAlias + '"]').length |
|
&& !elem.find(".post-rt-by .open-profile-modal").text()) |
|
|| elem.find(".post-rt-by .open-profile-modal").text() === '@' + req.peerAlias) |
|
elem.remove(); |
|
}); // FIXME also need to check list of pending posts to remove from there |
|
toggleFollowButton({peerAlias: req.peerAlias}); |
|
var followingList = getElem('.following-own-modal .following-list'); |
|
if (followingList.length) |
|
followingList.find('li[data-peer-alias="' + req.peerAlias + '"]').remove(); |
|
}, {peerAlias: peerAlias} |
|
); |
|
}, |
|
cbConfirmReq: peerAlias |
|
}); |
|
} |
|
|
|
function setFollowingMethod(event) { |
|
event.preventDefault(); |
|
event.stopPropagation(); |
|
|
|
var button = $(event.target); |
|
var peerAlias = button.closest('.following-config-modal-content').attr('data-screen-name'); |
|
|
|
follow(peerAlias, button.hasClass('private') ? false : true, |
|
function(req) { |
|
$('.mini-profile .following-count').text(followingUsers.length - 1); |
|
setTimeout(requestTimelineUpdate, 1000, 'latest', postsPerRefresh, [req.peerAlias], promotedPostsOnly); |
|
toggleFollowButton({peerAlias: req.peerAlias, toggleUnfollow: req.toggleUnfollow}); |
|
var followingList = getElem('.following-own-modal .following-list'); |
|
if (followingList.length) |
|
addPeerToFollowingList(followingList, req.peerAlias); |
|
}, {peerAlias: peerAlias, toggleUnfollow: true} |
|
); |
|
} |
|
|
|
function followingListPublicCheckbox(event) { |
|
event.preventDefault(); |
|
event.stopPropagation(); |
|
|
|
var tickSelection = function (req) { |
|
if (req.isPublic === req.wasPublic) return; |
|
|
|
var elem = $('.mini-profile-info[data-screen-name="' + req.peerAlias + '"] .public-following'); |
|
elem.toggleClass('private'); |
|
if (!req.isPublic) |
|
elem.text(polyglot.t('Private')); |
|
else |
|
elem.text(polyglot.t('Public')); |
|
|
|
//console.log('set following method of @' + peerAlias + ' for ' + isPublic); |
|
follow(req.peerAlias, req.isPublic); |
|
}; |
|
var elem = $(event.target); |
|
var peerAlias = elem.closest('.mini-profile-info').attr('data-screen-name'); |
|
var wasPublic = !elem.hasClass('private'); |
|
|
|
confirmPopup({ |
|
txtMessage: polyglot.t('select_way_to_follow_@', {alias: peerAlias}), |
|
txtConfirm: polyglot.t('Public'), |
|
cbConfirm: tickSelection, |
|
cbConfirmReq: {isPublic: true, wasPublic: wasPublic, peerAlias: peerAlias}, |
|
txtCancel: polyglot.t('Private'), |
|
cbCancel: tickSelection, |
|
cbCancelReq: {isPublic: false, wasPublic: wasPublic, peerAlias: peerAlias} |
|
}); |
|
} |
|
|
|
function postExpandFunction(e, postLi) { |
|
if (!postLi.hasClass('original')) |
|
return; |
|
|
|
var openClass = 'open'; |
|
var originalPost = postLi.find('.post-data.original'); |
|
var postInteractionText = originalPost.find('.post-expand'); |
|
var postExpandedContent = originalPost.find('.expanded-content'); |
|
var postsRelated = postLi.find('.related'); |
|
|
|
if (!postLi.hasClass(openClass)) { |
|
originalPost.detach(); |
|
postLi.empty(); |
|
postLi.addClass(openClass); |
|
postInteractionText.text(polyglot.t('Collapse')); |
|
|
|
var itemOl = $('<ol/>', {class:'expanded-post'}).appendTo(postLi); |
|
var originalLi = $('<li/>', {class: 'module post original'}).appendTo(itemOl) |
|
.append(originalPost); |
|
|
|
setPostImagePreview(postExpandedContent, originalPost.find('a[rel^="nofollow"]')); |
|
|
|
postExpandedContent.slideDown('fast'); |
|
|
|
// insert 'reply_to' before |
|
requestRepliedBefore(originalLi); |
|
// insert replies to this post after |
|
requestRepliesAfter(originalLi); |
|
// RTs faces and counter |
|
requestRTs(originalLi); |
|
} else { |
|
postLi.removeClass(openClass); |
|
|
|
postInteractionText.text( |
|
(typeof postLi.find('.post-data.original').attr('data-replied-to-id') === 'undefined') ? |
|
polyglot.t('Expand') : polyglot.t('Show conversation') |
|
); |
|
|
|
if (postsRelated) |
|
postsRelated.slideUp('fast'); |
|
|
|
postExpandedContent.slideUp('fast', function() { |
|
originalPost.detach().appendTo(postLi.empty()); |
|
}); |
|
} |
|
|
|
e.stopPropagation(); |
|
} |
|
|
|
function postReplyClick(event) { |
|
if (!defaultScreenName) { |
|
event.stopPropagation(); |
|
alertPopup({ |
|
//txtTitle: polyglot.t(''), add some title (not 'error', please) or just KISS |
|
txtMessage: polyglot.t('You have to log in to post replies.') |
|
}); |
|
return; |
|
} |
|
|
|
var post = $(this).closest('.post'); |
|
if (!post.hasClass('original')) |
|
replyInitPopup(event, $.evalJSON(post.find('.post-data').attr('data-userpost'))); |
|
else { |
|
if (!post.closest('.post.open').length) |
|
postExpandFunction(event, post); |
|
composeNewPost(event, post.find('.post-area-new')); |
|
} |
|
|
|
event.stopPropagation(); |
|
} |
|
|
|
// Expande Área do Novo post |
|
function composeNewPost(e, postAreaNew) { |
|
e.stopPropagation(); |
|
if (!postAreaNew.hasClass('open')) { |
|
postAreaNew.addClass('open'); |
|
//se o usuário clicar fora é pra fechar |
|
postAreaNew.clickoutside(unfocusPostAreaNew); |
|
|
|
if ($.Options.splitPosts.val === 'enable') |
|
usePostSpliting = true; |
|
else if ($.Options.splitPosts.val === 'only-new') { |
|
var postOrig = postAreaNew.closest('.post-data'); |
|
|
|
if (!postOrig.length) |
|
postOrig = postAreaNew.closest('.modal-content').find('.post-data'); |
|
|
|
if (postOrig.length) |
|
usePostSpliting = false; |
|
else |
|
usePostSpliting = true; |
|
} else |
|
usePostSpliting = false; |
|
} |
|
|
|
var textArea = postAreaNew.find('textarea'); |
|
if (textArea.attr('data-reply-to') && !textArea.val().length) { |
|
textArea.val(textArea.attr('data-reply-to')); |
|
poseTextareaPostTools(textArea); |
|
} |
|
if (!postAreaNew.find('textarea:focus').length) |
|
postAreaNew.find('textarea:last').focus(); |
|
} |
|
|
|
function poseTextareaPostTools(event) { |
|
if (event.jquery) |
|
var textArea = event; |
|
else |
|
var textArea = $(event.target); |
|
|
|
posePostPreview(textArea); |
|
poseTextareaEditBar(textArea); |
|
} |
|
|
|
function posePostPreview(textArea) { |
|
if (!$.Options.postPreview.val) |
|
return; |
|
|
|
var postPreview = textArea.siblings('#post-preview'); |
|
if (!postPreview.length) { |
|
postPreview = $('#post-preview-template').children().clone() |
|
.css('margin-left', textArea.css('margin-left')) |
|
.css('margin-right', textArea.css('margin-right')) |
|
; |
|
postPreview.width(textArea.width()); |
|
postPreview.width(postPreview.width() // width is not accurate if we do it with textArea.width() directly, don't know why |
|
- postPreview.css('padding-left') - postPreview.css('padding-right')); |
|
} |
|
if (textArea[0].value.length) |
|
fillElemWithTxt(postPreview.show(), textArea[0].value); |
|
else |
|
postPreview.hide(); |
|
textArea.before(postPreview); |
|
} |
|
|
|
function poseTextareaEditBar(textArea) { |
|
var editBar = textArea.siblings('.post-textarea-edit-bar'); |
|
if (!editBar.length) { |
|
editBar = twister.tmpl.postTextareaEditBar.clone(true) |
|
.css('margin-left', textArea.css('margin-left')) |
|
.css('margin-right', textArea.css('margin-right')) |
|
; |
|
editBar.find('.shorten-uri').text(polyglot.t('shorten_URI')); |
|
} |
|
editBar.insertAfter(textArea).show(); |
|
} |
|
|
|
// Reduz Área do Novo post |
|
function unfocusPostAreaNew() { |
|
var postAreaNew = $(this).removeClass('open'); |
|
postAreaNew.find('#post-preview').slideUp('fast'); |
|
postAreaNew.find('.post-textarea-edit-bar').hide(); |
|
} |
|
|
|
function checkPostForMentions(post, mentions, max) { |
|
return new RegExp('^.{0,' + max.toString() + '}(?:' + mentions.trim().replace(/ /g, '|') + ')').test(post); |
|
} |
|
|
|
var splitedPostsCount = 1; // FIXME it could be property of future textAreaInput and composeNewPost united thing; currently stuff is hell |
|
var usePostSpliting = false; |
|
|
|
function replyTextInput(event) { |
|
var textArea = $(event.target); |
|
var textAreaForm = textArea.closest('form'); |
|
if (textAreaForm.length) { |
|
if ($.Options.unicodeConversion.val !== 'disable') |
|
textArea.val(convert2Unicodes(textArea.val(), textArea)); |
|
|
|
if (usePostSpliting && !textArea.closest('.directMessages').length) { |
|
var caretPos = textArea.caret(); |
|
var reply_to = textArea.attr('data-reply-to'); |
|
var tas = textAreaForm.find('textarea'); |
|
splitedPostsCount = tas.length; |
|
var icurrentta = tas.index(event.target); // current textarea tas index |
|
var i = 0; |
|
var pml = getPostSplitingPML(); |
|
var cci = getPostSpittingCI(icurrentta); |
|
|
|
for (; i < tas.length; i++) { |
|
pml = getPostSplitingPML(); |
|
if (tas[i].value.length > pml) { |
|
var ci = getPostSpittingCI(i); |
|
if (i < splitedPostsCount - 1) { |
|
tas[i + 1].value = tas[i].value.substr(ci) + tas[i + 1].value; |
|
tas[i].value = tas[i].value.substr(0, ci); |
|
if (caretPos > cci) { |
|
caretPos -= ci; |
|
icurrentta += 1; |
|
cci = getPostSpittingCI(icurrentta); |
|
var targetta = $(tas[icurrentta]); |
|
} else if (i === icurrentta) |
|
$(tas[i]).caret(caretPos); |
|
} else { |
|
var oldta = $(tas[i]); |
|
if ($.fn.textcomplete) { |
|
oldta.textcomplete('destroy'); |
|
event.stopImmediatePropagation(); // something goes wrong in $.fn.textcomplete if we don't stop this immediately |
|
} |
|
var cp = oldta.val(); |
|
var newta = $(oldta).clone(true) |
|
.val(cp.substr(ci)) |
|
.insertAfter(oldta) |
|
; |
|
oldta.val(cp.substr(0, ci)) |
|
.addClass('splited-post') |
|
.on('focus', function() {this.style.height = '80px';}) |
|
.on('focusout', function() {this.style.height = '28px';}) // FIXME move this to CSS |
|
; |
|
|
|
tas = textAreaForm.find('textarea'); |
|
splitedPostsCount = tas.length; |
|
pml = getPostSplitingPML(); |
|
|
|
if (caretPos > cci) { |
|
caretPos -= ci; |
|
icurrentta += 1; |
|
cci = getPostSpittingCI(icurrentta); |
|
var targetta = newta; |
|
oldta[0].style.height = '28px'; // FIXME move this to CSS |
|
} else if (i === icurrentta) { |
|
$(tas[i]).caret(caretPos); |
|
replyTextUpdateRemaining(tas[i]); |
|
if ($.fn.textcomplete) |
|
setTextcompleteOnElement(tas[i], getMentionsForAutoComplete()); |
|
} |
|
} |
|
} else if (tas.length > 1 && tas[i].value.length === 0) { |
|
if (i === tas.length - 1) { |
|
tas[i].value = tas[i - 1].value; |
|
$(tas[i - 1]).remove(); |
|
} else |
|
$(tas[i]).remove(); |
|
tas = textAreaForm.find('textarea'); |
|
i--; |
|
splitedPostsCount = tas.length; |
|
pml = getPostSplitingPML(); |
|
caretPos = -1; |
|
if (icurrentta >= i && icurrentta > 0) { |
|
icurrentta -= 1; |
|
cci = getPostSpittingCI(icurrentta); |
|
} |
|
var targetta = $(tas[icurrentta]); |
|
} |
|
} |
|
|
|
if (typeof targetta !== 'undefined' && targetta[0] !== document.activeElement) { |
|
textArea = targetta; |
|
textArea.focus(); |
|
textArea.caret(caretPos); |
|
} |
|
} |
|
|
|
if ($.Options.postPreview.val) { |
|
if (textArea[0].value.length) |
|
fillElemWithTxt(textAreaForm.find('#post-preview').show(), textArea[0].value); |
|
else |
|
textAreaForm.find('#post-preview').html('').slideUp('fast'); |
|
} |
|
} |
|
|
|
function getPostSplitingPML() { |
|
var MaxPostSize = $.Options.MaxPostEditorChars.val; |
|
if (splitedPostsCount > 1) { |
|
var pml = MaxPostSize - (i+1).toString().length - splitedPostsCount.toString().length - 4; |
|
|
|
// if mention exists, we shouldn't add it while posting. |
|
if (typeof reply_to !== 'undefined' && |
|
!checkPostForMentions(tas[i].value, reply_to, pml -reply_to.length)) { |
|
pml -= reply_to.length; |
|
} |
|
} else |
|
var pml = MaxPostSize; |
|
return pml; |
|
} |
|
|
|
function getPostSpittingCI(ita) { |
|
var ci; |
|
var endings = tas[ita].value.match(/[\\\/\.,:;\?\!\*'"\]\)\}\^\|%\u201D\u2026\u2014\u4E00\u3002\uFF0C\uFF1A\uFF1F\uFF01\u3011>\s]/g) // unicode escaped stuff is '”…—一。,:?!】 |
|
|
|
if (endings) { |
|
ci = tas[ita].value.lastIndexOf(endings[endings.length - 1]); |
|
for (var j = endings.length - 2; j >= 0 && ci > pml; j--) |
|
ci = tas[ita].value.lastIndexOf(endings[j], ci - 1); |
|
} |
|
if (!(ci > 0)) |
|
ci = pml; |
|
|
|
return (ci > pml) ? pml : ci; |
|
} |
|
} |
|
|
|
function replyTextUpdateRemaining(ta) { |
|
if (ta.target) |
|
ta = ta.target; |
|
if (ta === document.activeElement) { |
|
var textArea = $(ta); |
|
var textAreaForm = textArea.closest('form'); |
|
if (textAreaForm.length) { |
|
var remainingCount = textAreaForm.find('.post-area-remaining'); |
|
var c = replyTextCountRemaining(ta); |
|
|
|
if (usePostSpliting && !textArea.closest('.directMessages').length && splitedPostsCount > 1) |
|
remainingCount.text((textAreaForm.find('textarea').index(ta) + 1).toString() |
|
+ '/' + splitedPostsCount.toString() + ': ' + c.toString()); |
|
else |
|
remainingCount.text(c.toString()); |
|
|
|
var buttonSend = textAreaForm.find('.post-submit'); |
|
if (!buttonSend.length) |
|
buttonSend = textAreaForm.find('.dm-submit'); |
|
|
|
var disable = false; |
|
textAreaForm.find('textarea').each(function() { |
|
if (replyTextCountRemaining(this) < 0) { |
|
disable = true; // alternatively we could call replyTextInput() |
|
return false; |
|
} |
|
}); |
|
if (!disable && c >= 0 && c < $.Options.MaxPostEditorChars.val && |
|
textArea.val() !== textArea.attr('data-reply-to')) { |
|
remainingCount.removeClass('warn'); |
|
$.MAL.enableButton(buttonSend); |
|
} else { |
|
if (disable) |
|
remainingCount.addClass('warn'); |
|
$.MAL.disableButton(buttonSend); |
|
} |
|
} |
|
} |
|
} |
|
|
|
function replyTextCountRemaining(ta) { |
|
var textArea = $(ta); |
|
var c; |
|
|
|
var MaxPostSize = $.Options.MaxPostEditorChars.val; |
|
if (usePostSpliting && !textArea.closest('.directMessages').length && splitedPostsCount > 1) { |
|
c = MaxPostSize - ta.value.length - (textArea.closest('form').find('textarea').index(ta) + 1).toString().length - splitedPostsCount.toString().length - 4; |
|
var reply_to = textArea.attr('data-reply-to'); |
|
if (typeof reply_to !== 'undefined' && |
|
!checkPostForMentions(ta.value, reply_to, MaxPostSize -c -reply_to.length)) |
|
c -= reply_to.length; |
|
} else |
|
c = MaxPostSize - ta.value.length; |
|
|
|
return c; |
|
} |
|
|
|
function replyTextKeySend(event) { |
|
if (event.keyCode === 13) { |
|
if ((!event.metaKey && !event.ctrlKey && $.Options.keysSend.val === 'enter' && |
|
$('.dropdown-menu').css('display') === 'none') |
|
|| ((event.metaKey || event.ctrlKey) && $.Options.keysSend.val === 'ctrlenter')) { |
|
var textArea = $(event.target); |
|
var textAreaForm = textArea.closest('form'); |
|
var buttonSend = textAreaForm.find('.post-submit'); |
|
if (!buttonSend.length) |
|
buttonSend = textAreaForm.find('.dm-submit'); |
|
|
|
if (buttonSend.length) { |
|
textArea.val(textArea.val().trim()); |
|
|
|
if (!buttonSend.hasClass('disabled')) |
|
buttonSend.click(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* |
|
* unicode convertion list |
|
* k: original string to be replaced |
|
* u: unicode |
|
* n: index of char to be stored and appended to result |
|
*/ |
|
var unicodeConversionList = { |
|
'punctuation': [ |
|
{ |
|
'k': /\.\.\./, |
|
'u': '\u2026', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /\.\../, |
|
'u': '\u2025', |
|
'n': 2 |
|
}, |
|
{ |
|
'k': /\?\?/, |
|
'u': '\u2047', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /\?!/, |
|
'u': '\u2048', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /!\?/, |
|
'u': '\u2049', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /!!/, |
|
'u': '\u203C', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /--/, |
|
'u': '\u2014', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /~~/, |
|
'u': '\u2053', |
|
'n': -1 |
|
} |
|
], |
|
'emotions': [ |
|
{ |
|
'k': /:.{0,1}D/, |
|
'u': '\uD83D\uDE03', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /(0|O):-{0,1}\)/i, |
|
'u': '\uD83D\uDE07', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /:beer:/, |
|
'u': '\uD83C\uDF7A', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /3:-{0,1}\)/, |
|
'u': '\uD83D\uDE08', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /<3/, |
|
'u':'\u2764', |
|
'n': -1 |
|
}, |
|
// disabled due to urls :/ |
|
// { |
|
// 'k': /o.O|:\/|:\\/, |
|
// 'u': '\uD83D\uDE15', |
|
// 'n': -1 |
|
// }, |
|
{ |
|
'k': /:\'\(/, |
|
'u': '\uD83D\uDE22', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /(:|=)-{0,1}\(/, |
|
'u': '\uD83D\uDE1E', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /8(\)<|\|)/, |
|
'u': '\uD83D\uDE0E', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /(:|=)-{0,1}(\)|\])/, |
|
'u': '\uD83D\uDE0A', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /(\(|\[)-{0,1}(:|=)/, |
|
'u': '\uD83D\uDE0A', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /:\*/, |
|
'u': '\uD83D\uDE17', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /\^-{0,1}\^/, |
|
'u': '\uD83D\uDE06', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /:p/i, |
|
'u': '\uD83D\uDE1B', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /;-{0,1}\)/, |
|
'u': '\uD83D\uDE09', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /\(-{0,1};/, |
|
'u': '\uD83D\uDE09', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /:(O|0)/, |
|
'u': '\uD83D\uDE2E', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /:@/, |
|
'u': '\uD83D\uDE31', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /:\|/, |
|
'u': '\uD83D\uDE10', |
|
'n': -1 |
|
} |
|
], |
|
'signs': [ |
|
{ |
|
'k': / tel(|:|=)/i, |
|
'u': ' \u2121', |
|
'n': 4 |
|
}, |
|
{ |
|
'k': /^tel(|:|=)/i, |
|
'u': '\u2121', |
|
'n': 3 |
|
}, |
|
{ |
|
'k': / fax(|:|=)/i, |
|
'u': ' \u213B', |
|
'n': 4 |
|
}, |
|
{ |
|
'k': /^fax(|:|=)/i, |
|
'u': '\u213B', |
|
'n': 3 |
|
} |
|
], |
|
'fractions': [ |
|
{ |
|
'k': /1\/2/, |
|
'u': '\u00BD', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /1\/3/, |
|
'u': '\u2153', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /2\/3/, |
|
'u': '\u2154', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /1\/4/, |
|
'u': '\u00BC', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /3\/4/, |
|
'u': '\u00BE', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /1\/5/, |
|
'u': '\u2155', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /2\/5/, |
|
'u': '\u2156', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /3\/5/, |
|
'u': '\u2157', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /4\/5/, |
|
'u': '\u2158', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /1\/6/, |
|
'u': '\u2159', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /5\/6/, |
|
'u': '\u215A', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /1\/7/, |
|
'u': '\u2150', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /1\/8/, |
|
'u': '\u215B', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /3\/8/, |
|
'u': '\u215C', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /5\/8/, |
|
'u': '\u215D', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /7\/8/, |
|
'u': '\u215E', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /1\/9/, |
|
'u': '\u2151', |
|
'n': -1 |
|
}, |
|
{ |
|
'k': /1\/10/, |
|
'u': '\u2152', |
|
'n': -1 |
|
} |
|
]}; |
|
|
|
// Marks ranges in a message where unicode replacements will be ignored (inside URLs). |
|
function getRangesForUnicodeConversion(msg) { |
|
if (!msg) |
|
return; |
|
|
|
var tempMsg = msg; |
|
var results = []; |
|
var regexHttpStart = /http[s]?:\/\//; |
|
var regexHttpEnd = /[ \n\t]/; |
|
var start = 0, end, position, rep = true; |
|
|
|
position = tempMsg.search(regexHttpStart); |
|
while (position !== -1) { |
|
end = start + position; |
|
if (end > start) |
|
results.push({start: start, end: end, replace: rep}); |
|
rep = !rep; |
|
start = end; |
|
tempMsg = tempMsg.substring(position, tempMsg.length); |
|
|
|
if (rep === true) |
|
position = tempMsg.search(regexHttpStart); |
|
else |
|
position = tempMsg.search(regexHttpEnd); |
|
} |
|
|
|
end = msg.length; |
|
if (end > start) |
|
results.push({start: start, end: end, replace: rep}); |
|
|
|
return results; |
|
} |
|
|
|
function getUnicodeReplacement(msg, list, ranges, ta) { |
|
if (!msg || !list || !ranges) |
|
return; |
|
if (ranges.length === 0) |
|
return ''; |
|
|
|
var position, substrings = []; |
|
for (var j = 0; j < ranges.length; j++) { |
|
substrings[j] = msg.substring(ranges[j].start, ranges[j].end); |
|
if (ranges[j].replace === true) { |
|
for (var i = 0; i < list.length; i++) { |
|
position = substrings[j].search(list[i].k); |
|
if (position !== -1 && ta.data('disabledUnicodeRules').indexOf(list[i].u) === -1) { |
|
var oldSubstring = substrings[j]; |
|
substrings[j] = substrings[j].replace(list[i].k, list[i].u); |
|
|
|
var len = oldSubstring.length - substrings[j].length + list[i].u.length; |
|
ta.data('unicodeConversionStack').unshift({ |
|
'k': oldSubstring.substr(position, len), |
|
'u': list[i].u, |
|
'p': ranges[j].start + position |
|
}); |
|
} |
|
} |
|
} |
|
} |
|
var returnString = substrings[0]; |
|
for (var j = 1; j < ranges.length; j++) { |
|
returnString += substrings[j]; |
|
} |
|
return returnString; |
|
} |
|
|
|
function convert2Unicodes(s, ta) { |
|
if (!ta.data('unicodeConversionStack')) // A stack of undo steps |
|
ta.data('unicodeConversionStack', []); |
|
if (!ta.data('disabledUnicodeRules')) // A list of conversion rules that are temporarily disabled |
|
ta.data('disabledUnicodeRules', []); |
|
var ranges = getRangesForUnicodeConversion(s); |
|
|
|
if ($.Options.unicodeConversion.val === 'enable' || $.Options.convertPunctuationsOpt.val) |
|
s = getUnicodeReplacement(s, unicodeConversionList.punctuation, ranges, ta); |
|
if ($.Options.unicodeConversion.val === 'enable' || $.Options.convertEmotionsOpt.val) |
|
s = getUnicodeReplacement(s, unicodeConversionList.emotions, ranges, ta); |
|
if ($.Options.unicodeConversion.val === 'enable' || $.Options.convertSignsOpt.val) |
|
s = getUnicodeReplacement(s, unicodeConversionList.signs, ranges, ta); |
|
if ($.Options.unicodeConversion.val === 'enable' || $.Options.convertFractionsOpt.val) |
|
s = getUnicodeReplacement(s, unicodeConversionList.fractions, ranges, ta); |
|
|
|
if (ta.data('unicodeConversionStack').length > 0) { |
|
var ub = ta.closest('.post-area-new').find('.undo-unicode'); |
|
ub.text(polyglot.t('undo') + ': ' + ta.data('unicodeConversionStack')[0].u); |
|
$.MAL.enableButton(ub); |
|
} else |
|
$.MAL.disableButton(ta.closest('.post-area-new').find('.undo-unicode')); |
|
|
|
return s; |
|
} |
|
|
|
function undoLastUnicode(e) { |
|
e.stopPropagation(); |
|
e.preventDefault(); |
|
|
|
var $ta = $(this).closest('.post-area-new').find('textarea'); |
|
if ($ta.data('unicodeConversionStack').length === 0) |
|
return; |
|
|
|
var uc = $ta.data('unicodeConversionStack').shift(); |
|
|
|
var pt = $ta.val(); |
|
|
|
// If the text was shifted, and character is no longer at the saved position, this function |
|
// searches for it to the right. If it is not there, it searches in the oposite direction. |
|
// if it's not there either, it means it was deleted, so it is skipped. |
|
var substrLeft = pt.substring(0, uc.p); |
|
var substrRight = pt.substring(uc.p, pt.length); |
|
if (substrRight.search(uc.u) !== -1) { |
|
substrRight = substrRight.replace(uc.u, uc.k); |
|
$ta.val(substrLeft + substrRight); |
|
$ta.data('disabledUnicodeRules').push(uc.u); |
|
} else if (substrLeft.search(uc.u) !== -1) { |
|
var closestToTheLeft = substrLeft.lastIndexOf(uc.u); |
|
var substrCenter = substrLeft.substring(closestToTheLeft, substrLeft.length).replace(uc.u, uc.k); |
|
substrLeft = substrLeft.substring(0, closestToTheLeft); |
|
$ta.val(substrLeft + substrCenter + substrRight); |
|
$ta.data('disabledUnicodeRules').push(uc.u); |
|
} |
|
|
|
if ($ta.data('unicodeConversionStack').length > 0) |
|
$(this).text(polyglot.t('undo') + ': ' + $ta.data('unicodeConversionStack')[0].u); |
|
else { |
|
$(this).text('undo'); |
|
$.MAL.disableButton($(this)); |
|
} |
|
} |
|
|
|
function postSubmit(e, oldLastPostId) { |
|
var btnPostSubmit; |
|
|
|
if (e instanceof $) { |
|
btnPostSubmit = e; |
|
//check if previous part was sent... |
|
if (oldLastPostId === lastPostId) { |
|
setTimeout(postSubmit, 1000, btnPostSubmit, oldLastPostId); |
|
return; |
|
} |
|
} else { |
|
e.stopPropagation(); |
|
e.preventDefault(); |
|
btnPostSubmit = $(this); |
|
} |
|
$.MAL.disableButton(btnPostSubmit); |
|
|
|
var textArea = btnPostSubmit.closest('.post-area-new').find('textarea'); |
|
|
|
textArea.siblings('#post-preview').slideUp('fast'); |
|
|
|
var postData = btnPostSubmit.closest('.post-data'); |
|
if (!postData.length) { |
|
postData = btnPostSubmit.closest('.modal-content').find('.post-data'); |
|
} |
|
|
|
if (btnPostSubmit.hasClass('with-reference')) { |
|
var doSubmitPost = function (postText, postData) { |
|
newRtMsg(postData, postText); |
|
} |
|
} else { |
|
if (splitedPostsCount > 1) { |
|
if (textArea.length < splitedPostsCount) { |
|
//current part will be sent as reply to the previous part... |
|
postData = $('<div data-id="' + lastPostId + '" data-screen-name="' + defaultScreenName + '"></div>'); |
|
} |
|
} |
|
|
|
var doSubmitPost = function (postText, postData) { |
|
newPostMsg(postText, postData); |
|
} |
|
} |
|
|
|
if (textArea.length <= 1) { |
|
if (splitedPostsCount > 1) { |
|
var postText = ''; |
|
var reply_to = textArea.attr('data-reply-to'); |
|
var val = textArea.val(); |
|
if (typeof reply_to === 'undefined' || checkPostForMentions(val, reply_to, $.Options.MaxPostEditorChars.val)) |
|
postText = val + ' (' + splitedPostsCount.toString() + '/' + splitedPostsCount.toString() + ')'; |
|
else |
|
postText = reply_to + val + ' (' + splitedPostsCount.toString() + '/' + splitedPostsCount.toString() + ')'; |
|
|
|
doSubmitPost(postText, postData); |
|
} else |
|
doSubmitPost(textArea.val(), postData); |
|
|
|
splitedPostsCount = 1; |
|
} else { |
|
var postText = ''; |
|
var reply_to = textArea.attr('data-reply-to'); |
|
var val = textArea[0].value; |
|
if (typeof reply_to === 'undefined' || checkPostForMentions(val, reply_to, $.Options.MaxPostEditorChars.val)) |
|
postText = val + ' (' + (splitedPostsCount - textArea.length + 1).toString() + '/' + splitedPostsCount.toString() + ')'; |
|
else |
|
postText = reply_to + val + ' (' + (splitedPostsCount - textArea.length + 1).toString() + '/' + splitedPostsCount.toString() + ')'; |
|
|
|
$(textArea[0]).remove(); |
|
|
|
doSubmitPost(postText, postData); |
|
setTimeout(postSubmit, 1000, btnPostSubmit, lastPostId); |
|
|
|
return; |
|
} |
|
|
|
if (btnPostSubmit.closest('.prompt-wrapper').length) |
|
closePrompt(btnPostSubmit); |
|
else { |
|
textArea.val('').attr('placeholder', polyglot.t('Your message was sent!')); |
|
btnPostSubmit.closest('form').find('.post-area-remaining').text(''); |
|
|
|
if (btnPostSubmit.closest('.post-area,.post-reply-content')) { |
|
$('.post-area-new').removeClass('open').find('textarea').blur(); |
|
}; |
|
textArea.data('unicodeConversionStack', []); |
|
textArea.data('disabledUnicodeRules', []); |
|
} |
|
} |
|
|
|
function retweetSubmit(event) { |
|
event.preventDefault(); |
|
event.stopPropagation(); |
|
|
|
var prompt = $(event.target).closest('.prompt-wrapper'); |
|
|
|
newRtMsg(prompt.find('.post-data')); |
|
closePrompt(prompt); |
|
} |
|
|
|
function favSubmit(event) { |
|
event.preventDefault(); |
|
event.stopPropagation(); |
|
|
|
var prompt = $(event.target).closest('.prompt-wrapper'); |
|
var priv = (event.target.className.indexOf('private') > -1); |
|
|
|
newFavMsg(prompt.find('.post-data'), priv); |
|
closePrompt(prompt); |
|
} |
|
|
|
function changeStyle() { |
|
var style, profile, menu; |
|
var theme = $.Options.theme.val; |
|
|
|
if (theme === 'nin') { |
|
style = 'theme_nin/css/style.css'; |
|
profile = 'theme_nin/css/profile.css'; |
|
$.getScript('theme_nin/js/theme_option.js'); |
|
} else if (theme === 'calm') { |
|
style = 'theme_calm/css/style.css'; |
|
profile = 'theme_calm/css/profile.css'; |
|
} else if (theme === 'original') { |
|
style = 'css/style.css'; |
|
profile = 'css/profile.css'; |
|
$.getScript('theme_original/js/theme_option.js'); |
|
} |
|
|
|
$('#stylecss').attr('href', style); |
|
$('#profilecss').attr('href', profile); |
|
$('<style type="text/css"> .selectable_theme:not(.theme_' + theme + ')' + |
|
'{display:none!important;}\n</style>').appendTo('head'); |
|
setTimeout(function() {$(menu).removeAttr('style');}, 0); |
|
} |
|
|
|
function getMentionsForAutoComplete() { |
|
if (defaultScreenName && typeof followingUsers !== 'undefined') { |
|
var suggests = followingUsers.slice(); |
|
|
|
if (suggests.indexOf(defaultScreenName) > -1) |
|
suggests.splice(suggests.indexOf(defaultScreenName), 1); |
|
if (suggests.length > 0) { |
|
suggests.sort(); |
|
|
|
return [{ |
|
mentions: suggests, |
|
match: /\B@(\w*)$/, |
|
search: function (term, callback) { |
|
callback($.map(this.mentions, function (mention) { |
|
return mention.indexOf(term) === 0 ? mention : null; |
|
})); |
|
}, |
|
index: 1, |
|
replace: function (mention) { |
|
return '@'+mention+' '; |
|
} |
|
}]; |
|
} |
|
} |
|
} |
|
|
|
function replaceDashboards() { |
|
var width = $(window).width(); |
|
var wrapper = $('.wrapper'); |
|
|
|
if (width >= 1200 && !wrapper.hasClass('w1200')) { |
|
wrapper.addClass('w1200'); |
|
$('.userMenu').addClass('w1200'); |
|
$('.module.who-to-follow').detach().appendTo($('.dashboard.right')); |
|
$('.module.twistday-reminder').detach().appendTo($('.dashboard.right')); |
|
$('#modals-minimized').addClass('w1200'); |
|
} else if (width < 1200 && wrapper.hasClass('w1200')) { |
|
wrapper.removeClass('w1200'); |
|
$('.userMenu').removeClass('w1200'); |
|
$('.module.who-to-follow').detach().insertAfter($('.module.mini-profile')); |
|
$('.module.twistday-reminder').detach().insertAfter($('.module.toptrends')); |
|
$('#modals-minimized').removeClass('w1200'); |
|
} |
|
} |
|
|
|
function initInterfaceCommon() { |
|
twister.tmpl.modalComponentWarn = extractTemplate('#template-inline-warn'); |
|
twister.tmpl.modalComponentWarn.find('.close').on('click', |
|
function(event) { |
|
var i = $(event.target).closest('.modal-wrapper').attr('data-modal-id'); |
|
|
|
if (!i || !twister.modal[i]) return; |
|
|
|
var modal = twister.modal[i]; |
|
|
|
modal.self.find('.inline-warn').hide(); |
|
|
|
modal.content.outerHeight(modal.self.height() - modal.self.find('.modal-header').outerHeight()); |
|
|
|
var windowHeight = $(window).height(); |
|
if (modal.self.outerHeight() > windowHeight) { |
|
modal.content.outerHeight(modal.content.outerHeight() - modal.self.outerHeight() + windowHeight); |
|
modal.self.outerHeight(windowHeight); |
|
modal.self.css('margin-top', - windowHeight / 2); |
|
} |
|
} |
|
); |
|
twister.tmpl.modalComponentWarn.find('.options .never-again').on('change', |
|
function(event) { |
|
$.Options.set('skipWarn' + $(event.target).closest('.inline-warn') |
|
.attr('data-warn-name'), event.target.checked); // e.g. 'skipWarnFollowersNotAll' |
|
} |
|
); |
|
twister.tmpl.commonDMsList = extractTemplate('#template-direct-messages-list'); |
|
twister.tmpl.uriShortenerMC = extractTemplate('#template-uri-shortener-modal-content'); |
|
twister.tmpl.uriShortenerMC |
|
.find('.shorten-uri').on('click', |
|
{cbFunc: |
|
function (long, short) { |
|
if (short) { |
|
var urisList = getElem('.uri-shortener-modal .uris-list'); |
|
if (urisList.length) { |
|
var item = urisList.find('.short:contains("' + short + '")').closest('li'); |
|
if (!item.length) { |
|
item = twister.tmpl.uriShortenerUrisListItem.clone(true); |
|
item.find('.short').text(short); |
|
item.find('.long').text(long).attr('href', long); |
|
item.appendTo(urisList); |
|
} |
|
urisList.children('.highlighted').removeClass('highlighted'); |
|
item.addClass('highlighted'); |
|
var mc = urisList.closest('.modal-content'); |
|
mc.scrollTop(item.offset().top - mc.offset().top + mc.scrollTop()); |
|
} |
|
showURIPair(long, short); |
|
} else |
|
showURIShortenerErrorRPC(short); |
|
} |
|
}, |
|
function (event) { |
|
muteEvent(event); |
|
openRequestShortURIForm(event); |
|
} |
|
) |
|
.siblings('.clear-cache').on('click', |
|
function () { |
|
confirmPopup({ |
|
txtMessage: polyglot.t('confirm_uri_shortener_clear_cache'), |
|
cbConfirm: function () { |
|
twister.URIs = {}; |
|
$.localStorage.set('twistaURIs', twister.URIs); |
|
getElem('.uri-shortener-modal .uris-list').empty(); |
|
} |
|
}); |
|
} |
|
) |
|
; |
|
twister.tmpl.uriShortenerUrisListItem = extractTemplate('#template-uri-shortener-uris-list-item') |
|
.on('click', function (event) { |
|
var elem = $(event.target); |
|
elem.closest('.uris-list').children('.highlighted').removeClass('highlighted'); |
|
elem.addClass('highlighted'); |
|
}) |
|
; |
|
|
|
getElem('.modal-wrapper .modal-close, .modal-wrapper .modal-blackout').on('click', closeModal); |
|
|
|
$('.minimize-modal').on('click', function (event) { |
|
minimizeModal($(event.target).closest('.modal-wrapper')); |
|
}); |
|
|
|
$('.modal-back').on('click', function() {history.back();}); |
|
|
|
$('.prompt-close').on('click', closePrompt); |
|
|
|
getElem('button.follow', true).on('click', clickFollow); |
|
|
|
$('.following-config-method-buttons .public-following').on('click', function(event) { |
|
setFollowingMethod(event); |
|
closePrompt(event); |
|
}); |
|
|
|
$('.module.mini-profile .open-followers').on('mouseup', {route: '#followers'}, routeOnClick); |
|
|
|
$('.post-text').on('click', 'a', muteEvent); |
|
$('.post-reply').on('click', postReplyClick); |
|
$('.post-propagate').on('click', reTwistPopup); |
|
$('.post-favorite').on('click', favPopup); |
|
$('.userMenu-config').clickoutside(closeThis.bind($('.config-menu'))); |
|
$('.userMenu-config-dropdown').on('click', dropDownMenu); |
|
$('#post-template.module.post').on('click', function(event) { |
|
if (event.button === 0 && window.getSelection() == 0) |
|
postExpandFunction(event, $(this)); |
|
}); |
|
$('.post-area-new') |
|
.on('click', function(e) {composeNewPost(e, $(this));}) |
|
.clickoutside(unfocusPostAreaNew) |
|
.children('textarea') |
|
.on({ |
|
'focus': poseTextareaPostTools, |
|
'input': replyTextInput, // input event fires in modern browsers (IE9+) on any changes in textarea (and copypasting with mouse too) |
|
'input focus': replyTextUpdateRemaining, |
|
'keyup': replyTextKeySend |
|
}) |
|
; |
|
$('.post-submit').on('click', postSubmit); |
|
$('.modal-propagate').on('click', retweetSubmit); |
|
$('.modal-fav-public').on('click', favSubmit); |
|
$('.modal-fav-private').on('click', favSubmit); |
|
$('.expanded-content .show-more').on('mouseup', |
|
{feeder: '.module.post.original.open .module.post.original .post-data'}, handleClickOpenConversation) |
|
.on('click', muteEvent) // to prevent post collapsing |
|
; |
|
if ($.Options.unicodeConversion.val === 'disable') |
|
$('.undo-unicode').on('click', undoLastUnicode).css('display', 'none'); |
|
else |
|
$('.undo-unicode').on('click', undoLastUnicode); |
|
|
|
getElem('.open-profile-modal', true) |
|
.on('click', muteEvent).on('mouseup', handleClickOpenProfileModal); |
|
//$('.open-hashtag-modal').on('click', openHashtagModal); |
|
//$('.open-following-modal').on('click', openFollowingModal); |
|
$('.userMenu-connections a').on('click', openMentionsModal); |
|
$('.mentions-from-user').on('click', openMentionsModal); |
|
$('.userMenu-favs a').on('click', openFavsModal); |
|
$('.favs-from-user').on('click', openFavsModal); |
|
|
|
getElem('.latest-activity', true).on('mouseup', |
|
{feeder: '.latest-activity'}, handleClickOpenConversation); |
|
|
|
replaceDashboards(); |
|
$(window).resize(replaceDashboards); |
|
|
|
$('.profile-card .profile-bio .group-description') |
|
.on('focus', function (event) { |
|
$(event.target) |
|
.siblings('.save').show() |
|
.siblings('.cancel').show() |
|
; |
|
}) |
|
.on('input', |
|
{parentSelector: '.profile-bio', enterSelector: '.save'}, inputEnterActivator) |
|
.siblings('.save').on('click', function (event) { |
|
var elemEvent = $(event.target); |
|
var descElem = elemEvent.siblings('.group-description'); |
|
|
|
groupMsgSetGroupDescription(elemEvent.closest('.profile-card').attr('data-screen-name'), |
|
descElem.val().trim(), |
|
function(req) { |
|
req.descElem.attr('val-origin', req.descElem.val().trim()) |
|
.siblings('.save').hide() |
|
.siblings('.cancel').hide() |
|
; |
|
}, {descElem: descElem} |
|
); |
|
}) |
|
.siblings('.cancel').on('click', function (event) { // FIXME it would be nice to bind some 'clickoutside' event instead and remove cancel button, but current implementation of that doesn't unbind events when element dies |
|
var descElem = $(event.target).hide() |
|
.siblings('.save').hide() |
|
.siblings('.group-description') |
|
; |
|
|
|
descElem.val(descElem.attr('val-origin')); |
|
}) |
|
; |
|
|
|
$('.tox-ctc').on('click', promptCopyAttrData); |
|
$('.bitmessage-ctc').on('click', promptCopyAttrData); |
|
|
|
$('.uri-shortener').on('mouseup', {route: '#/uri-shortener'}, routeOnClick); |
|
|
|
$('.updates-check-client').text(polyglot.t('updates_check_client')) |
|
.on('mouseup', function (event) { |
|
muteEvent(event); |
|
checkUpdatesClient(true); |
|
} |
|
); |
|
|
|
$('.post-area-new textarea') |
|
.on('focus', |
|
function (event) { |
|
twister.focus.textareaPostCur = $(event.target); |
|
if ($.fn.textcomplete) { // because some pages don't have that. // network.html |
|
event.data = {req: getMentionsForAutoComplete}; |
|
setTextcompleteOnEventTarget(event); |
|
} |
|
} |
|
) |
|
.on('focusout', |
|
function (event) { |
|
twister.focus.textareaPostCur = undefined; |
|
twister.focus.textareaPostPrev = $(event.target); |
|
if ($.fn.textcomplete) // because some pages don't have that. // network.html |
|
unsetTextcompleteOnEventTarget(event); |
|
} |
|
) |
|
; |
|
} |
|
|
|
function extractTemplate(selector) { |
|
return $(selector).appendTo(twister.tmpl.root).children(); |
|
} |
|
|
|
function promptCopyAttrData(event) { |
|
window.prompt(polyglot.t('copy_to_clipboard'), $(event.target).attr('data')); |
|
} |
|
|
|
function initInterfaceModule(module) { |
|
return $('.module.'+module).html($('#'+module+'-template').html()).show(); |
|
} |
|
|
|
function killInterfaceModule(module) { |
|
$('.module.'+module).empty().hide(); |
|
} |
|
|
|
function elemFitNextIntoParentHeight(elem) { |
|
var parent = elem.parent(); |
|
var elemNext = elem.nextAll(); |
|
var elemNextHeight = parent.actual('height') - elem.actual('outerHeight', {includeMargin: true}); |
|
|
|
if (elemNextHeight > 0) // FIXME actually it's here because of nin theme's two vertical columns layout of profile modal |
|
elemNext.outerHeight(elemNextHeight); |
|
else |
|
elemNext.outerHeight(parent.actual('outerHeight')); |
|
} |
|
|
|
function inputEnterActivator(event) { |
|
var elemEvent = $(event.target); |
|
elemEvent.closest(event.data.parentSelector).find(event.data.enterSelector) |
|
.attr('disabled', elemEvent.val().trim() === ''); |
|
} |
|
|
|
function pasteToTextarea(ta, p) { |
|
if (!ta || typeof ta.val !== 'function') return; |
|
|
|
var s = ta.val(); |
|
var c = ta.caret(); |
|
|
|
if (s.length) { |
|
if (c < 1) |
|
ta.val(p + (s[c].match(/\s/) ? '' : ' ') + s); |
|
else if (c < s.length) |
|
ta.val(s.slice(0, c) + (s[c - 1].match(/\s/) ? '' : ' ') + p + (s[c].match(/\s/) ? '' : ' ') + s.slice(c)); |
|
else |
|
ta.val(s + (s[c - 1].match(/\s/) ? '' : ' ') + p + ' '); |
|
} else |
|
ta.val(p + ' '); |
|
|
|
ta.focus().caret(c + p.length + ((ta.val().length - s.length - p.length) > 1 ? 2 : 1)); |
|
} |
|
|
|
function setTextcompleteOnEventTarget(event) { |
|
// cursor has not set yet and we need to wait 100ms to skip global click event |
|
setTimeout(setTextcompleteOnElement, 100, event.target, |
|
typeof event.data.req === 'function' ? event.data.req() : event.data.req); |
|
} |
|
|
|
function setTextcompleteOnElement(elem, req) { |
|
elem = $(elem); |
|
elem.textcomplete(req, { |
|
appendTo: (elem.closest('.dashboard').length) ? elem.parent() : $('body'), |
|
listPosition: setTextcompleteDropdownListPos |
|
}); |
|
} |
|
|
|
function unsetTextcompleteOnEventTarget(event) { |
|
$(event.target).textcomplete('destroy'); |
|
} |
|
|
|
// following workaround function is for calls from $.fn.textcomplete only |
|
// we need this because currently implementation of caret position detection is way too imperfect |
|
function setTextcompleteDropdownListPos(position) { |
|
position = this._applyPlacement(position); |
|
|
|
if (this.option.appendTo.closest('.dashboard').length > 0) { |
|
position.position = 'fixed'; |
|
position.top = (parseFloat(position.top) - window.pageYOffset).toString() + 'px'; |
|
} else |
|
position.position = 'absolute'; |
|
|
|
this.$el.css(position); |
|
|
|
return this; |
|
} |
|
|
|
$(document).ready(function () { |
|
setupTimeGmtToText($.Options.locLang.val); |
|
if ($.localStorage.isSet('twistaURIs')) |
|
twister.URIs = $.localStorage.get('twistaURIs'); |
|
twister.html.blanka.appendTo('body').hide(); |
|
twister.tmpl.loginMC = extractTemplate('#template-login-modal'); |
|
twister.tmpl.loginMC.find('.login').on('click', handleClickAccountLoginLogin); |
|
var module = twister.tmpl.loginMC.closest('.create-account'); |
|
module.find('.alias').on('input', handleInputAccountCreateSetReq); |
|
module.find('.check').on('click', handleClickAccountCreateCheckReq); |
|
module.find('.create').on('click', handleClickAccountCreateCreate); |
|
module = twister.tmpl.loginMC.closest('.import-account'); |
|
module.find('.secret-key').on('input', handleInputAccountImportSetReq); |
|
module.find('.alias').on('input', handleInputAccountImportSetReq); |
|
module.find('.import').on('click', handleClickAccountImportImport); |
|
twister.tmpl.followersList = extractTemplate('#template-followers-list'); |
|
twister.tmpl.followersPeer = extractTemplate('#template-followers-peer'); |
|
twister.tmpl.followingList = extractTemplate('#template-following-list'); |
|
twister.tmpl.followingPeer = extractTemplate('#template-following-peer'); |
|
twister.tmpl.whoTofollowPeer = extractTemplate('#template-whotofollow-peer'); |
|
twister.tmpl.commonDMsListItem = extractTemplate('#template-direct-messages-list-item') |
|
.on('mouseup', function (event) { |
|
event.data = {route: |
|
$.MAL.dmchatUrl($(event.target).closest('.module').attr('data-screen-name'))}; |
|
routeOnClick(event); |
|
}) |
|
; |
|
twister.tmpl.shortenUri = extractTemplate('#template-shorten-uri') |
|
.on('click', |
|
{cbFunc: |
|
function (uriLong, uriShort, textArea) { |
|
if (uriShort) |
|
pasteToTextarea(textArea, uriShort); |
|
else |
|
showURIShortenerErrorRPC(uriShort); |
|
} |
|
}, |
|
function (event) { |
|
muteEvent(event); |
|
|
|
var postAreaNew = $(event.target).closest('.post-area-new'); |
|
var textArea = postAreaNew.find(twister.focus.textareaPostCur); |
|
if (!textArea.length) textArea = postAreaNew.find(twister.focus.textareaPostPrev); |
|
if (!textArea.length) textArea = postAreaNew.find('textarea:last'); |
|
|
|
event.data.cbReq = textArea; |
|
if (postAreaNew.closest('.directMessages').length) |
|
confirmPopup({ |
|
txtMessage: polyglot.t('shorten_URI_its_public_is_it_ok'), |
|
txtConfirm: polyglot.t('shorten_URI'), |
|
cbConfirm: openRequestShortURIForm, |
|
cbConfirmReq: event |
|
}); |
|
else if ($.mobile && postAreaNew.closest('.dm-form').length) { |
|
if (confirm(polyglot.t('shorten_URI_its_public_is_it_ok'))) |
|
openRequestShortURIForm(event); |
|
} else |
|
openRequestShortURIForm(event); |
|
} |
|
) |
|
; |
|
twister.tmpl.postTextareaEditBar = extractTemplate('#template-post-textarea-edit-bar') |
|
.append(twister.tmpl.shortenUri.clone(true)) |
|
; |
|
twister.tmpl.postRtReference = extractTemplate('#template-post-rt-reference') |
|
.on('mouseup', {feeder: '.post-rt-reference'}, handleClickOpenConversation) |
|
.on('click', muteEvent) // to prevent post expanding or collapsing |
|
; |
|
twister.tmpl.postRtBy = extractTemplate('#template-post-rt-by'); |
|
twister.tmpl.profileShowMoreFollowers = extractTemplate('#template-profile-show-more-followers'); |
|
|
|
var path = window.location.pathname; |
|
var page = path.split("/").pop(); |
|
if (page.indexOf('network.html') === 0) { |
|
initInterfaceNetwork(); |
|
} else if (page.indexOf('options.html') === 0) { |
|
initInterfaceCommon(); |
|
$.Options.initControls(); |
|
} else if (page.indexOf("profile-edit.html") === 0) { |
|
initProfileEdit(); |
|
} |
|
|
|
changeStyle(); |
|
|
|
// need to play something once in result of click on Firefox to be able to play sound notifications |
|
// FXIME it's just workaround of FF's bug, see https://bugzilla.mozilla.org/show_bug.cgi?id=1197631 |
|
$('body').on('click', function () { |
|
playSound('player', $.Options.sndMention.val, 0.001); |
|
setTimeout(function () {$('body').off('click');}, 10000); // sound must be played before unbinding |
|
}); |
|
});
|
|
|