Browse Source

Better handling of pictures in Messages or Posts

Fix NSFW spoilers
Cleanup CSS grid and reuse it for PublishBrief image picker
Add a little gallery for Contacts and Chatrooms based on recent pictures published
pull/933/head
Timothée Jaussoin 6 years ago
parent
commit
d8e95ad3f0
  1. 1
      CHANGELOG.md
  2. 9
      app/Conference.php
  3. 2
      app/Info.php
  4. 2
      app/Message.php
  5. 7
      app/helpers/StringHelper.php
  6. 2
      app/widgets/Chat/Chat.php
  7. 6
      app/widgets/ChatActions/ChatActions.php
  8. 7
      app/widgets/ContactActions/ContactActions.php
  9. 28
      app/widgets/ContactActions/_contactactions_drawer.tpl
  10. 1
      app/widgets/ContactData/locales.ini
  11. 2
      app/widgets/Presence/presence.css
  12. 2
      app/widgets/Preview/preview.css
  13. 20
      app/widgets/PublishBrief/_publishbrief_images.tpl
  14. 27
      app/widgets/PublishBrief/publishbrief.css
  15. 2
      app/widgets/Rooms/Rooms.php
  16. 20
      app/widgets/Rooms/_rooms_info.tpl
  17. 30
      database/migrations/20200514143915_change_picture_type_messages_table.php
  18. 6
      public/scripts/movim_utils.js
  19. 30
      public/theme/css/article.css
  20. 71
      public/theme/css/grid.css

1
CHANGELOG.md

@ -24,6 +24,7 @@ v0.18 (trunk)
* Add support for Unicode 12.0 emojis
* Add a refresh system for the Chat header based on the presences (and filtered by notifications)
* Use UNION ALL instead of OR for messages request (to prevent optimisations issues in SQL)
* Better handling of Pictures in Posts and Messages
v0.17.1
---------------------------

9
app/Conference.php

