(function () {
let firstCombinationPressed = false;
document.addEventListener("keydown", function (event) {
// Check for Ctrl+Alt+T or Control+Option+T
if (event.ctrlKey && event.altKey && event.keyCode === 84) {
firstCombinationPressed = true;
// Set a timeout to reset the state after a short period (e.g., 1 seconds)
setTimeout(() => {
firstCombinationPressed = false;
}, 1000);
}
if (firstCombinationPressed && event.shiftKey &&
(event.keyCode === 65 ||
event.keyCode === 70 || event.keyCode === 71 ||
event.keyCode === 76)) {
const theme =
// Shift + A : Authentic theme
event.keyCode === 65 ? 1 :
// Shift + F / Shift + G : Framed theme / Gray theme
(event.keyCode === 70 || event.keyCode === 71) ? 2 :
// Shift + L : Legacy theme
event.keyCode === 76 ? 3 : null;
firstCombinationPressed = false;
try {
top.document.documentElement.style.filter = 'grayscale(100%) blur(0.5px) brightness(0.75) opacity(0.5)';
top.document.documentElement.style.cursor = 'wait';
top.document.documentElement.style.pointerEvents = 'none';
} catch (error) {}
top.location.href = "$webprefix/switch_theme.cgi?theme=" + theme + "";
}
});
})();
document.currentScript.remove();
EOF
return $switch_script;
}
####################### grid layout functions
=head2 ui_grid_table(&elements, columns, [width-percent], [&tds], [tabletags], [title])
Given a list of HTML elements, formats them into a table with the given
number of columns. However, themes are free to override this to use fewer
columns where space is limited. Parameters are :
=item elements - An array reference of table elements, each of which can be any HTML you like.
=item columns - Desired number of columns in the grid.
=item width-percent - Optional desired width as a percentage.
=item tds - Array ref of HTML attributes for tags in the tables.
=item tabletags - HTML attributes for the tag.
=item title - Optional title to add to the top of the grid.
=cut
sub ui_grid_table
{
return &theme_ui_grid_table(@_) if (defined(&theme_ui_grid_table));
my ($elements, $cols, $width, $tds, $tabletags, $title) = @_;
return "" if (!@$elements);
my $rv = "\n";
my $i;
for($i=0; $i<@$elements; $i++) {
$rv .= "" if ($i%$cols == 0);
$rv .= "| [$i%$cols]." valign='top' class='ui_grid_cell'>".
$elements->[$i]." | \n";
$rv .= " " if ($i%$cols == $cols-1);
}
if ($i%$cols) {
while($i%$cols) {
$rv .= "[$i%$cols]." class='ui_grid_cell'>".
"
| \n";
$i++;
}
$rv .= "\n";
}
$rv .= " \n";
if (defined($title)) {
$rv = "\n".
($title ? "| $title | \n" : "").
"| $rv | \n".
" ";
}
return $rv;
}
=head2 ui_radio_table(name, selected, &rows, [no-bold])
Returns HTML for a table of radio buttons, each of which has a label and
some associated inputs to the right. The parameters are :
=item name - Unique name for this table, which is also the radio buttons' name.
=item selected - Value for the initially selected radio button.
=item rows - Array ref of array refs, one per button. The elements of each are the value for this option, a label, and option additional HTML to appear next to it.
=item no-bold - When set to 1, labels in the table will not be bolded
=cut
sub ui_radio_table
{
return &theme_ui_radio_table(@_) if (defined(&theme_ui_radio_table));
my ($name, $sel, $rows, $nobold) = @_;
return "" if (!@$rows);
my $rv = "\n";
foreach my $r (@$rows) {
$rv .= "\n";
$rv .= "| [2]) ? "" : " colspan='2'").">".
($nobold ? "" : "").
&ui_oneradio($name, $r->[0], $r->[1], $r->[0] eq $sel, $r->[3]).
($nobold ? "" : "").
" | \n";
if (defined($r->[2])) {
$rv .= "".$r->[2]." | \n";
}
$rv .= " \n";
}
$rv .= " \n";
return $rv;
}
=head2 ui_up_down_arrows(uplink, downlink, up-show, down-show)
Returns HTML for moving some objects in a table up or down. The parameters are :
=item uplink - URL for the up-arrow link.
=item downlink - URL for the down-arrow link.
=item up-show - Set to 1 if the up-arrow should be shown, 0 if not.
=item down-show - Set to 1 if the down-arrow should be shown, 0 if not.
=item up-icon - Optional path to icon for up link
=item down-icon - Optional path to icon for down link
=cut
sub ui_up_down_arrows
{
return &theme_ui_up_down_arrows(@_) if (defined(&theme_ui_up_down_arrows));
my ($uplink, $downlink, $upshow, $downshow, $upicon, $downicon) = @_;
my $mover;
my $imgdir = "@{[&get_webprefix()]}/images";
$upicon ||= "$imgdir/moveup.gif";
$downicon ||= "$imgdir/movedown.gif";
if ($downshow) {
$mover .= "".
" ";
}
else {
$mover .= " ";
}
if ($upshow) {
$mover .= "".
" ";
}
else {
$mover .= " ";
}
return $mover;
}
=head2 ui_hr
Returns a horizontal row tag, typically just an
=item tags - Additional HTML attributes for the tag.
=cut
sub ui_hr
{
return &theme_ui_hr(@_) if (defined(&theme_ui_hr));
my ($tags) = @_;
return " \n";
}
=head2 ui_nav_link(direction, url, disabled)
Returns an arrow icon linking to the provided url.
=cut
sub ui_nav_link
{
return &theme_ui_nav_link(@_) if (defined(&theme_ui_nav_link));
my ($direction, $url, $disabled) = @_;
my $alt = $direction eq "left" ? '<-' : '->';
if ($disabled) {
return " \n";
}
else {
return " \n";
}
}
=head2 ui_confirmation_form(cgi, message, &hiddens, [&buttons], [otherinputs], [extra-warning])
Returns HTML for a form asking for confirmation before performing some
action, such as deleting a user. The parameters are :
=item cgi - Script to which the confirmation form submits, like delete.cgi.
=item message - Warning message for the user to see.
=item hiddens - Array ref of two-element array refs, containing hidden form field names and values.
=item buttons - Array ref of two-element array refs, containing form button names and labels.
=item otheirinputs - HTML for extra inputs to include in their form.
=item extra-warning - An additional separate warning message to show.
=cut
sub ui_confirmation_form
{
my ($cgi, $message, $hiddens, $buttons, $others, $warning) = @_;
my $rv;
$rv .= "\n";
$rv .= &ui_form_start($cgi, "post");
foreach my $h (@$hiddens) {
$rv .= &ui_hidden(@$h);
}
$rv .= "$message\n";
if ($warning) {
$rv .= "$warning \n";
}
if ($others) {
$rv .= $others." \n";
}
$rv .= &ui_form_end($buttons);
$rv .= " \n";
return $rv;
}
=head2 ui_text_color(text, type)
Returns HTML for a text string, with its color determined by $type.
=item text - contains any text string
=item type - returned text color
=cut
sub ui_text_color
{
my ($text, $type) = @_;
my ($color);
if (defined (&theme_ui_text_color)) {
return &theme_ui_text_color(@_);
}
if ($type eq "success") { $color = "#3c763d"; }
elsif ($type eq "info") { $color = "#31708f"; }
elsif ($type eq "warn") { $color = "#8a6d3b"; }
elsif ($type eq "danger") { $color = "#a94442"; }
return "$text";
}
=head2 ui_alert_box(msg, type)
Returns HTML for an alert box, with background color determined by $type.
$msg contains any text or HTML to be contained within the alert box, and
can include forms.
Type of alert:
=item success - green
=item info - blue
=item warn - yellow
=item danger - red
=cut
sub ui_alert_box
{
my ($msg, $type) = @_;
my ($rv, $color);
if (defined (&theme_ui_alert_box)) {
return &theme_ui_alert_box(@_);
}
if ($type eq "success") { $color = "DFF0D8"; }
elsif ($type eq "info") { $color = "D9EDF7"; }
elsif ($type eq "warn") { $color = "FCF8E3"; }
elsif ($type eq "danger") { $color = "F2DEDE"; }
$rv .= "\n";
$rv .= "$msg\n";
$rv .= " |
\n";
return $rv;
}
####################### javascript functions
=head2 js_disable_inputs(&disable-inputs, &enable-inputs, [tag])
Returns Javascript to disable some form elements and enable others. Mainly
for internal use.
=cut
sub js_disable_inputs
{
my $rv;
my $f;
foreach $f (@{$_[0]}) {
$rv .= "e = form.elements[\"$f\"]; e.disabled = true; ";
$rv .= "for(i=0; i= 0) {
# When enabling both a _def field and its associated text field,
# disable the text if the _def is set to 1
my $tf = $1;
$rv .= "e = form.elements[\"$f\"]; for(i=0; i";
$rv .= &ui_form_start($cgi) if ($cgi);
# Far left link, if needed
if (@_ > 5) {
if ($farleft) {
$rv .= "".
" \n";
}
else {
$rv .= " \n";
}
}
# Left link
if ($left) {
$rv .= "".
" \n";
}
else {
$rv .= " \n";
}
# Message and inputs
$rv .= $msg;
$rv .= " ".$inputs if ($inputs);
# Right link
if ($right) {
$rv .= "".
" \n";
}
else {
$rv .= " \n";
}
# Far right link, if needed
if (@_ > 5) {
if ($farright) {
$rv .= "".
" \n";
}
else {
$rv .= " \n";
}
}
$rv .= " ".$below if ($below);
$rv .= &ui_form_end() if ($cgi);
$rv .= "\n";
return $rv;
}
=head2 js_checkbox_disable(name, &checked-disable, &checked-enable, [tag])
For internal use only.
=cut
sub js_checkbox_disable
{
my $rv;
my $f;
foreach $f (@{$_[1]}) {
$rv .= "form.elements[\"$f\"].disabled = $_[0].checked; ";
}
foreach $f (@{$_[2]}) {
$rv .= "form.elements[\"$f\"].disabled = !$_[0].checked; ";
}
return $_[3] ? "$_[3]='$rv'" : $rv;
}
=head2 js_redirect(url, [window-object], [timeout])
Returns HTML to trigger a redirect to some URL.
=cut
sub js_redirect
{
my ($url, $window, $timeout) = @_;
if (defined(&theme_js_redirect)) {
return &theme_js_redirect(@_);
}
$window ||= "window";
$timeout ||= 0;
if ($url =~ /^\//) {
# If the URL is like /foo , add webprefix
$url = &get_webprefix().$url;
}
return "";
}
=head2 ui_webmin_link(module, page)
Returns the URL for a link to this Webmin instance that can be used in an email
=cut
sub ui_webmin_link
{
my ($mod, $page) = @_;
if (defined(&theme_ui_webmin_link)) {
return &theme_ui_webmin_link(@_);
}
my %miniserv;
&get_miniserv_config(\%miniserv);
my $proto = $miniserv{'ssl'} ? 'https' : 'http';
my $port = $miniserv{'port'};
my $host = $ENV{'HTTP_HOST'} || &get_display_hostname();
if ($host =~ /^([a-zA-Z0-9\-\_\.]+):(\d+)$/) {
$host = $1;
$port = $2;
}
my $rv = $proto."://$host:$port";
if ($mod) {
$rv .= "/$mod";
}
if ($page) {
$rv .= "/$page";
}
return $rv;
}
=head2 ui_line_break_double()
Create double line break, with accessible second break
=cut
sub ui_line_break_double
{
if (defined(&theme_ui_line_break_double)) {
return &theme_ui_line_break_double(@_);
}
return "
\n";
}
=head2 ui_page_refresh()
Returns theme based JavaScript function to refresh current page
=cut
sub ui_page_refresh
{
if (defined(&theme_ui_page_refresh)) {
return &theme_ui_page_refresh(@_);
}
return "window.location.reload()";
}
=head2 ui_details(Config, Opened)
Creates a disclosure widget in which information is visible only when
the widget is toggled into an "open" state.
=cut
sub ui_details
{
my ($c, $o) = @_;
if (defined(&theme_ui_details)) {
return &theme_ui_details(@_);
}
my $rv;
if (!$c->{'html'}) {
$c->{'title'} = &html_escape($c->{'title'}, 1);
$c->{'content'} = &html_escape($c->{'content'}, 1);
}
$c->{'class'} = " class=\"@{["e_escape($c->{'class'})]}\"" if($c->{'class'});
$o = ' open' if ($o);
$rv = "{'class'}$o>";
$rv .= "$c->{'title'}";
$rv .= "$c->{'content'}";
$rv .= " ";
return $rv;
}
=head2 ui_div_row(label, content)
Prints a row without using a table and
places label and content in a way
ui_table_row does
=cut
sub ui_div_row
{
if (defined(&theme_ui_div_row)) {
return &theme_ui_div_row(@_);
}
my ($label, $content) = @_;
return "$label$content \n";
}
=head2 ui_space(number)
Prints no breakable space number of given times
=cut
sub ui_space
{
if (defined(&theme_ui_space)) {
return &theme_ui_space(@_);
}
my ($number) = @_;
$number ||= 1;
return "".(" " x $number)."\n";
}
=head2 ui_newline(number)
Prints new lines given number of times
=cut
sub ui_newline
{
if (defined(&theme_ui_newline)) {
return &theme_ui_newline(@_);
}
my ($number) = @_;
$number ||= 1;
return "".(" " x $number)."\n";
}
=head2 ui_text_wrap(text)
Wraps any text into span tags
=cut
sub ui_text_wrap
{
if (defined(&theme_ui_text_wrap)) {
return &theme_ui_text_wrap(@_);
}
my ($text) = @_;
return "$text\n";
}
=head2 ui_element_inline(text)
Wraps any text into span tags
=cut
sub ui_element_inline
{
if (defined(&theme_ui_element_inline)) {
return &theme_ui_element_inline(@_);
}
my ($element, $type) = @_;
return "$element\n";
}
=head2 ui_paginations(&array, &opts)
Given an array reference, slice it and return hash
reference with pagination buttons, search form
and other elements to be inserted on the page
=cut
sub ui_paginations
{
return &theme_ui_paginations(@_)
if (defined(&theme_ui_paginations));
my ($arr, $opts) = @_;
my %rv;
my $id = $main::ui_paginations++;
my @arr = @{$arr};
my ($script_name) = $0 =~ /([^\/]*\.cgi)$/;
my $top_offset_px = int($opts->{'paginations'}->{'offset'}->{'top'}) || 105;
my $bottom_offset_px = int($opts->{'paginations'}->{'offset'}->{'bottom'}) || 90;
my $row_size_px = int($opts->{'paginations'}->{'offset'}->{'row'}) || 24;
my $items_per_page = int($tconfig{'paginate'}) || int($opts->{"paginate${id}"}) || 20;
my $curent_page = int($opts->{"page${id}"}) || 1;
my $search_term = &un_urlize($opts->{"search${id}"});
my $pagination_target = $opts->{'paginations'}->{'paginator'}->{'target'} || $script_name;
my $paginator_wrap_class = $opts->{'paginations'}->{'paginator'}->{'class'}->{'wrap'} || "ui_form_elements_wrapper_paginator";
my $link_page_cls = $opts->{'paginations'}->{'paginator'}->{'class'}->{'links'} || 'ui_link_pagination';
my $link_search_cls = $opts->{'paginations'}->{'paginator'}->{'class'}->{'textbox'} || 'ui_textbox_pagination';
my $text_showing_cls = $opts->{'paginations'}->{'paginator'}->{'class'}->{'text'} || 'ui_showing_items';
my $search_target = $opts->{'paginations'}->{'search'}->{'target'} || $script_name;
my $search_wrap_class = $opts->{'paginations'}->{'search'}->{'class'}->{'wrap'} || "ui_form_elements_wrapper_search";
my $search_placeholder = $opts->{'paginations'}->{'search'}->{'placeholder'} || $text{'ui_searchok'};
my $exported_form = $opts->{'paginations'}->{'form'};
my $ui_column_colspan = int($exported_form->{'colspan'} || 4);
# If we have a search string filter existing content
if (ref($arr) eq 'ARRAY' && $arr->[0]) {
if ($search_term) {
my @sarr;
map {
if (ref($_) eq 'ARRAY') {
arr: for (my $i = 0; $i <= $#$_; $i++) {
push(@sarr, $_), last arr
if(index(lc($_->[$i]), lc($search_term)) != -1);
}
}
if (ref($_) eq 'HASH') {
hash: foreach my $__ (values %{$_}) {
push(@sarr, $_), last hash
if(index(lc($__), lc($search_term)) != -1);
}
}
} @arr;
@arr = @sarr;
}
# Can pagination be done automatically
# depending on the client screen height?
my $items_per_page_client = int($opts->{'client_height'} || get_http_cookie('client_height'));
my $items_per_page =
$tconfig{'paginate'} ?
$items_per_page :
(int($ENV{'HTTP_X_CLIENT_PAGINATE'}) ||
($items_per_page_client ?
((int(($items_per_page_client -
$top_offset_px - $bottom_offset_px) / $row_size_px))) : $items_per_page));
# If caller wants specific pagination number,
# e.g. a module config, use that instead
if ($exported_form && $exported_form->{'paginate'}) {
$items_per_page = $exported_form->{'paginate'};
}
# Sanity check for minimum items per page
if ($items_per_page <= 0) {
$items_per_page = 2;
}
# Pagination
my $totals_items_original = scalar(@arr);
my $total_pages = ceil(($totals_items_original) / $items_per_page);
my $total_pages_length = length($total_pages);
# Dynamically parse external form elements into query string
my $exported_form_query = "";
if ($exported_form) {
foreach (keys %{$exported_form}) {
$exported_form_query .= "$_=@{[&urlize($exported_form->{$_})]}&";
}
$exported_form_query =~ s/\&$//;
}
# Return pagination jumper only
# if there is more than one page
if ($total_pages > 1) {
my $totals_items_spliced = $totals_items_original;
my $start_page_with = $curent_page * $items_per_page;
$curent_page = $total_pages
if ($curent_page > $total_pages);
$curent_page = 1
if ($curent_page <= 0);
my $curent_page_prev = $curent_page - 1;
my $page_prev_disabled = $curent_page_prev <= 0 ? " disabled" : "";
my $curent_page_next = $curent_page + 1;
my $page_next_disabled = $curent_page_next > $total_pages ? " disabled" : "";
my $splice_start = $items_per_page * $curent_page_prev;
my $splice_end = $items_per_page;
@arr = splice(@arr, $splice_start, $splice_end);
$totals_items_spliced = scalar(@arr);
#
# Pagination jumper
#
my $paginator_id = 'paginator-form';
my $paginator_data = "$paginator_id-data";
# Paginator form
$rv{'paginator'}->{'form'} =
&ui_form_start($pagination_target, 'get', undef, "id='$paginator_id${id}'");
$rv{'paginator'}->{'form'} .= &ui_form_end();
# Paginator form data
$rv{'paginator'}->{'form-data'} = &ui_hidden("search${id}", $search_term, "$paginator_id${id}")
if ($search_term);
$rv{'paginator'}->{'form-data'} .= &ui_hidden("paginate${id}", $items_per_page, "$paginator_id${id}");
# Calculate showing start and range numbers
my $current_showing_start =
$curent_page == 1 ? 1 : int(($items_per_page * $curent_page + 1) - $items_per_page);
my $current_showing_range =
int($current_showing_start + $items_per_page > $totals_items_original ?
$totals_items_original : $current_showing_start + $items_per_page - 1);
# Showing items range selector text
$rv{'paginator'}->{'form-data'} .=
"@{[
&text('paginator_showing_start', $current_showing_start,
$current_showing_range, $totals_items_original) ]} ";
#
# Arrow links
#
my $search_term_urlize = &urlize($search_term);
my $curent_page_prev_urlize = &urlize($curent_page_prev);
my $curent_page_next_urlize = &urlize($curent_page_next);
my $items_per_page_urlize = &urlize($items_per_page);
my $total_pages_html_escape = &html_escape($total_pages);
# Arrow link left
$rv{'paginator'}->{'form-data'} .=
&ui_link("$pagination_target?page${id}=$curent_page_prev_urlize".
"&search${id}=$search_term_urlize&paginate${id}=$items_per_page_urlize".'&'."$exported_form_query",
' ⏴ ',
"@{[&html_escape($link_page_cls)]} @{[&html_escape($link_page_cls)]}_left$page_prev_disabled",
"data-formid='$id'");
# Page number input selector
$rv{'paginator'}->{'form-data'} .=
&ui_textbox("page${id}", $curent_page, $total_pages_length, undef, $total_pages_length,
"data-class='@{["e_escape($link_search_cls)]}' form='$paginator_id${id}'");
# Out of pages text
$rv{'paginator'}->{'form-data'} .=
" @{[&text('paginator_showing_end',
$total_pages_html_escape)]}";
# Arrow link right
$rv{'paginator'}->{'form-data'} .=
&ui_link("$pagination_target?page${id}=$curent_page_next_urlize".
"&search${id}=$search_term_urlize&paginate${id}=$items_per_page_urlize".'&'."$exported_form_query",
' ⏵ ',
"@{[&html_escape($link_page_cls)]} @{[&html_escape($link_page_cls)]}_right$page_next_disabled",
"data-formid='$id'");
# Allow listing pages using "Alt + left/right" hotkeys
if (!$ENV{'HTTP_X_CLIENT_PAGINATE_NO_SCRIPT'}) {
$rv{'paginator'}->{'form-scripts'} .=
"";
}
# Dynamically adding external form elements
if ($exported_form) {
foreach (keys %{$exported_form}) {
$rv{'paginator'}->{'form-data'} .=
&ui_hidden($_, $exported_form->{$_}, "$paginator_id${id}");
}
}
$rv{'paginator'}->{'form-data'} =
&ui_form_elements_wrapper($rv{'paginator'}->{'form-data'}, "$paginator_id${id}",
"e_escape($paginator_wrap_class))
}
#
# Search form
#
if ($total_pages > 1 || $search_term) {
my $search_id = 'search-form';
my $search_data = "$search_id-data";
# Paginator search form
$rv{'search'}->{'form'} =
&ui_form_start($search_target, 'get', undef, "id='$search_id${id}'");
$rv{'search'}->{'form'} .= &ui_form_end();
# Paginator search form data
$rv{'search'}->{'form-data'} .= &ui_hidden("paginate${id}", $items_per_page, "$search_id${id}");
$rv{'search'}->{'form-data'} .= &ui_hidden("page${id}", 1, "$search_id${id}");
my $search_placeholder_length = length($search_term) || length($search_placeholder);
$search_placeholder_length = $search_placeholder_length < 8 ? 8 : $search_placeholder_length;
$search_placeholder_length = 24 if ($search_placeholder_length >= 24);
# Search box
$rv{'search'}->{'form-data'} .=
&ui_textbox("search${id}", $search_term, $search_placeholder_length, undef, undef,
"data-class='@{["e_escape($link_search_cls)]}_search' ".
"placeholder='@{["e_escape($search_placeholder)]}' form='$search_id${id}'");
# Search reset using JS
$rv{'search'}->{'form-data'} .=
&ui_reset('⛌', undef,
"onclick='document.getElementById(\"$search_id${id}\").search${id}.value = \"\";".
"document.getElementById(\"$search_id${id}\").submit()'");
# Dynamically adding external form elements
if ($exported_form) {
foreach (keys %{$exported_form}) {
$rv{'search'}->{'form-data'} .=
&ui_hidden($_, $exported_form->{$_}, "$search_id${id}");
}
}
$rv{'search'}->{'form-data'} =
&ui_form_elements_wrapper($rv{'search'}->{'form-data'},
"$search_id${id}", "e_escape($search_wrap_class));
# Search no results
$rv{'search'}->{'no-results'} =
&ui_columns_row([&text('paginator_nosearchrs', &html_escape($search_term))],
['colspan="'.$ui_column_colspan.'" align="center"']);
}
# Elements for the parent form, so after submission to
# make sure that we return to the right paginated page
$rv{'form'} = &ui_hidden("page${id}", $curent_page).
&ui_hidden("paginate${id}", $items_per_page).
&ui_hidden("search${id}", $search_term);
}
@$arr = @arr;
return \%rv;
}
=head2 ui_hide_outside_of_viewport(elem)
Prints element if not in viewport. Useful
when printing top and bottom controls as
printing bottom controls only if it's not
in view already
=cut
sub ui_hide_outside_of_viewport
{
my ($elem_sel) = @_;
$elem_sel ||= "[data-outside-of-viewport]";
if (defined(&theme_ui_hide_outside_of_viewport)) {
return &theme_ui_hide_outside_of_viewport(@_);
}
return <
try {
(function() {
var elems = document.querySelectorAll('$elem_sel'),
i;
for (i = 0; i < elems.length; i++) {
if (elems[i].offsetTop < window.innerHeight) {
elems[i].style.display = "none";
}
}
})();
} catch (e) {};
EOF
}
=head2 ui_read_file_contents_limit(\%data)
Reads file content with options and
returns head and/or tail separated with
chomped message
=cut
sub ui_read_file_contents_limit
{
if (defined(&theme_ui_read_file_contents_limit)) {
return &theme_ui_read_file_contents_limit(@_);
}
my ($opts) = @_;
my $binary = -s $opts->{'file'} >= 128 && -B $opts->{'file'};
my $data = &read_file_contents_limit($opts->{'file'}, $opts->{'limit'}, $opts);
my $error = $data->{'error'};
if ($error) {
return $error;
}
my $nonulls = sub {
$_[0] =~ s/[^[:print:]\n\r\t]/\ /g;
return $_[0];
};
my $head = $data->{'head'};
my $tail = $data->{'tail'};
my $chomped = $data->{'chomped'};
my $fsize = $data->{'size'};
my $flimit = $data->{'limit'};
my $msg_type = !$head && $tail ? '_tail' :
$head && !$tail ? '_head' : undef;
my $nlines = $nslines = $nelines = "\n" x 10;
$nslines = undef if (!$head);
$nelines = undef if (!$tail);
my $chomped_msg;
$chomped_msg =
"${nslines}[--- @{[&text(\"file_truncated_message$msg_type\",
&nice_size($flimit),
&nice_size($chomped),
&nice_size($fsize))]} ---]$nelines"
if ($chomped);
# Trim nulls
$head = &$nonulls($head)
if ($binary && $head);
$tail = &$nonulls($tail)
if ($binary && $tail);
# Return data
if ($head && $tail) {
return $head . $chomped_msg . $tail;
}
if ($tail) {
return $chomped_msg . $tail;
}
if ($head) {
return $head . $chomped_msg;
}
}
=head2 ui_note(text)
Returns a note as a small font size text
=cut
sub ui_note
{
return &theme_ui_note(@_) if (defined(&theme_ui_note));
my ($text) = @_;
return " ⓘ ".
"$text";
}
=head2 ui_brh()
Returns a break line with ability to style height
=cut
sub ui_brh
{
return &theme_ui_brh() if (defined(&theme_ui_brh));
return " \n";
}
# ui_tag_start(tag, [attrs], [no-new-line])
# Function to create an opening HTML tag with optional attributes.
# Attributes are passed as a hash reference and its values are quote escaped.
sub ui_tag_start
{
return theme_ui_tag_start(@_) if (defined(&theme_ui_tag_start));
my ($tag, $attrs, $nnl) = @_;
# Ensure every tag gets a proper marker class
$attrs ||= {};
$attrs->{'class'} = defined($attrs->{class})
? "ui--$tag $attrs->{class}"
: "ui--$tag";
# Start building tag
my $rv = "<$tag";
# Add attributes if provided
if ($attrs && ref($attrs) eq 'HASH') {
foreach my $key (keys %$attrs) {
my $value = $attrs->{$key};
if (defined($value)) {
$value = "e_escape($value, '"');
$value =~ tr/\n\t//d;
$value =~ s/\s+/ /g;
$rv .= " $key=\"$value\"" ;
}
elsif ($key) {
$rv .= " $key";
}
}
}
# Close the opening tag
$rv .= $nnl ? ">" : ">\n";
# Handle special case for tag
$rv = "\n$rv" if ($tag eq 'html');
return $rv;
}
# ui_tag_content(content)
# Function to handle the content of an HTML tag.
sub ui_tag_content
{
return theme_ui_tag_content(@_) if (defined(&theme_ui_tag_content));
my ($content) = @_;
my $rv;
$rv = $content if (defined($content));
return $rv;
}
# ui_tag_end(tag)
# Function to create a closing HTML tag.
sub ui_tag_end
{
return theme_ui_tag_end(@_) if (defined(&theme_ui_tag_end));
my ($tag) = @_;
return "$tag>\n";
}
# ui_tag(tag, [content], [attrs])
# Function to create a complete HTML tag with optional content and attributes.
sub ui_tag
{
return theme_ui_tag(@_) if (defined(&theme_ui_tag));
my ($tag, $content, $attrs) = @_;
my $rv = ui_tag_start($tag, $attrs, !defined($content));
$rv .= ui_tag_content($content) if (defined($content));
my %void_tags = map { $_ => 1 }
qw(
area base br col embed hr img input link
meta param source track wbr
);
$rv .= ui_tag_end($tag) if (!exists($void_tags{lc($tag)}));
return $rv;
}
# ui_alert(content, type, [icon], [attrs])
# Generates an HTML alert with the specified content, type, and optional icon
# and attributes.
#
# Parameters:
# content - The main message/body of the alert
# type - Alert style: "success", "info", "warning", "danger", "danger-fatal"
# icon - Optional. Controls icon and title display:
# - If undefined: uses default icon and title for the alert type
# - If string: uses as icon class with default title
# - If array ref [icon, title, no_break]:
# - icon: Icon class
# - title: Custom title (if undef, uses default for type)
# - no_break: If 1, no line break after title (space instead)
# attrs - Optional hash ref of additional HTML attributes for the alert div
#
# Examples:
# ui_alert("Operation completed", "success");
# ui_alert("Access denied", "danger", "fa-lock");
# ui_alert("Settings changed", "info", ["fa-info-circle", "", 1]);
# ui_alert("Server offline", "warning", undef, {id => "server-status"});
sub ui_alert
{
return theme_ui_alert(@_) if (defined(&theme_ui_alert));
my ($content, $type, $icon, $attrs) = @_;
# Default alert type
$type ||= 'info';
# Default icons and titles based on type
my %type_defaults = (
'success' => {
'icon' => 'fa-check-circle',
'title' => $text{'ui_success'}
},
'info' => {
'icon' => 'fa-info-circle',
'title' => $text{'ui_info'}
},
'warning' => {
'icon' => 'fa-exclamation-triangle',
'title' => $text{'ui_warning'}
},
'danger' => {
'icon' => 'fa-bolt',
'title' => $text{'ui_error'}
},
'danger-fatal' => {
'icon' => 'fa-exclamation-triangle',
'title' => $text{'ui_error_fatal'}
}
);
my $use_icon = '';
my $use_title = '';
my $use_br = 1; # Default to using line break
# Process icon parameter
if (!defined($icon)) {
# Use defaults based on type
if ($type_defaults{$type}) {
$use_icon = $type_defaults{$type}{'icon'};
$use_title = $type_defaults{$type}{'title'};
}
}
elsif (ref($icon)) {
# Array format [icon_class, title, no_br]
if (defined($icon->[0])) {
$use_icon = $icon->[0];
}
else {
$use_icon = $type_defaults{$type}{'icon'};
}
# Title: if provided use it, else use default for type
if (defined($icon->[1])) {
$use_title = $icon->[1];
}
elsif ($type_defaults{$type}) {
$use_title = $type_defaults{$type}{'title'};
}
# Line break flag: 1 = no break, anything else = break
$use_br = $icon->[2] ? 0 : 1 if (defined($icon->[2]));
}
else {
# String format: just the icon class
$use_icon = $icon;
$use_title = $type_defaults{$type} ? $type_defaults{$type}{'title'} : '';
}
# Prepare attributes for the alert div
my $all_attrs = $attrs || {};
# Add alert class
my $class = 'alert';
$class .= ' alert-'.$type if ($type);
$all_attrs->{'class'} = $all_attrs->{'class'}
? "$class $all_attrs->{'class'}"
: $class;
# Build alert
my $rv = '';
# Start alert container
$rv .= ui_tag_start('div', $all_attrs);
# Add icon and title if either is available
if ($use_icon || $use_title) {
# Add icon if available
if ($use_icon) {
$rv .= ui_tag('i', undef, { 'class' => "fa fa-fw $use_icon" });
$rv .= ' ';
}
# Add title if available
if ($use_title) {
$rv .= ui_tag('strong', $use_title);
}
# Add line break if needed
if ($use_br) {
$rv .= ' ';
}
else {
$rv .= ' ';
}
$rv .= "\n";
}
# Add main content
$rv .= ui_tag_content(ui_tag('span', $content));
# Close alert container
$rv .= ui_tag_end('div');
return $rv;
}
# ui_button_icon(text, icon, [attrs])
# Creates a button with an icon and text
# Parameters:
# text - The text/label for the button
# icon - Icon class
# attrs - Optional hash ref of additional HTML attributes
#
# Examples:
# ui_button_icon("Save", "save", {class => "primary"})
# ui_button_icon("Delete", "trash", {type => "submit", name => "delete"})
sub ui_button_icon
{
return theme_ui_button_icon(@_) if (defined(&theme_ui_button_icon));
my ($text, $icon, $attrs) = @_;
# Default to button type if not specified
my $all_attrs = $attrs || {};
$all_attrs->{'type'} ||= 'button';
# Button class
my $btn_cls = $all_attrs->{'class'};
$all_attrs->{'class'} = "btn " . ($btn_cls
? ($btn_cls =~ /^btn-/ ? $btn_cls
: "btn-$btn_cls") : 'btn-default');
# Build the button
my $rv = ui_tag_start('button', $all_attrs);
# Add icon if specified
if ($icon) {
my $icon_class = "";
# Check if icon specifies a specific bundle (fa2)
if ($icon =~ /^fa2-/) {
$icon_class = "fa2 $icon";
}
# Check if it already has fa- prefix
elsif ($icon =~ /^fa-/) {
$icon_class = "fa $icon";
}
# Otherwise add the default fa- prefix
else {
$icon_class = "fa fa-$icon";
}
$rv .= ui_tag('i', undef, {'class' => $icon_class});
$rv .= " ";
}
# Add text
$rv .= ui_tag_content($text) if defined($text);
# Close the button
$rv .= ui_tag_end('button');
return $rv;
}
# ui_link_icon(href, text, [icon], [attrs])
# Creates a link with an icon and text
# Parameters:
# href - The URL for the link
# text - The text/label for the link
# icon - Icon class
# attrs - Optional hash ref of additional HTML attributes
#
# Examples:
# ui_link_icon("view.cgi?id=1", "View Details", "eye", {class => "primary"})
# ui_link_icon("docs.html", "Documentation", "book", {target => "_blank"})
sub ui_link_icon
{
return theme_ui_link_icon(@_) if (defined(&theme_ui_link_icon));
my ($href, $text, $icon, $attrs) = @_;
# Create attribute hash and set href
my $all_attrs = $attrs || {};
$all_attrs->{'href'} = $href if (defined($href));
# Button class
my $btn_cls = $all_attrs->{'class'};
$all_attrs->{'class'} = "btn " . ($btn_cls
? ($btn_cls =~ /^btn-/ ? $btn_cls
: "btn-$btn_cls") : 'btn-default');
# Build the link
my $rv = ui_tag_start('a', $all_attrs);
# Add icon if specified
if ($icon) {
my $icon_class = "";
# Check if icon specifies a specific bundle (fa2)
if ($icon =~ /^fa2-/) {
$icon_class = "fa2 $icon";
}
# Check if it already has fa- prefix
elsif ($icon =~ /^fa-/) {
$icon_class = "fa $icon";
}
# Otherwise add the default fa- prefix
else {
$icon_class = "fa fa-$icon";
}
$rv .= ui_tag('i', undef, {'class' => $icon_class});
$rv .= " ";
}
# Add text
$rv .= ui_tag_content($text) if (defined($text));
# Close the link
$rv .= ui_tag_end('a');
return $rv;
}
# ui_icon(icon, [attrs])
# Creates an icon element
# Parameters:
# icon - Icon class (with or without fa- prefix)
# attrs - Optional hash ref of additional HTML attributes
#
# Examples:
# ui_icon("search") # Standard icon
# ui_icon("fa2-warning") # Extended icon set
sub ui_icon
{
return theme_ui_icon(@_) if (defined(&theme_ui_icon));
my ($icon, $attrs) = @_;
return "" if (!defined($icon)) || $icon eq '';
# Create attribute hash
my $all_attrs = $attrs || {};
# Process icon class
my $icon_class = "";
# Check if icon is in a specific bundle
if ($icon =~ /^fa2-/) {
$icon_class = "fa2 $icon";
}
elsif ($icon =~ /^fa-/) {
$icon_class = "fa $icon";
}
else {
$icon_class = "fa fa-$icon";
}
# Make icon always fixed width unless specified otherwise
$icon_class .= " fa-fw" if ($all_attrs->{'class'} !~ /fa-dw/);
# Add icon class to any existing classes
if ($all_attrs->{'class'}) {
$all_attrs->{'class'} .= " $icon_class";
} else {
$all_attrs->{'class'} = $icon_class;
}
# Build the icon tag
return ui_tag('i', undef, $all_attrs);
}
# ui_br([attrs])
# Creates a line break element
sub ui_br
{
return theme_ui_br(@_) if (defined(&theme_ui_br));
my ($attrs) = @_;
return ui_tag('br', undef, $attrs);
}
# ui_p(content, [attrs])
# Creates a paragraph element with optional content
sub ui_p
{
return theme_ui_p(@_) if (defined(&theme_ui_p));
my ($content, $attrs) = @_;
return ui_tag('p', $content, $attrs);
}
=head2 ui_text_mask(text, [tag], [extra_class])
Returns an HTML string with the given text hidden inside a tag that only shows
on hover. If a second parameter is given, it is used as the outer tag that
triggers the hover (default is "td"). If a third parameter is provided,
it is added as an extra class to both the tag and its style.
=cut
sub ui_text_mask
{
return &theme_ui_text_mask(@_) if (defined(&theme_ui_text_mask));
my ($text, $tag, $extra_class) = @_;
my $class = 'hover-mask';
my $classcss = ".$class";
if ($extra_class) {
$class .= " $extra_class";
$classcss .= ".$extra_class";
}
$tag ||= 'td';
my $style_content = <<"CSS";
x-ui-text-mask${classcss} {
position: relative;
display: inline-block;
color: transparent;
transition:color .25s ease;
}
x-ui-text-mask${classcss}::after{
content: attr(data-mask);
position: absolute;
inset: 0;
color: var(--ui-password-mask-color, #000);
pointer-events: none;
transition: opacity .25s ease;
}
$tag:has(>*>x-ui-text-mask${classcss}):hover x-ui-text-mask${classcss},
$tag:has(>x-ui-text-mask${classcss}):hover x-ui-text-mask${classcss}{
color: inherit;
}
$tag:has(>*>x-ui-text-mask${classcss}):hover x-ui-text-mask${classcss}::after,
$tag:has(>x-ui-text-mask${classcss}):hover x-ui-text-mask${classcss}::after{
opacity: 0;
}
CSS
my $rv = '';
$rv .= &ui_tag('style', $style_content, { type => 'text/css' })
if (!$main::ui_text_mask_donecss->{"$tag$class"}++);
$rv .= &ui_tag('x-ui-text-mask', $text,
{ 'class' => $class, 'data-mask' => '••••••••' });
return $rv;
}
1;
|