add: post titles
This commit is contained in:
parent
1279536609
commit
5b1db42c51
14 changed files with 230 additions and 13 deletions
|
@ -94,6 +94,7 @@ version = "1.0.0"
|
|||
"communities:action.configure" = "Configure"
|
||||
"communities:label.create_post" = "Create post"
|
||||
"communities:label.content" = "Content"
|
||||
"communities:label.title" = "Title"
|
||||
"communities:label.posts" = "Posts"
|
||||
"communities:label.questions" = "Questions"
|
||||
"communities:label.not_allowed_to_read" = "You're not allowed to view this community's posts"
|
||||
|
|
|
@ -87,7 +87,7 @@
|
|||
(text "{{ components::avatar(username=user.id, size=\"32px\", selector_type=\"id\") }}")
|
||||
(select
|
||||
("id" "community_to_post_to")
|
||||
("onchange" "update_community_avatar(event)")
|
||||
("onchange" "update_community_avatar(event); check_community_supports_title(event)")
|
||||
(option
|
||||
("value" "{{ config.town_square }}")
|
||||
("selected" "{% if not selected_community -%}true{% else %}false{%- endif %}")
|
||||
|
@ -102,6 +102,19 @@
|
|||
("class" "card flex flex-col gap-2")
|
||||
("id" "create_form")
|
||||
("onsubmit" "create_post_from_form(event)")
|
||||
(div
|
||||
("class" "flex flex-col gap-1 hidden")
|
||||
("id" "title_field")
|
||||
(label
|
||||
("for" "content")
|
||||
(text "{{ text \"communities:label.title\" }}"))
|
||||
(input
|
||||
("type" "text")
|
||||
("name" "title")
|
||||
("id" "title")
|
||||
("placeholder" "title")
|
||||
("minlength" "2")
|
||||
("maxlength" "128")))
|
||||
(div
|
||||
("class" "flex flex-col gap-1")
|
||||
(label
|
||||
|
@ -179,6 +192,7 @@
|
|||
\"community_to_post_to\",
|
||||
).selectedOptions[0].value,
|
||||
poll: poll_data[1],
|
||||
title: e.target.title.value,
|
||||
}),
|
||||
);
|
||||
|
||||
|
@ -397,10 +411,29 @@
|
|||
}
|
||||
}
|
||||
|
||||
function check_community_supports_title(e) {
|
||||
const element = document.getElementById(\"title_field\");
|
||||
const id = e.target.selectedOptions[0].value;
|
||||
|
||||
fetch(`/api/v1/communities/${id}/supports_titles`)
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
if (res.message === \"yes\") {
|
||||
element.classList.remove(\"hidden\");
|
||||
} else {
|
||||
element.classList.add(\"hidden\");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
update_community_avatar({
|
||||
target: document.getElementById(\"community_to_post_to\"),
|
||||
});
|
||||
|
||||
check_community_supports_title({
|
||||
target: document.getElementById(\"community_to_post_to\"),
|
||||
});
|
||||
}, 150);
|
||||
|
||||
async function cancel_create_post() {
|
||||
|
|
|
@ -922,6 +922,22 @@
|
|||
\"{{ community.context.enable_questions }}\",
|
||||
\"checkbox\",
|
||||
],
|
||||
[
|
||||
[
|
||||
\"enable_titles\",
|
||||
\"Allow users to attach a title to their posts\",
|
||||
],
|
||||
\"{{ community.context.enable_titles }}\",
|
||||
\"checkbox\",
|
||||
],
|
||||
[
|
||||
[
|
||||
\"require_titles\",
|
||||
\"Require users to attach a title to their posts\",
|
||||
],
|
||||
\"{{ community.context.require_titles }}\",
|
||||
\"checkbox\",
|
||||
],
|
||||
],
|
||||
settings,
|
||||
);
|
||||
|
|
|
@ -111,7 +111,7 @@
|
|||
("style" "display: contents")
|
||||
(text "{{ self::post(post=post, owner=owner, secondary=secondary, community=community, show_community=show_community, can_manage_post=can_manage_post, repost=repost, expect_repost=true) }}"))
|
||||
|
||||
(text "{%- endmacro %} {% macro post(post, owner, question=false, secondary=false, community=false, show_community=true, can_manage_post=false, repost=false, expect_repost=false, poll=false) -%} {% if community and show_community and community.id != config.town_square or question %}")
|
||||
(text "{%- endmacro %} {% macro post(post, owner, question=false, secondary=false, community=false, show_community=true, can_manage_post=false, repost=false, expect_repost=false, poll=false, dont_show_title=false) -%} {% if community and show_community and community.id != config.town_square or question %}")
|
||||
(div
|
||||
("class" "card-nest")
|
||||
(text "{% if question -%} {{ self::question(question=question[0], owner=question[1], profile=owner) }} {% else %}")
|
||||
|
@ -188,11 +188,32 @@
|
|||
("style" "color: var(--color-primary)")
|
||||
(text "{{ icon \"trash-2\" }}"))
|
||||
(text "{%- endif %}"))
|
||||
(text "{% if not dont_show_title and post.title and community and community.context.enable_titles -%}")
|
||||
; post has a title AND whatever is rendering this component wants to see it
|
||||
(a
|
||||
("class" "flush")
|
||||
("href" "/post/{{ post.id }}")
|
||||
(h2
|
||||
("id" "post-content:{{ post.id }}")
|
||||
("class" "no_p_margin post_content")
|
||||
("hook" "long")
|
||||
(text "{{ post.title }}"))
|
||||
|
||||
(button ("class" "small quaternary") (icon (text "ellipsis"))))
|
||||
(text "{% else %}")
|
||||
(text "{% if not post.context.content_warning -%}")
|
||||
(span
|
||||
("id" "post-content:{{ post.id }}")
|
||||
("class" "no_p_margin post_content")
|
||||
("hook" "long")
|
||||
|
||||
; title
|
||||
(text "{% if post.title and community and community.context.enable_titles -%}")
|
||||
(h2 (text "{{ post.title }}"))
|
||||
(hr ("class" "margin"))
|
||||
(text "{%- endif %}")
|
||||
|
||||
; content
|
||||
(text "{{ post.content|markdown|safe }} {% if expect_repost -%} {% if repost -%} {{ self::post(post=repost[1], owner=repost[0], secondary=not secondary, community=false, show_community=false, can_manage_post=false) }} {% else %}")
|
||||
(div
|
||||
("class" "card tertiary red flex items-center gap-2")
|
||||
|
@ -213,6 +234,13 @@
|
|||
("id" "post-content:{{ post.id }}")
|
||||
("class" "no_p_margin post_content")
|
||||
("hook" "long")
|
||||
|
||||
; title
|
||||
(text "{% if post.title and community and community.settings.enable_titles %}")
|
||||
(h2 (text "{{ post.title }}"))
|
||||
(text "{% endif %}")
|
||||
|
||||
; content
|
||||
(text "{{ post.content|markdown|safe }} {% if expect_repost -%} {% if repost -%} {{ self::post(post=repost[1], owner=repost[0], secondary=not secondary, community=false, show_community=false, can_manage_post=false) }} {% else %}")
|
||||
(div
|
||||
("class" "card tertiary red flex items-center gap-2")
|
||||
|
@ -221,7 +249,7 @@
|
|||
(text "Could not find original post...")))
|
||||
(text "{%- endif %} {%- endif %}"))
|
||||
(text "{{ self::post_media(upload_ids=post.uploads) }}")))
|
||||
(text "{%- endif %}")
|
||||
(text "{%- endif %} {%- endif %}")
|
||||
|
||||
(text "{% if poll -%} {{ self::poll(post=post, poll=poll) }} {%- endif %}")
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
(text "{%- endif %}")
|
||||
(div
|
||||
("style" "display: contents;")
|
||||
(text "{% if post.context.repost and post.context.repost.reposting -%} {{ components::repost(repost=reposting, post=post, owner=owner, community=community, show_community=true, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post, owner=owner, question=question, community=community, show_community=true, can_manage_post=can_manage_posts) }} {%- endif %}"))
|
||||
(text "{% if post.context.repost and post.context.repost.reposting -%} {{ components::repost(repost=reposting, post=post, owner=owner, community=community, show_community=true, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post, owner=owner, question=question, community=community, show_community=true, can_manage_post=can_manage_posts, dont_show_title=true) }} {%- endif %}"))
|
||||
(div
|
||||
("class" "pillmenu")
|
||||
(a
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
(text "{%- endif %}")
|
||||
(div
|
||||
("style" "display: contents;")
|
||||
(text "{% if post.context.repost and post.context.repost.reposting -%} {{ components::repost(repost=reposting, post=post, owner=owner, community=community, show_community=true, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post, owner=owner, question=question, community=community, show_community=true, can_manage_post=can_manage_posts, poll=poll) }} {%- endif %}"))
|
||||
(text "{% if post.context.repost and post.context.repost.reposting -%} {{ components::repost(repost=reposting, post=post, owner=owner, community=community, show_community=true, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post, owner=owner, question=question, community=community, show_community=true, can_manage_post=can_manage_posts, poll=poll, dont_show_title=true) }} {%- endif %}"))
|
||||
(text "{% if user and post.context.comments_enabled -%}")
|
||||
(div
|
||||
("class" "card-nest")
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
(text "{%- endif %}")
|
||||
(div
|
||||
("style" "display: contents;")
|
||||
(text "{% if post.context.repost and post.context.repost.reposting -%} {{ components::repost(repost=reposting, post=post, owner=owner, community=community, show_community=true, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post, owner=owner, question=question, community=community, show_community=true, can_manage_post=can_manage_posts) }} {%- endif %}"))
|
||||
(text "{% if post.context.repost and post.context.repost.reposting -%} {{ components::repost(repost=reposting, post=post, owner=owner, community=community, show_community=true, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post, owner=owner, question=question, community=community, show_community=true, can_manage_post=can_manage_posts, dont_show_title=true) }} {%- endif %}"))
|
||||
(div
|
||||
("class" "pillmenu")
|
||||
(a
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
(text "{%- endif %}")
|
||||
(div
|
||||
("style" "display: contents;")
|
||||
(text "{% if post.context.repost and post.context.repost.reposting -%} {{ components::repost(repost=reposting, post=post, owner=owner, community=community, show_community=true, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post, owner=owner, question=question, community=community, show_community=true, can_manage_post=can_manage_posts) }} {%- endif %}"))
|
||||
(text "{% if post.context.repost and post.context.repost.reposting -%} {{ components::repost(repost=reposting, post=post, owner=owner, community=community, show_community=true, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post, owner=owner, question=question, community=community, show_community=true, can_manage_post=can_manage_posts, dont_show_title=true) }} {%- endif %}"))
|
||||
(div
|
||||
("class" "pillmenu")
|
||||
(a
|
||||
|
|
|
@ -427,3 +427,29 @@ pub async fn update_membership_role(
|
|||
Err(e) => Json(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn supports_titles_request(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
Path(id): Path<usize>,
|
||||
) -> impl IntoResponse {
|
||||
let data = &(data.read().await).0;
|
||||
if get_user_from_token!(jar, data).is_none() {
|
||||
return Json(Error::NotAllowed.into());
|
||||
}
|
||||
|
||||
let community = match data.get_community_by_id(id).await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Json(e.into()),
|
||||
};
|
||||
|
||||
Json(ApiReturn {
|
||||
ok: true,
|
||||
message: if community.context.enable_titles {
|
||||
"yes".to_string()
|
||||
} else {
|
||||
"no".to_string()
|
||||
},
|
||||
payload: (),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -115,6 +115,8 @@ pub async fn create_request(
|
|||
Ok(x) => x,
|
||||
Err(e) => return Json(Error::MiscError(e.to_string()).into()),
|
||||
};
|
||||
} else {
|
||||
props.title = req.title;
|
||||
}
|
||||
|
||||
// check sizes
|
||||
|
|
|
@ -90,6 +90,10 @@ pub fn routes() -> Router {
|
|||
"/communities/{id}/banner",
|
||||
get(communities::images::banner_request),
|
||||
)
|
||||
.route(
|
||||
"/communities/{id}/supports_titles",
|
||||
get(communities::communities::supports_titles_request),
|
||||
)
|
||||
// posts
|
||||
.route("/posts", post(communities::posts::create_request))
|
||||
.route("/posts/{id}", delete(communities::posts::delete_request))
|
||||
|
@ -447,6 +451,8 @@ pub struct CreatePost {
|
|||
pub answering: String,
|
||||
#[serde(default)]
|
||||
pub poll: Option<CreatePostPoll>,
|
||||
#[serde(default)]
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
|
|
@ -111,6 +111,7 @@ impl DataManager {
|
|||
is_deleted: get!(x->11(i32)) as i8 == 1,
|
||||
// SKIP tsvector (12)
|
||||
poll_id: get!(x->13(i64)) as usize,
|
||||
title: get!(x->14(String)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1212,6 +1213,8 @@ impl DataManager {
|
|||
}
|
||||
}
|
||||
|
||||
let community = self.get_community_by_id(data.community).await?;
|
||||
|
||||
// check values (if this isn't reposting something else)
|
||||
let is_reposting = if let Some(ref repost) = data.context.repost {
|
||||
repost.reposting.is_some()
|
||||
|
@ -1225,14 +1228,24 @@ impl DataManager {
|
|||
} else if data.content.len() > 4096 {
|
||||
return Err(Error::DataTooLong("content".to_string()));
|
||||
}
|
||||
|
||||
// check title
|
||||
if !community.context.enable_titles {
|
||||
if !data.title.is_empty() {
|
||||
return Err(Error::MiscError(
|
||||
"Community does not allow titles".to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
if data.title.len() < 2 && community.context.require_titles {
|
||||
return Err(Error::DataTooShort("title".to_string()));
|
||||
} else if data.title.len() > 128 {
|
||||
return Err(Error::DataTooLong("title".to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check permission in community
|
||||
let community = match self.get_community_by_id(data.community).await {
|
||||
Ok(p) => p,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
if !self.check_can_post(&community, data.owner).await {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
@ -1428,7 +1441,7 @@ impl DataManager {
|
|||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"INSERT INTO posts VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, DEFAULT, $13)",
|
||||
"INSERT INTO posts VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, DEFAULT, $13, $14)",
|
||||
params![
|
||||
&(data.id as i64),
|
||||
&(data.created as i64),
|
||||
|
@ -1447,6 +1460,7 @@ impl DataManager {
|
|||
&serde_json::to_string(&data.uploads).unwrap(),
|
||||
&{ if data.is_deleted { 1 } else { 0 } },
|
||||
&(data.poll_id as i64),
|
||||
&data.title
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -1743,6 +1757,13 @@ impl DataManager {
|
|||
}
|
||||
}
|
||||
|
||||
// check length
|
||||
if x.len() < 2 {
|
||||
return Err(Error::DataTooShort("content".to_string()));
|
||||
} else if x.len() > 4096 {
|
||||
return Err(Error::DataTooLong("content".to_string()));
|
||||
}
|
||||
|
||||
// ...
|
||||
let conn = match self.0.connect().await {
|
||||
Ok(c) => c,
|
||||
|
@ -1767,6 +1788,59 @@ impl DataManager {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_post_title(&self, id: usize, user: User, x: String) -> Result<()> {
|
||||
let mut y = self.get_post_by_id(id).await?;
|
||||
|
||||
if user.id != y.owner {
|
||||
if !user.permissions.check(FinePermission::MANAGE_POSTS) {
|
||||
return Err(Error::NotAllowed);
|
||||
} else {
|
||||
self.create_audit_log_entry(AuditLogEntry::new(
|
||||
user.id,
|
||||
format!("invoked `update_post_title` with x value `{id}`"),
|
||||
))
|
||||
.await?
|
||||
}
|
||||
}
|
||||
|
||||
let community = self.get_community_by_id(y.community).await?;
|
||||
|
||||
if !community.context.enable_titles {
|
||||
return Err(Error::MiscError(
|
||||
"Community does not allow titles".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if x.len() < 2 && community.context.require_titles {
|
||||
return Err(Error::DataTooShort("title".to_string()));
|
||||
} else if x.len() > 128 {
|
||||
return Err(Error::DataTooLong("title".to_string()));
|
||||
}
|
||||
|
||||
// ...
|
||||
let conn = match self.0.connect().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||
};
|
||||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"UPDATE posts SET title = $1 WHERE id = $2",
|
||||
params![&x, &(id as i64)]
|
||||
);
|
||||
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
// update context
|
||||
y.context.edited = unix_epoch_timestamp() as usize;
|
||||
self.update_post_context(id, user, y.context).await?;
|
||||
|
||||
// return
|
||||
Ok(())
|
||||
}
|
||||
|
||||
auto_method!(incr_post_likes() -> "UPDATE posts SET likes = likes + 1 WHERE id = $1" --cache-key-tmpl="atto.post:{}" --incr);
|
||||
auto_method!(incr_post_dislikes() -> "UPDATE posts SET dislikes = dislikes + 1 WHERE id = $1" --cache-key-tmpl="atto.post:{}" --incr);
|
||||
auto_method!(decr_post_likes() -> "UPDATE posts SET likes = likes - 1 WHERE id = $1" --cache-key-tmpl="atto.post:{}" --decr);
|
||||
|
|
|
@ -76,6 +76,17 @@ pub struct CommunityContext {
|
|||
pub is_nsfw: bool,
|
||||
#[serde(default)]
|
||||
pub enable_questions: bool,
|
||||
/// If posts are allowed to set a `title` field.
|
||||
#[serde(default)]
|
||||
pub enable_titles: bool,
|
||||
/// If posts are required to set a `title` field.
|
||||
///
|
||||
/// `enable_titles` is required for this setting to work.
|
||||
#[serde(default)]
|
||||
pub require_titles: bool,
|
||||
/// The community's layout in the UI.
|
||||
#[serde(default)]
|
||||
pub layout: CommunityLayout,
|
||||
}
|
||||
|
||||
/// Who can read a [`Community`].
|
||||
|
@ -129,6 +140,21 @@ impl Default for CommunityJoinAccess {
|
|||
}
|
||||
}
|
||||
|
||||
/// The layout of the [`Community`]'s UI.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum CommunityLayout {
|
||||
/// The classic timeline-like layout.
|
||||
Classic,
|
||||
/// A GitHub-esque bug tracker layout.
|
||||
BugTracker,
|
||||
}
|
||||
|
||||
impl Default for CommunityLayout {
|
||||
fn default() -> Self {
|
||||
Self::Classic
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CommunityMembership {
|
||||
pub id: usize,
|
||||
|
@ -242,6 +268,8 @@ pub struct Post {
|
|||
pub is_deleted: bool,
|
||||
/// The ID of the poll associated with this post. 0 means no poll is connected.
|
||||
pub poll_id: usize,
|
||||
/// The title of the post (in communities where titles are enabled).
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
impl Post {
|
||||
|
@ -267,6 +295,7 @@ impl Post {
|
|||
uploads: Vec::new(),
|
||||
is_deleted: false,
|
||||
poll_id,
|
||||
title: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
2
sql_changes/posts_title.sql
Normal file
2
sql_changes/posts_title.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE posts
|
||||
ADD COLUMN title TEXT NOT NULL DEFAULT '';
|
Loading…
Add table
Add a link
Reference in a new issue