334 lines
14 KiB
JavaScript
334 lines
14 KiB
JavaScript
// I'm not a React developer. Be advised!
|
|
// If you are, and want to rewrite this the "right way", go ahead and do it :)
|
|
// This is plain Javascript and only requires basic calls to implement customizations
|
|
|
|
|
|
// Do simple XHR requests to the API on Mastodon
|
|
// make sure you control errors correctly, as a Mastodon Server might not setup CORS correctly
|
|
function mastodon_get(path, payload, callbk) { return mastodon_request('GET', path, payload, callbk); }
|
|
function mastodon_post(path, payload, callbk) { return mastodon_request('POST', path, payload, callbk); }
|
|
function mastodon_request(method, path, payload, callbk) {
|
|
payload = payload || null;
|
|
var url = 'https://'+fediloveApi.getCurrentInstance()+path;
|
|
|
|
var oReq = new XMLHttpRequest();
|
|
oReq.addEventListener("load", function() { callbk(this.responseText); });
|
|
oReq.open(method, url);
|
|
oReq.setRequestHeader('Authorization', 'Bearer '+fediloveApi.getAccessToken());
|
|
oReq.send(payload);
|
|
}
|
|
|
|
function api_status_fav(dom, status_id) {
|
|
var dislike = false;
|
|
var path = `/api/v1/statuses/${status_id}/favourite`;
|
|
if (dom.getAttribute('data-liked') === "1") {
|
|
path = `/api/v1/statuses/${status_id}/unfavourite`;
|
|
dislike = true;
|
|
}
|
|
mastodon_post(path, {}, function(data) {
|
|
// to-do: check "data" if the POST succeded (for now we expect it did xD)
|
|
if (!dislike) {
|
|
$(dom).addClass('liked-msg');
|
|
$(dom).attr('data-liked', 1);
|
|
dom.style.animation = "spin2 .5s linear 1";
|
|
} else {
|
|
$(dom).removeClass('liked-msg');
|
|
$(dom).attr('aria-label', '0 '+$(dom).attr('aria-label'));
|
|
$(dom).removeAttr('data-liked');
|
|
dom.style.animation = undefined;
|
|
}
|
|
});
|
|
}
|
|
|
|
// The api to send a message on the current chat thread has been improved a lot
|
|
// and now is much more rock-solid, and we reuse the React functions
|
|
function api_send_message() {
|
|
|
|
// the message is composed from the current chat Acct (@user@domain) + the compose textarea value
|
|
const text = fediloveData.chatAvatarCache.acct + ' ' + $('div#chat-compose-global textarea').val();
|
|
|
|
//async function postStatus(realm, text, inReplyToId, mediaIds, sensitive, spoilerText, visibility, mediaDescriptions, inReplyToUuid, poll, mediaFocalPoints)
|
|
const lastStatus = fediloveApi.getChatLastMessageId(true);
|
|
fediloveFunctions.postStatus(lastStatus.id, text, lastStatus.id, [], false, undefined,
|
|
'direct', undefined, lastStatus.uuid, undefined, undefined);
|
|
|
|
// empty the current compose box
|
|
$('div#chat-compose-global textarea').val('');
|
|
|
|
// scroll to the end as all chat Apps do
|
|
fediloveUI.scrollChatToLastItem();
|
|
}
|
|
|
|
var fediloveUI = {
|
|
scrollChatToLastItem: function() {
|
|
const startLen = $('div.the-list > div').length;
|
|
var count = 0;
|
|
var _this = setInterval(function() {
|
|
count++;
|
|
var newLen = $('div.the-list > div').length;
|
|
if (count >= 100 || newLen != startLen) {
|
|
document.querySelector('div.the-list > div:last-child').scrollIntoView();
|
|
clearInterval(_this);
|
|
}
|
|
}, 150);
|
|
},
|
|
paintChatAvatarAndName: function(accid, acct, avatar, name) {
|
|
accid = accid || null;
|
|
acct = acct || null;
|
|
avatar = avatar || null;
|
|
name = name || null;
|
|
|
|
if ((accid+acct+avatar+name) === 0) {
|
|
accid = 0;
|
|
avatar = '/missing.png';
|
|
name = '...';
|
|
} else {
|
|
fediloveData.chatAvatarCache = {
|
|
id: accid, acct: acct,
|
|
avatar: avatar, name: name
|
|
};
|
|
// add domain part on acct on painting it to be clear
|
|
if (acct.split('@').length-1 === 1) {
|
|
acct += `@${fediloveApi.getCurrentInstance()}`;
|
|
}
|
|
}
|
|
|
|
// to-do: check is it XSS safe to add it like this :) ??
|
|
$('div#chat-party-global > div#image > img').attr('src', avatar);
|
|
$('div#chat-party-global > div#name > a > span').html(name);
|
|
$('div#chat-party-global > div#name > span').text(acct);
|
|
if (accid != 0) $('div#chat-party-global > div#name > a').attr('href', `/accounts/${accid}`);
|
|
}
|
|
};
|
|
|
|
// objects to access from React code
|
|
var fediloveApi = {
|
|
getChatMessageId: function() {
|
|
var parts = window.location.pathname.split('/');
|
|
return parts[parts.length-1];
|
|
},
|
|
getChatLastMessageId: function(andUuid) {
|
|
andUuid = andUuid || false;
|
|
const lastUuid = $('div.the-list article.status-article').last().attr('id');
|
|
if (lastUuid === undefined) {
|
|
return undefined;
|
|
}
|
|
const parts = lastUuid.split('/');
|
|
return andUuid? { id: parts[parts.length-1], uuid: lastUuid } : parts[parts.length-1];
|
|
},
|
|
getAccessToken: function() {
|
|
var instance = fediloveApi.getCurrentInstance();
|
|
return JSON.parse(localStorage.store_loggedInInstances)[instance].access_token;
|
|
},
|
|
getCurrentInstance: function() {
|
|
return JSON.parse(localStorage.store_currentInstance);
|
|
}
|
|
};
|
|
var fediloveFunctions = {};
|
|
var fediloveData = {
|
|
chatAvatarCache: undefined,
|
|
gotEmojifyTextFunction: false,
|
|
composeTxtKeypressEvent: false
|
|
};
|
|
var fediloveEvents = {
|
|
onGotEmojifyTextFunction: function() {
|
|
fediloveData.gotEmojifyTextFunction = true;
|
|
},
|
|
onChatGetData: function(data) {
|
|
// dont do anything if avatar and name is cached
|
|
if (fediloveData.chatAvatarCache !== undefined) return;
|
|
|
|
// waits for the React code to call the "onGotEmojifyTextFunction" so we can use it
|
|
var waitForEmojifyAndDo = function(accid, acct, avatar, dname, emojis) {
|
|
var count = 0;
|
|
var _this = setInterval(function() {
|
|
if (count > 100) {
|
|
clearInterval(_this);
|
|
return;
|
|
}
|
|
if (fediloveData.gotEmojifyTextFunction) {
|
|
dname = dname.replace(/#fedilove/gi, '').trim();
|
|
dname = dname.replace(/\s+/g, ' ').trim().replace(/\s/g, ' ');
|
|
fediloveUI.paintChatAvatarAndName(accid, acct, avatar, fediloveFunctions.emojifyText(dname, emojis));
|
|
clearInterval(_this);
|
|
}
|
|
count++;
|
|
}, 150);
|
|
};
|
|
if (data) {
|
|
// if the message is mine, search which account is doing it for,
|
|
// and do the same with the party's data
|
|
if (localStorage.store_userAccountId == data.account.id) {
|
|
var account_id = data.in_reply_to_account_id;
|
|
if (account_id === null && data.mentions.length > 0) {
|
|
account_id = data.mentions[0].id;
|
|
}
|
|
if (account_id === null || account_id === undefined) return;
|
|
mastodon_get(`/api/v1/accounts/${account_id}`, {}, function(newData) {
|
|
var json = JSON.parse(newData);
|
|
if (json !== undefined) {
|
|
waitForEmojifyAndDo( account_id, `@${json.acct}`, json.avatar, json.display_name, json.emojis );
|
|
}
|
|
});
|
|
} else {
|
|
// this means the message we are loading is from the other party, so we continue as normal
|
|
waitForEmojifyAndDo( data.account.id, `@${data.account.acct}`, data.account.avatar,
|
|
data.account.display_name, data.account.emojis );
|
|
}
|
|
}
|
|
},
|
|
onEmojiPicked: function(emoji) {
|
|
var $txt = $("#chat-compose-global textarea");
|
|
var caretPos = $txt[0].selectionStart;
|
|
var textAreaTxt = $txt.val();
|
|
$txt.val(textAreaTxt.substring(0, caretPos) + emoji + textAreaTxt.substring(caretPos));
|
|
},
|
|
onNewNotification: function (dataItems) {
|
|
if (window.location.pathname.startsWith('/statuses/')) {
|
|
if (fediloveFunctions.updateStatusAndThread !== undefined) {
|
|
fediloveFunctions.updateStatusAndThread( fediloveApi.getCurrentInstance(), fediloveApi.getAccessToken(),
|
|
window.location.pathname, fediloveApi.getChatMessageId() );
|
|
fediloveUI.scrollChatToLastItem();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
// this is our URL-based customizations made by JavaScript
|
|
var intervalChatCssChange = null;
|
|
function fedilove_customization() {
|
|
if (document.querySelector('#main-nav > div#dummy-nav') != null) {
|
|
document.querySelector('#main-nav > div#dummy-nav').remove();
|
|
document.querySelector('#main-nav > ul.main-nav-ul').style = '';
|
|
}
|
|
|
|
document.body.classList = '';
|
|
document.querySelector('.main-content').classList = "main-content";
|
|
document.querySelector('#chat-compose-global').style = 'visibility: collapse';
|
|
document.querySelector('#chat-party-hide').style = 'display: none !important';
|
|
document.querySelector('nav#main-nav > ul.main-nav-ul').style = '';
|
|
|
|
if (window.location.pathname.startsWith('/statuses/'))
|
|
{
|
|
$('div.main-content').addClass('chat');
|
|
$('body').addClass('chat');
|
|
document.querySelector('#chat-compose-global').style = '';
|
|
document.querySelector('#chat-party-hide').style = '';
|
|
document.querySelector('nav#main-nav > ul.main-nav-ul').style = 'display: none !important';
|
|
|
|
if (!fediloveData.composeTxtKeypressEvent) {
|
|
$('div#chat-compose-global textarea').keypress(function(e) {
|
|
const keycode = (e.keyCode ? e.keyCode : e.which);
|
|
if (keycode == '13') {
|
|
if (!e.ctrlKey && !e.shiftKey) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
setTimeout(function() { api_send_message() }, 100);
|
|
return true;
|
|
} else {
|
|
if (!e.shiftKey && e.ctrlKey) {
|
|
$(this).val($(this).val()+'\n');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
fediloveData.composeTxtKeypressEvent = true;
|
|
}
|
|
|
|
// *******
|
|
// load cached avatars or paint it empty (automated process after this will fill it)
|
|
if (fediloveData.chatAvatarCache !== undefined) {
|
|
fediloveUI.paintChatAvatarAndName( fediloveData.chatAvatarCache.id, fediloveData.chatAvatarCache.acct,
|
|
fediloveData.chatAvatarCache.avatar, fediloveData.chatAvatarCache.name );
|
|
} else {
|
|
fediloveUI.paintChatAvatarAndName();
|
|
}
|
|
|
|
// add some animations
|
|
var style = document.createElement('style');
|
|
style.innerHTML = '@keyframes spin2 { 100% { transform:rotate(-360deg); } }';
|
|
document.body.appendChild(style);
|
|
|
|
// this function changes the css class on articles (messages)
|
|
// that match the given account_id
|
|
var makeMessageUIModifications = function(account_id) {
|
|
if (intervalChatCssChange !== null) return;
|
|
var theint = 150;
|
|
setInterval(function()
|
|
{
|
|
intervalChatCssChange = this;
|
|
|
|
// paint MY messages as mine
|
|
$('div.main-content.chat article.status-article').each(function(i) {
|
|
if ($(this).find('a.status-author-name').attr('href').endsWith(`/accounts/${account_id}`)) {
|
|
$(this).addClass('mymsg');
|
|
theint = 250;
|
|
} else {
|
|
$(this).addClass('partymsg');
|
|
}
|
|
});
|
|
|
|
// paint LIKES
|
|
$('a.status-favs-reblogs.status-favs').each(function(i) {
|
|
// easy, aria-label contains the times this status was fav
|
|
// so, we use this as the input source to search for "0",
|
|
// if no "0" found in text, we mark element as liked so we can apply styles and "undo like" function
|
|
if ($(this).attr('aria-label').search(/0/) == -1) {
|
|
$(this).addClass('liked-msg');
|
|
$(this).attr('data-liked', 1);
|
|
}
|
|
});
|
|
|
|
// remove liking functionality from our own messages
|
|
$('div.the-list article.status-article.mymsg a.status-favs-reblogs').each(function(i) {
|
|
$(this).attr('onclick', 'return false;');
|
|
});
|
|
|
|
// resize likes acording to the outer parent
|
|
$('div.main-content.chat div.the-list article.status-article').each(function(e) {
|
|
$(this).find('div.like-div').width($(this).width());
|
|
});
|
|
|
|
}, theint);
|
|
};
|
|
|
|
// get the userAccountId to check against the href /account/NUM on the messages
|
|
// so we can apply different style to my messages
|
|
if (localStorage.store_userAccountId === undefined) {
|
|
mastodon_get('/api/v1/accounts/verify_credentials', {}, function(data) {
|
|
var json = JSON.parse(data);
|
|
if (json !== undefined) {
|
|
localStorage.store_userAccountId = json.id;
|
|
makeMessageUIModifications(json.id);
|
|
}
|
|
});
|
|
} else {
|
|
makeMessageUIModifications(localStorage.store_userAccountId);
|
|
}
|
|
}
|
|
else if (window.location.pathname == '/direct') {
|
|
$('div.main-content').addClass('direct');
|
|
}
|
|
else if (window.location.pathname.startsWith('/notifications/mentions')) {
|
|
$('nav.notification-filters li > a.focus-fix').attr('onclick', 'return false;');
|
|
}
|
|
|
|
if (!window.location.pathname.startsWith('/statuses/')) {
|
|
fediloveData.chatAvatarCache = undefined;
|
|
}
|
|
}
|
|
|
|
// we inject this script.js into the React framework at timelines.js
|
|
// Watch for URL changes every 1/2 seconds (this is efficient, don't worry about it!)
|
|
// and dispatch a call to "fedilove_customization" to load page customizations depending on URL context
|
|
var theurl = null;
|
|
setInterval(function() {
|
|
var newurl = window.location.pathname;
|
|
if (newurl != theurl) {
|
|
fedilove_customization();
|
|
theurl = newurl;
|
|
}
|
|
}, 250);
|
|
|