Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | 1x 1x 6x 2x 4x 2x 2x 1x 9x 9x 3x 3x 6x 4x 2x 1x 1x 4x 1x 3x 3x 3x 1x 2x 1x 3x 1x 2x 1x 1x 1x 1x 7x 5x 5x 3x 3x 3x 2x 5x 2x 1x 1x | import { isValidDate } from '@/utils/formatting';
/**
* Pure helpers for ClickToFilter; kept separate from the component so the
* URL and date logic is unit-testable without the hook/store dependencies.
*/
const URL_FILTERS = [ 'page_url', 'referrer' ];
/**
* Whether this filter value can be opened as an external link.
*
* @param filter The filter type.
* @param filterValue The value to filter by.
* @return True when the value represents an openable URL.
*/
export const isExternalLinkable = (
filter: string,
filterValue: string | undefined | null
): boolean => {
if ( ! filterValue ) {
return false;
}
if ( URL_FILTERS.includes( filter ) ) {
return true;
}
return /^https?:\/\/.+/i.test( filterValue );
};
/**
* Normalize a date input to yyyy-MM-dd.
*
* Accepts a Unix timestamp in seconds or milliseconds, or a string already
* in yyyy-MM-dd format. Returns an empty string for anything else.
*
* @param value The raw date input.
* @return The date as yyyy-MM-dd, or '' when unrecognized.
*/
export const normalizeToIsoDate = ( value: string | number ): string => {
const raw = String( value );
if ( /^\d+$/.test( raw ) ) {
// Unix timestamp (10 digits) or Unix in milliseconds (13 digits)
const unixTime = 10 === raw.length ? Number( raw ) * 1000 : Number( raw );
return new Date( unixTime ).toISOString().split( 'T' )[0];
}
if ( /\d{4}-\d{2}-\d{2}/.test( raw ) ) {
return raw;
}
return '';
};
/**
* Whether this filter type exists in the filter configuration.
*
* @param filter The filter type.
* @param filtersConf The filter configuration keyed by filter type.
* @return True when the filter can be applied.
*/
export const isConfiguredFilter = (
filter: string | undefined,
filtersConf: Record< string, unknown > | undefined
): boolean =>
Boolean(
filter &&
filtersConf &&
Object.prototype.hasOwnProperty.call( filtersConf, filter )
);
/**
* Resolve a start/end date pair to apply alongside a filter.
*
* The end date defaults to today. Returns null when no start date is given
* or when either date cannot be normalized to a valid yyyy-MM-dd date.
*
* @param startDate The raw start date input.
* @param endDate The raw end date input.
* @return The normalized range, or null when it should not be applied.
*/
export const resolveDateRange = (
startDate: string | number | undefined,
endDate: string | number | undefined
): { startDate: string; endDate: string } | null => {
if ( ! startDate ) {
return null;
}
const start = normalizeToIsoDate( startDate );
// Default to today if no end date is provided.
const end = endDate ?
normalizeToIsoDate( endDate ) :
new Date().toISOString().split( 'T' )[0];
if ( ! isValidDate( start ) || ! isValidDate( end ) ) {
return null;
}
return { startDate: start, endDate: end };
};
type ActiveFilters = Record< string, string | undefined >;
interface ExternalUrlArgs {
filter: string;
filterValue: string;
row?: Record< string, unknown > | null;
filterByDomain?: unknown;
getActiveFilters: () => ActiveFilters;
}
/**
* Resolve the host to link to when filtering by domain: prefer the row's
* own host, fall back to an active host filter (stripping a leading
* exclusion marker), or null when neither is set.
*/
const resolveFilterHost = (
row: Record< string, unknown > | null | undefined,
activeFilters: ActiveFilters
): string | null => {
if ( row && Object.prototype.hasOwnProperty.call( row, 'host' ) ) {
return String( row.host );
}
if ( Object.prototype.hasOwnProperty.call( activeFilters, 'host' ) ) {
const host = activeFilters.host;
return host?.replace?.( /^!/, '' ) ?? String( host );
}
return null;
};
/**
* Build the URL to open for an external-linkable filter value.
*
* Relative page URLs are prefixed with the site URL — or, when filtering by
* domain, with the host from the row or the active host filter. Referrers
* without a scheme are assumed to be https.
*
* @param args The filter context.
* @return The URL to open.
*/
export const buildExternalUrl = ({
filter,
filterValue,
row,
filterByDomain,
getActiveFilters
}: ExternalUrlArgs ): string => {
if ( 'page_url' === filter && ! filterValue.startsWith( 'http' ) ) {
// Get the site URL from window.burst_settings if available, otherwise use current origin
let siteUrl = window.burst_settings?.site_url || window.location.origin;
if ( filterByDomain ) {
const protocol =
-1 !== siteUrl.indexOf( 'https:' ) ? 'https://' : 'http://';
const host = resolveFilterHost( row, getActiveFilters() );
if ( null !== host ) {
siteUrl = `${protocol}${host}`;
}
}
return `${siteUrl}${filterValue.startsWith( '/' ) ? '' : '/'}${filterValue}`;
}
if ( 'referrer' === filter && ! filterValue.startsWith( 'http' ) ) {
// Assuming https always.
return `https://${filterValue}`;
}
return filterValue;
};
|