@ -49,6 +49,15 @@ class Conference extends Model
->where('mucjid', \App\User::me()->id);
}
public function pictures()
{
return $this->hasMany('App\Message', 'jidfrom', 'conference')
->where('user_id', \App\User::me()->id)
->where('type', 'groupchat')
->where('picture', true)
->orderBy('published', 'desc');
}
public function info()
{
return $this->hasOne('App\Info', 'server', 'conference')

2
app/Info.php

@ -19,7 +19,7 @@ class Info extends Model
public function save(array $options = [])
{
// Empty features, we're not saving anything
if (empty($this->attributes['features'])) return;
if (is_array($this->attributes['features']) && empty($this->attributes['features'])) return;
try {
unset($this->identities);

2
app/Message.php

@ -66,6 +66,7 @@ class Message extends Model
public function setFileAttribute(array $file)
{
$this->resolved = true;
$this->picture = typeIsPicture($file['type']);
$this->attributes['file'] = serialize($file);
}
@ -426,7 +427,6 @@ class Message extends Model
public function isEmpty()
{
return (empty($this->body)
&& empty($this->picture)
&& empty($this->file)
&& empty($this->sticker)
);

7
app/helpers/StringHelper.php

@ -295,7 +295,7 @@ function colorize($string, string $color): string
*/
function typeIsPicture(string $type): bool
{
return in_array($type, ['image/jpeg', 'image/png', 'image/jpg', 'image/gif']);
return in_array($type, ['image/jpeg', 'image/png', 'image/jpg', 'image/gif', 'image/webp']);
}
/**
@ -306,8 +306,9 @@ function typeIsAudio(string $type): bool
return in_array(
$type,
[
'audio/aac', 'audio/ogg', 'video/ogg', 'audio/opus',
'audio/vorbis', 'audio/speex', 'audio/mpeg']
'audio/aac', 'audio/ogg', 'video/ogg', 'audio/opus',
'audio/vorbis', 'audio/speex', 'audio/mpeg'
]
);
}

2
app/widgets/Chat/Chat.php

@ -819,7 +819,7 @@ class Chat extends \Movim\Widget\Base
$emoji = \Movim\Emoji::getInstance();
// URL messages
$message->url = filter_var($message->body, FILTER_VALIDATE_URL);
$message->url = filter_var(trim($message->body), FILTER_VALIDATE_URL);
// If the message doesn't contain a file but is a URL, we try to resolve it
if (!$message->file && $message->url && $message->resolved == false) {

6
app/widgets/ChatActions/ChatActions.php

@ -83,10 +83,10 @@ class ChatActions extends \Movim\Widget\Base
->first();
if ($message && $message->resolved == false) {
$file = resolvePictureFileFromUrl($message->body);
$picture = resolvePictureFileFromUrl(trim($message->body));
if ($file != false) {
$message->file = (array)$file;
if ($picture != false) {
$message->file = (array)$picture;
$this->rpc('Chat.refreshMessage', $message->mid);
}

7
app/widgets/ContactActions/ContactActions.php

@ -47,7 +47,12 @@ class ContactActions extends Base
}
$tpl = $this->tpl();
$tpl->assign('contact', App\Contact::firstOrNew(['id' => $jid]));
$tpl->assign('contact', \App\Contact::firstOrNew(['id' => $jid]));
$tpl->assign('pictures', \App\Message::jid($jid)
->where('picture', true)
->orderBy('published', 'desc')
->take(8)
->get());
$tpl->assign('roster', $this->user->session->contacts()->where('jid', $jid)->first());
$tpl->assign('clienttype', getClientTypes());

28
app/widgets/ContactActions/_contactactions_drawer.tpl

@ -83,10 +83,36 @@
{/if}
{/loop}
</ul>
<hr class="thick"/>
{/if}
{if="$pictures->count() > 0"}
<ul class="list">
<li class="subheader">
<div>
<p>
{$c->__('general.pictures')}
</p>
</div>
</li>
</ul>
<ul class="grid active">
{loop="$pictures"}
<li style="background-image: url('{$value->file['uri']|protectPicture}')"
onclick="Preview_ajaxShow('{$value->file['uri']}')">
<i class="material-icons">visibility</i>
</li>
{/loop}
</ul>
{/if}
<ul class="list middle">
<li class="subheader">
<div>
<p>
{$c->__('vcard.title')}
</p>
</div>
</li>
{if="$contact->fn != null"}
<li>
<span class="primary icon gray">{$contact->fn|firstLetterCapitalize}</span>

1
app/widgets/ContactData/locales.ini

@ -10,6 +10,7 @@ website = Website
about = About Me
accounts = Other Accounts
tune = Is Listening
pictures = Pictures
[position]
legend = Geographic Position

2
app/widgets/Presence/presence.css

@ -2,7 +2,7 @@ ul#presence_widget li span.primary {
opacity: 1;
}
@media screen and (min-width: 1024px) and (max-width: 1680px) {
@media screen and (min-width: 1025px) and (max-width: 1680px) {
ul#presence_widget li span.control {
display: none;
}

2
app/widgets/Preview/preview.css

@ -29,7 +29,7 @@
/* Previewable */
.previewable {
display: block;
display: inline-block;
position: relative;
overflow: hidden;
}

20
app/widgets/PublishBrief/_publishbrief_images.tpl

@ -1,20 +1,16 @@
<section id="publishbriefimages" class="scroll">
<ul class="list flex active">
<ul class="grid third active">
{loop="$embed->images"}
<li class="block"
<li style="background-image: url('{$value.url|protectPicture}')"
onclick="PublishBrief.setEmbedImage({$key})">
<div style="background-image: url('{$value.url|protectPicture}')">
<span>
{$value.width} × {$value.height} · {$value.size|sizeToCleanSize:0}
</span>
</div>
<i class="material-icons">photo</i>
<span>
{$value.width} × {$value.height} · {$value.size|sizeToCleanSize:0}
</span>
</li>
{/loop}
<li class="block"
onclick="PublishBrief.setEmbedImage('none')">
<div>
<i class="material-icons">visibility_off</i>
</div>
<li onclick="PublishBrief.setEmbedImage('none')">
<i class="material-icons">visibility_off</i>
</li>
</ul>
</section>

27
app/widgets/PublishBrief/publishbrief.css

@ -24,33 +24,6 @@ header#publishbrief form > div textarea {
border-radius: 0.5rem;
}
#publishbriefimages li div {
padding-top: 100%;
background-size: cover;
background-position: center center;
position: relative;
}
#publishbriefimages li div i {
position: absolute;
text-align: center;
width: calc(100% - 4rem);
font-size: 10rem;
margin-top: calc(-50% - 5rem);
}
#publishbriefimages li div span {
position: absolute;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.8);
color: white;
font-size: 2rem;
padding: 0 1rem;
line-height: 3.5rem;
border-radius: 0.3rem 0 0;
}
#publishbrief span.privacy {
margin-right: 0.75rem;
}

2
app/widgets/Rooms/Rooms.php

