Browse Source
Implement full text search of Message bodies using the PostgreSQL tsvector and tssearch features
pull/1456/head
Implement full text search of Message bodies using the PostgreSQL tsvector and tssearch features
pull/1456/head
23 changed files with 323 additions and 140 deletions
-
1CHANGELOG.md
-
2INSTALL.md
-
20app/Message.php
-
31app/Widgets/Chat/Chat.php
-
16app/Widgets/Chat/_chat_header.tpl
-
1app/Widgets/Chat/chat.css
-
66app/Widgets/ChatActions/ChatActions.php
-
93app/Widgets/ChatActions/_chatactions_message.tpl
-
84app/Widgets/ChatActions/_chatactions_message_dialog.tpl
-
15app/Widgets/ChatActions/_chatactions_search.tpl
-
5app/Widgets/ChatActions/_chatactions_search_placeholder.tpl
-
15app/Widgets/ChatActions/_chatactions_search_result.tpl
-
27app/Widgets/ChatActions/chatactions.css
-
10app/Widgets/ChatActions/chatactions.js
-
2app/Widgets/ChatActions/locales.ini
-
2app/Widgets/Search/_search.tpl
-
2app/Widgets/Search/_search_results_empty.tpl
-
1app/Widgets/Search/locales.ini
-
26database/migrations/20250806055432_add_body_ts_vector_to_messages_table.php
-
1locales/locales.ini
-
5public/theme/css/chip.css
-
1public/theme/css/color.css
-
7src/Movim/Widget/Base.php
@ -0,0 +1,84 @@ |
|||
<section id="chat_actions"> |
|||
<ul class="list" id="message_preview"> |
|||
{autoescape="off"} |
|||
{$c->prepareMessage($message)} |
|||
{/autoescape} |
|||
<li class="reactions"> |
|||
<div> |
|||
<p>{loop="$message->reactions->groupBy('emoji')"}<a class="chip" href="#" title="{$value->implode('truename', ', ')}">{autoescape="off"}{$key|addEmojis:true}{/autoescape}{$value->implode('truename', ', ')}</a>{/loop}</p> |
|||
</div> |
|||
</li> |
|||
</ul> |
|||
<ul class="list divided active middle"> |
|||
{if="!$message->encrypted"} |
|||
<li onclick="Stickers_ajaxReaction({$message->mid}); Dialog_ajaxClear()"> |
|||
<span class="primary icon gray"> |
|||
<i class="material-symbols">add_reaction</i> |
|||
</span> |
|||
<div> |
|||
<p class="normal">{$c->__('message.react')}</p> |
|||
</div> |
|||
</li> |
|||
<li |
|||
onclick="MovimUtils.copyToClipboard(MovimUtils.decodeHTMLEntities(ChatActions.message.body)); ChatActions_ajaxCopiedMessageText(); Dialog_ajaxClear()"> |
|||
<span class="primary icon gray"> |
|||
<i class="material-symbols">content_copy</i> |
|||
</span> |
|||
<div> |
|||
<p class="normal">{$c->__('chatactions.copy_text')}</p> |
|||
</div> |
|||
</li> |
|||
{/if} |
|||
<li onclick="Chat_ajaxHttpDaemonReply({$message->mid}); Dialog_ajaxClear()"> |
|||
<span class="primary icon gray"> |
|||
<i class="material-symbols">reply</i> |
|||
</span> |
|||
<div> |
|||
<p class="normal">{$c->__('button.reply')}</p> |
|||
</div> |
|||
</li> |
|||
|
|||
{if="$message->isLast()"} |
|||
<li onclick="Chat.editPrevious(); Dialog_ajaxClear();"> |
|||
<span class="primary icon gray"> |
|||
<i class="material-symbols">edit</i> |
|||
</span> |
|||
<div> |
|||
<p class="normal">{$c->__('button.edit')}</p> |
|||
</div> |
|||
</li> |
|||
{/if} |
|||
|
|||
{if="$message->isMine()"} |
|||
<li onclick="ChatActions_ajaxHttpDaemonRetract({$message->mid})"> |
|||
<span class="primary icon gray"> |
|||
<i class="material-symbols">delete</i> |
|||
</span> |
|||
<div> |
|||
<p class="normal">{$c->__('message.retract')}</p> |
|||
</div> |
|||
</li> |
|||
{/if} |
|||
|
|||
{if="$conference && $conference->presence && $conference->presence->mucrole == 'moderator' && $conference->info && $conference->info->hasModeration()"} |
|||
<li class="subheader"> |
|||
<div> |
|||
<p>{$c->__('chatroom.administration')}</p> |
|||
</div> |
|||
</li> |
|||
<li onclick="ChatActions_ajaxHttpDaemonModerate({$message->mid})"> |
|||
<span class="primary icon gray"> |
|||
<i class="material-symbols">delete</i> |
|||
</span> |
|||
<div> |
|||
<p class="normal">{$c->__('message.retract')}</p> |
|||
</div> |
|||
</li> |
|||
{/if} |
|||
</ul> |
|||
</section> |
|||
<footer> |
|||
<button onclick="Dialog_ajaxClear()" class="button flat"> |
|||
{$c->__('button.close')} |
|||
</button> |
|||
</footer> |
@ -0,0 +1,15 @@ |
|||
<section id="chat_search"> |
|||
{autoescape="off"} |
|||
{$c->prepareSearchPlaceholder()} |
|||
{/autoescape} |
|||
</section> |
|||
<ul class="list"> |
|||
<li class="search"> |
|||
<form name="search" onsubmit="return false;"> |
|||
<div> |
|||
<input name="keyword" autocomplete="off" |
|||
placeholder="{$c->__('button.search')}" oninput="console.log(this.value); ChatActions_ajaxSearchMessages('{$jid|echapJS}', this.value, {if="$muc"}true{else}false{/if});" type=" text"> |
|||
</div> |
|||
</form> |
|||
</li> |
|||
</ul> |
@ -0,0 +1,5 @@ |
|||
<div class="placeholder"> |
|||
<i class="material-symbols">search</i> |
|||
<h1>{$c->__('chatactions.search_messages')}</h1> |
|||
<h4>{$c->__('input.open_me_using')} <span class="chip outline">Ctrl</span> + <span class="chip outline">F</span></h4> |
|||
</div> |
@ -0,0 +1,15 @@ |
|||
{if="$messages->isEmpty()"} |
|||
<div class="placeholder"> |
|||
<i class="material-symbols">search_off</i> |
|||
<h1>{$c->__('chatactions.search_messages')}</h1> |
|||
<h4>{$c->__('chatactions.search_messages_empty')}</h4> |
|||
</div> |
|||
{else} |
|||
<ul class="list active divided" id="message_preview"> |
|||
{loop="$messages"} |
|||
{autoescape="off"} |
|||
{$c->prepareMessage($value, true)} |
|||
{/autoescape} |
|||
{/loop} |
|||
</ul> |
|||
{/if} |
@ -1,34 +1,45 @@ |
|||
#chat_actions ul#message_preview { |
|||
ul#message_preview { |
|||
background-color: rgb(var(--movim-background)); |
|||
padding-top: 1.5rem; |
|||
min-height: 100%; |
|||
box-sizing: border-box; |
|||
padding: 0.75rem 0; |
|||
} |
|||
|
|||
#chat_actions ul#message_preview li div.bubble { |
|||
ul#message_preview li { |
|||
padding: 0.75rem; |
|||
} |
|||
|
|||
ul#message_preview li > div.bubble::after { |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
ul#message_preview li div.bubble { |
|||
max-width: calc(100% - 2rem); |
|||
pointer-events: none; |
|||
} |
|||
|
|||
#chat_actions ul#message_preview li div.bubble:not(.file) { |
|||
ul#message_preview li div.bubble:not(.file) { |
|||
padding-right: 5rem; |
|||
margin-bottom: 0; |
|||
padding-bottom: 3rem; |
|||
} |
|||
|
|||
#chat_actions ul#message_preview li.oppose { |
|||
ul#message_preview li.oppose { |
|||
flex-direction: row-reverse; |
|||
} |
|||
|
|||
#chat_actions ul#message_preview li div.bubble div.message > p { |
|||
ul#message_preview li div.bubble div.message > p { |
|||
white-space: initial; |
|||
display: -webkit-box; |
|||
-webkit-line-clamp: 4; |
|||
-webkit-box-orient: vertical; |
|||
} |
|||
|
|||
#chat_actions ul#message_preview li.reactions { |
|||
ul#message_preview li.reactions { |
|||
padding: 0 0.5rem; |
|||
padding-bottom: 0.75rem; |
|||
} |
|||
|
|||
#chat_actions ul#message_preview li.reactions > p { |
|||
ul#message_preview li.reactions > p { |
|||
line-height: 4.75rem; |
|||
} |
@ -1,3 +1,5 @@ |
|||
[chatactions] |
|||
copy_text = Copy the text |
|||
copied_text = Text copied |
|||
search_messages = Search messages by content |
|||
search_messages_empty = No messages found |
@ -0,0 +1,26 @@ |
|||
<?php |
|||
|
|||
use Movim\Migration; |
|||
use Illuminate\Database\Schema\Blueprint; |
|||
use Illuminate\Database\Capsule\Manager as DB; |
|||
|
|||
class AddBodyTsVectorToMessagesTable extends Migration |
|||
{ |
|||
public function up() |
|||
{ |
|||
if ($this->schema->getConnection()->getDriverName() == 'pgsql') { |
|||
$this->schema->table('messages', function (Blueprint $table) { |
|||
DB::statement("create index messages_body_gin_index on messages using gin (to_tsvector('simple', body));"); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
public function down() |
|||
{ |
|||
if ($this->schema->getConnection()->getDriverName() == 'pgsql') { |
|||
$this->schema->table('messages', function (Blueprint $table) { |
|||
$table->dropIndex('messages_body_gin_index'); |
|||
}); |
|||
} |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue