This commit is contained in:
Hannes Mannerheim 2016-02-02 16:25:34 +01:00
parent d247c08aeb
commit c01906055e
34 changed files with 596 additions and 163 deletions

View File

@ -218,6 +218,9 @@ class QvitterPlugin extends Plugin {
$m->connect(':nickname/notifications',
array('action' => 'qvitter',
'nickname' => Nickname::INPUT_FMT));
$m->connect(':nickname/blocks',
array('action' => 'qvitter',
'nickname' => Nickname::INPUT_FMT));
$m->connect('settings/qvitter',
array('action' => 'qvittersettings'));
$m->connect('panel/qvitter',
@ -770,10 +773,14 @@ class QvitterPlugin extends Plugin {
$twitter_user['profile_background_color'] = Profile_prefs::getConfigData($profile, 'theme', 'backgroundcolor');
$twitter_user['profile_banner_url'] = Profile_prefs::getConfigData($profile, 'qvitter', 'cover_photo');
// follows me?
if ($scoped) {
$twitter_user['follows_you'] = $profile->isSubscribed($scoped);
}
// follows me?
$twitter_user['follows_you'] = $profile->isSubscribed($scoped);
// blocks me?
$twitter_user['blocks_you'] = $profile->hasBlocked($scoped);
}
// local user?
$twitter_user['is_local'] = $profile->isLocal();

View File

@ -2,7 +2,7 @@ Qvitter
==========================================
* Author: Hannes Mannerheim (<h@nnesmannerhe.im>)
* Last mod.: Oct, 2015
* Last mod.: Jan, 2016
* Version: 5-alpha
* Homepage: <https://git.gnu.io/h2p/Qvitter>
@ -87,7 +87,6 @@ Qvitter extends this API in a few undocumented ways. See onRouterInitialized() i
for ideas about paths for the API extensions.
Notes
-----

View File

@ -4310,40 +4310,43 @@ button.shorten:not(.disabled):active {
.edit-profile-button .edit-profile-text,
.save-profile-button .edit-profile-text,
.abort-edit-profile-button .edit-profile-text,
.crop-and-save-button .edit-profile-text {
.crop-and-save-button .edit-profile-text,
.member-button.member .ismember-text,
.external-follow-button.following .following-text,
.qvitter-follow-button.following .following-text,
.member-button.member:hover .leave-text,
.external-follow-button.following:hover .unfollow-text,
.qvitter-follow-button.following:hover .unfollow-text,
.qvitter-follow-button.blocking .blocking-text,
.qvitter-follow-button.blocking:hover .unblock-text {
display:block;
height: 28px;
line-height: 28px;
}
.member-button.member .join-text,
.external-follow-button.following .follow-text,
.qvitter-follow-button.following .follow-text {
display:none;
height: 28px;
line-height: 28px;
}
.member-button.member .ismember-text,
.external-follow-button.following .following-text,
.qvitter-follow-button.following .following-text {
display:block;
height: 28px;
line-height: 28px;
}
.qvitter-follow-button.following .follow-text,
.member-button.member:hover .ismember-text,
.external-follow-button.following:hover .following-text,
.qvitter-follow-button.following:hover .following-text {
.qvitter-follow-button.following:hover .following-text,
.qvitter-follow-button.blocking .follow-text,
.qvitter-follow-button.blocking .unfollow-text,
.qvitter-follow-button.blocking:hover .blocking-text {
display:none;
height: 28px;
line-height: 28px;
}
.member-button.member:hover .leave-text,
.external-follow-button.following:hover .unfollow-text,
.qvitter-follow-button.following:hover .unfollow-text {
display:block;
height: 28px;
line-height: 28px;
}
.user-actions .blocks-you::before {
content:"\f05e";
color: #aaa;
cursor:default;
font-family: FontAwesome;
font-size: 20px;
line-height: 32px;
margin-right: 5px;
}
.queet-box-extras button {
box-shadow: none;
background:none !important;

View File

@ -457,6 +457,44 @@ function APIFollowOrUnfollowUser(followOrUnfollow,user_id,this_element,actionOnS
});
}
/* ·
·
· Post follow or unfollow user request
·
· @param followOrUnfollow: either 'follow' or 'unfollow'
· @param user_id: the user id of the user we want to follow
· @param actionOnSuccess: callback function, false on error, data on success
·
· · · · · · · · · · · · · */
function APIBlockOrUnblockUser(blockOrUnblock,user_id,actionOnSuccess) {
if(blockOrUnblock == 'block') {
var postRequest = 'blocks/create.json';
}
else if (blockOrUnblock == 'unblock') {
var postRequest = 'blocks/destroy.json';
}
$.ajax({ url: window.apiRoot + postRequest,
cache: false,
type: "POST",
data: {
id: user_id
},
dataType:"json",
error: function(data){ actionOnSuccess(false); console.log(data); },
success: function(data) {
data = convertEmptyObjectToEmptyArray(data);
data = iterateRecursiveReplaceHtmlSpecialChars(data);
searchForUserDataToCache(data);
updateUserDataInStream();
actionOnSuccess(data);
}
});
}
/* ·
·

View File

@ -285,6 +285,54 @@ function showFavsAndRequeetsInQueet(q,data) {
}
/* ·
·
· Build a follow/block button
·
· @param obj: an object with a user array
·
· · · · · · · · · */
function buildFollowBlockbutton(obj) {
// following?
var followingClass = '';
if(obj.following) {
followingClass = ' following';
}
// blocking?
var blockingClass = '';
if(obj.statusnet_blocking) {
blockingClass = ' blocking';
}
var followButton = '';
if(typeof window.loggedIn.screen_name != 'undefined' // if logged in
&& window.loggedIn.id != obj.id) { // not if this is me
if(!(obj.statusnet_profile_url.indexOf('/twitter.com/')>-1 && obj.following === false)) { // only unfollow twitter users
if(obj.blocks_you) {
var followButton = '<div class="user-actions">\
<div class="blocks-you" data-tooltip="' + window.sL.tooltipBlocksYou.replace('{username}','@' + obj.screen_name) + '"></div>\
</div>';
}
else {
var followButton = '<div class="user-actions">\
<button data-follow-user-id="' + obj.id + '" data-follow-user="' + obj.statusnet_profile_url + '" type="button" class="qvitter-follow-button' + followingClass + blockingClass + '">\
<span class="button-text follow-text"><i class="follow"></i>' + window.sL.userFollow + '</span>\
<span class="button-text following-text">' + window.sL.userFollowing + '</span>\
<span class="button-text unfollow-text">' + window.sL.userUnfollow + '</span>\
<span class="button-text blocking-text">' + window.sL.buttonBlocked + '</span>\
<span class="button-text unblock-text">' + window.sL.buttonUnblock + '</span>\
</button>\
</div>';
}
}
}
return followButton;
}
/* ·
·
@ -310,28 +358,23 @@ function buildProfileCard(data) {
var follows_you = '<span class="follows-you">' + window.sL.followsYou + '</span>';
}
// show user actions if logged in
var followingClass = '';
if(data.following) {
followingClass = 'following';
}
var followButton = '';
// only add follow button if this is a local user
if(data.is_local == true) {
if(typeof window.loggedIn.screen_name != 'undefined' && window.loggedIn.id != data.id) {
var followButton = '<div class="user-actions"><button data-follow-user-id="' + data.id + '" data-follow-user="' + data.statusnet_profile_url + '" type="button" class="qvitter-follow-button ' + followingClass + '"><span class="button-text follow-text"><i class="follow"></i>' + window.sL.userFollow + '</span><span class="button-text following-text">' + window.sL.userFollowing + '</span><span class="button-text unfollow-text">' + window.sL.userUnfollow + '</span></button></div>';
followButton = buildFollowBlockbutton(data);
}
// follow from external instance if logged out
if(typeof window.loggedIn.screen_name == 'undefined') {
var followButton = '<div class="user-actions"><button type="button" class="external-follow-button ' + followingClass + '"><span class="button-text follow-text"><i class="follow"></i>' + window.sL.userExternalFollow + '</span></button></div>';
followButton = '<div class="user-actions"><button type="button" class="external-follow-button"><span class="button-text follow-text"><i class="follow"></i>' + window.sL.userExternalFollow + '</span></button></div>';
}
// edit profile button if me
if(typeof window.loggedIn.screen_name != 'undefined' && window.loggedIn.id == data.id) {
var followButton = '<div class="user-actions"><button type="button" class="edit-profile-button"><span class="button-text edit-profile-text">' + window.sL.editMyProfile + '</span></button></div>';
followButton = '<div class="user-actions"><button type="button" class="edit-profile-button"><span class="button-text edit-profile-text">' + window.sL.editMyProfile + '</span></button></div>';
}
}
@ -393,23 +436,18 @@ function buildProfileCard(data) {
function buildExternalProfileCard(data) {
// local profile id and follow class
var followLocalIdHtml = '';
var followingClass = '';
if(typeof data.local != 'undefined' && data.local !== null) {
followLocalIdHtml = ' data-follow-user-id="' + data.local.id + '"';
if(data.local.following) {
followingClass = 'following';
}
}
// follows me?
var follows_you = '';
if(data.local !== null && data.local.follows_you === true && window.loggedIn.id != data.local.id) {
var follows_you = '<span class="follows-you">' + window.sL.followsYou + '</span>';
}
// follow button
var followButton = '';
if(window.loggedIn !== false && typeof data.local != 'undefined' && data.local !== null) {
var followButton = buildFollowBlockbutton(data.local);
}
// empty strings and zeros instead of null
data = cleanUpUserObject(data.external);
@ -442,13 +480,6 @@ function buildExternalProfileCard(data) {
var serverUrl = guessInstanceUrlWithoutProtocolFromProfileUrlAndNickname(data.statusnet_profile_url, data.screen_name);
data.screenNameWithServer = '@' + data.screen_name + '@' + serverUrl;
var followButton = '';
// we can only follow remote users if we're logged in at the moment
if(window.loggedIn !== false) {
var followButton = '<div class="user-actions"><button' + followLocalIdHtml + ' data-follow-user="' + data.statusnet_profile_url + '" type="button" class="qvitter-follow-button ' + followingClass + '"><span class="button-text follow-text"><i class="follow"></i>' + window.sL.userFollow + '</span><span class="button-text following-text">' + window.sL.userFollowing + '</span><span class="button-text unfollow-text">' + window.sL.userUnfollow + '</span></button></div>';
}
data.profileCardHtml = '\
<div class="profile-card">\
<div class="profile-header-inner" style="background-image:url(\'' + cover_photo + '\')">\
@ -717,10 +748,11 @@ function setNewCurrentStream(streamObject,setLocation,fallbackId,actionOnSuccess
});
}
// hide all notices from blocked users
if(typeof window.allBlocking != 'undefined') {
// hide all notices from blocked users (not for user lists)
if(window.currentStreamObject.type != 'users' && typeof window.allBlocking != 'undefined') {
$.each(window.allBlocking,function(){
oldStreamState.find('strong.name[data-user-id="' + this + '"]').closest('.stream-item').addClass('profile-blocked-by-me');
oldStreamState.find('strong.name[data-user-id="' + this + '"]').closest('.stream-item').children('.queet').attr('data-tooltip',window.sL.thisIsANoticeFromABlockedUser);
});
}
@ -1870,26 +1902,27 @@ function buildUserStreamItemHtml(obj) {
rtlOrNot = 'rtl';
}
// show user actions
// following?
var followingClass = '';
if(obj.following) {
followingClass = 'following';
followingClass = ' following';
}
// blocking?
var blockingClass = '';
if(obj.statusnet_blocking) {
blockingClass = ' blocking';
}
var followButton = '';
if(typeof window.loggedIn.screen_name != 'undefined' // if logged in
&& window.loggedIn.id != obj.id) { // not if this is me
if(!(obj.statusnet_profile_url.indexOf('/twitter.com/')>-1 && obj.following === false)) { // only unfollow twitter users
var followButton = '<div class="user-actions">\
<button data-follow-user-id="' + obj.id + '" data-follow-user="' + obj.statusnet_profile_url + '" type="button" class="qvitter-follow-button ' + followingClass + '">\
<span class="button-text follow-text"><i class="follow"></i>' + window.sL.userFollow + '</span>\
<span class="button-text following-text">' + window.sL.userFollowing + '</span>\
<span class="button-text unfollow-text">' + window.sL.userUnfollow + '</span>\
</button>\
</div>';
var followButton = buildFollowBlockbutton(obj);
}
}
return '<div id="stream-item-' + obj.id + '" class="stream-item user">\
return '<div id="stream-item-' + obj.id + '" class="stream-item user" data-user-id="' + obj.id + '">\
<div class="queet ' + rtlOrNot + '">\
' + followButton + '\
<div class="queet-content">\
@ -2080,6 +2113,7 @@ function buildQueetHtml(obj, idInStream, extraClasses, requeeted_by, isConversat
data-quitter-id-in-stream="' + idInStream + '" \
data-in-reply-to-screen-name="' + in_reply_to_screen_name + '" \
data-in-reply-to-status-id="' + obj.in_reply_to_status_id + '"\
data-user-id="' + obj.user.id + '"\
' + requeetedByMe + '>\
<div class="queet" id="' + idPrepend + 'q-' + idInStream + '"' + blockingTooltip + '>\
<script class="attachment-json" type="application/json">' + JSON.stringify(obj.attachments) + '</script>\

View File

@ -301,6 +301,137 @@ function localStorageIsEnabled() {
}
/* ·
·
· Block/unblock user and do necessary stuff
·
· · · · · · · · · */
function blockUser(arg, callback) {
// arguments is sent as an object, for easier use with a menu's function-row
var userId = arg.userId;
var blockButton_jQueryElement = arg.blockButton_jQueryElement;
display_spinner();
APIBlockOrUnblockUser('block', userId, function(data) {
remove_spinner();
// activate the button, if we were passed one
if(typeof blockButton_jQueryElement != 'undefined') {
blockButton_jQueryElement.removeClass('disabled');
}
if(data && data.statusnet_blocking === true) {
// success
markUserAsBlockedInDOM(userId, data.following);
if(typeof callback == 'function') {
callback(blockButton_jQueryElement);
}
}
else {
// failed!
showErrorMessage(window.sL.failedBlockingUser);
}
});
}
function unblockUser(arg, callback) {
// arguments is sent as an object, for easier use with a menu's function-row
var userId = arg.userId;
var blockButton_jQueryElement = arg.blockButton_jQueryElement;
display_spinner();
APIBlockOrUnblockUser('unblock', userId, function(data) {
remove_spinner();
// activate the button, if we were passed one
if(typeof blockButton_jQueryElement != 'undefined') {
blockButton_jQueryElement.removeClass('disabled');
}
if(data && data.statusnet_blocking === false) {
// success
markUserAsUnblockedInDOM(userId, data.following);
if(typeof callback == 'function') {
callback(blockButton_jQueryElement);
}
}
else {
// failed!
showErrorMessage(window.sL.failedUnblockingUser);
}
});
}
function markUserAsBlockedInDOM(userId, following) {
// display buttons accordingly
$('.qvitter-follow-button[data-follow-user-id="' + userId + '"]').addClass('blocking');
if(following) {
$('.qvitter-follow-button[data-follow-user-id="' + userId + '"]').addClass('following');
}
else {
$('.qvitter-follow-button[data-follow-user-id="' + userId + '"]').removeClass('following');
}
// hide notices from the blocked user
$.each($('.stream-item[data-quitter-id-in-stream][data-user-id="' + userId + '"]'),function(){
$(this).addClass('profile-blocked-by-me');
$(this).children('.queet').attr('data-tooltip',window.sL.thisIsANoticeFromABlockedUser);
});
// add to the window.allBlocking array
if (userIsBlocked(userId) === false) {
window.allBlocking.push(userId);
}
}
function markUserAsUnblockedInDOM(userId, following) {
// display buttons accordingly
$('.qvitter-follow-button[data-follow-user-id="' + userId + '"]').removeClass('blocking');
if(following) {
$('.qvitter-follow-button[data-follow-user-id="' + userId + '"]').addClass('following');
}
else {
$('.qvitter-follow-button[data-follow-user-id="' + userId + '"]').removeClass('following');
}
// hide the user from lists of blocked users
$.each($('.stream-item.user[data-user-id="' + userId + '"]'),function(){
slideUpAndRemoveStreamItem($(this));
});
// unhide notices from the blocked user
$.each($('.stream-item[data-quitter-id-in-stream][data-user-id="' + userId + '"]'),function(){
$(this).removeClass('profile-blocked-by-me');
$(this).children('.queet').removeAttr('data-tooltip');
});
// remove from the window.allBlocking array
var existingBlockIndex = window.allBlocking.indexOf(userId);
if (existingBlockIndex > -1) {
window.allBlocking.splice(existingBlockIndex, 1);
}
}
/* ·
·
· Is this user id blocked?
·
· · · · · · · · · */
function userIsBlocked(userId) {
var existingBlock = window.allBlocking.indexOf(userId);
if (existingBlock > -1) {
return true;
}
else {
return false;
}
}
/* ·
·
· Updates the times for all queets loaded to DOM
@ -1002,7 +1133,13 @@ function rememberStreamStateInLocalStorage() {
return false;
}
});
var feed = $('<div/>').append(firstTwentyVisibleHTML);
// we add these again (with updated blocking list) when the notices are fetched from the cache
feed.children('.stream-item').removeClass('profile-blocked-by-me');
feed.children('.stream-item').children('.queet').removeAttr('data-tooltip'); // can contain tooltip about blocked user
feed.find('.temp-post').remove();
feed.children('.stream-item').removeClass('not-seen');
feed.children('.stream-item').removeClass('hidden-repeat'); // this means we need hide repeats when adding cached notices to feed later

View File

@ -1214,28 +1214,42 @@ $('body').on('click','.sm-ellipsis',function(e){
var streamItemUserID = $(this).closest('.queet').find('.stream-item-header').find('.name').attr('data-user-id');
var streamItemID = $(this).closest('.queet').parent('.stream-item').attr('data-quitter-id');
// menu
var menuArray = [];
// my notice
if(streamItemUserID == window.loggedIn.id) {
var menuArray = [
{
type: 'function',
functionName: 'deleteQueet',
functionArguments: {
streamItemID: streamItemID
},
label: window.sL.deleteVerb
menuArray.push({
type: 'function',
functionName: 'deleteQueet',
functionArguments: {
streamItemID: streamItemID
},
];
label: window.sL.deleteVerb
});
}
// other users' notices
else {
var menuArray = [
{
type: 'link',
href: window.siteInstanceURL + 'main/block?profileid=' + streamItemUserID,
if(userIsBlocked(streamItemUserID)) {
menuArray.push({
type: 'function',
functionName: 'unblockUser',
functionArguments: {
userId: streamItemUserID
},
label: window.sL.unblockUser.replace('{username}',streamItemUsername)
});
}
else {
menuArray.push({
type: 'function',
functionName: 'blockUser',
functionArguments: {
userId: streamItemUserID
},
label: window.sL.blockUser.replace('{username}',streamItemUsername)
},
];
});
}
}
// add menu to DOM and align it
@ -1453,67 +1467,73 @@ $('body').on('click','.external-member-button',function(event){
/* ·
·
· When clicking a follow button
· When clicking a follow/block button
·
· · · · · · · · · · · · · */
$('body').on('click','.qvitter-follow-button',function(event){
if(!$(this).hasClass('disabled')) {
$(this).addClass('disabled');
// get user id
var user_id = $(this).attr('data-follow-user-id');
// if there's no local user id, we have to take a detour
if(typeof user_id == 'undefined') {
$.ajax({ url: window.siteInstanceURL + 'main/ostatussub',
type: "POST",
data: {
token: window.commonSessionToken,
profile: $(this).attr('data-follow-user'),
submit: 'Confirm'
},
error: function(data){ console.log('error'); console.log(data); },
success: function(data) {
// reload page on success
// since ostatussub doesn't have an api, there's no good way to get the local user id here,
// and change the button to an unsubscribe button.
window.location.replace(window.siteInstanceURL + window.loggedIn.screen_name + '/subscriptions');
}
});
}
// if we have a local id, it's straightforward, but we could be handling an unfollow
else {
// follow or unfollow?
if($(this).hasClass('following')) {
var followOrUnfollow = 'unfollow';
}
else {
var followOrUnfollow = 'follow';
}
// post to api
display_spinner();
APIFollowOrUnfollowUser(followOrUnfollow,user_id, this, function(data,this_element) {
remove_spinner();
$(this_element).removeClass('disabled');
if(data) {
if(data.following) {
$(this_element).addClass('following');
$('#user-following strong').html(parseInt($('#user-following strong').html(),10)+1);
appendUserToMentionsSuggestionsArray(data);
}
else {
$(this_element).removeClass('following');
$('#user-following strong').html(parseInt($('#user-following strong').html(),10)-1);
}
}
});
}
if($(this).hasClass('disabled')) {
return true;
}
$(this).addClass('disabled');
var user_id = $(this).attr('data-follow-user-id');
// if we have a local id, it's straightforward, but we could be handling an unfollow
if(typeof user_id != 'undefined') {
// unblock?
if($(this).hasClass('blocking')) {
unblockUser({userId: user_id, blockButton_jQueryElement: $(this)});
return true;
}
// follow or unfollow?
if($(this).hasClass('following')) {
var followOrUnfollow = 'unfollow';
}
else {
var followOrUnfollow = 'follow';
}
// post to api
display_spinner();
APIFollowOrUnfollowUser(followOrUnfollow,user_id, this, function(data,this_element) {
remove_spinner();
$(this_element).removeClass('disabled');
if(data) {
if(data.following) {
$(this_element).addClass('following');
$('#user-following strong').html(parseInt($('#user-following strong').html(),10)+1);
appendUserToMentionsSuggestionsArray(data);
}
else {
$(this_element).removeClass('following');
$('#user-following strong').html(parseInt($('#user-following strong').html(),10)-1);
}
}
});
return true;
}
// if there's no local user id, we have to take a detour
$.ajax({ url: window.siteInstanceURL + 'main/ostatussub',
type: "POST",
data: {
token: window.commonSessionToken,
profile: $(this).attr('data-follow-user'),
submit: 'Confirm'
},
error: function(data){ console.log('error'); console.log(data); },
success: function(data) {
// reload page on success
// since ostatussub doesn't have an api, there's no good way to get the local user id here,
// and change the button to an unsubscribe button.
window.location.replace(window.siteInstanceURL + window.loggedIn.screen_name + '/subscriptions');
}
});
});

View File

@ -614,6 +614,19 @@ function pathToStreamRouter(path) {
return streamObject;
}
// {screen_name}/blocks
if(pathSplit.length == 2 && /^[a-zA-Z0-9]+$/.test(pathSplit[0]) && pathSplit[1] == 'blocks') {
streamObject.name = 'user blocks';
streamObject.nickname = pathSplit[0];
streamObject.parentPath = streamObject.nickname;
streamObject.streamHeader = '@' + replaceHtmlSpecialChars(streamObject.nickname);
streamObject.streamSubHeader = window.sL.userBlocks;
streamObject.stream = 'qvitter/blocks.json?count=20&id=' + streamObject.nickname + '&withuserarray=1';
streamObject.maxIdOrPage = 'page';
streamObject.type = 'users';
return streamObject;
}
// {screen_name}/groups
if(pathSplit.length == 2 && /^[a-zA-Z0-9]+$/.test(pathSplit[0]) && pathSplit[1] == 'groups') {
streamObject.name = 'user group list';

View File

@ -163,5 +163,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Hide embedded content in this timeline",
"hideQuotesInTimeline":"Hide quotes in this timeline"
"hideQuotesInTimeline":"Hide quotes in this timeline",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Hide embedded content in this timeline",
"hideQuotesInTimeline":"Hide quotes in this timeline"
"hideQuotesInTimeline":"Hide quotes in this timeline",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Hide embedded content in this timeline",
"hideQuotesInTimeline":"Hide quotes in this timeline"
"hideQuotesInTimeline":"Hide quotes in this timeline",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Eingebettete Inhaltsvorschau nicht anzeigen in dieser Timeline",
"hideQuotesInTimeline":"Verberge Zitate in dieser Timeline"
"hideQuotesInTimeline":"Verberge Zitate in dieser Timeline",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Hide embedded content in this timeline",
"hideQuotesInTimeline":"Hide quotes in this timeline"
"hideQuotesInTimeline":"Hide quotes in this timeline",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Eki plendon",
"continueRant":"Daŭrigi la plendon",
"hideEmbeddedInTimeline":"Kaŝi enkorpigitajn enhavojn en ĉi tiu tempolinio",
"hideQuotesInTimeline":"Kaŝi citaĵojn en ĉi tiu tempolinio"
"hideQuotesInTimeline":"Kaŝi citaĵojn en ĉi tiu tempolinio",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Ocultar contenido incrustado en esta línea temporal",
"hideQuotesInTimeline":"Ocultar citas en esta línea temporal"
"hideQuotesInTimeline":"Ocultar citas en esta línea temporal",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Hide embedded content in this timeline",
"hideQuotesInTimeline":"Hide quotes in this timeline"
"hideQuotesInTimeline":"Hide quotes in this timeline",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Hide embedded content in this timeline",
"hideQuotesInTimeline":"Hide quotes in this timeline"
"hideQuotesInTimeline":"Hide quotes in this timeline",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Hide embedded content in this timeline",
"hideQuotesInTimeline":"Hide quotes in this timeline"
"hideQuotesInTimeline":"Hide quotes in this timeline",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Hide embedded content in this timeline",
"hideQuotesInTimeline":"Hide quotes in this timeline"
"hideQuotesInTimeline":"Hide quotes in this timeline",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Cacher le contenu intégré dans ces avis",
"hideQuotesInTimeline":"Cacher les citations dans ces avis"
"hideQuotesInTimeline":"Cacher les citations dans ces avis",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Ocultar contido incrustado nesta liña temporal",
"hideQuotesInTimeline":"Ocultar citas nesta liña temporal"
"hideQuotesInTimeline":"Ocultar citas nesta liña temporal",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Hide embedded content in this timeline",
"hideQuotesInTimeline":"Hide quotes in this timeline"
"hideQuotesInTimeline":"Hide quotes in this timeline",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Hide embedded content in this timeline",
"hideQuotesInTimeline":"Hide quotes in this timeline"
"hideQuotesInTimeline":"Hide quotes in this timeline",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Hide embedded content in this timeline",
"hideQuotesInTimeline":"Hide quotes in this timeline"
"hideQuotesInTimeline":"Hide quotes in this timeline",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"連投する",
"continueRant":"連投を続ける",
"hideEmbeddedInTimeline":"タイムライン上の埋め込まれたコンテンツを隠す",
"hideQuotesInTimeline":"タイムライン上の引用を隠す"
"hideQuotesInTimeline":"タイムライン上の引用を隠す",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Hide embedded content in this timeline",
"hideQuotesInTimeline":"Hide quotes in this timeline"
"hideQuotesInTimeline":"Hide quotes in this timeline",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Start en utblåsning",
"continueRant":"Fortsett utblåsningen",
"hideEmbeddedInTimeline":"Skjul inkluderende innhold i denne tidslinjen",
"hideQuotesInTimeline":"Skjul sitater i denne tidslinjen"
"hideQuotesInTimeline":"Skjul sitater i denne tidslinjen",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Hide embedded content in this timeline",
"hideQuotesInTimeline":"Hide quotes in this timeline"
"hideQuotesInTimeline":"Hide quotes in this timeline",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Iniciar um debate",
"continueRant":"Continuar o debate",
"hideEmbeddedInTimeline":"Ocultar conteúdo incorporado nessa linha do tempo",
"hideQuotesInTimeline":"Ocultar citações nesta linha do tempo"
"hideQuotesInTimeline":"Ocultar citações nesta linha do tempo",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -161,5 +161,12 @@
"addEditLanguageLink":"Помогите перевести {site-title} на другой язык",
"onlyPartlyTranslated":"{site-title} только частично переведён на <em>{language-name}</em> ({percent}%). Вы можете помочь завершить перевод на <a href=\"https://git.gnu.io/h2p/Qvitter/tree/master/locale\">на домашней странице репозитория Qvitter</a>",
"startRant":"Начать декламацию",
"continueRant":"Продолжить декламацию"
"continueRant":"Продолжить декламацию",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Filloni një dërdëllitje",
"continueRant":"Vazhdojeni dërdëllitjen",
"hideEmbeddedInTimeline":"Fshih lëndë të trupëzuar në këtë rrjedhë kohore",
"hideQuotesInTimeline":"Fshih citime në këtë rrjedhë kohore"
"hideQuotesInTimeline":"Fshih citime në këtë rrjedhë kohore",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -163,5 +163,12 @@
"startRant":"Påbörja en harang",
"continueRant":"Fortsätt harangen",
"hideEmbeddedInTimeline":"Dölj inbäddat innehåll i detta flöde",
"hideQuotesInTimeline":"Dölj citat i detta flöde"
"hideQuotesInTimeline":"Dölj citat i detta flöde",
"userBlocks":"Konton du blockerar",
"buttonBlocked":"Blockerad",
"buttonUnblock":"Avblockera",
"failedBlockingUser":"Misslyckades med att blockera användaren.",
"failedUnblockingUser":"Misslyckades med att avblockera användaren.",
"unblockUser": "Avblockera {username}",
"tooltipBlocksYou":"Du är blockerad från att följa {username}."
}

View File

@ -162,5 +162,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Hide embedded content in this timeline",
"hideQuotesInTimeline":"Hide quotes in this timeline"
}
"hideQuotesInTimeline":"Hide quotes in this timeline",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}

View File

@ -162,5 +162,12 @@
"startRant":"Start a rant",
"continueRant":"Continue the rant",
"hideEmbeddedInTimeline":"Hide embedded content in this timeline",
"hideQuotesInTimeline":"Hide quotes in this timeline"
"hideQuotesInTimeline":"Hide quotes in this timeline",
"userBlocks":"Accounts you're blocking",
"buttonBlocked":"Blocked",
"buttonUnblock":"Unblock",
"failedBlockingUser":"Failed to block the user.",
"failedUnblockingUser":"Failed to unblock the user.",
"unblockUser": "Unblock {username}",
"tooltipBlocksYou":"You are blocked from following {username}."
}