@ -280,7 +280,7 @@ class Rooms extends Base
->get());
$view->assign('me', $this->user->id);
Drawer::fill($view->draw('_rooms_subject_show'));
Drawer::fill($view->draw('_rooms_info'));
}
/**

20
app/widgets/Rooms/_rooms_subject_show.tpl → app/widgets/Rooms/_rooms_info.tpl

@ -77,6 +77,26 @@
{/if}
</ul>
{if="$conference->pictures()->count() > 0"}
<ul class="list">
<li class="subheader">
<div>
<p>
{$c->__('general.pictures')}
</p>
</div>
</li>
</ul>
<ul class="grid active">
{loop="$conference->pictures()->take(8)->get()"}
<li style="background-image: url('{$value->file['uri']|protectPicture}')"
onclick="Preview_ajaxShow('{$value->file['uri']}')">
<i class="material-icons">visibility</i>
</li>
{/loop}
</ul>
{/if}
<ul class="list">
<li class="subheader">
<div>

30
database/migrations/20200514143915_change_picture_type_messages_table.php

@ -0,0 +1,30 @@
<?php
use Movim\Migration;
use Illuminate\Database\Schema\Blueprint;
class ChangePictureTypeMessagesTable extends Migration
{
public function up()
{
$this->schema->table('messages', function (Blueprint $table) {
$table->dropColumn('picture');
});
$this->schema->table('messages', function (Blueprint $table) {
$table->boolean('picture')->default(false);
$table->index('picture');
});
}
public function down()
{
$this->schema->table('messages', function (Blueprint $table) {
$table->dropColumn('picture');
});
$this->schema->table('messages', function (Blueprint $table) {
$table->text('picture')->nullable();
});
}
}

6
public/scripts/movim_utils.js

@ -116,13 +116,13 @@ var MovimUtils = {
.replace(/>/g, '&gt;');
},
enhanceArticlesContent: function() {
document.querySelectorAll('article section content video')
document.querySelectorAll('article section > div video')
.forEach(item => item.setAttribute('controls', 'controls'));
document.querySelectorAll('article section content a:not(.innertag)')
document.querySelectorAll('article section > div a:not(.innertag)')
.forEach(link => link.setAttribute('target', '_blank'));
document.querySelectorAll('article section content img')
document.querySelectorAll('article section > div img')
.forEach(img => {
if (img.parentNode.localName != 'a') {
var div = document.createElement('div');

30
public/theme/css/article.css

@ -65,7 +65,7 @@ article video {
/* Some CSS to style the quote XHTML generated by Movim */
article section content div.quote {
article section > div div.quote {
display: block;
border-radius: 2px;
border: 1px solid rgba(0, 0, 0, 0.12);
@ -74,63 +74,63 @@ article section content div.quote {
box-sizing: border-box;
}
article section content div.quote:before,
article section content div.quote:after {
article section > div div.quote:before,
article section > div div.quote:after {
content: '';
display: none;
}
article section content div.quote ul {
article section > div div.quote ul {
display: flex;
flex-flow: row wrap;
}
article section content div.quote ul:not(.list):not(.tabs):not(.grid) > li {
article section > div div.quote ul:not(.list):not(.tabs):not(.grid) > li {
list-style-type: none;
padding-left: 0;
}
article section content div.quote ul li > * {
article section > div div.quote ul li > * {
margin-right: 1rem;
}
article section content div.quote li:first-child {
article section > div div.quote li:first-child {
flex: 1 75%;
padding-right: 2rem;
box-sizing: border-box;
}
@media screen and (max-width: 1024px) {
article section content div.quote li {
article section > div div.quote li {
flex: 1 100%;
}
}
article section content div.quote li img {
article section > div div.quote li img {
max-height: 10rem;
max-width: 100%;
float: right;
}
article section content img.big_picture {
article section > div img.big_picture {
display: block;
margin: 0rem auto;
margin-bottom: 1rem;
border-radius: 0.25rem;
}
article section content img.active:hover {
article section > div img.active:hover {
cursor: pointer;
}
article section content div.video_embed {
article section > div div.video_embed {
position: relative;
padding-bottom: 56.25%; /* 16:9 */
margin-bottom: 2rem;
height: 0;
}
article section content div.video_embed iframe {
article section > div div.video_embed iframe {
position: absolute;
top: 0;
left: 0;
@ -143,7 +143,7 @@ article ul li.pic img {
max-height: 30rem;
}
article section content div hr {
article section > div div hr {
margin: 2rem 0;
}
@ -157,7 +157,7 @@ article input[type=checkbox].spoiler:not(:checked) + section {
position: relative;
}
article input[type=checkbox].spoiler:not(:checked) + section content > * {
article input[type=checkbox].spoiler:not(:checked) + section > div > * {
opacity: 0.05;
pointer-events: none;
filter: blur(1rem);

71
public/theme/css/grid.css

@ -1,56 +1,47 @@
/* Grid */
ul.grid {
display: flex;
flex-wrap: wrap;
}
ul.grid > li {
background-size: cover;
width: calc(25% - 0.2em);
padding: 0;
padding-bottom: 25%;
float: left;
position: relative;
flex: 0 1 25%;
background-repeat: no-repeat;
background-position: center center;
box-sizing: border-box;
margin: 0.1em;
background-size: cover;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
ul.grid.third > li {
flex: 0 1 33.33%;
}
ul.grid.active > li:hover {
cursor: pointer;
opacity: 0.8;
}
ul.grid > li > nav {
ul.grid.active > li i {
font-size: 4rem;
}
ul.grid.active > li span {
position: absolute;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.8);
color: white;
background-color: rgba(0, 0, 0, 0.5);
width: 100%;
padding: 0 1rem;
box-sizing: border-box;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font-size: 2rem;
line-height: 5rem;
}
@media screen and (max-width: 1024px) {
ul.grid > li {
width: calc(33.33% - 0.2em);
padding-bottom: 33.33%;
}
}
@media screen and (max-width: 640px) {
ul.grid > li {
width: calc(50% - 0.2em);
padding-bottom: 50%;
}
font-size: 1.5rem;
padding: 0 0.5rem;
line-height: 2.5rem;
border-radius: 0.3rem 0 0;
}
ul.grid:after {
content: "";
ul.grid > li:after {
content: '';
display: block;
clear: both;
}
ul.grid.padded {
padding: 0;
}
padding-bottom: 100%;
}
Loading…
Cancel
Save