Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions components/DangerConfirmButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface DangerConfirmButtonProps {
onConfirm: () => void;
disabled?: boolean;
autoCancelMs?: number;
size?: 'normal' | 'compact';
}

export function DangerConfirmButton({
Expand All @@ -18,6 +19,7 @@ export function DangerConfirmButton({
onConfirm,
disabled,
autoCancelMs = 4000,
size = 'normal',
}: DangerConfirmButtonProps) {
const t = useTranslation();
const [armed, setArmed] = useState(false);
Expand Down Expand Up @@ -45,18 +47,20 @@ export function DangerConfirmButton({
onConfirm();
}

const armedPadding = size === 'compact' ? 'px-3 py-1.5 text-xs' : 'px-3 py-2 text-sm';

if (armed) {
return (
<div className="flex gap-2">
<button
onClick={confirm}
className="flex-1 rounded-md cursor-pointer bg-red-600 hover:bg-red-500 text-white py-2 text-sm font-medium"
className={`flex-1 rounded-md cursor-pointer bg-red-600 hover:bg-red-500 text-white font-medium whitespace-nowrap ${armedPadding}`}
>
{confirmLabel}
</button>
<button
onClick={cancel}
className="rounded-md cursor-pointer border border-stone-300 hover:bg-stone-100 dark:border-stone-600 dark:hover:bg-stone-700 px-3 py-2 text-sm"
className={`rounded-md cursor-pointer border border-stone-300 hover:bg-stone-100 dark:border-stone-600 dark:hover:bg-stone-700 whitespace-nowrap ${armedPadding}`}
>
{t('cancel')}
</button>
Expand All @@ -70,6 +74,7 @@ export function DangerConfirmButton({
onClick={arm}
disabled={disabled}
variant="secondary"
size={size}
idleLabel={idleLabel}
/>
);
Expand Down
3 changes: 3 additions & 0 deletions components/HistoryList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export function HistoryList({ items, onDelete }: HistoryListProps) {

return (
<ul className="divide-y divide-stone-200 rounded-md border border-stone-200 dark:divide-stone-700 dark:border-stone-700 max-h-80 overflow-y-auto">
{items.length === 0 && (
<li className="px-3 py-4 text-sm text-stone-500 text-center">{t('noMatchingHistory')}</li>
)}
{items.map((item) => (
<li key={item.id} className="flex items-center justify-between px-3 py-2">
<span className="text-sm truncate max-w-md">{item.title || item.url}</span>
Expand Down
9 changes: 8 additions & 1 deletion components/StatusButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface StatusButtonProps {
doneLabel?: ReactNode;
failedLabel?: ReactNode;
variant?: 'primary' | 'secondary';
size?: 'normal' | 'compact';
className?: string;
}

Expand All @@ -21,6 +22,11 @@ const VARIANT_CLASSES: Record<NonNullable<StatusButtonProps['variant']>, string>
'border border-stone-300 hover:bg-stone-100 dark:border-stone-600 dark:hover:bg-stone-700',
};

const SIZE_CLASSES: Record<NonNullable<StatusButtonProps['size']>, string> = {
normal: 'w-full py-2 text-sm',
compact: 'px-3 py-1.5 text-xs whitespace-nowrap',
};

export function StatusButton({
status,
onClick,
Expand All @@ -30,6 +36,7 @@ export function StatusButton({
doneLabel,
failedLabel,
variant = 'primary',
size = 'normal',
className = '',
}: StatusButtonProps) {
const t = useTranslation();
Expand All @@ -49,7 +56,7 @@ export function StatusButton({
<button
onClick={onClick}
disabled={disabled || status === 'clearing'}
className={`w-full rounded-md cursor-pointer disabled:cursor-not-allowed disabled:opacity-50 py-2 text-sm font-medium ${VARIANT_CLASSES[variant]} ${className}`}
className={`rounded-md cursor-pointer disabled:cursor-not-allowed disabled:opacity-50 font-medium ${SIZE_CLASSES[size]} ${VARIANT_CLASSES[variant]} ${className}`}
>
{label}
</button>
Expand Down
36 changes: 36 additions & 0 deletions components/VisitedSiteList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useTranslation } from '../hooks/useTranslation';
import type { VisitedSite } from '../utils/clearing';

interface VisitedSiteListProps {
sites: VisitedSite[];
busyHostnames: Set<string>;
failedHostnames: Set<string>;
onClear: (site: VisitedSite) => void;
}

export function VisitedSiteList({ sites, busyHostnames, failedHostnames, onClear }: VisitedSiteListProps) {
const t = useTranslation();

return (
<ul className="divide-y divide-stone-200 rounded-md border border-stone-200 dark:divide-stone-700 dark:border-stone-700 max-h-80 overflow-y-auto">
{sites.length === 0 && (
<li className="px-3 py-4 text-sm text-stone-500 text-center">{t('noMatchingSites')}</li>
)}
{sites.map((site) => {
const busy = busyHostnames.has(site.hostname);
return (
<li key={site.hostname} className="flex items-center justify-between px-3 py-2 gap-3">
<span className="text-sm truncate min-w-0">{site.hostname}</span>
<button
onClick={() => onClear(site)}
disabled={busy}
className="rounded-md border border-stone-300 hover:bg-stone-100 dark:border-stone-600 dark:hover:bg-stone-700 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50 px-3 py-1 text-xs whitespace-nowrap"
>
{busy ? t('clearing') : failedHostnames.has(site.hostname) ? t('failed') : t('clear')}
</button>
</li>
);
})}
</ul>
);
}
54 changes: 43 additions & 11 deletions components/options/PerSiteSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import type { Browser } from 'wxt/browser';
import { useTranslation } from '../../hooks/useTranslation';
import { isGeckoBased } from '../../utils/browser-info';
import type { DataTypeDef, DataTypeId } from '../../utils/data-types';
import type { VisitedSite } from '../../utils/clearing';
import { DataTypeGrid } from '../DataTypeGrid';
import { OriginMappingsEditor } from '../OriginMappingsEditor';
import { type ClearStatus } from '../StatusButton';
import { SiteTabsSection } from './SiteTabsSection';
import { VisitedSitesSection } from './VisitedSitesSection';

interface PerSiteSectionProps {
types: DataTypeDef[];
Expand All @@ -26,6 +28,14 @@ interface PerSiteSectionProps {
failedTabIds: Set<number>;
reloadingTabIds: Set<number>;
onClearTab: (tab: Browser.tabs.Tab) => void;
visitedSiteFilter: string;
onVisitedSiteFilterChange: (value: string) => void;
visitedSites: VisitedSite[];
visitedBulkStatus: ClearStatus;
onClearAllVisitedSites: () => void;
busyVisitedHostnames: Set<string>;
failedVisitedHostnames: Set<string>;
onClearVisitedSite: (site: VisitedSite) => void;
}

export function PerSiteSection({
Expand All @@ -47,6 +57,14 @@ export function PerSiteSection({
failedTabIds,
reloadingTabIds,
onClearTab,
visitedSiteFilter,
onVisitedSiteFilterChange,
visitedSites,
visitedBulkStatus,
onClearAllVisitedSites,
busyVisitedHostnames,
failedVisitedHostnames,
onClearVisitedSite,
}: PerSiteSectionProps) {
const t = useTranslation();

Expand Down Expand Up @@ -89,18 +107,32 @@ export function PerSiteSection({
<hr className="border-stone-200 dark:border-stone-700 mb-3" />

{!isGeckoBased() && (
<SiteTabsSection
filter={siteFilter}
onFilterChange={onSiteFilterChange}
status={bulkStatus}
onClearAll={onClearAllTabs}
tabs={tabs}
busyTabIds={busyTabIds}
failedTabIds={failedTabIds}
reloadingTabIds={reloadingTabIds}
onClearTab={onClearTab}
/>
<>
<SiteTabsSection
filter={siteFilter}
onFilterChange={onSiteFilterChange}
status={bulkStatus}
onClearAll={onClearAllTabs}
tabs={tabs}
busyTabIds={busyTabIds}
failedTabIds={failedTabIds}
reloadingTabIds={reloadingTabIds}
onClearTab={onClearTab}
/>
<hr className="border-stone-200 dark:border-stone-700 mb-3 mt-3" />
</>
)}

<VisitedSitesSection
filter={visitedSiteFilter}
onFilterChange={onVisitedSiteFilterChange}
sites={visitedSites}
status={visitedBulkStatus}
onClearAll={onClearAllVisitedSites}
busyHostnames={busyVisitedHostnames}
failedHostnames={failedVisitedHostnames}
onClearSite={onClearVisitedSite}
/>
</section>
);
}
65 changes: 65 additions & 0 deletions components/options/VisitedSitesSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useTranslation } from '../../hooks/useTranslation';
import type { VisitedSite } from '../../utils/clearing';
import { DangerConfirmButton } from '../DangerConfirmButton';
import { type ClearStatus } from '../StatusButton';
import { VisitedSiteList } from '../VisitedSiteList';

interface VisitedSitesSectionProps {
filter: string;
onFilterChange: (value: string) => void;
sites: VisitedSite[];
status: ClearStatus;
onClearAll: () => void;
busyHostnames: Set<string>;
failedHostnames: Set<string>;
onClearSite: (site: VisitedSite) => void;
}

export function VisitedSitesSection({
filter,
onFilterChange,
sites,
status,
onClearAll,
busyHostnames,
failedHostnames,
onClearSite,
}: VisitedSitesSectionProps) {
const t = useTranslation();

return (
<div className="mt-4">
<h3 className="text-sm uppercase tracking-wide text-stone-500 dark:text-stone-400 mb-2">
{t('visitedSites')}
</h3>
<p className="text-xs text-stone-500 mb-2">{t('visitedSitesDescription')}</p>

<div className="flex gap-2 mb-2">
<input
type="text"
value={filter}
onChange={(e) => onFilterChange(e.target.value)}
placeholder={t('filterVisitedSitesPlaceholder')}
className="flex-1 rounded-md border border-stone-300 bg-stone-50 dark:border-stone-600 dark:bg-stone-800 px-3 py-1.5 text-sm placeholder:text-stone-500 focus:outline-none focus:border-blue-500"
/>
<div className="shrink-0">
<DangerConfirmButton
status={status}
idleLabel={t('clearAllCount', { count: sites.length })}
confirmLabel={t('yesClearAllVisited')}
onConfirm={onClearAll}
disabled={sites.length === 0}
size="compact"
/>
</div>
</div>

<VisitedSiteList
sites={sites}
busyHostnames={busyHostnames}
failedHostnames={failedHostnames}
onClear={onClearSite}
/>
</div>
);
}
Loading
Loading