use crate::discord::ids::{
Id,
marker::{ChannelMarker, ForumTagMarker},
};
use crate::discord::{
AppCommand, ForumPostCreate, MAX_UPLOAD_ATTACHMENT_COUNT, MessageAttachmentUpload,
};
use crate::tui::keybindings::ScrollAction;
use crate::tui::text_input::TextEditAction;
use ratatui_image::protocol::Protocol;
use super::super::composer::expand_emoji_shortcodes;
use super::super::local_upload_preview::{
LocalUploadPreviewState, LocalUploadPreviewStatus, local_upload_preview_candidate,
local_upload_preview_view,
};
use super::super::scroll::clamp_list_scroll;
use super::super::{
DashboardState, FocusPane, ForumPostComposerAttachmentView, ForumPostComposerField,
ForumPostComposerTagView, ForumPostComposerView, LocalUploadPreviewView,
};
use super::{
ActiveModalPopupKind, ForumPostComposerFieldState, ForumPostComposerState, ModalPopup,
};
const MAX_FORUM_POST_TAGS: usize = 5;
impl DashboardState {
pub fn open_forum_post_composer(&mut self, channel_id: Id<ChannelMarker>) {
let can_create = self
.discord
.cache
.channel(channel_id)
.is_some_and(|channel| {
channel.is_forum()
&& self.discord.cache.can_send_in_channel(channel)
&& self
.discord
.cache
.guild_participation_block(channel)
.is_none()
&& self.slow_mode_remaining_seconds(channel.id).is_none()
});
if !can_create {
return;
}
self.cancel_composer();
self.popups.modal = Some(ModalPopup::ForumPostComposer(ForumPostComposerState::new(
channel_id,
)));
self.navigation.focus = FocusPane::Messages;
}
pub fn close_forum_post_composer(&mut self) {
if self.is_active_modal_popup(ActiveModalPopupKind::ForumPostComposer) {
self.popups.clear_modal();
self.runtime.clipboard_paste_pending = false;
}
}
pub fn is_forum_post_composer_active(&self) -> bool {
self.is_active_modal_popup(ActiveModalPopupKind::ForumPostComposer)
}
pub fn request_open_forum_post_body_in_editor(&mut self) {
if self
.popups
.forum_post_composer()
.is_some_and(|popup| popup.editing == Some(ForumPostComposerFieldState::Body))
{
self.runtime.open_forum_post_body_in_editor_requested = true;
}
}
pub fn take_open_forum_post_body_in_editor_request(&mut self) -> bool {
std::mem::take(&mut self.runtime.open_forum_post_body_in_editor_requested)
}
pub fn forum_post_body_for_editor(&self) -> Option<String> {
self.popups
.forum_post_composer()
.filter(|popup| popup.editing == Some(ForumPostComposerFieldState::Body))
.map(|popup| popup.edit_input.value().to_owned())
}
pub fn replace_forum_post_body_from_editor(&mut self, content: String) {
if let Some(popup) = self.popups.forum_post_composer_mut()
&& popup.editing == Some(ForumPostComposerFieldState::Body)
{
popup.edit_input.set_value(content);
popup.status = None;
}
}
pub fn forum_post_composer_scroll(&self) -> usize {
self.popups
.forum_post_composer()
.map(|popup| popup.scroll.scroll())
.unwrap_or(0)
}
pub fn scroll_forum_post_composer(&mut self, action: ScrollAction) {
if let Some(popup) = self.popups.forum_post_composer_mut() {
match action {
ScrollAction::Down => popup.scroll.scroll_down(),
ScrollAction::Up => popup.scroll.scroll_up(),
}
}
}
pub fn request_forum_post_scroll_reveal(&mut self) {
if let Some(popup) = self.popups.forum_post_composer_mut() {
popup.pending_scroll_reveal = true;
}
}
pub fn set_forum_post_composer_metrics(&mut self, view_height: usize, total_lines: usize) {
if let Some(popup) = self.popups.forum_post_composer_mut() {
popup.scroll.set_view_height(view_height);
popup.scroll.set_total_lines(total_lines);
}
}
pub fn reveal_forum_post_composer_rows(&mut self, start: usize, end: usize) {
if let Some(popup) = self.popups.forum_post_composer_mut()
&& popup.pending_scroll_reveal
{
popup.scroll.reveal(start, end);
popup.pending_scroll_reveal = false;
}
}
pub fn forum_post_composer_view(&self) -> Option<ForumPostComposerView> {
let popup = self.popups.forum_post_composer()?;
let channel = self.discord.cache.channel(popup.channel_id)?;
let editing_tags = popup.editing == Some(ForumPostComposerFieldState::Tags);
let display_ids: Vec<Id<ForumTagMarker>> = if editing_tags && !popup.tag_order.is_empty() {
popup.tag_order.clone()
} else {
channel
.available_tags
.iter()
.map(|tag| tag.id)
.filter(|id| popup.selected_tag_ids.contains(id))
.chain(
channel
.available_tags
.iter()
.map(|tag| tag.id)
.filter(|id| !popup.selected_tag_ids.contains(id)),
)
.collect()
};
let cap_reached = popup.selected_tag_ids.len() >= MAX_FORUM_POST_TAGS;
let can_use_moderated_tags = self.discord.cache.can_manage_threads_in_channel(channel);
let guild_id = channel.guild_id;
let tags = display_ids
.iter()
.enumerate()
.filter_map(|(index, id)| {
let tag = channel.available_tags.iter().find(|tag| tag.id == *id)?;
let selected = popup.selected_tag_ids.contains(&tag.id);
let emoji = self.forum_tag_emoji_fields(tag, guild_id);
Some(ForumPostComposerTagView {
name: tag.name.clone(),
unicode_emoji: emoji.unicode_emoji,
custom_emoji_url: emoji.custom_emoji_url,
custom_emoji_label: emoji.custom_emoji_label,
selected,
active: editing_tags && index == popup.selected_tag_index,
selectable: (selected || !cap_reached)
&& (!tag.moderated || can_use_moderated_tags),
})
})
.collect();
let attachments = popup
.attachments
.iter()
.map(|attachment| ForumPostComposerAttachmentView {
filename: attachment.filename.clone(),
size_bytes: attachment.size_bytes,
})
.collect();
Some(ForumPostComposerView {
channel_label: format!("#{}", channel.name),
active_field: popup.active_field.into(),
editing_field: popup.editing.map(Into::into),
title: forum_post_text_field_value(popup, ForumPostComposerFieldState::Title)
.to_owned(),
title_cursor: forum_post_text_field_cursor(popup, ForumPostComposerFieldState::Title),
body: forum_post_text_field_value(popup, ForumPostComposerFieldState::Body).to_owned(),
body_cursor: forum_post_text_field_cursor(popup, ForumPostComposerFieldState::Body),
attachments,
tags,
tag_scroll: popup.tag_scroll,
requires_tag: channel.requires_forum_tag(),
paste_pending: self.runtime.clipboard_paste_pending,
status: popup.status.clone(),
})
}
pub fn cycle_forum_post_field_next(&mut self) {
if let Some(popup) = self.popups.forum_post_composer_mut() {
if popup.editing.is_some() {
return;
}
popup.active_field = match popup.active_field {
ForumPostComposerFieldState::Title => ForumPostComposerFieldState::Body,
ForumPostComposerFieldState::Body => ForumPostComposerFieldState::Attachments,
ForumPostComposerFieldState::Attachments => ForumPostComposerFieldState::Tags,
ForumPostComposerFieldState::Tags => ForumPostComposerFieldState::Submit,
ForumPostComposerFieldState::Submit => ForumPostComposerFieldState::Cancel,
ForumPostComposerFieldState::Cancel => ForumPostComposerFieldState::Cancel,
};
}
}
pub fn cycle_forum_post_field_previous(&mut self) {
if let Some(popup) = self.popups.forum_post_composer_mut() {
if popup.editing.is_some() {
return;
}
popup.active_field = match popup.active_field {
ForumPostComposerFieldState::Title => ForumPostComposerFieldState::Title,
ForumPostComposerFieldState::Body => ForumPostComposerFieldState::Title,
ForumPostComposerFieldState::Attachments => ForumPostComposerFieldState::Body,
ForumPostComposerFieldState::Tags => ForumPostComposerFieldState::Attachments,
ForumPostComposerFieldState::Submit => ForumPostComposerFieldState::Tags,
ForumPostComposerFieldState::Cancel => ForumPostComposerFieldState::Submit,
};
}
}
pub fn push_forum_post_char(&mut self, value: char) {
if let Some(popup) = self.popups.forum_post_composer_mut() {
match popup.editing {
Some(ForumPostComposerFieldState::Title) if value != '\n' => {
popup.edit_input.insert_char(value);
popup.status = None;
}
Some(ForumPostComposerFieldState::Body) => {
popup.edit_input.insert_char(value);
popup.status = None;
}
Some(ForumPostComposerFieldState::Title)
| Some(ForumPostComposerFieldState::Attachments)
| Some(ForumPostComposerFieldState::Tags)
| Some(ForumPostComposerFieldState::Submit)
| Some(ForumPostComposerFieldState::Cancel)
| None => {}
}
}
}
pub fn insert_forum_post_text(&mut self, value: &str) -> bool {
let Some(popup) = self.popups.forum_post_composer_mut() else {
return false;
};
let pasted: String = value.chars().filter(|value| *value != '\r').collect();
if pasted.is_empty() {
return false;
}
match popup.editing {
Some(ForumPostComposerFieldState::Title) => {
let single_line = pasted.lines().next().unwrap_or_default();
if single_line.is_empty() {
return false;
}
popup.edit_input.insert_str(single_line);
}
Some(ForumPostComposerFieldState::Body) => popup.edit_input.insert_str(&pasted),
Some(ForumPostComposerFieldState::Attachments)
| Some(ForumPostComposerFieldState::Tags)
| Some(ForumPostComposerFieldState::Submit)
| Some(ForumPostComposerFieldState::Cancel)
| None => {
return false;
}
}
popup.status = None;
true
}
pub fn edit_forum_post_active_text_input(&mut self, action: TextEditAction) {
if let Some(popup) = self.popups.forum_post_composer_mut() {
let changed = match popup.editing {
Some(ForumPostComposerFieldState::Title | ForumPostComposerFieldState::Body) => {
popup.edit_input.apply_edit_action(action)
}
Some(
ForumPostComposerFieldState::Attachments
| ForumPostComposerFieldState::Tags
| ForumPostComposerFieldState::Submit
| ForumPostComposerFieldState::Cancel,
)
| None => false,
};
if changed {
popup.status = None;
}
}
}
pub fn move_forum_post_selection_down(&mut self) {
let Some((editing, tag_count)) = self
.popups
.forum_post_composer()
.map(|popup| (popup.editing, popup.tag_order.len()))
else {
return;
};
match editing {
Some(ForumPostComposerFieldState::Tags) if tag_count > 0 => {
if let Some(popup) = self.popups.forum_post_composer_mut() {
popup.selected_tag_index =
(popup.selected_tag_index + 1).min(tag_count.saturating_sub(1));
}
}
Some(_) => {}
None => self.cycle_forum_post_field_next(),
}
}
pub fn move_forum_post_selection_up(&mut self) {
match self
.popups
.forum_post_composer()
.and_then(|popup| popup.editing)
{
Some(ForumPostComposerFieldState::Tags) => {
if let Some(popup) = self.popups.forum_post_composer_mut() {
popup.selected_tag_index = popup.selected_tag_index.saturating_sub(1);
}
}
Some(_) => {}
None => self.cycle_forum_post_field_previous(),
}
}
pub fn set_forum_post_tag_picker_view_height(&mut self, height: usize) {
if let Some(popup) = self.popups.forum_post_composer_mut() {
let len = popup.tag_order.len();
let cursor = popup.selected_tag_index.min(len.saturating_sub(1));
popup.tag_scroll = clamp_list_scroll(cursor, popup.tag_scroll, height.max(1), len);
}
}
pub fn toggle_selected_forum_post_tag(&mut self) {
let Some((channel_id, tag_id)) = self.popups.forum_post_composer().and_then(|popup| {
popup
.tag_order
.get(popup.selected_tag_index)
.copied()
.map(|tag_id| (popup.channel_id, tag_id))
}) else {
return;
};
let moderated_tag_denied = self
.discord
.cache
.channel(channel_id)
.is_some_and(|channel| {
channel
.available_tags
.iter()
.any(|tag| tag.id == tag_id && tag.moderated)
&& !self.discord.cache.can_manage_threads_in_channel(channel)
});
if moderated_tag_denied {
if let Some(popup) = self.popups.forum_post_composer_mut() {
popup.status =
Some("Manage Threads permission is required for moderated tags".to_owned());
}
return;
}
if let Some(popup) = self.popups.forum_post_composer_mut() {
if let Some(position) = popup.selected_tag_ids.iter().position(|id| *id == tag_id) {
popup.selected_tag_ids.remove(position);
popup.status = None;
} else if popup.selected_tag_ids.len() < MAX_FORUM_POST_TAGS {
popup.selected_tag_ids.push(tag_id);
popup.status = None;
}
}
}
pub fn forum_post_composer_accepts_attachments(&self) -> bool {
let Some(popup) = self.popups.forum_post_composer() else {
return false;
};
self.discord
.cache
.channel(popup.channel_id)
.is_some_and(|channel| {
channel.is_forum() && self.discord.cache.can_attach_in_channel(channel)
})
}
pub fn forum_post_composer_is_editing_body(&self) -> bool {
self.popups
.forum_post_composer()
.is_some_and(|popup| popup.editing == Some(ForumPostComposerFieldState::Body))
}
pub fn add_pending_forum_post_attachments(
&mut self,
attachments: Vec<MessageAttachmentUpload>,
) {
if attachments.is_empty() {
return;
}
if !self.forum_post_composer_accepts_attachments() {
self.show_error_toast(
"Attach Files permission is required in this channel",
std::time::Instant::now(),
);
return;
}
if let Some(popup) = self.popups.forum_post_composer_mut() {
let available = MAX_UPLOAD_ATTACHMENT_COUNT.saturating_sub(popup.attachments.len());
popup
.attachments
.extend(attachments.into_iter().take(available));
popup.status = None;
}
self.refresh_forum_post_attachment_previews();
}
pub fn pop_pending_forum_post_attachment(&mut self) {
if let Some(popup) = self.popups.forum_post_composer_mut() {
if popup.attachments.pop().is_none() {
return;
}
popup.status = None;
}
self.refresh_forum_post_attachment_previews();
}
pub fn clear_forum_post_active_field(&mut self) {
if let Some(popup) = self.popups.forum_post_composer_mut() {
if popup.editing.is_some() {
popup.edit_input.clear();
popup.status = None;
return;
}
match popup.active_field {
ForumPostComposerFieldState::Title => popup.title.clear(),
ForumPostComposerFieldState::Body => popup.body.clear(),
ForumPostComposerFieldState::Tags => popup.selected_tag_ids.clear(),
ForumPostComposerFieldState::Attachments => {
popup.attachments.clear();
popup.attachment_previews.clear();
}
ForumPostComposerFieldState::Submit | ForumPostComposerFieldState::Cancel => {}
}
popup.status = None;
}
}
pub fn activate_forum_post_composer(&mut self) -> Option<AppCommand> {
let (active_field, editing) = self
.popups
.forum_post_composer()
.map(|popup| (popup.active_field, popup.editing))?;
if editing == Some(ForumPostComposerFieldState::Tags)
&& active_field == ForumPostComposerFieldState::Tags
{
self.toggle_selected_forum_post_tag();
return None;
}
if matches!(
editing,
Some(ForumPostComposerFieldState::Title | ForumPostComposerFieldState::Body)
) && editing == Some(active_field)
{
self.commit_forum_post_edit();
return None;
}
match active_field {
ForumPostComposerFieldState::Title | ForumPostComposerFieldState::Body => {
self.start_forum_post_edit(active_field);
}
ForumPostComposerFieldState::Tags => self.start_forum_post_tag_selection(),
ForumPostComposerFieldState::Submit => return self.save_forum_post_composer(),
ForumPostComposerFieldState::Cancel => self.close_forum_post_composer(),
ForumPostComposerFieldState::Attachments => {}
}
None
}
pub fn close_or_cancel_forum_post_composer(&mut self) {
match self
.popups
.forum_post_composer()
.and_then(|popup| popup.editing)
{
Some(ForumPostComposerFieldState::Title | ForumPostComposerFieldState::Body) => {
self.commit_forum_post_edit();
return;
}
Some(
ForumPostComposerFieldState::Attachments
| ForumPostComposerFieldState::Tags
| ForumPostComposerFieldState::Submit
| ForumPostComposerFieldState::Cancel,
) => {
if let Some(popup) = self.popups.forum_post_composer_mut() {
popup.editing = None;
popup.edit_input.clear();
popup.status = None;
}
return;
}
None => {}
}
self.close_forum_post_composer();
}
pub fn is_forum_post_composer_editing(&self) -> bool {
self.popups
.forum_post_composer()
.is_some_and(|popup| popup.editing.is_some())
}
pub fn forum_post_attachment_previews(&self) -> Vec<LocalUploadPreviewView<'_>> {
self.popups
.forum_post_composer()
.map(|popup| {
popup
.attachment_previews
.iter()
.map(local_upload_preview_view)
.collect()
})
.unwrap_or_default()
}
pub(in crate::tui) fn take_pending_forum_post_attachment_preview(
&mut self,
) -> Option<(usize, u64, String, MessageAttachmentUpload)> {
if !self.show_images() {
return None;
}
let popup = self.popups.forum_post_composer_mut()?;
let preview = popup
.attachment_previews
.iter_mut()
.find(|preview| matches!(preview.state, LocalUploadPreviewStatus::Pending))?;
let attachment = popup.attachments.get(preview.attachment_index)?.clone();
preview.state = LocalUploadPreviewStatus::Loading;
Some((
preview.attachment_index,
preview.generation,
preview.filename.clone(),
attachment,
))
}
pub(in crate::tui) fn store_forum_post_attachment_preview_result(
&mut self,
attachment_index: usize,
generation: u64,
filename: String,
result: std::result::Result<Protocol, String>,
) {
let Some(popup) = self.popups.forum_post_composer_mut() else {
return;
};
let Some(preview) = popup.attachment_previews.iter_mut().find(|preview| {
preview.attachment_index == attachment_index && preview.generation == generation
}) else {
return;
};
preview.filename = filename;
preview.state = match result {
Ok(protocol) => LocalUploadPreviewStatus::Ready(protocol),
Err(message) => LocalUploadPreviewStatus::Failed(message),
};
}
pub fn is_forum_post_tag_picker_active(&self) -> bool {
self.popups
.forum_post_composer()
.is_some_and(|popup| popup.editing == Some(ForumPostComposerFieldState::Tags))
}
pub fn save_forum_post_composer(&mut self) -> Option<AppCommand> {
if let Some(popup) = self.popups.forum_post_composer_mut()
&& let Some(editing) = popup.editing
{
let message = if editing == ForumPostComposerFieldState::Tags {
"Press Esc to finish selecting tags first"
} else {
"Press Enter to finish editing first"
};
popup.status = Some(message.to_owned());
return None;
}
self.submit_forum_post_composer()
}
fn start_forum_post_edit(&mut self, field: ForumPostComposerFieldState) {
let Some(popup) = self.popups.forum_post_composer_mut() else {
return;
};
let value = match field {
ForumPostComposerFieldState::Title => popup.title.value().to_owned(),
ForumPostComposerFieldState::Body => popup.body.value().to_owned(),
ForumPostComposerFieldState::Attachments
| ForumPostComposerFieldState::Tags
| ForumPostComposerFieldState::Submit
| ForumPostComposerFieldState::Cancel => return,
};
popup.editing = Some(field);
popup.edit_input.set_value(value);
popup.status = None;
}
fn start_forum_post_tag_selection(&mut self) {
let Some(channel_id) = self
.popups
.forum_post_composer()
.map(|popup| popup.channel_id)
else {
return;
};
let selected_ids = self
.popups
.forum_post_composer()
.map(|popup| popup.selected_tag_ids.clone())
.unwrap_or_default();
let ordered: Vec<Id<ForumTagMarker>> = self
.discord
.cache
.channel(channel_id)
.map(|channel| {
channel
.available_tags
.iter()
.map(|tag| tag.id)
.filter(|id| selected_ids.contains(id))
.chain(
channel
.available_tags
.iter()
.map(|tag| tag.id)
.filter(|id| !selected_ids.contains(id)),
)
.collect()
})
.unwrap_or_default();
let Some(popup) = self.popups.forum_post_composer_mut() else {
return;
};
if ordered.is_empty() {
popup.status = Some("no tags available".to_owned());
return;
}
popup.tag_order = ordered;
popup.selected_tag_index = 0;
popup.editing = Some(ForumPostComposerFieldState::Tags);
popup.edit_input.clear();
popup.status = None;
}
fn refresh_forum_post_attachment_previews(&mut self) {
let show_images = self.show_images();
let Some(popup) = self.popups.forum_post_composer_mut() else {
return;
};
if !show_images {
popup.attachment_previews.clear();
return;
}
let mut previous = std::mem::take(&mut popup.attachment_previews);
let mut previews = Vec::new();
for (index, attachment) in popup
.attachments
.iter()
.enumerate()
.filter(|(_, attachment)| local_upload_preview_candidate(attachment))
{
if let Some(previous_index) = previous.iter().position(|preview| {
preview.attachment_index == index && preview.filename == attachment.filename
}) {
previews.push(previous.remove(previous_index));
continue;
}
popup.attachment_preview_generation =
popup.attachment_preview_generation.saturating_add(1);
previews.push(LocalUploadPreviewState {
attachment_index: index,
generation: popup.attachment_preview_generation,
filename: attachment.filename.clone(),
state: LocalUploadPreviewStatus::Pending,
});
}
popup.attachment_previews = previews;
}
fn commit_forum_post_edit(&mut self) {
let Some(popup) = self.popups.forum_post_composer_mut() else {
return;
};
let Some(field) = popup.editing else {
return;
};
let value = popup.edit_input.value().to_owned();
match field {
ForumPostComposerFieldState::Title => popup.title.set_value(value),
ForumPostComposerFieldState::Body => popup.body.set_value(value),
ForumPostComposerFieldState::Attachments
| ForumPostComposerFieldState::Tags
| ForumPostComposerFieldState::Submit
| ForumPostComposerFieldState::Cancel => {}
}
popup.editing = None;
popup.edit_input.clear();
popup.status = None;
}
pub fn submit_forum_post_composer(&mut self) -> Option<AppCommand> {
let result = self.build_forum_post_create();
match result {
Ok(post) => {
self.close_forum_post_composer();
Some(AppCommand::CreateForumPost { post })
}
Err(message) => {
if let Some(popup) = self.popups.forum_post_composer_mut() {
popup.status = Some(message);
}
None
}
}
}
fn build_forum_post_create(&mut self) -> Result<ForumPostCreate, String> {
let Some(popup) = self.popups.forum_post_composer() else {
return Err("forum post composer is not open".to_owned());
};
let channel_id = popup.channel_id;
let title = popup.title.value().trim().to_owned();
let content = expand_emoji_shortcodes(popup.body.value())
.trim()
.to_owned();
let applied_tags = popup.selected_tag_ids.clone();
if title.is_empty() {
return Err("title is required".to_owned());
}
if title.chars().count() > 100 {
return Err("title must be 100 characters or fewer".to_owned());
}
if content.is_empty() {
return Err("body is required".to_owned());
}
let Some(channel) = self.discord.cache.channel(channel_id) else {
return Err("forum channel is no longer available".to_owned());
};
if !channel.is_forum() || !self.discord.cache.can_send_in_channel(channel) {
return Err("cannot create posts in this channel".to_owned());
}
if let Some(remaining_seconds) = self.slow_mode_remaining_seconds(channel.id) {
return Err(format!(
"Slow mode is active. Try again in {remaining_seconds}s"
));
}
if channel.requires_forum_tag() && applied_tags.is_empty() {
return Err("at least one tag is required".to_owned());
}
if applied_tags.iter().any(|tag_id| {
channel
.available_tags
.iter()
.any(|tag| tag.id == *tag_id && tag.moderated)
}) && !self.discord.cache.can_manage_threads_in_channel(channel)
{
return Err("Manage Threads permission is required for moderated tags".to_owned());
}
if !popup.attachments.is_empty() && !self.discord.cache.can_attach_in_channel(channel) {
return Err("Attach Files permission is required in this channel".to_owned());
}
let attachments = self
.popups
.forum_post_composer_mut()
.map(|popup| std::mem::take(&mut popup.attachments))
.unwrap_or_default();
Ok(ForumPostCreate {
channel_id,
title,
content,
applied_tags,
attachments,
})
}
}
impl From<ForumPostComposerFieldState> for ForumPostComposerField {
fn from(value: ForumPostComposerFieldState) -> Self {
match value {
ForumPostComposerFieldState::Title => Self::Title,
ForumPostComposerFieldState::Body => Self::Body,
ForumPostComposerFieldState::Attachments => Self::Attachments,
ForumPostComposerFieldState::Tags => Self::Tags,
ForumPostComposerFieldState::Submit => Self::Submit,
ForumPostComposerFieldState::Cancel => Self::Cancel,
}
}
}
fn forum_post_text_field_value(
popup: &ForumPostComposerState,
field: ForumPostComposerFieldState,
) -> &str {
if popup.editing == Some(field) {
return popup.edit_input.value();
}
match field {
ForumPostComposerFieldState::Title => popup.title.value(),
ForumPostComposerFieldState::Body => popup.body.value(),
ForumPostComposerFieldState::Attachments
| ForumPostComposerFieldState::Tags
| ForumPostComposerFieldState::Submit
| ForumPostComposerFieldState::Cancel => "",
}
}
fn forum_post_text_field_cursor(
popup: &ForumPostComposerState,
field: ForumPostComposerFieldState,
) -> usize {
if popup.editing == Some(field) {
return popup.edit_input.cursor_byte_index();
}
match field {
ForumPostComposerFieldState::Title => popup.title.cursor_byte_index(),
ForumPostComposerFieldState::Body => popup.body.cursor_byte_index(),
ForumPostComposerFieldState::Attachments
| ForumPostComposerFieldState::Tags
| ForumPostComposerFieldState::Submit
| ForumPostComposerFieldState::Cancel => 0,
}
}