Skip to content

Commit

Permalink
Conversation history (desktop-only)
Browse files Browse the repository at this point in the history
  • Loading branch information
grishka committed Oct 13, 2023
1 parent f6c12b4 commit 942fd75
Show file tree
Hide file tree
Showing 15 changed files with 198 additions and 17 deletions.
1 change: 1 addition & 0 deletions src/main/java/smithereen/SmithereenApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ public static void main(String[] args){
getLoggedIn("/outbox", MailRoutes::outbox);
getLoggedIn("/compose", MailRoutes::compose);
postWithCSRF("/send", MailRoutes::sendMessage);
getLoggedIn("/history", MailRoutes::history);
path("/messages/:id", ()->{
Filter idParserFilter=(req, resp)->{
long id=Utils.decodeLong(req.params(":id"));
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/smithereen/controllers/MailController.java
Original file line number Diff line number Diff line change
Expand Up @@ -344,4 +344,12 @@ public void putForeignMessage(MailMessage msg){
throw new InternalServerErrorException(x);
}
}

public PaginatedList<MailMessage> getHistory(User self, User peer, int offset, int count){
try{
return MailStorage.getHistory(self.id, peer.id, offset, count);
}catch(SQLException x){
throw new InternalServerErrorException(x);
}
}
}
11 changes: 10 additions & 1 deletion src/main/java/smithereen/lang/Lang.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
import java.util.Map;

import smithereen.Utils;
import smithereen.model.User;
import smithereen.lang.formatting.ICUMessageParser;
import smithereen.lang.formatting.ICUMessageSyntaxException;
import smithereen.lang.formatting.StringTemplate;
import smithereen.model.User;
import spark.utils.StringUtils;

public class Lang{
Expand Down Expand Up @@ -242,6 +242,15 @@ public String formatTime(Instant time, ZoneId timeZone){
return String.format(locale, "%d:%02d", dt.getHour(), dt.getMinute());
}

public String formatTimeOrDay(Instant time, ZoneId timeZone){
ZonedDateTime dt=time.atZone(timeZone);
if(dt.toLocalDate().equals(LocalDate.now(timeZone))){
return String.format(locale, "%d:%02d:%02d", dt.getHour(), dt.getMinute(), dt.getSecond());
}else{
return String.format(locale, "%02d.%02d.%02d", dt.getDayOfMonth(), dt.getMonthValue(), dt.getYear()%100);
}
}

public String getAsJS(String key){
if(!data.containsKey(key)){
if(fallback!=null)
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/smithereen/routes/MailRoutes.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public static Object viewMessage(Request req, Response resp, Account self, Appli
model.with("tab", "view").with("message", msg).with("users", users);
boolean isOutgoing=msg.senderID==self.user.id;
User peer=users.get(isOutgoing ? msg.to.iterator().next() : msg.senderID);
model.with("peer", peer);
model.pageTitle(lang(req).get(isOutgoing ? "mail_message_title_outgoing" : "mail_message_title_incoming", Map.of("name", peer.getFirstLastAndGender())));
if(StringUtils.isNotEmpty(msg.subject)){
String subject=msg.subject;
Expand Down Expand Up @@ -246,4 +247,18 @@ public static Object restore(Request req, Response resp, Account self, Applicati
.remove("msgDeletedRow"+msg.encodedID)
.show(origElementID);
}

public static Object history(Request req, Response resp, Account self, ApplicationContext ctx){
if(!isAjax(req)){
resp.redirect("/my/mail");
return "";
}
requireQueryParams(req, "peer");
int peerID=safeParseInt(req.queryParams("peer"));
User peer=ctx.getUsersController().getUserOrThrow(peerID);
RenderedTemplateResponse model=new RenderedTemplateResponse("mail_history", req);
model.paginate(ctx.getMailController().getHistory(self.user, peer, offset(req), 50));
model.with("users", Map.of(self.user.id, self.user, peerID, peer));
return new WebDeltaResponse(resp).setContent("mailHistoryWrap", model.renderToString());
}
}
15 changes: 15 additions & 0 deletions src/main/java/smithereen/storage/MailStorage.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package smithereen.storage;

import java.net.URI;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.time.Instant;
import java.util.Collection;
Expand Down Expand Up @@ -241,4 +242,18 @@ public static void consumePrivacyGrant(int ownerID, int userID) throws SQLExcept
.where("owner_id=? AND user_id=?", ownerID, userID)
.executeNoResult();
}

public static PaginatedList<MailMessage> getHistory(int ownerID, int peerID, int offset, int count) throws SQLException{
try(DatabaseConnection conn=DatabaseConnectionManager.getConnection()){
PreparedStatement stmt=SQLQueryBuilder.prepareStatement(conn, "SELECT COUNT(*) FROM mail_messages_peers JOIN mail_messages ON message_id=mail_messages.id" +
" WHERE mail_messages_peers.owner_id=? AND mail_messages_peers.peer_id=? AND mail_messages.deleted_at IS NULL", ownerID, peerID);
int total=DatabaseUtils.oneFieldToInt(stmt.executeQuery());
if(total==0)
return PaginatedList.emptyList(count);
stmt=SQLQueryBuilder.prepareStatement(conn, "SELECT mail_messages.* FROM mail_messages_peers JOIN mail_messages ON message_id=mail_messages.id" +
" WHERE mail_messages_peers.owner_id=? AND mail_messages_peers.peer_id=? AND mail_messages.deleted_at IS NULL ORDER BY message_id DESC LIMIT ? OFFSET ?", ownerID, peerID, count, offset);
List<MailMessage> msgs=DatabaseUtils.resultSetToObjectStream(stmt.executeQuery(), MailMessage::fromResultSet, null).toList();
return new PaginatedList<>(msgs, total, offset, count);
}
}
}
21 changes: 16 additions & 5 deletions src/main/java/smithereen/templates/LangDateFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,28 @@ public class LangDateFunction implements Function{
public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context, int lineNumber){
Object arg=args.get("date");
boolean forceAbsolute=(Boolean) args.getOrDefault("forceAbsolute", Boolean.FALSE);
Lang lang=Lang.get(context.getLocale());
ZoneId timeZone=(ZoneId) context.getVariable("timeZone");
if(arg instanceof java.sql.Date sd)
return Lang.get(context.getLocale()).formatDay(sd.toLocalDate());
return lang.formatDay(sd.toLocalDate());
if(arg instanceof LocalDate ld)
return forceAbsolute ? Lang.get(context.getLocale()).formatDay(ld) : Lang.get(context.getLocale()).formatDayRelative(ld, (ZoneId) context.getVariable("timeZone"));
if(arg instanceof Instant instant)
return Lang.get(context.getLocale()).formatDate(instant, (ZoneId) context.getVariable("timeZone"), forceAbsolute);
return forceAbsolute ? lang.formatDay(ld) : lang.formatDayRelative(ld, timeZone);
if(arg instanceof Instant instant){
String format=(String) args.get("format");
if(format!=null){
switch(format){
case "timeOrDay" -> {
return lang.formatTimeOrDay(instant, timeZone);
}
}
}
return lang.formatDate(instant, timeZone, forceAbsolute);
}
return "????";
}

@Override
public List<String> getArgumentNames(){
return List.of("date", "forceAbsolute");
return List.of("date", "forceAbsolute", "format");
}
}
22 changes: 22 additions & 0 deletions src/main/java/smithereen/templates/RandomStringFunction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package smithereen.templates;

import java.util.List;
import java.util.Map;
import java.util.UUID;

import io.pebbletemplates.pebble.extension.Function;
import io.pebbletemplates.pebble.template.EvaluationContext;
import io.pebbletemplates.pebble.template.PebbleTemplate;
import smithereen.Utils;

public class RandomStringFunction implements Function{
@Override
public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context, int lineNumber){
return Utils.randomAlphanumericString((Integer)args.getOrDefault("length", 10));
}

@Override
public List<String> getArgumentNames(){
return List.of("length");
}
}
3 changes: 2 additions & 1 deletion src/main/java/smithereen/templates/SmithereenExtension.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ public Map<String, Function> getFunctions(){
"getTime", new InstantToTimeFunction(),
"getDate", new InstantToDateFunction(),
"describeAttachments", new DescribeAttachmentsFunction(),
"addQueryParams", new AddQueryParamsFunction()
"addQueryParams", new AddQueryParamsFunction(),
"randomString", new RandomStringFunction()
);
}

Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/langs/en/mail.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@
"mail_in_reply_to_own_post": "your post",
"mail_in_reply_to_own_comment": "your comment",
"profile_write_message": "Send a message",
"messages_title": "Messages"
"messages_title": "Messages",
"mail_conversation_history": "Conversation history"
}
3 changes: 2 additions & 1 deletion src/main/resources/langs/ru/mail.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@
"mail_in_reply_to_own_post": "вашу запись",
"mail_in_reply_to_own_comment": "ваш комментарий",
"profile_write_message": "Написать сообщение",
"messages_title": "Сообщения"
"messages_title": "Сообщения",
"mail_conversation_history": "История сообщений"
}
17 changes: 11 additions & 6 deletions src/main/resources/templates/common/pagination.twig
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@
{#- NB: curPage starts at 0, users expect pages to start at 1 -#}
{% if totalPages>1 %}
<div class="pagination">
{%- if curPage>2 %}<a href="{{ paginationFirstPageUrl ? paginationFirstPageUrl : (paginationUrlPrefix+'0') }}"{% if paginationAjax %} data-ajax="1"{% endif %}>&laquo;</a>{% endif %}
{%- if curPage>1 %}<a href="{{ (paginationFirstPageUrl and curPage==2) ? paginationFirstPageUrl : (paginationUrlPrefix+(paginationPerPage*(curPage-2))) }}" class="page"{% if paginationAjax %} data-ajax="1"{% endif %}>{{curPage-1}}</a>{% endif %}
{%- if curPage>0 %}<a href="{{ (paginationFirstPageUrl and curPage==1) ? paginationFirstPageUrl : (paginationUrlPrefix+(paginationPerPage*(curPage-1))) }}" class="page"{% if paginationAjax %} data-ajax="1"{% endif %}>{{curPage}}</a>{% endif -%}
{%- if paginationAjax %}
{%- set loaderID="paginationLoader_"+randomString() %}
{%- set ajaxAttrs=' data-ajax data-ajax-show="'+loaderID+'"' -%}
<span class="loader" id="{{ loaderID }}" style="display: none"></span>
{%- endif %}
{%- if curPage>2 %}<a href="{{ paginationFirstPageUrl ? paginationFirstPageUrl : (paginationUrlPrefix+'0') }}"{{ ajaxAttrs | raw }}>&laquo;</a>{% endif %}
{%- if curPage>1 %}<a href="{{ (paginationFirstPageUrl and curPage==2) ? paginationFirstPageUrl : (paginationUrlPrefix+(paginationPerPage*(curPage-2))) }}" class="page"{{ ajaxAttrs | raw }}>{{curPage-1}}</a>{% endif %}
{%- if curPage>0 %}<a href="{{ (paginationFirstPageUrl and curPage==1) ? paginationFirstPageUrl : (paginationUrlPrefix+(paginationPerPage*(curPage-1))) }}" class="page"{{ ajaxAttrs | raw }}>{{curPage}}</a>{% endif -%}
<span class="curPage">{{ curPage+1 }}</span>
{%- if totalPages-curPage>1 %}<a href="{{ paginationUrlPrefix }}{{ paginationPerPage*(curPage+1) }}" class="page"{% if paginationAjax %} data-ajax="1"{% endif %}>{{ curPage+2 }}</a>{% endif %}
{%- if totalPages-curPage>2 %}<a href="{{ paginationUrlPrefix }}{{ paginationPerPage*(curPage+2) }}" class="page"{% if paginationAjax %} data-ajax="1"{% endif %}>{{ curPage+3 }}</a>{% endif %}
{%- if totalPages-curPage>3 %}<a href="{{ paginationUrlPrefix }}{{ paginationPerPage*(totalPages-1) }}"{% if paginationAjax %} data-ajax="1"{% endif %}>&raquo;</a>{% endif %}
{%- if totalPages-curPage>1 %}<a href="{{ paginationUrlPrefix }}{{ paginationPerPage*(curPage+1) }}" class="page"{{ ajaxAttrs | raw }}>{{ curPage+2 }}</a>{% endif %}
{%- if totalPages-curPage>2 %}<a href="{{ paginationUrlPrefix }}{{ paginationPerPage*(curPage+2) }}" class="page"{{ ajaxAttrs | raw }}>{{ curPage+3 }}</a>{% endif %}
{%- if totalPages-curPage>3 %}<a href="{{ paginationUrlPrefix }}{{ paginationPerPage*(totalPages-1) }}"{{ ajaxAttrs | raw }}>&raquo;</a>{% endif %}
</div>
{% endif %}
14 changes: 14 additions & 0 deletions src/main/resources/templates/desktop/mail_history.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<div class="mailHistory">
<div class="summaryWrap">
<div class="summary">{{ L('mail_conversation_history') }}</div>
{% include "pagination" with {'paginationAjax': true} %}
</div>
{% for msg in items %}
<div class="messageRow{{ msg.unread ? ' unread' : '' }}">
<div class="name {{ msg.senderID==currentUser.id ? 'self' : 'peer' }}"><a href="{{ users[msg.senderID].profileURL }}" class="ellipsize">{{ users[msg.senderID].firstName }}</a></div>
<div class="content">{{ msg.text | postprocessHTML }}{% if msg.attachments is not empty %}{{ renderAttachments(msg.processedAttachments, null) }}{% endif %}</div>
<div class="time"><a href="/my/mail/messages/{{ msg.encodedID }}">{{ LD(msg.createdAt, format="timeOrDay") }}</a></div>
</div>
{% endfor %}
<div class="bottomSummaryWrap">{% include "pagination" with {'paginationAjax': true} %}</div>
</div>
8 changes: 7 additions & 1 deletion src/main/resources/templates/desktop/mail_message.twig
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</div>
<span class="flR loader" id="msgActionsLoader" style="display: none">&nbsp;</span>
</h2>
<div class="time">{{ LD(message.createdAt) }}{% if message.updatedAt is not null %} | {{ L('mail_edited_at', {'time': LD(message.updatedAt)}) }}{% endif %}</div>
<div class="time">{{ LD(message.createdAt, true) }}{% if message.updatedAt is not null %} | {{ L('mail_edited_at', {'time': LD(message.updatedAt)}) }}{% endif %}</div>
<div class="messageViewHeader">
<a href="{{ users[message.senderID].profileURL }}" class="avaW">{{ users[message.senderID] | pictureForAvatar('s') }}</a>
<div class="label">{{ L('mail_from') }}:</div>
Expand Down Expand Up @@ -55,5 +55,11 @@
</div>
</div>
</div>
{% if peer is not null %}
<div id="mailHistoryWrap">
<a id="mailShowHistory" class="ellipsize" href="/my/mail/history?peer={{ peer.id }}" data-ajax data-ajax-show="mailHistoryLoader" data-ajax-hide="mailShowHistory">{{ L('mail_show_history', {'name': peer.firstLastAndGender}) }}</a>
<div class="loader" id="mailHistoryLoader" style="display: none"></div>
</div>
{% endif %}
{% endblock %}

2 changes: 1 addition & 1 deletion src/main/resources/templates/mobile/mail_message.twig
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<a href="{{ users[message.senderID].profileURL }}" class="avaW">{{ users[message.senderID] | pictureForAvatar('s') }}</a>
<div class="nameAndTime">
<div class="name"><a href="{{ users[message.senderID].profileURL }}">{{ users[message.senderID].completeName }}</a></div>
<div class="time">{{ LD(message.createdAt) }}{% if message.updatedAt is not null %} | {{ L('mail_edited_at', {'time': LD(message.updatedAt)}) }}{% endif %}</div>
<div class="time">{{ LD(message.createdAt, true) }}{% if message.updatedAt is not null %} | {{ L('mail_edited_at', {'time': LD(message.updatedAt)}) }}{% endif %}</div>
</div>
</div>
<div class="messageViewFields">
Expand Down
72 changes: 72 additions & 0 deletions src/main/web/desktop.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,9 @@ select{
.curPage{
font-weight: bold;
}
.loader{
margin-right: 5px;
}
}

.summaryWrap .pagination{
Expand Down Expand Up @@ -2911,3 +2914,72 @@ h2, h3, h4{
background: none;
padding: 0;
}

#mailHistoryWrap{
padding: 0;
border-top: solid 1px $wallPostSeparator;
>.loader{
height: 49px;
}
}

#mailShowHistory{
display: block;
text-align: center;
padding: 18px 10px;
}

.mailHistory{
width: 465px;
margin: auto;
padding-bottom: 10px;
.summaryWrap{
padding-left: 0;
padding-right: 0;
margin-bottom: 5px;
}
.bottomSummaryWrap{
padding: 0;
margin-top: 5px;
}
.messageRow{
display: grid;
grid-template-columns: 1fr 300px 50px;
grid-gap: 10px;
padding: 5px;
&.unread{
background: #fef8ee;
}
>*{
min-width: 0;
}
>.name{
text-align: right;
font-weight: bold;
a{
display: block;
}
&.self a{
color: #a4ae46;
}
}
>.content{
line-height: 130%;
p:first-child{
margin-top: 0;
}
p:last-child{
margin-bottom: 0;
}
.postAttachments .aspectWrapper .pseudoImage{
max-width: 200px;
}
}
>.time{
text-align: right;
a{
color: #999;
}
}
}
}

0 comments on commit 942fd75

Please sign in to comment.