// 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 = { profiles: {}, avatars: {}, URIs: {}, // shortened URIs are cached here after fetching torrentIds: {}, // auto-download torrentIds focus: {}, // focused elements are counted here html: { detached: $('
'), // here elements go to detach themself blanka: $('') // to open stuff in new tab, see routeOnClick() }, tmpl: { // templates pointers are stored here root: $('
') // templates should be detached from DOM and attached here; use extractTemplate() }, modal: {}, res: {}, // reses for various reqs are cached here twists: {}, var: { dateFormatter: { format: function (req) { return req.toString().replace(/GMT.*/g, ''); } }, initializated: false, isCurrentInputSplittable: false, 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 = $('
').appendTo(twister.html.detached); // here modal goes instead detaching modal.content.outerHeight(modal.self.height() - modal.self.find('.modal-header').outerHeight() - (modal.warn && !$.Options.get('skipWarn' + modal.warn.name) ? modal.self.find('.inline-warn').outerHeight() : 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 = $('
  • ' + modal.find('.modal-header h3').text() + '
  • ') .on('click', {hashString: window.location.hash}, function (event) { if (!event.button) // only if left mouse (button is 0) or elem.trigger('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.trigger('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 openModalAccount() { if (!defaultScreenName) { alertPopup({ //txtTitle: polyglot.t(''), add some title (not 'error', please) or just KISS txtMessage: polyglot.t('username_undefined') }); history.back(); return; } var modal = openModal({ classAdd: 'account-modal', content: twister.tmpl.accountMC.clone(true), title: polyglot.t('Edit profile') }); modal.content.find('.alias').text('@' + defaultScreenName); loadProfileForEdit(defaultScreenName, { fullname: modal.content.find('.input-name').attr('placeholder', polyglot.t('Full name here')), bio: modal.content.find('.input-bio').attr('placeholder', polyglot.t('Describe yourself')), location: modal.content.find('.input-location').attr('placeholder', polyglot.t('Location')), url: modal.content.find('.input-url').attr('placeholder', polyglot.t('website')), tox: modal.content.find('.input-tox').attr('placeholder', polyglot.t('Tox address')), bitmessage: modal.content.find('.input-bitmessage').attr('placeholder', polyglot.t('Bitmessage address')) }); loadAvatarForEdit(defaultScreenName, modal.content.find('.avatar img')); dumpPubkey(defaultScreenName, checkAccountRegistrationCB, { peerAlias: defaultScreenName, cbFunc: checkAccountRegistrationCB, submitChangesElem: modal.content.find('.submit-changes') }); } function checkAccountRegistrationCB(req, res) { if (res.length > 0) { if (followingUsers.indexOf('twister') !== -1) { if (!isModalWithElemExists(req.submitChangesElem)) return; req.submitChangesElem.attr('data-blocked', 'false'); if (req.submitChangesElem.attr('data-profile-changed') === 'true' || req.submitChangesElem.attr('data-avatar-changed') === 'true') $.MAL.enableButton(req.submitChangesElem); } else { follow('twister', true, function (req) { // TODO add alertPopup() with welcome message if (!isModalWithElemExists(req)) return; req.attr('data-blocked', 'false'); if (req.attr('data-profile-changed') === 'true' || req.attr('data-avatar-changed') === 'true') $.MAL.enableButton(req); }, req.submitChangesElem ); } } else { if (!req.notYetAcceptedWarnDisplayed) { req.notYetAcceptedWarnDisplayed = true; alertPopup({ //txtTitle: polyglot.t(''), add some title or just KISS txtMessage: polyglot.t('user_not_yet_accepted') }); } setTimeout(dumpPubkey, 5000, req.peerAlias, req.cbFunc, req); } } function handleAvatarFileSelect(event, avatarElem) { if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { alert('The File APIs are not fully supported in this browser.'); return; } if (!event || !event.target || !event.target.files || !event.target.files.length || !avatarElem || !avatarElem.length) return; var file = event.target.files[0]; if (!file.type.startsWith('image/')) return; var reader = new FileReader(); reader.data = avatarElem; reader.onload = function (event) { var img = document.createElement('img'); img.data = event.target.data; img.onload = function (event) { var ratio = 64 / Math.max(event.target.width, event.target.height); var canvas = document.createElement('canvas'); canvas.width = Math.round(event.target.width * ratio); canvas.height = Math.round(event.target.height * ratio); canvas.getContext('2d').drawImage(event.target, 0, 0, canvas.width, canvas.height); var imgURL; for (var quality = 1.0; (!imgURL || imgURL.length > 4096) && quality > 0.1; quality -= 0.01) imgURL = canvas.toDataURL('image/jpeg', quality); event.target.data.attr('src', imgURL); }; img.src = event.target.result; }; reader.readAsDataURL(file); } 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: '' + groupAlias + ''}) }); 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').prop('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 = $('') .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 = $('') .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, // it's impossible yet to get or to set an avatar of a group itself item.find('.post-photo').attr('data-peer-alias', message.from).find('img')); 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( '…
    ' + polyglot.t('busted_oh') + ' ' + polyglot.t('busted_avowal') + ':
    ' + long .replace(/&(?!lt;|gt;)/g, '&') .replace(/"/g, '"') .replace(/'/g, ''') + '

    ' ); 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 = $('
    Start WebTorrent download of this media'); startTorrentLink.on('click', function(event) { event.stopPropagation(); startTorrentDownloadAndPreview(long, previewContainer, isMedia) }); previewContainer.append(startTorrentLink); } } else { var enableWebTorrentWarning = $('' + polyglot.t('Enable WebTorrent support in options page to display this content') + ''); 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 = $(''); previewContainer.append(speedStatus); function updateSpeed () { var progress = (100 * torrent.progress).toFixed(1) speedStatus[0].innerHTML = 'Peers: ' + torrent.numPeers + ' ' + 'Progress: ' + progress + '% ' + 'Download: ' + torrent.downloadSpeed + '/s ' + 'Upload: ' + 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|webp|mp4|webm|mp3|ogg|wav|)$/i.test(file.name) } if (isMedia) { var imagePreview = $('
    '); 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 = $('' + 'Download ' + file.name + ''); 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].trigger('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.trigger('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.trigger('click'); return; } var hashstring = decodeURIComponent(window.location.hash); if (hashstring === '') { closeModal(); // close active modal(s) return; } var hashdata = hashstring.split(':'); if (!twister.var.initializated) { setTimeout(loadModalFromHash, 1000); return; } // 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 === '#/account') openModalAccount(); 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 = JSON.parse($(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.trigger('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 = JSON.parse($(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.trigger('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: ''+post.userpost.n+''}) }); 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 = $('
      ', {class:'expanded-post'}).appendTo(postLi); var originalLi = $('
    1. ', {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(originalPost.attr('data-screen-name'), originalPost.attr('data-id')); } 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, JSON.parse(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(); } function postTranslateClick(event) { if ($.Options.TranslationEnabled.val === 'enable' && $.Options.TranslationAPI.val.length > 0) { var post = $(this).closest('.post').find('.post-text'); // prevent merge string on tabulation usage post.html(post.html().replaceAll('
      ',' ')); // some APIs not accept slash as the part of the get request routing var request = post.text().replaceAll('/','|'); $.ajax({ dataType: 'json', url: $.Options.TranslationAPI.val + encodeURIComponent(request), success: function(json) { if (json.translation) { var translation = htmlFormatMsg(json.translation.replaceAll('|','/')); post.html(translation.html); } if (json.error) { alert(json.error); } }, error: function(xhr, ajaxOptions, thrownError) { alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText); } }) } 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); } 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').trigger('focus'); } function prepareTextareaToInput(event) { var elem = $(event.target); if (!elem.closest('.directMessages').length && !elem.closest('.network').length && ($.Options.splitPosts.val === 'enable' || ($.Options.splitPosts.val === 'only-new' && !(elem.closest('.post-data').length || elem.closest('.modal-content').find('.post-data').length)))) twister.var.isCurrentInputSplittable = true; else twister.var.isCurrentInputSplittable = false; poseTextareaPostTools(elem); } 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')) ; if (textArea.closest('.network').length) editBar.find('.shorten-uri').remove(); else 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 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 (twister.var.isCurrentInputSplittable) { 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.trigger('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 (twister.var.isCurrentInputSplittable && 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 (twister.var.isCurrentInputSplittable && 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.trigger('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, postDataElem) { newRtMsg(JSON.parse(postDataElem.attr('data-content_to_rt')), postDataElem.attr('data-content_to_sigrt'), postText, updateRTsWithOwnOne ); }; } else { if (splitedPostsCount > 1) { if (textArea.length < splitedPostsCount) { //current part will be sent as reply to the previous part... postData = $('
      '); } } if (postData.length) var doSubmitPost = function (postText, postDataElem) { var reply_n = postDataElem.attr('data-screen-name'); var reply_k = parseInt(postDataElem.attr('data-id')); var part_id = postDataElem.attr('data-reply-part-id'); var cbFunc = function (req, ret) { var postDataElem = getElem('.expanded-post .post-data' + '[data-screen-name=\'' + req.reply_n + '\']' + '[data-id=\'' + req.reply_k + '\']'); if (!postDataElem.length) { if (req.part_id && ++req.tries < 5) setTimeout(req.cbFunc, 2000, req); return; } for (var k = 0, twistElem = undefined; k < postDataElem.length; k++) { var formerPostElem = postDataElem.eq(k).closest('li.post'); if (!formerPostElem.next().hasClass('post-replies')) var containerElem = $('
      1. ') // FIXME replace with template as like as a reqRepAfterCB()'s similar thing .insertAfter(formerPostElem) .children('.sub-replies') ; else { var containerElem = formerPostElem.next().children('.sub-replies'); if (containerElem.find('.post-data' + '[data-screen-name=\'' + ret.userpost.n + '\']' + '[data-id=\'' + ret.userpost.k + '\']').length) continue; } if (typeof twistElem !== 'undefined') twistElem.clone(true) .appendTo(containerElem) .slideDown('fast') ; else twistElem = postToElem(ret, 'related').hide() .addClass('new') .appendTo(containerElem) .slideDown('fast') ; } }; newPostMsg(postText, reply_n, reply_k, cbFunc, {reply_n: reply_n, reply_k: reply_k, part_id: part_id, cbFunc: cbFunc, tries: 0} ); }; else var doSubmitPost = function (postText) { newPostMsg(postText); }; } 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').length) { btnPostSubmit.closest('.post-area-new').removeClass('open') .find('textarea').trigger('blur'); } textArea.data('unicodeConversionStack', []); textArea.data('disabledUnicodeRules', []); } } function retweetSubmit(event) { event.preventDefault(); event.stopPropagation(); var prompt = $(event.target).closest('.prompt-wrapper'); var postDataElem = prompt.find('.post-data'); newRtMsg(JSON.parse(postDataElem.attr('data-content_to_rt')), postDataElem.attr('data-content_to_sigrt'), '', updateRTsWithOwnOne ); 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'; // we use .ajax because .getScript requires 'unsafe-inline' CSP rule for now, see https://github.com/jquery/jquery/issues/3969 $.ajax({dataType: 'text', url: 'theme_nin/js/theme_option.js'}) .done(function(res) {eval(res);}); } else if (theme === 'nin_night') { style = 'theme_nin_night/css/style.css'; profile = 'theme_nin_night/css/profile.css'; $.ajax({dataType: 'text', url: 'theme_nin_night/js/theme_option.js'}) .done(function(res) {eval(res);}); } else if (theme === 'nin_original') { theme = 'nin'; // related to native theme in class definitions, so easiest way to integrate original version style = 'theme_nin_original/css/style.css'; profile = 'theme_nin_original/css/profile.css'; $.ajax({dataType: 'text', url: 'theme_nin_original/js/theme_option.js'}) .done(function(res) {eval(res);}); } 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'; $.ajax({dataType: 'text', url: 'theme_original/js/theme_option.js'}) .done(function(res) {eval(res);}); } $('#stylecss').attr('href', style); $('#profilecss').attr('href', profile); $('').appendTo('head'); setTimeout(function() {console.log($(menu)); $(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); $('.userMenu-config').clickoutside(closeThis.bind($('.config-menu'))); $('.userMenu-config-dropdown').on('click', dropDownMenu); $('.post-area-new') .on('click', function (e) {composeNewPost(e, $(this));}) .clickoutside(unfocusPostAreaNew) .children('textarea') .on({ 'focus': prepareTextareaToInput, '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); 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).on('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); } ) ; twister.tmpl.post = extractTemplate('#template-post') .on('click', function (event) { if (event.button === 0 && window.getSelection() == 0) postExpandFunction(event, $(this)); }) ; if ($.Options.TranslationEnabled.val !== 'enable') { twister.tmpl.post.find('.post-translate').remove(); } twister.tmpl.post.find('.post-translate').on('click', postTranslateClick); twister.tmpl.post.find('.post-reply').on('click', postReplyClick); twister.tmpl.post.find('.post-propagate').on('click', reTwistPopup); twister.tmpl.post.find('.post-favorite').on('click', favPopup); twister.tmpl.post.find('.expanded-content .show-more') .on('mouseup', {feeder: '.module.post.original.open .module.post.original .post-data'}, handleClickOpenConversation ) .on('click', muteEvent) // to prevent post collapsing ; twister.tmpl.post.find('.new-replies-available button').hide() .on('click', function (event) { event.stopPropagation(); $(event.target).hide() .closest('li.post').next('.post-replies').find('.post.pending') .removeClass('pending').slideDown('fast') ; }) ; twister.tmpl.post.find('.post-stats').hide(); twister.tmpl.accountMC = extractTemplate('#template-account-modal'); twister.tmpl.accountMC.find('.avatar').on('click', function (event) { $(event.target).closest('.modal-content').find('.input-avatar').trigger('click'); } ); twister.tmpl.accountMC.find('input[type="text"], textarea').on('input', function (event) { var saveElem = $(event.target).closest('.modal-content').find('.submit-changes') .attr('data-profile-changed', 'true'); if (saveElem.prop('disabled') && saveElem.attr('data-blocked') !== 'true') $.MAL.enableButton(saveElem); }); twister.tmpl.accountMC.filter('.input-avatar').on('change', function (event) { var containerElem = $(event.target).closest('.modal-content'); var saveElem = containerElem.find('.submit-changes') .attr('data-avatar-changed', 'true'); if (saveElem.prop('disabled') && saveElem.attr('data-blocked') !== 'true') $.MAL.enableButton(saveElem); handleAvatarFileSelect(event, containerElem.find('.avatar img')); }); twister.tmpl.accountMC.find('.submit-changes').attr('data-blocked', 'true') .on('click', function (event) { var saveElem = $(event.target); var containerElem = saveElem.closest('.modal-content'); $.MAL.disableButton(saveElem); var profileDataChanged = saveElem.attr('data-profile-changed') === 'true'; var avatarDataChanged = saveElem.attr('data-avatar-changed') === 'true'; if (profileDataChanged) { saveElem.attr('data-profile-changed', 'false'); var profileData = { fullname: containerElem.find('.input-name').val(), bio: containerElem.find('.input-bio').val(), location: containerElem.find('.input-location').val(), url: containerElem.find('.input-url').val(), tox: containerElem.find('.input-tox').val(), bitmessage: containerElem.find('.input-bitmessage').val() }; } if (avatarDataChanged) { saveElem.attr('data-avatar-changed', 'false'); var avatarData = containerElem.find('.avatar img').attr('src'); } if (profileDataChanged && avatarDataChanged) { redrawProfileAndAvatar(defaultScreenName, profileData, avatarData); saveProfile(defaultScreenName, profileData, function (req) { saveAvatar(req.peerAlias, req.avatarData, alertPopup, { txtMessage: polyglot.t('profile_saved') }, alertPopup, { txtMessage: polyglot.t('profile_not_saved') + '\n\nCan\'t save avatar data.', cbConfirm: function (req) { $.MAL.enableButton(req.attr('data-avatar-changed', 'true')); }, cbConfirmReq: req.saveElem, cbClose: 'cbConfirm' } ); }, { peerAlias: defaultScreenName, avatarData: avatarData, saveElem: saveElem }, alertPopup, { txtMessage: polyglot.t('profile_not_saved') + '\n\nCan\'t save profile data.', cbConfirm: function (req) { $.MAL.enableButton(req.attr('data-profile-changed', 'true')); }, cbConfirmReq: saveElem, cbClose: 'cbConfirm' } ); } else if (profileDataChanged) { redrawProfile(defaultScreenName, profileData); saveProfile(defaultScreenName, profileData, alertPopup, { txtMessage: polyglot.t('profile_saved') }, alertPopup, { txtMessage: polyglot.t('profile_not_saved') + '\n\nCan\'t save profile data.', cbConfirm: function (req) { $.MAL.enableButton(req.attr('data-profile-changed', 'true')); }, cbConfirmReq: saveElem, cbClose: 'cbConfirm' } ); } else if (avatarDataChanged) { redrawAvatar(defaultScreenName, avatarData); saveAvatar(defaultScreenName, avatarData, alertPopup, { txtMessage: polyglot.t('profile_saved') }, alertPopup, { txtMessage: polyglot.t('profile_not_saved') + '\n\nCan\'t save avatar data.', cbConfirm: function (req) { $.MAL.enableButton(req.attr('data-avatar-changed', 'true')); }, cbConfirmReq: saveElem, cbClose: 'cbConfirm' } ); } else $.MAL.enableButton(saveElem); }) ; $.MAL.disableButton(twister.tmpl.accountMC.find('.submit-changes')); twister.tmpl.accountMC.filter('.secret-key-container').hide(); twister.tmpl.accountMC.find('.toggle-secret-key').on('click', function (event) { var containerElem = $(event.target).closest('.modal-content').find('.secret-key-container'); if (containerElem.is(':visible')) containerElem.slideUp('fast'); else { var secretKeyElem = containerElem.find('.secret-key'); if (secretKeyElem.text()) containerElem.slideDown('fast'); else dumpPrivkey(defaultScreenName, function(req, res) { req.secretKeyElem.text(res); req.containerElem.slideDown('fast'); }, { containerElem: containerElem, secretKeyElem: secretKeyElem } ); } }); } 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) .prop('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.trigger('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; } $(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.avatarTiny = extractTemplate('#template-avatar-tiny'); 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(); } 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 () { if ($.Options.sndMention.val !== 'false') playSound('player', $.Options.sndMention.val, 0.001); setTimeout(function () {$('body').off('click');}, 10000); // sound must be played before unbinding }); });