mirror of
https://github.com/twisterarmy/twister-calm.git
synced 2025-01-11 23:47:59 +00:00
b54e611797
change files order, new DHT indicator in top menu, ‘back’ button in DM conversations, sound notifications (alpha) and new page ‘Options’, few fixes
554 lines
19 KiB
JavaScript
554 lines
19 KiB
JavaScript
// twister_following.js
|
|
// 2013 Miguel Freitas
|
|
//
|
|
// Manage list of following users. Load/Save to localstorage and DHT.
|
|
// Provides random user suggestions to follow.
|
|
|
|
var followingUsers = [];
|
|
var _isFollowPublic = {};
|
|
var _followsPerPage = 200;
|
|
var _maxFollowingPages = 50;
|
|
var _followingSeqNum = 0;
|
|
var _followSuggestions = [];
|
|
var _searchingPartialUsers = "";
|
|
var _searchKeypressTimer = undefined;
|
|
var _lastSearchUsersResults = [];
|
|
var _lastLoadFromDhtTime = 0;
|
|
|
|
// load followingUsers from localStorage
|
|
function loadFollowingFromStorage() {
|
|
var ns=$.initNamespaceStorage(defaultScreenName);
|
|
if( ns.localStorage.isSet("followingUsers") )
|
|
followingUsers = ns.localStorage.get("followingUsers");
|
|
if( ns.localStorage.isSet("isFollowPublic") )
|
|
_isFollowPublic = ns.localStorage.get("isFollowPublic");
|
|
if( ns.localStorage.get("followingSeqNum") > _followingSeqNum)
|
|
_followingSeqNum = ns.localStorage.get("followingSeqNum");
|
|
if( ns.localStorage.isSet("lastLoadFromDhtTime") )
|
|
_lastLoadFromDhtTime = ns.localStorage.get("lastLoadFromDhtTime");
|
|
// follow ourselves
|
|
if(followingUsers.indexOf(defaultScreenName) < 0) {
|
|
followingUsers.push(defaultScreenName);
|
|
}
|
|
}
|
|
|
|
// save list of following to localStorage
|
|
function saveFollowingToStorage() {
|
|
var ns=$.initNamespaceStorage(defaultScreenName);
|
|
ns.localStorage.set("followingUsers", followingUsers);
|
|
ns.localStorage.set("isFollowPublic", _isFollowPublic);
|
|
ns.localStorage.set("followingSeqNum", _followingSeqNum);
|
|
ns.localStorage.set("lastLoadFromDhtTime", _lastLoadFromDhtTime);
|
|
}
|
|
|
|
// load public list of following users from dht resources
|
|
// "following1", "following2" etc.
|
|
// it will stop loading when resource is empty
|
|
// callback is called as: doneCb(doneArg, followingList, seqNum)
|
|
function loadFollowingFromDht(username, pageNumber, followingList, seqNum, doneCb, doneArg) {
|
|
if( !pageNumber ) pageNumber = 1;
|
|
|
|
dhtget( username, "following" + pageNumber, "s",
|
|
function(args, following, rawdata) {
|
|
if( rawdata ) {
|
|
var seq = parseInt(rawdata[0]["p"]["seq"]);
|
|
if( seq > args.seqNum ) args.seqNum = seq;
|
|
}
|
|
|
|
if( following ) {
|
|
for( var i = 0; i < following.length; i++ ) {
|
|
if( args.followingList.indexOf(following[i]) < 0 ) {
|
|
args.followingList.push(following[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if( following && following.length && args.pageNumber < _maxFollowingPages) {
|
|
loadFollowingFromDht(username, args.pageNumber,
|
|
args.followingList, args.seqNum,
|
|
args.doneCb, args.doneArg);
|
|
} else {
|
|
if( args.doneCb )
|
|
args.doneCb(args.doneArg, args.followingList, args.seqNum);
|
|
}
|
|
}, {pageNumber:pageNumber+1, followingList:followingList, seqNum:seqNum,
|
|
doneCb:doneCb, doneArg:doneArg});
|
|
}
|
|
|
|
// get number of following from dht and set item.text()
|
|
function getNumFollowing( username, item ) {
|
|
loadFollowingFromDht( username, 1, [], 0,
|
|
function(args, following, seqNum) {
|
|
item.text( following.length );
|
|
}, null);
|
|
}
|
|
|
|
function loadFollowingIntoList( username, html_list ) {
|
|
loadFollowingFromDht( username, 1, [], 0,
|
|
function(args, following, seqNum) {
|
|
html_list.html("");
|
|
$.each(following, function(i, following_user){
|
|
var following_user_li = $( "#following-by-user-template" ).children().clone(true);
|
|
|
|
// link follower to profile page
|
|
$(following_user_li.children()[0]).attr("data-screen-name", following_user);
|
|
$(following_user_li.children()[0]).attr("href", $.MAL.userUrl(following_user));
|
|
|
|
following_user_li.find(".following-screen-name b").text(following_user);
|
|
getAvatar( following_user, following_user_li.find(".mini-profile-photo") );
|
|
var $followingName = following_user_li.find(".mini-following-name");
|
|
$followingName.text(following_user);
|
|
getFullname( following_user, $followingName );
|
|
|
|
html_list.append( following_user_li );
|
|
});
|
|
}, null);
|
|
}
|
|
|
|
// load following list from localStorage and then from the dht resource
|
|
function loadFollowing(cbFunc, cbArg) {
|
|
loadFollowingFromStorage();
|
|
updateFollowing();
|
|
|
|
var curTime = new Date().getTime() / 1000;
|
|
|
|
// optimization to avoid costly dht lookup everytime the home is loaded
|
|
if( curTime > _lastLoadFromDhtTime + 3600*24 ||
|
|
document.URL.indexOf("following") >= 0 ) {
|
|
var numFollow = followingUsers.length;
|
|
loadFollowingFromDht( defaultScreenName, 1, [], _followingSeqNum,
|
|
function(args, following, seqNum) {
|
|
var curTime = new Date().getTime() / 1000;
|
|
_lastLoadFromDhtTime = curTime;
|
|
|
|
for( var i = 0; i < following.length; i++ ) {
|
|
if( followingUsers.indexOf(following[i]) < 0 ) {
|
|
followingUsers.push(following[i]);
|
|
}
|
|
_isFollowPublic[following[i]] = true;
|
|
}
|
|
|
|
if( args.numFollow != followingUsers.length ||
|
|
seqNum != _followingSeqNum ) {
|
|
_followingSeqNum = seqNum;
|
|
// new following loaded from dht
|
|
saveFollowingToStorage();
|
|
updateFollowing();
|
|
}
|
|
|
|
if( args.cbFunc )
|
|
args.cbFunc(args.cbArg);
|
|
}, {numFollow:numFollow, cbFunc:cbFunc, cbArg:cbArg} );
|
|
} else {
|
|
if( cbFunc )
|
|
cbFunc(cbArg);
|
|
}
|
|
}
|
|
|
|
// save list of following to dht resource. each page ("following1", following2"...)
|
|
// constains up to _followsPerPage elements. alternatively we might keep track
|
|
// of total strings size to optimize the maximum storage (8kb in node.cpp, but 4kb is
|
|
// probably a good target).
|
|
function saveFollowingToDht() {
|
|
var following = [];
|
|
var pageNumber = 1;
|
|
for( var i = 0; i < followingUsers.length; i++ ) {
|
|
if( followingUsers[i] in _isFollowPublic &&
|
|
_isFollowPublic[followingUsers[i]] ) {
|
|
following.push(followingUsers[i]);
|
|
}
|
|
if( following.length == _followsPerPage || i == followingUsers.length-1) {
|
|
dhtput( defaultScreenName, "following" + pageNumber, "s",
|
|
following, defaultScreenName, _followingSeqNum+1 );
|
|
pageNumber++;
|
|
following = [];
|
|
}
|
|
}
|
|
dhtput( defaultScreenName, "following" + pageNumber, "s",
|
|
following, defaultScreenName, _followingSeqNum+1 );
|
|
|
|
_followingSeqNum++;
|
|
}
|
|
|
|
// save following to local storage, dht and json rpc
|
|
function saveFollowing(cbFunc, cbArg) {
|
|
saveFollowingToStorage();
|
|
saveFollowingToDht();
|
|
updateFollowing(cbFunc, cbArg);
|
|
}
|
|
|
|
// update json rpc with current list of following
|
|
function updateFollowing(cbFunc, cbArg) {
|
|
twisterRpc("follow", [defaultScreenName,followingUsers],
|
|
function(args, ret) {
|
|
if( args.cbFunc )
|
|
args.cbFunc(args.cbArg, true);
|
|
}, {cbFunc:cbFunc, cbArg:cbArg},
|
|
function(args, ret) {
|
|
console.log("ajax error:" + ret);
|
|
if( args.cbFunc )
|
|
args.cbFunc(args.cbArg, false);
|
|
}, cbArg);
|
|
}
|
|
|
|
// follow a new single user.
|
|
// it is safe to call this even if username is already in followingUsers.
|
|
// may also be used to set/clear publicFollow.
|
|
function follow(user, publicFollow, cbFunc, cbArg) {
|
|
if( followingUsers.indexOf(user) < 0 ) {
|
|
followingUsers.push(user);
|
|
}
|
|
if( publicFollow == undefined || publicFollow )
|
|
_isFollowPublic[user] = true;
|
|
else
|
|
delete _isFollowPublic[user];
|
|
saveFollowing(cbFunc, cbArg);
|
|
}
|
|
|
|
// unfollow a single user
|
|
function unfollow(user, cbFunc, cbArg) {
|
|
var i = followingUsers.indexOf(user);
|
|
if( i >= 0 ) {
|
|
followingUsers.splice(i,1);
|
|
}
|
|
delete _isFollowPublic[user];
|
|
saveFollowing();
|
|
|
|
twisterRpc("unfollow", [defaultScreenName,[user]],
|
|
function(args, ret) {
|
|
if( args.cbFunc )
|
|
args.cbFunc(args.cbArg, true);
|
|
}, {cbFunc:cbFunc, cbArg:cbArg},
|
|
function(args, ret) {
|
|
console.log("ajax error:" + ret);
|
|
if( args.cbFunc )
|
|
args.cbFunc(args.cbArg, false);
|
|
}, {cbFunc:cbFunc, cbArg:cbArg});
|
|
}
|
|
|
|
// check if public following
|
|
function isPublicFollowing(user) {
|
|
if( followingUsers.indexOf(user) < 0 ) {
|
|
return false;
|
|
}
|
|
if( (user in _isFollowPublic) && _isFollowPublic[user] == true )
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
// check if following list is empty
|
|
function followingEmptyOrMyself() {
|
|
if( followingUsers.length == 0 ||
|
|
followingUsers.length == 1 && followingUsers[0] == defaultScreenName )
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
// randomly choose a user we follow, get "following1" from him and them
|
|
// choose a suggestion from their list. this function could be way better, but
|
|
// that's about the simplest we may get to start with.
|
|
function getRandomFollowSuggestion(cbFunc, cbArg) {
|
|
|
|
if( followingEmptyOrMyself() ) {
|
|
cbFunc(cbArg, null, null);
|
|
return;
|
|
}
|
|
|
|
var i;
|
|
do{
|
|
var i = parseInt( Math.random() * followingUsers.length );
|
|
} while( i < followingUsers.length && followingUsers[i] == defaultScreenName);
|
|
|
|
if( i < followingUsers.length ) {
|
|
loadFollowingFromDht( followingUsers[i], 1, [], 0,
|
|
function(args, following, seqNum) {
|
|
if( following ) {
|
|
var suggested = false;
|
|
var j = parseInt( Math.random() * following.length );
|
|
for( ; j < following.length; j++ ) {
|
|
if( followingUsers.indexOf(following[j]) < 0 &&
|
|
_followSuggestions.indexOf(following[j]) < 0 ) {
|
|
args.cbFunc(args.cbArg, following[j], args.followedBy);
|
|
_followSuggestions.push(following[j]);
|
|
suggested = true;
|
|
break;
|
|
}
|
|
}
|
|
if( !suggested ) {
|
|
args.cbFunc(args.cbArg, null, null);
|
|
}
|
|
}
|
|
}, {cbFunc:cbFunc, cbArg:cbArg, followedBy:followingUsers[i]});
|
|
} else {
|
|
cbFunc(cbArg, null, null);
|
|
}
|
|
}
|
|
|
|
// adds following users to the interface (following.html)
|
|
function showFollowingUsers(){
|
|
var $notFollowing = $(".not-following-any");
|
|
if( followingEmptyOrMyself() ) {
|
|
$notFollowing.show();
|
|
} else {
|
|
$notFollowing.hide();
|
|
}
|
|
|
|
var $followingList = $(".following-list");
|
|
var $template = $("#following-user-template").detach();
|
|
|
|
$followingList.empty();
|
|
$followingList.append($template);
|
|
|
|
for( var i = 0; i < followingUsers.length; i++ ) {
|
|
var resItem = $template.clone(true);
|
|
resItem.removeAttr('id');
|
|
resItem.show();
|
|
resItem.find(".mini-profile-info").attr("data-screen-name", followingUsers[i]);
|
|
resItem.find(".following-screen-name").text(followingUsers[i]);
|
|
resItem.find("a.open-profile-modal").attr("href",$.MAL.userUrl(followingUsers[i]));
|
|
resItem.find("a.unfollow").attr("href",$.MAL.unfollowUrl(followingUsers[i]));
|
|
resItem.find("a.direct-messages-with-user").attr("href", $.MAL.dmchatUrl(followingUsers[i]));
|
|
resItem.find(".public-following").prop("checked",isPublicFollowing(followingUsers[i]));
|
|
getAvatar(followingUsers[i],resItem.find(".mini-profile-photo"));
|
|
getFullname(followingUsers[i],resItem.find(".mini-profile-name"));
|
|
if( followingUsers[i] == defaultScreenName ) {
|
|
resItem.find("button").hide();
|
|
}
|
|
|
|
resItem.appendTo($followingList);
|
|
}
|
|
$.MAL.followingListLoaded();
|
|
}
|
|
|
|
|
|
function processSuggestion(arg, suggestion, followedBy) {
|
|
var dashboard = $(".follow-suggestions");
|
|
if( suggestion ) {
|
|
var item = $("#follow-suggestion-template").clone(true);
|
|
item.removeAttr("id");
|
|
|
|
item.find(".twister-user-info").attr("data-screen-name", suggestion);
|
|
|
|
item.find(".twister-user-name").attr("href", $.MAL.userUrl(suggestion));
|
|
item.find(".twister-by-user-name").attr("href", $.MAL.userUrl(followedBy));
|
|
item.find(".twister-user-tag").text("@" + suggestion);
|
|
|
|
getAvatar(suggestion,item.find(".twister-user-photo"));
|
|
|
|
getFullname(suggestion,item.find(".twister-user"));
|
|
$spanFollowedBy = item.find(".followed-by");
|
|
$spanFollowedBy.text(followedBy);
|
|
getFullname(followedBy,$spanFollowedBy);
|
|
|
|
dashboard.append(item);
|
|
}
|
|
}
|
|
|
|
function closeSearchDialog()
|
|
{
|
|
var $this = $(".userMenu-search-field");//$( this );
|
|
$( this ).siblings().slideUp( "fast" );
|
|
removeUsersFromDhtgetQueue( _lastSearchUsersResults );
|
|
_lastSearchUsersResults = [];
|
|
}
|
|
|
|
function userSearchKeypress(item) {
|
|
var partialName = $(".userMenu-search-field").val().toLowerCase();
|
|
|
|
if ( partialName.substr( 0, 1 ) == '@' ) {
|
|
partialName = partialName.substr( 1 );
|
|
}
|
|
|
|
//var partialName = item.val();
|
|
|
|
if( !partialName.length ) {
|
|
closeSearchDialog();
|
|
} else {
|
|
if( _searchKeypressTimer !== undefined )
|
|
clearTimeout(_searchKeypressTimer);
|
|
|
|
if( _searchingPartialUsers.length ) {
|
|
_searchingPartialUsers = partialName;
|
|
} else {
|
|
_searchKeypressTimer = setTimeout( function() {
|
|
_searchKeypressTimer = undefined;
|
|
searchPartialUsername(partialName);
|
|
}, 600);
|
|
}
|
|
}
|
|
}
|
|
|
|
function searchPartialUsername(partialName) {
|
|
_searchingPartialUsers = partialName;
|
|
twisterRpc("listusernamespartial", [partialName,10],
|
|
function(partialName, ret) {
|
|
processDropdownUserResults(partialName, ret)
|
|
}, partialName,
|
|
function(cbArg, ret) {
|
|
console.log("ajax error:" + ret);
|
|
}, {});
|
|
}
|
|
|
|
function processDropdownUserResults(partialName, results){
|
|
|
|
if( partialName != _searchingPartialUsers ) {
|
|
searchPartialUsername( _searchingPartialUsers );
|
|
return;
|
|
}
|
|
|
|
removeUsersFromDhtgetQueue( _lastSearchUsersResults );
|
|
_lastSearchUsersResults = results;
|
|
|
|
var typeaheadAccounts = $(".userMenu-search-profiles");
|
|
var template = $("#search-profile-template").detach();
|
|
|
|
typeaheadAccounts.empty();
|
|
typeaheadAccounts.append(template);
|
|
|
|
if( results.length ) {
|
|
for( var i = 0; i < results.length; i++ ) {
|
|
if( results[i] == defaultScreenName )
|
|
continue;
|
|
|
|
var resItem = template.clone(true);
|
|
resItem.removeAttr('id');
|
|
resItem.show();
|
|
resItem.find(".mini-profile-info").attr("data-screen-name", results[i]);
|
|
resItem.find(".mini-screen-name b").text(results[i]);
|
|
resItem.find("a.open-profile-modal").attr("href",$.MAL.userUrl(results[i]));
|
|
getAvatar(results[i],resItem.find(".mini-profile-photo"));
|
|
getFullname(results[i],resItem.find(".mini-profile-name"));
|
|
resItem.appendTo(typeaheadAccounts);
|
|
}
|
|
|
|
$.MAL.searchUserListLoaded();
|
|
} else {
|
|
closeSearchDialog();
|
|
}
|
|
_searchingPartialUsers = "";
|
|
}
|
|
|
|
function userClickFollow(e) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
|
|
var $this = $(this);
|
|
var $userInfo = $this.closest("[data-screen-name]");
|
|
var username = $userInfo.attr("data-screen-name");
|
|
|
|
follow(username, true, function() {
|
|
// delay reload so dhtput may do it's job
|
|
window.setTimeout("location.reload();",500);
|
|
});
|
|
}
|
|
|
|
function initUserSearch() {
|
|
var $userSearchField = $( ".userMenu-search-field" );
|
|
$userSearchField.keyup( userSearchKeypress );
|
|
$userSearchField.bind( "click", userSearchKeypress );
|
|
$userSearchField.clickoutside( closeSearchDialog );
|
|
|
|
$("button.follow").bind( "click", userClickFollow );
|
|
}
|
|
|
|
function followingListUnfollow(e) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
|
|
var $this = $(this);
|
|
var username = $this.closest(".mini-profile-info").attr("data-screen-name");
|
|
|
|
unfollow(username, function() {
|
|
showFollowingUsers();
|
|
});
|
|
}
|
|
|
|
function followingListPublicCheckbox(e) {
|
|
e.stopPropagation();
|
|
|
|
var $this = $(this);
|
|
var username = $this.closest(".mini-profile-info").attr("data-screen-name");
|
|
var public = false;
|
|
$this.toggleClass( "private" );
|
|
if( $this.hasClass( "private" ) ) {
|
|
$this.text( polyglot.t("Private") );
|
|
} else {
|
|
$this.text( polyglot.t("Public") );
|
|
public = true;
|
|
}
|
|
|
|
follow(username, public);
|
|
}
|
|
|
|
|
|
function requestSwarmProgress() {
|
|
twisterRpc("getlasthave", [defaultScreenName],
|
|
function(args, ret) {processSwarmProgressPartial(ret);}, null,
|
|
function(args, ret) {console.log("ajax error:" + ret);}, null);
|
|
}
|
|
|
|
function processSwarmProgressPartial(lastHaves)
|
|
{
|
|
if( defaultScreenName in lastHaves ) {
|
|
incLastPostId(lastHaves[defaultScreenName]);
|
|
}
|
|
|
|
twisterRpc("getnumpieces", [defaultScreenName],
|
|
function(args, ret) {processSwarmProgressFinal(args.lastHaves, ret);},
|
|
{lastHaves:lastHaves},
|
|
function(args, ret) {console.log("ajax error:" + ret);}, null);
|
|
}
|
|
|
|
function processSwarmProgressFinal(lastHaves, numPieces)
|
|
{
|
|
for( var user in lastHaves ) {
|
|
if( lastHaves.hasOwnProperty(user) && numPieces.hasOwnProperty(user) ) {
|
|
var $userDiv = $(".mini-profile-info[data-screen-name='" + user + "']");
|
|
if( $userDiv.length ) {
|
|
var $status = $userDiv.find(".swarm-status");
|
|
$status.text(polyglot.t("download_posts_status", { portion: numPieces[user] + "/" + (lastHaves[user]+1) }));
|
|
$status.fadeIn();
|
|
}
|
|
}
|
|
}
|
|
window.setTimeout("requestSwarmProgress();",2000);
|
|
}
|
|
|
|
function followingChangedUser() {
|
|
followingUsers = [];
|
|
_isFollowPublic = {};
|
|
_followingSeqNum = 0;
|
|
_followSuggestions = [];
|
|
_lastLoadFromDhtTime = 0;
|
|
}
|
|
|
|
function initInterfaceFollowing() {
|
|
initInterfaceCommon();
|
|
initUserSearch();
|
|
initInterfaceDirectMsg();
|
|
|
|
$("button.unfollow").bind( "click", followingListUnfollow );
|
|
$(".public-following").bind( "click", followingListPublicCheckbox );
|
|
|
|
$(".mentions-from-user").bind( "click", openMentionsModal );
|
|
|
|
initUser( function() {
|
|
if( !defaultScreenName ) {
|
|
alert(polyglot.t("username_undefined"));
|
|
$.MAL.goLogin();
|
|
return;
|
|
}
|
|
checkNetworkStatusAndAskRedirect();
|
|
|
|
$(".postboard-loading").fadeIn();
|
|
loadFollowing( function(args) {
|
|
showFollowingUsers();
|
|
requestSwarmProgress();
|
|
});
|
|
initMentionsCount();
|
|
initDMsCount();
|
|
});
|
|
}
|