Bivio::UI::View::Mail
# Copyright (c) 2007-2026 Bivio Software, Inc. All Rights Reserved. package Bivio::UI::View::Mail; use strict; use Bivio::Base 'View.Base'; use Bivio::UI::ViewLanguageAUTOLOAD; my($_DT) = b_use('Type.DateTime'); my($_E) = b_use('Type.Email'); my($_M) = b_use('Biz.Model'); my($_MF) = b_use('Model.MailForm'); my($_T) = b_use('MIME.Type'); sub DEFAULT_COLS { return 80; } sub PART_TASK { return 'FORUM_MAIL_PART'; } sub WANT_BOARD_ONLY_OPTION { return 1; } sub delete_form { my($self) = @_; return $self->internal_body($self->internal_delete_form(@_)); } sub form_imail { my($self) = @_; return $self->internal_put_base_attr( from => [_name($self, 'Model.XxForm'), '->mail_header_from'], recipients => [ _name($self, 'Model.XxForm'), '->mail_envelope_recipients', ], headers_object => [_name($self, 'Model.XxForm')], body => [sub { my($source, $f) = @_; my($req) = $source->req; my($body) = $f->get('body'); return MIMEEntity({ mime_type => 'text/plain', mime_data => $body, mime_encoding => $_T->suggest_encoding('text/plain', \$body), mime_charset => 'utf-8', values => $f->map_attachments(sub { return unless my $a = $f->get(shift); return MIMEEntity({ mime_data => ${$a->{content}}, mime_filename => $a->{filename}, mime_type => $a->{content_type}, mime_disposition => 'inline', mime_encoding => $_T->suggest_encoding( $a->{content_type}, $a->{content}, ), }); }), }), }, [_name($self, 'Model.XxForm')]], ); } sub internal_delete_form { return vs_simple_form('RealmMailDeleteForm'); } sub internal_form_model { my($self, $req) = @_; my($m) = $_M->get_instance(_name($self, 'XxForm')); return $req ? $m->from_req($req) : $m; } sub internal_name { return shift->simple_package_name; } sub internal_part_list { my($self) = @_; return DIV_parts( With(['->get_mail_part_list'], If(['->show_as_attachment'], If(['!', '->was_attachment_visited'], DIV_attachment(_thread_list_director($self)) ), _thread_list_director($self), ), ), {id => ['->get_message_anchor']}, ); } sub internal_reply_links { my($self) = @_; return RoundedBox( TaskMenu([ map(+{ task_id => _name($self, 'FORUM_XX_FORM'), label => _name($self, 'FORUM_XX_FORM.reply_') . $_, query => $_MF->reply_query($_), }, $self->internal_reply_list), { task_id => 'FORUM_MAIL_SHOW_ORIGINAL_FILE', label => _name($self, 'FORUM_XX_FORM.view_rfc822'), query => undef, path_info => ['RealmFile.path'], control => [sub { return b_use('Model.RealmMail') ->can_view_original(shift->req); }], }, { task_id => 'GROUP_MAIL_DELETE_FORM', query => { 'ListQuery.this' => ['RealmMail.realm_file_id'], }, control => [sub { my($source) = @_; return ! $source->new_other('CRMThread')->unsafe_load({ thread_root_id => $source->get_query->get('parent_id'), }); }], }, { task_id => 'GROUP_MAIL_TOGGLE_PUBLIC', label => If( ['RealmFile.is_public'], vs_text_as_prose('realm_mail_make_private'), vs_text_as_prose('realm_mail_make_public'), ), query => { 'ListQuery.this' => ['RealmMail.realm_file_id'], }, control => Or( ['RealmFile.is_public'], [ b_use('Model.RealmMailPublicForm'), '->can_toggle_public', ['->req'], ], ), }, { task_id => 'GROUP_BULLETIN_FORM', query => { 'ListQuery.this' => ['RealmMail.realm_file_id'], }, control => vs_can_group_bulletin_form(), }, ], { class => 'task_menu pagination', }), 'actions', ); } sub internal_reply_list { return qw(realm all author); } sub internal_send_form { my($self, $extra_fields, $buttons) = @_; $buttons ||= vs_simple_form_submit(); return DIV_msg_compose(Join([ vs_simple_form(_name($self, 'XxForm') => [ [vs_blank_cell(), FormFieldError('from_email')], $buttons, @{$extra_fields || []}, $self->internal_send_form_email_field('to'), $self->internal_send_form_email_field('cc'), $self->WANT_BOARD_ONLY_OPTION ? _name($self, 'XxForm.board_only') : (), $self->internal_subject_body_attachments, $buttons, ]), If([_name($self, 'Model.XxForm'), '->is_reply'], _msg($self, 1)), ])); } sub internal_send_form_email_field { my($self, $field) = @_; return [_name($self, "XxForm.$field"), { cols => $self->DEFAULT_COLS, rows => 1, row_class => 'textarea', }]; } sub internal_standard_tools { my($self, $extra_tools) = @_; my(@tasks) = ( { task_id => _name($self, 'FORUM_XX_FORM'), control => ['!', 'task_id', _name($self, '->eq_forum_xx_form')], query => undef, }, { task_id => _name($self, 'FORUM_XX_THREAD_ROOT_LIST'), label => _name($self, 'XxThreadList') . '.' . _name($self, 'FORUM_XX_THREAD_ROOT_LIST'), control => [ '!', 'task_id', _name($self, '->eq_forum_xx_thread_root_list'), ], query => undef, }, ); @tasks = reverse(@tasks) if b_use('UI.Facade')->if_2014style; $self->internal_put_base_attr(tools => TaskMenu([ @tasks, @{$extra_tools || []}, ])); return; } sub internal_subject_body_attachments { my($self) = @_; return ( [_name($self, 'XxForm.subject'), { size => $self->DEFAULT_COLS + 2, }], [_name($self, 'XxForm.body'), { rows => 8, cols => $self->DEFAULT_COLS, row_class => 'textarea', }], @{Bivio::Biz::Model->get_instance(_name($self, 'XxForm')) ->map_attachments(sub {_name($self, 'XxForm.') . shift(@_)})}, ); } sub internal_thread_list { my($self) = @_; return DIV(Join([ META({ ITEMPROP => 'name', CONTENT => String(['Model.' . _name($self, 'XxThreadList'), '->get_subject']), }), _msg($self, 0), ]), { ITEMSCOPE => 'itemscope', ITEMTYPE => 'https://schema.org/Article', }); } sub internal_thread_root_list { my($self, $columns) = @_; my($name) = _name($self, 'XxThreadRootList'); return vs_paged_list( $name, $columns || $self->internal_thread_root_list_columns, { no_pager => 1, show_headings => $name =~ /Mail/ ? (b_use('UI.Facade')->is_2014style ? 0 : 1) : 1, }, ); } sub internal_thread_root_list_columns { return [ ['excerpt', { column_heading => '', column_data_class => 'b_msg_summary', column_widget => DIV(Join([ Link( String(['RealmMail.subject']), ['->drilldown_uri'], { class => 'b_subject', ITEMPROP => 'name', }, ), DIV_b_excerpt(String(['excerpt']), { ITEMPROP => 'description', }), DIV_byline(Join([ SPAN_author( String( Or( ['RealmMail.from_display_name'], [ sub { my(undef, $v) = @_; return $_E->get_local_part($v); }, ['RealmMail.from_email'], ], ), {escape_html => 1}, ), { ITEMPROP => 'creator', }, ), DIV_date( If2014Style( vs_smart_date('RealmFile_2.modified_date_time'), DateTime(['RealmFile_2.modified_date_time']), ), ), META({ ITEMPROP => 'dateCreated', CONTENT => [ $_DT, '->to_xml', ['RealmFile.modified_date_time']], }), ])), Link( Join([ AmountCell(['message_count']) ->put(decimals => 0), ' message', If( [sub { my(undef, $count) = @_; return $count > 1 ? 1 : 0; }, ['message_count']], Simple('s'), ), ]), ['->drilldown_uri'], {class => 'b_count'}, ), META({ ITEMPROP => 'interactionCount', CONTENT => Join([ 'UserComments:', ['message_count'], ]), }), ]), { ITEMSCOPE => 'itemscope', ITEMTYPE => 'https://schema.org/Article', }), }], ]; } sub internal_want_link_for_list { my($self, $field) = @_; return $field =~ /date_time|_num\b|subject/ ? 1 : 0; } sub pre_compile { my($self) = shift; my(@res) = $self->SUPER::pre_compile(@_); #TODO: Remove "base" is deprecated return @res unless $self->internal_base_type =~ /^(xhtml|base)$/; $self->internal_standard_tools; return @res; } sub send_form { my($self) = shift; return $self->internal_body($self->internal_send_form(@_)); } sub thread_list { my($self) = @_; vs_put_pager(_name($self, 'XxThreadList')); return $self->internal_body($self->internal_thread_list); } sub thread_root_list { my($self) = @_; vs_put_pager(_name($self, 'XxThreadRootList')); return $self->internal_body($self->internal_thread_root_list); } sub unsubscribe_form { return shift->internal_body( vs_simple_form(MailUnsubscribeForm => [ '*ok_button cancel_button all_button', ]), ); } sub _msg { my($self, $msg_only) = @_; return WithModel( $msg_only ? 'RealmMailList' : _name($self, 'XxThreadList'), Join([ DIV_msg_sep('', $msg_only ? () : {control =>['->get_cursor']}), DIV_msg( $msg_only ? $self->internal_part_list : Join([ $self->internal_part_list, $self->internal_reply_links, ]), { ITEMSCOPE => 'itemscope', ITEMTYPE => 'https://schema.org/Comment', ITEMPROP => 'comment', }, ), ]), ); } sub _name { my($self, $name) = @_; my($c) = $self->internal_name; $name =~ s{xx}{$name =~ /XX/ ? uc($c) : $name =~ /xx/ ? lc($c) : $c}ie || die($name, ': bad name'); return $name; } sub _thread_list_director { my($self) = @_; return Director( ['mime_type'], { map( ("image/$_" => Image( ['->format_uri_for_part', $self->PART_TASK], {class => 'inline'}, )), qw(png jpeg gif), ), 'text/html' => MailBodyHTML( OpenGraphProperty(['->get_body'], 'description'), $self->PART_TASK, ), 'text/plain' => MailBodyPlain( OpenGraphProperty(['->get_body'], 'description'), ), 'x-message/rfc822-headers' => If( ['index'], vs_text_as_prose('MailPartList.forward'), vs_text_as_prose('MailPartList.byline'), ), }, Link( vs_text_as_prose('MailPartList.attachment'), ['->format_uri_for_part', $self->PART_TASK], {class => 'download'}, ), ); } 1;