web: add per-message unread control and display
This commit is contained in:
parent
ce836cd1e8
commit
516eedb086
3
web/graphql/mark_read.graphql
Normal file
3
web/graphql/mark_read.graphql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
mutation MarkReadMutation($query: String!, $unread: Boolean!) {
|
||||||
|
setReadStatus(query:$query, unread:$unread)
|
||||||
|
}
|
||||||
@ -53,7 +53,9 @@
|
|||||||
"name": "skip"
|
"name": "skip"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"mutationType": null,
|
"mutationType": {
|
||||||
|
"name": "Mutation"
|
||||||
|
},
|
||||||
"queryType": {
|
"queryType": {
|
||||||
"name": "QueryRoot"
|
"name": "QueryRoot"
|
||||||
},
|
},
|
||||||
@ -536,6 +538,62 @@
|
|||||||
"name": "Message",
|
"name": "Message",
|
||||||
"possibleTypes": null
|
"possibleTypes": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": null,
|
||||||
|
"enumValues": null,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"description": null,
|
||||||
|
"name": "query",
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"description": null,
|
||||||
|
"name": "unread",
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Boolean",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deprecationReason": null,
|
||||||
|
"description": null,
|
||||||
|
"isDeprecated": false,
|
||||||
|
"name": "setReadStatus",
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Boolean",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inputFields": null,
|
||||||
|
"interfaces": [],
|
||||||
|
"kind": "OBJECT",
|
||||||
|
"name": "Mutation",
|
||||||
|
"possibleTypes": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Information about pagination in a connection",
|
"description": "Information about pagination in a connection",
|
||||||
"enumValues": null,
|
"enumValues": null,
|
||||||
@ -903,6 +961,22 @@
|
|||||||
"description": null,
|
"description": null,
|
||||||
"enumValues": null,
|
"enumValues": null,
|
||||||
"fields": [
|
"fields": [
|
||||||
|
{
|
||||||
|
"args": [],
|
||||||
|
"deprecationReason": null,
|
||||||
|
"description": null,
|
||||||
|
"isDeprecated": false,
|
||||||
|
"name": "threadId",
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"args": [],
|
"args": [],
|
||||||
"deprecationReason": null,
|
"deprecationReason": null,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
query ShowThreadQuery($threadId: String!) {
|
query ShowThreadQuery($threadId: String!) {
|
||||||
thread(threadId: $threadId) {
|
thread(threadId: $threadId) {
|
||||||
|
threadId,
|
||||||
subject
|
subject
|
||||||
messages {
|
messages {
|
||||||
id
|
id
|
||||||
|
|||||||
@ -5,23 +5,34 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<link rel="stylesheet" , href="https://jenil.github.io/bulmaswatch/cyborg/bulmaswatch.min.css">
|
<link rel="stylesheet" , href="https://jenil.github.io/bulmaswatch/cyborg/bulmaswatch.min.css">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css" integrity="sha512-SzlrxWUlpfuzQ+pcUCosxcglQRNAq/DZjVsC0lE40xsADsfeQoEypE+enwcOiGjk/bSuGGKHEyjSoQ1zVisanQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css"
|
||||||
|
integrity="sha512-SzlrxWUlpfuzQ+pcUCosxcglQRNAq/DZjVsC0lE40xsADsfeQoEypE+enwcOiGjk/bSuGGKHEyjSoQ1zVisanQ=="
|
||||||
|
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||||
<link rel="icon" href="https://static.xinu.tv/favicon/letterbox.svg" />
|
<link rel="icon" href="https://static.xinu.tv/favicon/letterbox.svg" />
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message .headers {
|
.message .headers {
|
||||||
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message .headers .read-status {
|
||||||
|
position: absolute;
|
||||||
|
right: 1em;
|
||||||
|
top: 0em;
|
||||||
|
}
|
||||||
|
|
||||||
.message .headers .header {
|
.message .headers .header {
|
||||||
overflow: clip;
|
overflow: clip;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
background: white;
|
background: white;
|
||||||
color: black;
|
color: black;
|
||||||
@ -31,14 +42,17 @@
|
|||||||
width: 0;
|
width: 0;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
background-color: red;
|
background-color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-part-text-plain {
|
.view-part-text-plain {
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -48,22 +62,26 @@ iframe {
|
|||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.index .from {
|
.index .from {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: 10em;
|
width: 10em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.index .subject {
|
.index .subject {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.index .date {
|
.index .date {
|
||||||
width: 10em;
|
width: 10em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lb-footer {
|
.lb-footer {
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
color: #222;
|
color: #222;
|
||||||
@ -74,37 +92,47 @@ iframe {
|
|||||||
height: 3em;
|
height: 3em;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.debug ul {
|
.debug ul {
|
||||||
padding-left: 2em;
|
padding-left: 2em;
|
||||||
}
|
}
|
||||||
.debug li {
|
|
||||||
}
|
.debug li {}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
animation-name: spin;
|
animation-name: spin;
|
||||||
animation-duration: 1000ms;
|
animation-duration: 1000ms;
|
||||||
animation-iteration-count: infinite;
|
animation-iteration-count: infinite;
|
||||||
animation-timing-function: linear;
|
animation-timing-function: linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
from {
|
from {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.section {
|
.section {
|
||||||
padding: 1.5em;
|
padding: 1.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
input, .input {
|
|
||||||
|
input,
|
||||||
|
.input {
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
input::placeholder, .input::placeholder{
|
|
||||||
|
input::placeholder,
|
||||||
|
.input::placeholder {
|
||||||
color: #555;
|
color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,24 +146,29 @@ input::placeholder, .input::placeholder{
|
|||||||
padding-bottom: .5em;
|
padding-bottom: .5em;
|
||||||
padding-top: .5em;
|
padding-top: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-results .row .subject {
|
.search-results .row .subject {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-results .row .from {
|
.search-results .row .from {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-results .row .tag {
|
.search-results .row .tag {
|
||||||
height: 1.5em;
|
height: 1.5em;
|
||||||
padding-left: .5em;
|
padding-left: .5em;
|
||||||
padding-right: .5em;
|
padding-right: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.float-right {
|
.float-right {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide quoted emails */
|
/* Hide quoted emails */
|
||||||
/*
|
/*
|
||||||
div[name="quote"],
|
div[name="quote"],
|
||||||
@ -150,27 +183,34 @@ blockquote[type="cite"],
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 12rem 1fr;
|
grid-template-columns: 12rem 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags-menu {
|
.tags-menu {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags-menu .menu-list a {
|
.tags-menu .menu-list a {
|
||||||
padding: 0.25em 0.5em;
|
padding: 0.25em 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags-menu .tag-indent {
|
.tags-menu .tag-indent {
|
||||||
padding-left: .5em;
|
padding-left: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags-menu .tag-tag {
|
.tags-menu .tag-tag {
|
||||||
margin-left: -1em;
|
margin-left: -1em;
|
||||||
padding-right: .25em;
|
padding-right: .25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.desktop nav.pagination,
|
.desktop nav.pagination,
|
||||||
.tablet nav.pagination {
|
.tablet nav.pagination {
|
||||||
margin-left: .5em;
|
margin-left: .5em;
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-tree {
|
.content-tree {
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,14 @@ pub struct FrontPageQuery;
|
|||||||
)]
|
)]
|
||||||
pub struct ShowThreadQuery;
|
pub struct ShowThreadQuery;
|
||||||
|
|
||||||
|
#[derive(GraphQLQuery)]
|
||||||
|
#[graphql(
|
||||||
|
schema_path = "graphql/schema.json",
|
||||||
|
query_path = "graphql/mark_read.graphql",
|
||||||
|
response_derives = "Debug"
|
||||||
|
)]
|
||||||
|
pub struct MarkReadMutation;
|
||||||
|
|
||||||
pub async fn send_graphql<Body, Resp>(body: Body) -> Result<graphql_client::Response<Resp>, Error>
|
pub async fn send_graphql<Body, Resp>(body: Body) -> Result<graphql_client::Response<Resp>, Error>
|
||||||
where
|
where
|
||||||
Body: Serialize,
|
Body: Serialize,
|
||||||
|
|||||||
@ -217,6 +217,25 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
orders.request_url(urls::search(&query, 0));
|
orders.request_url(urls::search(&query, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Msg::SetUnread(query, unread) => {
|
||||||
|
orders.skip().perform_cmd(async move {
|
||||||
|
let res: Result<
|
||||||
|
graphql_client::Response<graphql::mark_read_mutation::ResponseData>,
|
||||||
|
gloo_net::Error,
|
||||||
|
> = send_graphql(graphql::MarkReadMutation::build_query(
|
||||||
|
graphql::mark_read_mutation::Variables {
|
||||||
|
query: query.clone(),
|
||||||
|
unread,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
if let Err(e) = res {
|
||||||
|
error!("Failed to set read for {query} to {unread}: {e}");
|
||||||
|
}
|
||||||
|
Msg::RefreshStart
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Msg::FrontPageRequest {
|
Msg::FrontPageRequest {
|
||||||
query,
|
query,
|
||||||
after,
|
after,
|
||||||
@ -380,6 +399,8 @@ pub enum Msg {
|
|||||||
UpdateQuery(String),
|
UpdateQuery(String),
|
||||||
SearchQuery(String),
|
SearchQuery(String),
|
||||||
|
|
||||||
|
SetUnread(String, bool),
|
||||||
|
|
||||||
FrontPageRequest {
|
FrontPageRequest {
|
||||||
query: String,
|
query: String,
|
||||||
after: Option<String>,
|
after: Option<String>,
|
||||||
|
|||||||
@ -7,7 +7,10 @@ use chrono::{DateTime, Datelike, Duration, Local, Utc};
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
use seed_hooks::{state_access::CloneState, topo, use_state};
|
use seed_hooks::{
|
||||||
|
state_access::{CloneState, StateAccess},
|
||||||
|
topo, use_state,
|
||||||
|
};
|
||||||
use wasm_timer::Instant;
|
use wasm_timer::Instant;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -262,25 +265,72 @@ fn raw_text_message(contents: &str) -> Node<Msg> {
|
|||||||
div![C!["view-part-text-plain"], contents, truncated_msg,]
|
div![C!["view-part-text-plain"], contents, truncated_msg,]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn thread(thread: &ShowThreadQueryThread) -> Node<Msg> {
|
fn has_unread(tags: &[String]) -> bool {
|
||||||
// TODO(wathiede): show per-message subject if it changes significantly from top-level subject
|
for t in tags {
|
||||||
set_title(&thread.subject);
|
if t == "unread" {
|
||||||
let mut tags: Vec<_> = thread
|
return true;
|
||||||
.messages
|
}
|
||||||
.iter()
|
}
|
||||||
.fold(HashSet::new(), |mut tags, msg| {
|
false
|
||||||
tags.extend(msg.tags.clone());
|
}
|
||||||
tags
|
|
||||||
})
|
fn read_message_render(msg: &ShowThreadQueryThreadMessages, open: StateAccess<bool>) -> Node<Msg> {
|
||||||
.into_iter()
|
let id = msg.id.clone();
|
||||||
.collect();
|
let is_unread = has_unread(&msg.tags);
|
||||||
tags.sort();
|
|
||||||
let messages = thread.messages.iter().map(|msg| {
|
|
||||||
div![
|
div![
|
||||||
C!["message"],
|
C!["message"],
|
||||||
div![
|
div![
|
||||||
C!["headers"],
|
C!["headers"],
|
||||||
/* TODO(wathiede): collect all the tags and show them here. */
|
span![
|
||||||
|
C!["read-status"],
|
||||||
|
i![
|
||||||
|
style! {
|
||||||
|
St::Color => "gold"
|
||||||
|
},
|
||||||
|
C![if is_unread { "fa-regular" } else { "fa-solid" }, "fa-star"],
|
||||||
|
ev(Ev::Click, move |e| {
|
||||||
|
e.stop_propagation();
|
||||||
|
Msg::SetUnread(format!("id:{id}"), !is_unread)
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
" ",
|
||||||
|
msg.from
|
||||||
|
.as_ref()
|
||||||
|
.map(|from| span![C!["header"], view_address(&from)]),
|
||||||
|
" ",
|
||||||
|
msg.timestamp.map(|ts| span![C!["header"], human_age(ts)]),
|
||||||
|
// TODO(wathiede): add first line of message body
|
||||||
|
],
|
||||||
|
ev(Ev::Click, move |e| {
|
||||||
|
open.set(!open.get());
|
||||||
|
e.stop_propagation();
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
fn unread_message_render(
|
||||||
|
msg: &ShowThreadQueryThreadMessages,
|
||||||
|
open: StateAccess<bool>,
|
||||||
|
) -> Node<Msg> {
|
||||||
|
let id = msg.id.clone();
|
||||||
|
let is_unread = has_unread(&msg.tags);
|
||||||
|
div![
|
||||||
|
C!["message"],
|
||||||
|
div![
|
||||||
|
C!["headers"],
|
||||||
|
span![
|
||||||
|
C!["read-status"],
|
||||||
|
i![
|
||||||
|
style! {
|
||||||
|
St::Color => "gold"
|
||||||
|
},
|
||||||
|
C![if is_unread { "fa-regular" } else { "fa-solid" }, "fa-star"],
|
||||||
|
ev(Ev::Click, move |e| {
|
||||||
|
e.stop_propagation();
|
||||||
|
Msg::SetUnread(format!("id:{id}"), !is_unread)
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
],
|
||||||
msg.from
|
msg.from
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|from| div![C!["header"], "From: ", view_address(&from)]),
|
.map(|from| div![C!["header"], "From: ", view_address(&from)]),
|
||||||
@ -292,6 +342,10 @@ fn thread(thread: &ShowThreadQueryThread) -> Node<Msg> {
|
|||||||
IF!(!msg.to.is_empty() => span!["To: ", view_addresses(&msg.to)]),
|
IF!(!msg.to.is_empty() => span!["To: ", view_addresses(&msg.to)]),
|
||||||
IF!(!msg.cc.is_empty() => span!["CC: ", view_addresses(&msg.cc)])
|
IF!(!msg.cc.is_empty() => span!["CC: ", view_addresses(&msg.cc)])
|
||||||
],
|
],
|
||||||
|
ev(Ev::Click, move |e| {
|
||||||
|
open.set(!open.get());
|
||||||
|
e.stop_propagation();
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
div![
|
div![
|
||||||
C!["body"],
|
C!["body"],
|
||||||
@ -330,11 +384,54 @@ fn thread(thread: &ShowThreadQueryThread) -> Node<Msg> {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[topo::nested]
|
||||||
|
fn thread(thread: &ShowThreadQueryThread) -> Node<Msg> {
|
||||||
|
// TODO(wathiede): show per-message subject if it changes significantly from top-level subject
|
||||||
|
set_title(&thread.subject);
|
||||||
|
let mut tags: Vec<_> = thread
|
||||||
|
.messages
|
||||||
|
.iter()
|
||||||
|
.fold(HashSet::new(), |mut tags, msg| {
|
||||||
|
tags.extend(msg.tags.clone());
|
||||||
|
tags
|
||||||
|
})
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
tags.sort();
|
||||||
|
let messages = thread.messages.iter().map(|msg| {
|
||||||
|
let is_unread = has_unread(&msg.tags);
|
||||||
|
let open = use_state(|| is_unread);
|
||||||
|
if open.get() {
|
||||||
|
unread_message_render(&msg, open)
|
||||||
|
} else {
|
||||||
|
read_message_render(&msg, open)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
let any_unread = thread.messages.iter().any(|msg| has_unread(&msg.tags));
|
||||||
|
let thread_id = thread.thread_id.clone();
|
||||||
div![
|
div![
|
||||||
C!["thread"],
|
C!["thread"],
|
||||||
p![
|
p![
|
||||||
C!["is-size-4"],
|
C!["is-size-4"],
|
||||||
|
span![
|
||||||
|
C!["read-status"],
|
||||||
|
i![
|
||||||
|
style! {
|
||||||
|
St::Color => "gold"
|
||||||
|
},
|
||||||
|
C![
|
||||||
|
if any_unread { "fa-regular" } else { "fa-solid" },
|
||||||
|
"fa-star"
|
||||||
|
],
|
||||||
|
ev(Ev::Click, move |_| Msg::SetUnread(
|
||||||
|
format!("thread:{}", thread_id),
|
||||||
|
!any_unread
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
" ",
|
||||||
|
],
|
||||||
&thread.subject,
|
&thread.subject,
|
||||||
" ",
|
" ",
|
||||||
tags_chiclet(&tags, false)
|
tags_chiclet(&tags, false)
|
||||||
@ -475,9 +572,6 @@ pub fn view(model: &Model) -> Node<Msg> {
|
|||||||
.expect("window height")
|
.expect("window height")
|
||||||
.as_f64()
|
.as_f64()
|
||||||
.expect("window height f64");
|
.expect("window height f64");
|
||||||
info!("win: {w}x{h}");
|
|
||||||
|
|
||||||
info!("view called");
|
|
||||||
div![
|
div![
|
||||||
match w {
|
match w {
|
||||||
w if w < 800. => div![C!["mobile"], mobile::view(model)],
|
w if w < 800. => div![C!["mobile"], mobile::view(model)],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user