GitHost.io will be shut down on June 1, 2019. At that point this instance will be unreachable and all data will be irrevocably deleted. More details at https://about.gitlab.com/gitlab-hosted/#githost-is-shutting-down-on-june-1st-2019

Commit ffe14fe5 authored by mmn's avatar mmn

Merge branch 'nightly' into 'nightly'

fixed hard coded twitter char limit

See merge request !154
parents c285f80b 5af96d3e
EmbedNotice @ a01aa570
Subproject commit a01aa570fe3d7dfbcc5fdc5a7421213cdbb0ab40
Nominatim @ e3ff29f8
Subproject commit e3ff29f80438a58a140bb4cfaa3063b70c98d1c7
Qvitter @ 2be23b40
Subproject commit 2be23b403879d2f04a00f401ead47f698ddfdff3
QvitterPlus @ 0e98b2f2
Subproject commit 0e98b2f2ea5dee9de86cc2a6333ed30b85de5369
This diff is collapsed.
# "Sensitive" Content Plugin for GNU Social
## About
WARNING: THIS IS ALPHA CODE, IT IS PRERELEASE AND SHOULD ONLY BE INSTALLED TO
HELP TEST OR YOU ARE WILLING TO TAKE RISKS.
Create user option to allow a user to hide #NSFW-hashtagged notices behind a
blocker image until clicked.
Works for both vanilla GNUSocial and with the Qvitter plugin.
## Install
- Move the project directory to ${GNU_SOCIAL}/plugins
- Add addPlugin('SensitiveContent'); to your config.php
if you want to customize the blocker image, add a line to your config.php:
$config['site']['sensitivecontent']['blockerimage'] = "/path/to/image.jpg";
## Usage
Individual users must go to their Settings page. A new sidebar menu item "Sensitive Content"
will be available. User checks or unchecks the checkbox on this page, and presses save.
If you have GNU Social open in other browser tabs, refresh them. If you are using Qvitter, also
refresh, but because Qvitter caches notices on the client side, only new sensitive images will
be hidden, it will not apply to notices retroactively unless you clear your browser cache.
## License
GNU Affero License
## Thanks
Thanks in particular to Hannes and Qvitter because looking at his code helped me a lot.
A tiny bit of content was taken from Qvitter to enhance Qvitter with this functionality.
<?php
if (!defined('GNUSOCIAL')) {
exit(1);
}
class SensitiveContentPlugin extends Plugin
{
const VERSION = '0.0.1';
function onPluginVersion(array &$versions)
{
$versions[] = array('name' => 'Sensitive Content',
'version' => self::VERSION,
'author' => 'MoonMan',
'homepage' => 'https://gitgud.io/ShitposterClub/SensitiveContent/',
'description' =>
_m('Mark, hide/show sensitive notices like on Twitter.'));
return true;
}
static function settings($setting)
{
$settings['blockerimage'] = Plugin::staticPath('SensitiveContent', '').'img/blocker.png';
$configphpsettings = common_config('site','sensitivecontent') ?: array();
foreach($configphpsettings as $configphpsetting=>$value) {
$settings[$configphpsetting] = $value;
}
if(isset($settings[$setting])) {
return $settings[$setting];
}
else FALSE;
}
function onNoticeSimpleStatusArray($notice, &$twitter_status, $scoped)
{
$twitter_status['tags'] = $notice->getTags();
}
function onTwitterUserArray($profile, &$twitter_user, $scoped)
{
if ($scoped instanceof Profile && $scoped->sameAs($profile)) {
$twitter_user['hide_sensitive'] = $this->getHideSensitive($scoped);
}
}
public function onRouterInitialized(URLMapper $m)
{
$m->connect('settings/sensitivecontent',
array('action' => 'sensitivecontentsettings'));
}
function onEndAccountSettingsNav($action)
{
$action->menuItem(common_local_url('sensitivecontentsettings'),
_m('MENU', 'Sensitive Content'),
_m('Settings for display of sensitive content.'));
return true;
}
public function onQvitterEndShowHeadElements(Action $action)
{
$blocker = static::settings('blockerimage');
common_log( LOG_DEBUG, "SENSITIVECONTENT " . $blocker );
$styles = <<<EOB
.sensitive-blocker {
display: none;
}
div.stream-item.notice.sensitive-notice .sensitive-blocker {
display: block;
width: 100%;
height: 100%;
position: absolute;
z-index: 100;
background-color: #d4baba;
background-image: url($blocker);
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
transition: opacity 1s ease-in-out;
}
.sensitive-blocker:hover {
opacity: .5;
}
div.stream-item.notice.expanded.sensitive-notice .sensitive-blocker {
display: none;
background-color: transparent;
background-image: none;
}
EOB;
$action->style($styles);
}
function onQvitterEndShowScripts(Action $action)
{
$action->script( Plugin::staticPath('SensitiveContent', '').'js/sensitivecontent.js' );
}
function onEndShowStyles(Action $action)
{
$blocker = static::settings('blockerimage');
$styles = <<<EOB
/* default no show */
html .tagcontainer > footer > .attachments > .inline-attachment > .attachment-wrapper > .sensitive-blocker {
display: none;
}
html[data-hidesensitive='true'] .tagcontainer.data-tag-nsfw > footer > .attachments > .inline-attachment > .attachment-wrapper > .sensitive-blocker {
display: block;
width: 100%;
height: 100%;
position: absolute;
z-index: 100;
/*background-color: #d4baba;*/
background-color: black;
background-image: url($blocker);
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
transition: opacity 1s ease-in-out;
}
html[data-hidesensitive='true'] .tagcontainer.data-tag-nsfw > footer > .attachments > .inline-attachment > .attachment-wrapper > .sensitive-blocker.reveal {
opacity: 0;
}
EOB;
$action->style($styles);
}
function onStartShowAttachmentRepresentation($out, $file)
{
$profile = Profile::current();
if (!is_null($profile) && $profile instanceof Profile)
{
$hidesensitive = $this->getHideSensitive($profile);
}
else
{
$hidesensitive = false;
}
$classes = "sensitive-blocker"; //'sensitive-blocker';
$out->elementStart('div', array(
'class'=>'attachment-wrapper',
'style'=>'height: ' . $file->getThumbnail()->height . 'px; width: ' . $file->getThumbnail()->width . 'px;'
)); /*needs height of thumb*/
$out->elementStart('div', array(
'class'=>$classes,
'onclick'=>'toggleSpoiler(event)',
'style'=>'height: ' . $file->getThumbnail()->height . 'px; width: ' . $file->getThumbnail()->width . 'px;'
));
$out->raw('&nbsp;');
$out->elementEnd('div');
}
function onEndShowAttachmentRepresentation($out, $file)
{
$out->elementEnd('div');
}
function onEndShowScripts(Action $action)
{
$profile = $action->getScoped();
if (!is_null($profile) && $profile instanceof Profile)
{
$hidesensitive = $this->getHideSensitive($profile) ? "true" : "false";
}
else
{
$hidesensitive = "false";
}
$inline = <<<EOB
window.hidesensitive = $hidesensitive ;
function toggleSpoiler(evt) {
if (window.hidesensitive) evt.target.classList.toggle('reveal');
}
EOB;
$action->inlineScript($inline);
}
function onEndOpenNoticeListItemElement(NoticeListItem $nli)
{
$rawtags = $nli->getNotice()->getTags();
$classes = "tagcontainer";
foreach($rawtags as $tag)
{
$classes = $classes . ' data-tag-' . $tag;
}
$nli->elementStart('span', array('class' => $classes));
//$nli->elementEnd('span');
}
function onStartCloseNoticeListItemElement(NoticeListItem $nli)
{
$nli->elementEnd('span');
}
function onStartHtmlElement($action, &$attrs) {
$profile = Profile::current();
if (!is_null($profile) && $profile instanceof Profile)
{
$hidesensitive = $this->getHideSensitive($profile);
}
else
{
$hidesensitive = false;
}
$attrs = array_merge($attrs,
array('data-hidesensitive' => ($hidesensitive ? "true" : "false"))
);
}
function getHideSensitive($profile) {
$c = Cache::instance();
/*
if (!empty($c)) {
$hidesensitive = $c->get(Cache::key('profile:hide_sensitive:'.$profile->id));
if (is_numeric($hidesensitive)) {
return (boolean) $hidesensitive;
}
else return FALSE;
}
*/
$hidesensitive = $profile->getPref('MoonMan', 'hide_sensitive', '0');
if (!empty($c)) {
//not using it yet.
$c->set(Cache::key('profile:hide_sensitive:'.$profile->id), $hidesensitive);
}
//common_log(LOG_DEBUG, "SENSITIVECONTENT hidesensitive? id " . $profile->id . " value " . (boolean)$hidesensitive );
if (is_null($hidesensitive)) {
return FALSE;
} else
if (is_numeric($hidesensitive)) {
return (boolean) $hidesensitive;
}
else return FALSE;
}
}
<?php
if (!defined('GNUSOCIAL')) { exit(1); }
class SensitiveContentSettingsAction extends SettingsAction
{
function title()
{
return _m('Sensitive content settings');
}
function getInstructions()
{
return _m('Set preferences for display of "sensitive" content');
}
function showContent()
{
$user = $this->scoped->getUser();
$this->elementStart('form', array('method' => 'post',
'id' => 'sensitivecontent',
'class' => 'form_settings',
'action' => common_local_url('sensitivecontentsettings')));
$this->elementStart('fieldset');
$this->hidden('token', common_session_token());
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->checkbox('hidesensitive', _('Hide attachments in posts hashtagged #NSFW'),
($this->arg('hidesensitive')) ?
$this->boolean('hidesensitive') : $this->scoped->getPref('MoonMan','hide_sensitive',0));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->submit('save', _m('BUTTON','Save'));
$this->elementEnd('fieldset');
$this->elementEnd('form');
}
function doPost()
{
$hidesensitive = $this->booleanintstring('hidesensitive');
$this->scoped->setPref('MoonMan','hide_sensitive', $hidesensitive);
return _('Settings saved.');
}
}
window.buildAttachmentHTML = function(attachments){
var attachmentHTML = '';
var oembedHTML = '';
var quotedNotices = [];
var attachmentNum = 0;
var oembedNum = 0;
var urlsToHide = [];
if(typeof attachments != "undefined") {
$.each(attachments, function(){
// quoted notices
if(typeof this.quoted_notice != 'undefined') {
var quotedContent = this.quoted_notice.content;
// quoted notice's attachments' thumb urls
var quotedAttachmentsHTML = '';
var quotedAttachmentsHTMLbefore = '';
var quotedAttachmentsHTMLafter = '';
if(typeof this.quoted_notice.attachments != 'undefined' && this.quoted_notice.attachments.length > 0) {
quotedAttachmentsHTML += '<div class="quoted-notice-attachments quoted-notice-attachments-num-' + this.quoted_notice.attachments.length + '">'
$.each(this.quoted_notice.attachments,function(k,qAttach){
quotedAttachmentsHTML += '<div class="quoted-notice-img-container" style="background-image:url(\'' + qAttach.thumb_url + '\')"><img class="quoted-notice-img" src="' + qAttach.thumb_url + '" /></div>';
// remove attachment string from content
quotedContent = quotedContent.split(window.siteInstanceURL + 'attachment/' + qAttach.attachment_id).join('');
});
quotedAttachmentsHTML += '</div>';
// if there is only one attachment, it goes before, otherwise after
if(this.quoted_notice.attachments.length == 1) {
quotedAttachmentsHTMLbefore = quotedAttachmentsHTML;
}
else {
quotedAttachmentsHTMLafter = quotedAttachmentsHTML;
}
}
var quotedNoticeHTML = quotedAttachmentsHTMLbefore + '\
<div class="quoted-notice-header">\
<span class="quoted-notice-author-fullname">' + this.quoted_notice.fullname + '</span>\
<span class="quoted-notice-author-nickname">' + this.quoted_notice.nickname + '</span>\
</div>\
<div class="quoted-notice-body">' + $.trim(quotedContent) + '</div>\
' + quotedAttachmentsHTMLafter;
quotedNotices.push({
url: this.url,
html: quotedNoticeHTML,
href: window.siteInstanceURL + 'notice/' + this.quoted_notice.id,
class:'quoted-notice'
});
}
// if we have Twitter oembed data, we add is as quotes
else if(typeof this.oembed != 'undefined'
&& this.oembed !== false
&& this.oembed.provider == 'Twitter') {
var twitterHTML = '<div class="oembed-item-header">\
<span class="oembed-item-title">' + this.oembed.author_name + '</span>\
<span class="oembed-username">' + this.oembed.title + '</span>\
</div>\
<div class="oembed-item-body">' + this.oembed.oembedHTML + '</div>\
<div class="oembed-item-footer">\
<span class="oembed-item-provider">' + this.oembed.provider + '</span>\
</div>';
quotedNotices.push({
url: this.url,
html: twitterHTML,
href: this.url,
class:'oembed-item'
});
}
// if we have other oembed data (but not for photos and youtube, we handle those later)
else if(typeof this.oembed != 'undefined'
&& this.oembed !== false
&& this.oembed.title !== null
&& this.oembed.provider != 'YouTube'
&& this.oembed.provider != 'Vimeo'
&& this.oembed.type != 'photo') {
var oembedImage = '';
// only local images
if(typeof this.thumb_url != 'undefined'
&& this.thumb_url !== null
&& isLocalURL(this.thumb_url)) {
oembedImage = '<div class="oembed-img-container" style="background-image:url(\'' + this.thumb_url + '\')"><img class="oembed-img" src="' + this.thumb_url + '" /></div>';
}
var oembedBody = '';
// don't add body if html it's too similar (80%) to the title (wordpress does this..)
if(this.oembed.oembedHTML !== null
&& this.oembed.oembedHTML.length > 0) {
if(this.oembed.oembedHTML.length > 200) {
this.oembed.oembedHTML = this.oembed.oembedHTML.substring(0,200) + '…';
}
if(stringSimilarity(this.oembed.oembedHTML,this.oembed.title.substring(0,200)) < 80) {
oembedBody = this.oembed.oembedHTML;
}
}
if(this.oembed.provider === null) {
var oembedProvider = this.url;
var oembedProviderURL = '';
}
else {
var oembedProvider = this.oembed.provider;
var oembedProviderURL = removeProtocolFromUrl(this.oembed.provider_url);
// remove trailing /
if(oembedProviderURL.slice(-1) == '/') {
oembedProviderURL = oembedProviderURL.slice(0,-1);
}
}
// If the oembed data is generated by Qvitter, we know a better way of showing the title
var oembedTitle = this.oembed.title;
var oembedTitleHTML = '<span class="oembed-item-title">' + oembedTitle + '</span>';
if(oembedTitle.slice(-10) == ' (Qvitter)') {
var oembedTimePosted = parseTwitterLongDate(oembedTitle.slice(0,-10));
var oembedGNUsocialUsername = this.oembed.author_name.substring(this.oembed.author_name.lastIndexOf('(')+1,this.oembed.author_name.lastIndexOf(')'));
var oembedGNUsocialFullname = this.oembed.author_name.slice(0,-(oembedGNUsocialUsername.length+3));
oembedTitleHTML = '<span class="oembed-item-title">' + oembedGNUsocialFullname + '</span>\
<span class="oembed-username">@' + oembedGNUsocialUsername + '</span>';
}
oembedHTML += '<a href="' + this.url + '" class="oembed-item">\
' + oembedImage + '\
<div class="oembed-item-header">\
' + oembedTitleHTML + '\
</div>\
<div class="oembed-item-body">' + oembedBody + '</div>\
<div class="oembed-item-footer">\
<span class="oembed-item-provider">' + oembedProvider + '</span>\
<span class="oembed-item-provider-url">' + oembedProviderURL + '</span>\
</div>\
</a>';
oembedNum++;
}
// if there's a local thumb_url we assume this is a image or video
else if(typeof this.thumb_url != 'undefined'
&& this.thumb_url !== null
&& isLocalURL(this.thumb_url)) {
var bigThumbW = 1000;
var bigThumbH = 3000;
if(bigThumbW > window.siteMaxThumbnailSize) {
bigThumbW = window.siteMaxThumbnailSize;
}
if(bigThumbH > window.siteMaxThumbnailSize) {
bigThumbH = window.siteMaxThumbnailSize;
}
// very long landscape images should not have background-size:cover
var noCoverClass='';
if(this.width/this.height > 2) {
noCoverClass=' no-cover';
}
// play button for videos and animated gifs
var playButtonClass = '';
if(typeof this.animated != 'undefined' && this.animated === true
|| (this.url.indexOf('://www.youtube.com') > -1 || this.url.indexOf('://youtu.be') > -1)
|| this.url.indexOf('://vimeo.com') > -1) {
playButtonClass = ' play-button';
}
// gif-class
var animatedGifClass = '';
if(typeof this.animated != 'undefined' && this.animated === true) {
var animatedGifClass = ' animated-gif';
}
// animated gifs always get default small non-animated thumbnail
if(this.animated === true) {
var img_url = this.thumb_url;
}
// if no dimensions are set, go with default thumb
else if(this.width === null && this.height === null) {
var img_url = this.thumb_url;
}
// large images get large thumbnail
else if(this.width > 1000) {
var img_url = this.large_thumb_url;
}
// no thumbnails for small local images
else if (this.url.indexOf(window.siteInstanceURL) === 0) {
var img_url = this.url;
}
// small thumbnail for small remote images
else {
var img_url = this.thumb_url;
}
var urlToHide = window.siteInstanceURL + 'attachment/' + this.id;
attachmentHTML += '<a data-local-attachment-url="' + urlToHide + '" style="background-image:url(\'' + img_url + '\')" class="thumb-container' + noCoverClass + playButtonClass + animatedGifClass + ' ' + CSSclassNameByHostFromURL(this.url) + '" href="' + this.url + '"><img class="attachment-thumb" data-mime-type="' + this.mimetype + '" src="' + img_url + '"/ data-width="' + this.width + '" data-height="' + this.height + '" data-full-image-url="' + this.url + '" data-thumb-url="' + img_url + '"></a>';
urlsToHide.push(urlToHide); // hide this attachment url from the queet text
attachmentNum++;
}
else if (this.mimetype == 'image/svg+xml') {
var urlToHide = window.siteInstanceURL + 'attachment/' + this.id;
attachmentHTML += '<a data-local-attachment-url="' + urlToHide + '" style="background-image:url(\'' + this.url + '\')" class="thumb-container" href="' + this.url + '"><img class="attachment-thumb" data-mime-type="' + this.mimetype + '" src="' + this.url + '"/></a>';
urlsToHide.push(urlToHide); // hide this attachment url from the queet text
attachmentNum++;
}
});
}
return { html: '<div class="oembed-data oembed-num-' + oembedNum + '">' + oembedHTML + '</div><div class="queet-thumbs thumb-num-' + attachmentNum + '"><div class="sensitive-blocker">&nbsp;</div>' + attachmentHTML + '</div>',
urlsToHide: urlsToHide,
quotedNotices: quotedNotices
};
}
window.sensitiveContentOriginalBuildQueetHtml = window.buildQueetHtml;
window.buildQueetHtml = function(obj, idInStream, extraClasses, requeeted_by, isConversation) {
//add tags to json if they don't exit
if (obj.tags && obj.tags.length > 0) {
for (var tagI = 0; tagI < obj.tags.length; ++tagI) {
extraClasses += (' data-tag-' + obj.tags[tagI]);
if (window.loggedIn.hide_sensitive && obj.tags[tagI] === 'nsfw') extraClasses += ' sensitive-notice';
}
}
return window.sensitiveContentOriginalBuildQueetHtml(obj, idInStream, extraClasses, requeeted_by, isConversation);
}
\ No newline at end of file
StaleAccounts @ 5b4df73b
Subproject commit 5b4df73b6bbcff2dbb96dc62933665a395439149
Statistics @ b3e74188
Subproject commit b3e7418802be8c64624f1330b2045542cf03fd36
......@@ -401,11 +401,11 @@ function format_status($notice)
$statusWithoutLinks = preg_replace('`((http|https|ftp)://[^\s<]+[^\s<\.)])`i', '', $statustxt);
$statusLength = mb_strlen($statusWithoutLinks) + $numberOfLinks * 23;
// Twitter still has a 140-char hardcoded max.
if ($statusLength > 140) {
// Twitter raised it but still has a 280-char hardcoded max.
if ($statusLength > 280) {
$noticeUrl = common_shorten_url($notice->getUrl());
// each link uses 23 chars on twitter + 3 for the ' … ' => 26
$statustxt = mb_substr($statustxt, 0, 140 - 26) . ' … ' . $noticeUrl;
$statustxt = mb_substr($statustxt, 0, 280 - 26) . ' … ' . $noticeUrl;
}
return $statustxt;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment