From 36dd0c88eba8aff9d7c8ae82419a1d8898f6035a Mon Sep 17 00:00:00 2001 From: sunnavy Date: Mon, 25 Mar 2024 19:24:00 -0400 Subject: [PATCH] Convert inline edit to htmx on ticket display As only related widgets will be refreshed, this commit removes js code that submits data of all inline-edit forms instead of just current form. --- share/html/Elements/AddLinks | 2 + share/html/Elements/Header | 4 +- .../Elements/ShowCustomFieldCustomGroupings | 10 ++- share/html/Helpers/TicketUpdate | 74 ++++++++++++++--- share/html/Ticket/Display.html | 6 +- share/html/Ticket/Elements/EditDates | 4 +- share/html/Ticket/Elements/EditPeopleInline | 4 +- share/html/Ticket/Elements/ShowAssets | 8 +- share/html/Ticket/Elements/ShowBasics | 5 ++ share/html/Ticket/Elements/ShowDates | 6 +- share/html/Ticket/Elements/ShowPeople | 4 +- share/html/Ticket/Elements/ShowRequestor | 4 +- share/html/Ticket/Elements/ShowSummary | 41 ++++++---- share/html/Views/Component/dhandler | 7 ++ share/html/Views/Ticket/Title | 61 ++++++++++++++ share/html/Views/Ticket/dhandler | 58 +++++++++++++ share/static/js/assets.js | 17 ++-- share/static/js/util.js | 81 ++++++++++--------- t/web/self_service.t | 4 +- 19 files changed, 317 insertions(+), 83 deletions(-) create mode 100644 share/html/Views/Ticket/Title create mode 100644 share/html/Views/Ticket/dhandler diff --git a/share/html/Elements/AddLinks b/share/html/Elements/AddLinks index 32cbf9c8fe7..f9f9e3c1d49 100644 --- a/share/html/Elements/AddLinks +++ b/share/html/Elements/AddLinks @@ -140,6 +140,7 @@ foreach my $exclude_type ( keys %exclude_links ) { " <% $exclude_links{Refer} |n%>/> +
<& /Elements/EditCustomFields, Object => $Object, Grouping => 'Links', @@ -148,5 +149,6 @@ foreach my $exclude_type ( keys %exclude_links ) { ? (CustomFields => $CustomFields) : ()), &> +
% $m->callback( CallbackName => 'NewLink' ); diff --git a/share/html/Elements/Header b/share/html/Elements/Header index ac3a223432b..df734214b64 100644 --- a/share/html/Elements/Header +++ b/share/html/Elements/Header @@ -121,7 +121,7 @@ % } % if ($ShowTitle) { - + % }
@@ -201,4 +201,6 @@ $LinkRel => undef $SkipDoctype => 0 $RichText => 1 $BodyClass => undef +$TitleTrigger => '' +$TitleSource => '' diff --git a/share/html/Elements/ShowCustomFieldCustomGroupings b/share/html/Elements/ShowCustomFieldCustomGroupings index 86899d3b194..8935bbeb5d2 100644 --- a/share/html/Elements/ShowCustomFieldCustomGroupings +++ b/share/html/Elements/ShowCustomFieldCustomGroupings @@ -90,14 +90,16 @@ for my $group ( @Groupings ) { <&| /Widgets/TitleBox, %grouping_args &> % unless ($modify_behavior eq 'always') { -
+
<& ShowCustomFields, %ARGS, Object => $Object, Grouping => $group &>
% } % if ($modify_behavior ne 'hide') { -
+ - <& /Elements/EditCustomFields, Object => $Object, Grouping => $group, InTable => 0 &> +
+ <& /Elements/EditCustomFields, Object => $Object, Grouping => $group, InTable => 0 &> +
@@ -113,7 +115,7 @@ $Object $title_href => "" $InlineEdit => 0 @Groupings => () -$ActionURL => RT->Config->Get('WebPath')."/Ticket/Display.html" +$ActionURL => RT->Config->Get('WebPath') . '/Helpers/TicketUpdate' <%INIT> my $css_class = lc(ref($Object)||$Object); diff --git a/share/html/Helpers/TicketUpdate b/share/html/Helpers/TicketUpdate index 83a581df5df..b83c3fd0006 100644 --- a/share/html/Helpers/TicketUpdate +++ b/share/html/Helpers/TicketUpdate @@ -61,27 +61,83 @@ my $TicketObj = LoadTicket($id); # fill ACL cache $TicketObj->CurrentUser->PrincipalObj->HasRights( Object => $TicketObj ); +my @events; + $m->callback(CallbackName => 'ProcessArguments', Ticket => $TicketObj, ARGSRef => \%ARGS, - Actions => \@Actions); + Actions => \@Actions, + Events => \@events, + ); # It's common to change owner and add a reply/comment in the same # update. Process the owner change before the message update so the # new owner will see the message if they only see notifications when # they are the owner. -push @Actions, ProcessTicketOwnerUpdate(ARGSRef => \%ARGS, TicketObj => $TicketObj ); +my @owner_changes = ProcessTicketOwnerUpdate(ARGSRef => \%ARGS, TicketObj => $TicketObj ); -push @Actions, ProcessUpdateMessage( +my @message_changes = ProcessUpdateMessage( ARGSRef => \%ARGS, Actions => \@Actions, TicketObj => $TicketObj, ); -push @Actions, ProcessTicketWatchers(ARGSRef => \%ARGS, TicketObj => $TicketObj ); -push @Actions, ProcessTicketBasics( ARGSRef => \%ARGS, TicketObj => $TicketObj ); -push @Actions, ProcessTicketLinks( ARGSRef => \%ARGS, TicketObj => $TicketObj ); -push @Actions, ProcessTicketDates( ARGSRef => \%ARGS, TicketObj => $TicketObj ); -push @Actions, ProcessObjectCustomFieldUpdates(ARGSRef => \%ARGS, Object => $TicketObj ); -push @Actions, ProcessTicketReminders( ARGSRef => \%ARGS, TicketObj => $TicketObj ); +my @watchers_changes = ProcessTicketWatchers( ARGSRef => \%ARGS, TicketObj => $TicketObj ); +my @basics_changes = ProcessTicketBasics( ARGSRef => \%ARGS, TicketObj => $TicketObj ); +my @links_changes = ProcessTicketLinks( ARGSRef => \%ARGS, TicketObj => $TicketObj ); +my @dates_changes = ProcessTicketDates( ARGSRef => \%ARGS, TicketObj => $TicketObj ); +my @cfs_changes = ProcessObjectCustomFieldUpdates( ARGSRef => \%ARGS, Object => $TicketObj ); +my @reminders_changes = ProcessTicketReminders( ARGSRef => \%ARGS, TicketObj => $TicketObj ); + +push @events, 'ticketOwnerChanged' if @owner_changes; +push @events, 'ticketMessageChanged' if @message_changes; +push @events, 'ticketWatchersChanged' if @watchers_changes; +push @events, 'ticketBasicsChanged' if @basics_changes; +push @events, 'ticketLinksChanged' if @links_changes; +push @events, 'ticketDatesChanged' if @dates_changes; +push @events, 'ticketCustomFieldsChanged' if @cfs_changes; +push @events, 'ticketRemindersChanged' if @reminders_changes; + +push @Actions, @owner_changes, @message_changes, @watchers_changes, @basics_changes, @links_changes, @dates_changes, + @cfs_changes, @reminders_changes; + +for my $txn (@{ $TicketObj->{_TransactionBatch} || [] }) { + if ( $txn->Type eq 'Set' ) { + push @events, 'ticket' . $txn->Field . 'Changed'; + if ( $txn->Field eq 'Queue' ) { + push @events, 'reloadRequired'; + } + } + elsif ( $txn->Type eq 'CustomField' ) { + push @events, 'customField-' . $txn->Field . 'Changed'; + } + elsif ( $txn->Type =~ /(?:Add|Delete)Link/ ) { + if ( $txn->Field eq 'MergedInto' ) { + push @events, 'reloadRequired'; + } + elsif ( ( $txn->OldValue // '' ) =~ m{^asset://} || ( $txn->NewValue // '' ) =~ m{^asset://} ) { + push @events, 'ticketAssetsChanged'; + } + } + elsif ( $txn->Type =~ /(?:Add|Delete|Set)Watcher/ ) { + push @events, 'ticket' . $txn->Field . 'Changed'; + } +} + +$m->callback( + CallbackName => 'AfterProcessArguments', + Ticket => $TicketObj, + ARGSRef => \%ARGS, + Actions => \@Actions, + Events => \@events, +); + +$r->headers_out->{'HX-Trigger'} = JSON( + { + actionsChanged => \@Actions, + map { $_ => '' } @events + }, + utf8 => 1, +) if @events || @Actions; + diff --git a/share/html/Ticket/Display.html b/share/html/Ticket/Display.html index f831d98305e..e55f64020fc 100644 --- a/share/html/Ticket/Display.html +++ b/share/html/Ticket/Display.html @@ -47,6 +47,8 @@ %# END BPS TAGGED BLOCK }}} <& /Elements/Header, Title => $title, + TitleTrigger => 'ticketSubjectChanged from:body', + TitleSource => RT->Config->Get('WebPath') . '/Views/Ticket/Title?id=' . $TicketObj->Id, LinkRel => \%link_rel &> <& /Elements/Tabs &> @@ -54,7 +56,9 @@ <& /Elements/ListActions, actions => \@Actions &> <& Elements/ShowUpdateStatus, Ticket => $TicketObj &> -<& Elements/ShowDependencyStatus, Ticket => $TicketObj &> +
+ <& Elements/ShowDependencyStatus, Ticket => $TicketObj &> +
% $m->callback( %ARGS, Ticket => $TicketObj, Transactions => $transactions, Attachments => $attachments, CallbackName => 'BeforeShowSummary' ); diff --git a/share/html/Ticket/Elements/EditDates b/share/html/Ticket/Elements/EditDates index 49c0f0dd2f9..e1cb10b4d35 100644 --- a/share/html/Ticket/Elements/EditDates +++ b/share/html/Ticket/Elements/EditDates @@ -111,7 +111,9 @@
- <& /Elements/EditCustomFields, Object => $TicketObj, Grouping => 'Dates', InTable => 1 &> +
+ <& /Elements/EditCustomFields, Object => $TicketObj, Grouping => 'Dates', InTable => 1 &> +
% $m->callback( %ARGS, CallbackName => 'EndOfList', Ticket => $TicketObj );
<%ARGS> diff --git a/share/html/Ticket/Elements/EditPeopleInline b/share/html/Ticket/Elements/EditPeopleInline index 2e7efcdb64e..8393681897c 100644 --- a/share/html/Ticket/Elements/EditPeopleInline +++ b/share/html/Ticket/Elements/EditPeopleInline @@ -138,7 +138,9 @@ <& AddWatchers, Ticket => $Ticket, ShowLabel => 0 &> -<& /Elements/EditCustomFields, Object => $Ticket, Grouping => 'People', InTable => 1 &> +
+ <& /Elements/EditCustomFields, Object => $Ticket, Grouping => 'People', InTable => 1 &> +
<%ARGS> $Ticket => undef diff --git a/share/html/Ticket/Elements/ShowAssets b/share/html/Ticket/Elements/ShowAssets index 183b45d0cd1..6f178130f41 100644 --- a/share/html/Ticket/Elements/ShowAssets +++ b/share/html/Ticket/Elements/ShowAssets @@ -48,6 +48,7 @@ <%args> $Ticket $ShowRelatedTickets => 10 +$HTMXLoad => 0 <%init> my $target_assets = $Ticket->Links("Base")->Clone; @@ -108,9 +109,10 @@ $m->callback( title => loc('Assets'), class => 'ticket-assets', title_class => "inverse", + htmx_load => $HTMXLoad, &> -/Ticket/Display.html" method="POST" enctype="multipart/form-data"> +/Helpers/TicketUpdate" hx-swap="none" enctype="multipart/form-data"> % $m->callback( CallbackName => "Start", Ticket => $Ticket, Assets => $assets ); @@ -153,12 +155,12 @@ if ($Ticket->CurrentUserHasRight("ModifyTicket")) { for @{ $bases->ItemsArrayRef }; my $delete_url = RT->Config->Get("WebPath") - . "/Ticket/Display.html?" + . "/Helpers/TicketUpdate?" . $m->comp("/Elements/QueryString", id => $Ticket->id, %params); % } diff --git a/share/html/Ticket/Elements/ShowBasics b/share/html/Ticket/Elements/ShowBasics index 35272098ef8..d746047b985 100644 --- a/share/html/Ticket/Elements/ShowBasics +++ b/share/html/Ticket/Elements/ShowBasics @@ -86,9 +86,14 @@ <& /Elements/LabeledValue, Class =>"queue",Label => loc("Queue"), ValueSpanClass => "current-value", RawValue => $m->scomp("ShowQueue", Ticket => $Ticket, QueueObj => $Ticket->QueueObj) &> % } % $m->callback( %ARGS, CallbackName => 'AfterQueue', TicketObj => $Ticket ); + +
<& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Grouping => 'Basics', Table => 0 &> +
% if ($UngroupedCFs) { +
<& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Grouping => '', Table => 0 &> +
% } % $m->callback( %ARGS, CallbackName => 'EndOfList', TicketObj => $Ticket );
diff --git a/share/html/Ticket/Elements/ShowDates b/share/html/Ticket/Elements/ShowDates index 77690caaa5c..6f48623fc70 100644 --- a/share/html/Ticket/Elements/ShowDates +++ b/share/html/Ticket/Elements/ShowDates @@ -97,7 +97,11 @@ % } % $m->callback( %ARGS, CallbackName => 'AfterUpdated', TicketObj => $Ticket ); - <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Grouping => 'Dates', Table => 0 &> + +
+ <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Grouping => 'Dates', Table => 0 &> +
+ % $m->callback( %ARGS, CallbackName => 'EndOfList', TicketObj => $Ticket ); <%ARGS> diff --git a/share/html/Ticket/Elements/ShowPeople b/share/html/Ticket/Elements/ShowPeople index 343624ecccc..f2ed7ad907f 100644 --- a/share/html/Ticket/Elements/ShowPeople +++ b/share/html/Ticket/Elements/ShowPeople @@ -111,7 +111,9 @@ % } - <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Grouping => 'People', Table => 0 &> +
+ <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket, Grouping => 'People', Table => 0 &> +
<%ARGS> $Ticket => undef diff --git a/share/html/Ticket/Elements/ShowRequestor b/share/html/Ticket/Elements/ShowRequestor index e3fc15d7405..fc1ebdf6325 100644 --- a/share/html/Ticket/Elements/ShowRequestor +++ b/share/html/Ticket/Elements/ShowRequestor @@ -63,7 +63,8 @@ <&| /Widgets/TitleBox, title_raw => loc("More about the requestors"), - class => 'ticket-info-requestor fullwidth' + class => 'ticket-info-requestor fullwidth', + htmx_load => $HTMXLoad, &>
@@ -199,4 +200,5 @@ $ShowComments => 1 $ShowTickets => 1 $ShowGroups => 1 $Title => 'More about [_1]' +$HTMXLoad => undef diff --git a/share/html/Ticket/Elements/ShowSummary b/share/html/Ticket/Elements/ShowSummary index e98d672cec9..569d76f4fdf 100644 --- a/share/html/Ticket/Elements/ShowSummary +++ b/share/html/Ticket/Elements/ShowSummary @@ -69,15 +69,17 @@ my $modify_behavior = $InlineEdit ? ($inline_edit_behavior{Basics} || $inline_ed data => { 'inline-edit-behavior' => $modify_behavior }, &> % unless ($modify_behavior eq 'always') { -
+
<& /Ticket/Elements/ShowBasics, Ticket => $Ticket &>
% } % if ($modify_behavior ne 'hide') { - + <& /Ticket/Elements/EditBasics, TicketObj => $Ticket, InTable => 1, ExcludeOwner => 1, ExcludeCustomRoles => 1 &> - <& /Elements/EditCustomFields, Object => $Ticket, Grouping => 'Basics', InTable => 1 &> +
+ <& /Elements/EditCustomFields, Object => $Ticket, Grouping => 'Basics', InTable => 1 &> +
@@ -105,14 +107,16 @@ my $people_behavior = $InlineEdit ? ($inline_edit_behavior{People} || $inline_ed data => { 'inline-edit-behavior' => $people_behavior }, &> % unless ($people_behavior eq 'always') { -
+
<& /Ticket/Elements/ShowPeople, Ticket => $Ticket &>
% } % if ($people_behavior ne 'hide') { - + - <& /Ticket/Elements/EditPeopleInline, Ticket => $Ticket &> +
+ <& /Ticket/Elements/EditPeopleInline, Ticket => $Ticket &> +
@@ -125,7 +129,9 @@ my $people_behavior = $InlineEdit ? ($inline_edit_behavior{People} || $inline_ed % $m->callback( %ARGS, CallbackName => 'AfterPeople' ); <& /Ticket/Elements/ShowAttachments, Ticket => $Ticket, Attachments => $Attachments, Count => RT->Config->Get('AttachmentListCount') &> % $m->callback( %ARGS, CallbackName => 'AfterAttachments' ); - <& /Ticket/Elements/ShowRequestor, Ticket => $Ticket &> +
+ <& /Ticket/Elements/ShowRequestor, Ticket => $Ticket &> +
% $m->callback( %ARGS, CallbackName => 'LeftColumn' );
@@ -135,8 +141,10 @@ my $people_behavior = $InlineEdit ? ($inline_edit_behavior{People} || $inline_ed title_href => RT->Config->Get('WebPath')."/Ticket/Reminders.html?id=".$Ticket->Id, class => 'ticket-info-reminders fullwidth', &> - + +
<& /Ticket/Elements/Reminders, Ticket => $Ticket, ShowCompleted => 0 &> +
% } @@ -155,12 +163,12 @@ my $dates_behavior = $InlineEdit ? ($inline_edit_behavior{Dates} || $inline_edit data => { 'inline-edit-behavior' => $dates_behavior }, &> % unless ($dates_behavior eq 'always') { -
+
<& /Ticket/Elements/ShowDates, Ticket => $Ticket &>
% } % if ($dates_behavior ne 'hide') { -
+ <& /Ticket/Elements/EditDates, TicketObj => $Ticket &>
@@ -179,7 +187,10 @@ my $dates_behavior = $InlineEdit ? ($inline_edit_behavior{Dates} || $inline_edit TicketObj => $Ticket, &> -<& /Ticket/Elements/ShowAssets, Ticket => $Ticket &> +
+ <& /Ticket/Elements/ShowAssets, Ticket => $Ticket &> +
+ <%PERL> my $links_url = RT->Config->Get('WebPath')."/Ticket/ModifyLinks.html?id=".$Ticket->Id; my $links_inline = sprintf( $modify_inline, $m->interp->apply_escapes( $links_url, 'h' ) ); @@ -201,14 +212,16 @@ push @extra, (titleright_raw => $links_titleright) if $links_titleright; @extra, &> % unless ($links_behavior eq 'always') { -
+
<& /Elements/ShowLinks, Object => $Ticket &>
% } % if ($links_behavior ne 'hide') { - - + + +
<& /Elements/EditLinks, Object => $Ticket, TwoColumn => 0 &> +

<&|/l&>Merge

<& /Ticket/Elements/EditMerge, Ticket => $Ticket, MergeTextClass => '', %ARGS &> diff --git a/share/html/Views/Component/dhandler b/share/html/Views/Component/dhandler index 69c8189eb9b..fa877d4bdb9 100644 --- a/share/html/Views/Component/dhandler +++ b/share/html/Views/Component/dhandler @@ -64,6 +64,13 @@ if ( $component_name eq 'SavedSearch' ) { } } } +elsif ( $ARGS{ObjectType} && $ARGS{ObjectType}->can('Load') && $ARGS{ObjectId} ) { + my $object = $ARGS{ObjectType}->new( $session{CurrentUser} ); + $object->Load( $ARGS{ObjectId} ); + if ( $object->CurrentUserCanSee ) { + $ARGS{Object} = $object; + } +} <%args> diff --git a/share/html/Views/Ticket/Title b/share/html/Views/Ticket/Title new file mode 100644 index 00000000000..c1d6936f90f --- /dev/null +++ b/share/html/Views/Ticket/Title @@ -0,0 +1,61 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2024 Best Practical Solutions, LLC +%# +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +<% $title %> + +<%INIT> +return unless $id; +my $ticket = RT::Ticket->new( $session{CurrentUser} ); +$ticket->Load($id); + +my $title = loc( "#[_1]: [_2]", $ticket->Id, $ticket->Subject || '' ); +$r->headers_out->{'HX-Trigger'} = JSON( { titleChanged => $title }, utf8 => 1, ); + + +<%ARGS> +$id => undef + diff --git a/share/html/Views/Ticket/dhandler b/share/html/Views/Ticket/dhandler new file mode 100644 index 00000000000..64a2df8294b --- /dev/null +++ b/share/html/Views/Ticket/dhandler @@ -0,0 +1,58 @@ +%# BEGIN BPS TAGGED BLOCK {{{ +%# +%# COPYRIGHT: +%# +%# This software is Copyright (c) 1996-2024 Best Practical Solutions, LLC +%# +%# +%# (Except where explicitly superseded by other copyright notices) +%# +%# +%# LICENSE: +%# +%# This work is made available to you under the terms of Version 2 of +%# the GNU General Public License. A copy of that license should have +%# been provided with this software, but in any event can be snarfed +%# from www.gnu.org. +%# +%# This work is distributed in the hope that it will be useful, but +%# WITHOUT ANY WARRANTY; without even the implied warranty of +%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%# General Public License for more details. +%# +%# You should have received a copy of the GNU General Public License +%# along with this program; if not, write to the Free Software +%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +%# 02110-1301 or visit their web page on the internet at +%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +%# +%# +%# CONTRIBUTION SUBMISSION POLICY: +%# +%# (The following paragraph is not intended to limit the rights granted +%# to you to modify and distribute this software under the terms of +%# the GNU General Public License and is only of importance to you if +%# you choose to contribute your changes and enhancements to the +%# community by submitting them to Best Practical Solutions, LLC.) +%# +%# By intentionally submitting any modifications, corrections or +%# derivatives to this work, or any other work intended for use with +%# Request Tracker, to Best Practical Solutions, LLC, you confirm that +%# you are the copyright holder for those contributions and you grant +%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +%# royalty-free, perpetual, license to use, copy, create derivative +%# works based on those contributions, and sublicense and distribute +%# those contributions and any derivatives thereof. +%# +%# END BPS TAGGED BLOCK }}} +% $m->comp( "/Ticket/Elements/$component_name", Ticket => $ticket, %ARGS ); +<%INIT> +return unless $id; +my ($component_name) = $m->dhandler_arg; +my $ticket = RT::Ticket->new( $session{CurrentUser} ); +$ticket->Load($id); + + +<%ARGS> +$id => undef + diff --git a/share/static/js/assets.js b/share/static/js/assets.js index 12fb59c19ed..4d661e83026 100644 --- a/share/static/js/assets.js +++ b/share/static/js/assets.js @@ -12,12 +12,17 @@ htmx.onLoad(function(elt) { }; const form = elt.closest(".ticket-assets") ? jQuery(elt).find("form") : jQuery(elt).find(".ticket-assets form"); - form.submit(function(){ - var input = jQuery("[name*=RefersTo]", this); - if (input.val()) - input.val(input.val().match(/\S+/g) - .map(function(x){return "asset:"+x}) - .join(" ")); + form.each(function(){ + this.addEventListener('htmx:configRequest', function(evt) { + for ( const param in evt.detail.parameters ) { + if ( param.match(/RefersTo/) && evt.detail.parameters[param] ) { + evt.detail.parameters[param] = evt.detail.parameters[param] + .match(/\S+/g) + .map(function(x){return "asset:"+x}) + .join(" "); + } + } + }); }); jQuery(elt).find(".asset-create-linked-ticket").click(function(ev){ ev.preventDefault(); diff --git a/share/static/js/util.js b/share/static/js/util.js index f09a630717a..9175578e7b7 100644 --- a/share/static/js/util.js +++ b/share/static/js/util.js @@ -767,6 +767,25 @@ jQuery(function() { document.getElementById('hx-boost-spinner').classList.add('d-none'); } }); + + document.body.addEventListener('actionsChanged', function(evt) { + if ( evt.detail.value ) { + for ( const action of evt.detail.value ) { + // Need to decode action that is UTF-8 encoded + jQuery.jGrowl(decodeURIComponent(escape(action)), { themeState: 'none' }); + } + } + }); + + document.body.addEventListener('titleChanged', function(evt) { + document.title = decodeURIComponent(escape(evt.detail.value)); + }); + + document.body.addEventListener('reloadRequired', function(evt) { + setTimeout(function () { + document.location = document.location; + }, 3000); // Give users some time to see growl messages. + }); }); htmx.onLoad(function(elt) { @@ -1494,48 +1513,34 @@ htmx.onLoad(function(elt) { toggle_inline_edit(container.find('.inline-edit-toggle:visible')); }); - /* on submit, pull in all the other inline edit forms' fields into - * the currently-being-submitted form. that way we don't lose user - * input */ jQuery(elt).find('form.inline-edit').submit(function (e) { - var currentForm = jQuery(this); + toggle_inline_edit(jQuery(this).closest('.titlebox').find('.inline-edit-toggle:visible')); + }); - /* limit to currently-editing forms, since cancelling inline - * edit merely hides the form */ - jQuery('.titlebox.editing form.inline-edit').each(function () { - var siblingForm = jQuery(this); + // Register triggers for cf changes + elt.querySelectorAll('.show-custom-fields-container[hx-get], .edit-custom-fields-container[hx-get]').forEach(function (elt) { + let events = []; + if ( elt.classList.contains('show-custom-fields-container') ) { + elt.querySelectorAll('.row.custom-field').forEach(function (elt) { + const id = elt.id.match(/CF-(\d+)/)[1]; + events.push('customField-' + id + 'Changed from:body'); + }); + } + else { + elt.querySelectorAll('input[type=hidden][name*=-CustomField][name$="-Magic"]').forEach(function (elt) { + let id = elt.name.match(/CustomField.*-(\d+)-.*-Magic$/)[1]; + events.push('customField-' + id + 'Changed from:body'); + }); + } - if (siblingForm.is(currentForm)) { - return; + if ( events.length ) { + let orig_trigger = elt.getAttribute('hx-trigger'); + if ( orig_trigger && orig_trigger !== 'none' ) { + events.push(orig_trigger); } - - siblingForm.find(':input').each(function () { - var field = jQuery(this); - - if (field.attr('name') == "") { - return; - } - - /* skip duplicates, such as ticket id */ - /* Specifically exclude radio and checkbox because the name of all inputs are the same - * so checked values don't get properly submitted. This results in these CFs getting - * unset when a field in another portlet is updated because the current value isn't - * submitted. */ - if (field.attr('type') != 'radio' && field.attr('type') != 'checkbox' && currentForm.find('[name="' + field.attr('name') + '"]').length > 0) { - return; - } - - var clone = field.clone().hide().appendTo(currentForm); - - /* "For performance reasons, the dynamic state of certain - * form elements (e.g., user data typed into textarea - * and user selections made to a select) is not copied - * to the cloned elements", so manually copy them */ - if (clone.is('select, textarea')) { - clone.val(field.val()); - } - }); - }); + elt.setAttribute('hx-trigger', events.join(', ')); + htmx.process(elt); + } }); }); diff --git a/t/web/self_service.t b/t/web/self_service.t index 859e8213a3d..28a4b836092 100644 --- a/t/web/self_service.t +++ b/t/web/self_service.t @@ -37,7 +37,7 @@ $m->get_ok( '/SelfService/Display.html?id=' . $ticket->id, my $title = '#' . $ticket->id . ': test subject'; $m->title_is( $title ); -$m->content_contains( "

$title

", "contains

$title

" ); +$m->content_like( qr{]*>$title}, "contains $title in

" ); # $ShowUnreadMessageNotifications tests: $m->content_contains( "There are unread messages on this ticket." ); @@ -48,7 +48,7 @@ $m->follow_link_ok( 'followed mark as seen link' ); -$m->content_contains( "

$title

", "contains

$title

" ); +$m->content_like( qr{]*>$title}, "contains $title in

" ); $m->content_lacks( "There are unread messages on this ticket." ); $m->follow_link_ok( { url_regex => qr{^/SelfService/Transaction/Display.html}, n => 2 }, 'Followed transaction link' );