Add mark as read button
This commit is contained in:
parent
70a13235ac
commit
086f92903b
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT \n id as \"id!: String\",\n entry_id,\n title,\n published as \"published: Option<chrono::DateTime<Utc>>\",\n updated as \"updated: Option<chrono::DateTime<Utc>>\",\n summary,\n content,\n link\n FROM feed_entries \n WHERE feed_id = ?\n ORDER BY published DESC NULLS LAST\n LIMIT ?\n ",
|
"query": "\n SELECT \n id as \"id!: String\",\n entry_id,\n title,\n published as \"published: Option<chrono::DateTime<Utc>>\",\n updated as \"updated: Option<chrono::DateTime<Utc>>\",\n summary,\n content,\n link,\n marked_read as \"marked_read: Option<chrono::DateTime<Utc>>\"\n FROM feed_entries \n WHERE feed_id = ?\n ORDER BY published DESC NULLS LAST\n LIMIT ?\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@ -42,6 +42,11 @@
|
|||||||
"name": "link",
|
"name": "link",
|
||||||
"ordinal": 7,
|
"ordinal": 7,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "marked_read: Option<chrono::DateTime<Utc>>",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Datetime"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@ -55,8 +60,9 @@
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
true
|
true
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "aa1176c799d37727714cf346eb8a16ba561dc7a1b9b95992c38248c2102d6621"
|
"hash": "7698fc853218a67cf22338697be3b504220e8fbf845e32945273c7e5cd579152"
|
||||||
}
|
}
|
@ -31,6 +31,7 @@ struct Entry {
|
|||||||
summary: String,
|
summary: String,
|
||||||
content: Option<feed_rs::model::Content>,
|
content: Option<feed_rs::model::Content>,
|
||||||
link: Option<String>,
|
link: Option<String>,
|
||||||
|
marked_read: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -131,7 +132,8 @@ async fn read_entries(feed_id: &str, db: &mut SqliteConnection) -> Result<Vec<En
|
|||||||
updated as "updated: Option<chrono::DateTime<Utc>>",
|
updated as "updated: Option<chrono::DateTime<Utc>>",
|
||||||
summary,
|
summary,
|
||||||
content,
|
content,
|
||||||
link
|
link,
|
||||||
|
marked_read as "marked_read: Option<chrono::DateTime<Utc>>"
|
||||||
FROM feed_entries
|
FROM feed_entries
|
||||||
WHERE feed_id = ?
|
WHERE feed_id = ?
|
||||||
ORDER BY published DESC NULLS LAST
|
ORDER BY published DESC NULLS LAST
|
||||||
@ -164,6 +166,7 @@ async fn read_entries(feed_id: &str, db: &mut SqliteConnection) -> Result<Vec<En
|
|||||||
summary: row.summary.clone(),
|
summary: row.summary.clone(),
|
||||||
content,
|
content,
|
||||||
link: row.link.clone(),
|
link: row.link.clone(),
|
||||||
|
marked_read: row.marked_read.flatten(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, Status>>()?;
|
.collect::<Result<Vec<_>, Status>>()?;
|
||||||
@ -192,6 +195,7 @@ async fn fetch_new_entries(url: &Url) -> Result<Vec<Entry>, Status> {
|
|||||||
summary: get(feed_entry.summary, "summary"),
|
summary: get(feed_entry.summary, "summary"),
|
||||||
content: feed_entry.content,
|
content: feed_entry.content,
|
||||||
link: feed_entry.links.first().map(|l| l.href.clone()),
|
link: feed_entry.links.first().map(|l| l.href.clone()),
|
||||||
|
marked_read: None,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Ok(entries)
|
Ok(entries)
|
||||||
|
@ -513,19 +513,16 @@ button:disabled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.feed-entry-title {
|
.feed-entry-title {
|
||||||
margin: 0 0 1rem 0;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.feed-entry-title a {
|
.feed-entry-title a {
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 1.25rem;
|
flex-grow: 1;
|
||||||
font-weight: 500;
|
|
||||||
transition: color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed-entry-title a:hover {
|
|
||||||
color: var(--primary-red);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.feed-entry-meta {
|
.feed-entry-meta {
|
||||||
@ -637,4 +634,19 @@ button:disabled {
|
|||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-toggle {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-toggle:hover {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
@ -45,12 +45,46 @@ function renderFeedEntries(entries) {
|
|||||||
|
|
||||||
const title = document.createElement('h2');
|
const title = document.createElement('h2');
|
||||||
title.className = 'feed-entry-title';
|
title.className = 'feed-entry-title';
|
||||||
|
|
||||||
const titleLink = document.createElement('a');
|
const titleLink = document.createElement('a');
|
||||||
titleLink.href = entry.link || '#';
|
titleLink.href = entry.link || '#';
|
||||||
titleLink.target = '_blank';
|
titleLink.target = '_blank';
|
||||||
titleLink.textContent = entry.title;
|
titleLink.textContent = entry.title;
|
||||||
title.appendChild(titleLink);
|
title.appendChild(titleLink);
|
||||||
|
|
||||||
|
const readToggle = document.createElement('button');
|
||||||
|
readToggle.className = 'read-toggle';
|
||||||
|
readToggle.title = entry.marked_read ? 'Mark as unread' : 'Mark as read';
|
||||||
|
readToggle.innerHTML = entry.marked_read
|
||||||
|
? '<i class="fa-solid fa-circle-check"></i>'
|
||||||
|
: '<i class="fa-regular fa-circle"></i>';
|
||||||
|
readToggle.onclick = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/entries/${entry.id}/state`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
read: !entry.marked_read
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
entry.marked_read = !entry.marked_read;
|
||||||
|
readToggle.title = entry.marked_read ? 'Mark as unread' : 'Mark as read';
|
||||||
|
readToggle.innerHTML = entry.marked_read
|
||||||
|
? '<i class="fa-solid fa-circle-check"></i>'
|
||||||
|
: '<i class="fa-regular fa-circle"></i>';
|
||||||
|
} else {
|
||||||
|
console.error('Failed to update read state:', response.status);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update read state:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
title.appendChild(readToggle);
|
||||||
|
|
||||||
const meta = document.createElement('div');
|
const meta = document.createElement('div');
|
||||||
meta.className = 'feed-entry-meta';
|
meta.className = 'feed-entry-meta';
|
||||||
if (entry.published) {
|
if (entry.published) {
|
||||||
|
Loading…
Reference in New Issue
Block a user