Skip to content

Commit de870eb

Browse files
fix: Hyperlinks do not work after review
1 parent 9a07d6c commit de870eb

8 files changed

Lines changed: 166 additions & 256 deletions

File tree

frontend/src/core/components/tools/FullscreenToolList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from "@app/data/toolsTaxonomy";
1010
import { ToolId } from "@app/types/toolId";
1111
import { useToolSections } from "@app/hooks/useToolSections";
12-
import { openUrl } from "@app/utils/urlUtils";
12+
import { openUrl } from "@app/utils/urlExtensions";
1313
import NoToolsFound from "@app/components/tools/shared/NoToolsFound";
1414
import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
1515
import StarRoundedIcon from "@mui/icons-material/StarRounded";

frontend/src/core/components/tools/toolPicker/ToolButton.tsx

Lines changed: 96 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import { ToolIcon } from "@app/components/shared/ToolIcon";
66
import { ToolRegistryEntry } from "@app/data/toolsTaxonomy";
77
import { useToolNavigation } from "@app/hooks/useToolNavigation";
88
import { handleUnlessSpecialClick } from "@app/utils/clickHandlers";
9-
import { openUrl } from "@app/utils/urlUtils";
9+
import { openUrl } from "@app/utils/urlExtensions";
1010
import FitText from "@app/components/shared/FitText";
1111
import { useHotkeys } from "@app/contexts/HotkeyContext";
1212
import HotkeyDisplay from "@app/components/hotkeys/HotkeyDisplay";
13-
import FavoriteStar from "@app/components/tools/toolpicker/FavoriteStar";
13+
import FavoriteStar from "@app/components/tools/toolPicker/FavoriteStar";
1414
import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
1515
import type { ToolId } from "@app/types/toolId";
1616
import {
@@ -28,45 +28,27 @@ interface ToolButtonProps {
2828
onSelect: (id: ToolId) => void;
2929
rounded?: boolean;
3030
disableNavigation?: boolean;
31+
onUnavailableClick?: () => void;
3132
matchedSynonym?: string;
3233
hasStars?: boolean;
33-
/** Called when an unavailable tool is clicked; if provided, overrides the default no-op */
34-
onUnavailableClick?: () => void;
3534
}
3635

3736
const ToolButton: React.FC<ToolButtonProps> = ({
3837
id,
3938
tool,
4039
isSelected,
4140
onSelect,
41+
rounded = false,
4242
disableNavigation = false,
43+
onUnavailableClick,
4344
matchedSynonym,
4445
hasStars = false,
45-
onUnavailableClick,
4646
}) => {
4747
const { t } = useTranslation();
48+
const { getToolNavigation } = useToolNavigation();
49+
const { toolAvailability } = useToolWorkflow();
4850
const { config } = useAppConfig();
4951
const premiumEnabled = config?.premiumEnabled;
50-
const { isFavorite, toggleFavorite, toolAvailability } = useToolWorkflow();
51-
const disabledReason = getToolDisabledReason(
52-
id,
53-
tool,
54-
toolAvailability,
55-
premiumEnabled,
56-
);
57-
const isUnavailable = disabledReason !== null;
58-
// If onUnavailableClick is provided for a non-comingSoon tool, render as "cloud-available":
59-
// full opacity, cloud badge, normal tooltip — clicking still fires onUnavailableClick (e.g. sign-in).
60-
const showAsCloudAvailable =
61-
isUnavailable &&
62-
!!onUnavailableClick &&
63-
disabledReason !== "comingSoon" &&
64-
disabledReason !== "selfHostedOffline";
65-
const visuallyUnavailable = isUnavailable && !showAsCloudAvailable;
66-
const { hotkeys } = useHotkeys();
67-
const binding = hotkeys[id];
68-
const { getToolNavigation } = useToolNavigation();
69-
const fav = isFavorite(id as ToolId);
7052

7153
// Check if this tool will route to SaaS backend (desktop only)
7254
const rawEndpoint = tool.operationConfig?.endpoint;
@@ -94,109 +76,7 @@ const ToolButton: React.FC<ToolButtonProps> = ({
9476
? getToolNavigation(id, tool)
9577
: null;
9678

97-
const { key: disabledKey, fallback: disabledFallback } =
98-
getDisabledLabel(disabledReason);
99-
const disabledMessage = t(disabledKey, disabledFallback);
100-
101-
const tooltipContent = visuallyUnavailable ? (
102-
<span>
103-
<strong>{disabledMessage}</strong> {tool.description}
104-
</span>
105-
) : (
106-
<div style={{ display: "flex", flexDirection: "column", gap: "0.35rem" }}>
107-
<span>{tool.description}</span>
108-
<div
109-
style={{
110-
display: "flex",
111-
alignItems: "center",
112-
gap: "0.5rem",
113-
fontSize: "0.75rem",
114-
}}
115-
>
116-
{binding ? (
117-
<>
118-
<span
119-
style={{ color: "var(--mantine-color-dimmed)", fontWeight: 500 }}
120-
>
121-
{t("settings.hotkeys.shortcut", "Shortcut")}
122-
</span>
123-
<HotkeyDisplay binding={binding} />
124-
</>
125-
) : (
126-
<span
127-
style={{
128-
color: "var(--mantine-color-dimmed)",
129-
fontWeight: 500,
130-
fontStyle: "italic",
131-
}}
132-
>
133-
{t("settings.hotkeys.noShortcut", "No shortcut set")}
134-
</span>
135-
)}
136-
</div>
137-
</div>
138-
);
139-
140-
const buttonContent = (
141-
<>
142-
<ToolIcon icon={tool.icon} opacity={visuallyUnavailable ? 0.25 : 1} />
143-
<div
144-
style={{
145-
display: "flex",
146-
flexDirection: "column",
147-
alignItems: "flex-start",
148-
flex: 1,
149-
overflow: "visible",
150-
}}
151-
>
152-
<div
153-
style={{
154-
display: "flex",
155-
alignItems: "center",
156-
gap: "0.5rem",
157-
width: "100%",
158-
}}
159-
>
160-
<FitText
161-
text={tool.name}
162-
lines={1}
163-
minimumFontScale={0.8}
164-
as="span"
165-
style={{
166-
display: "inline-block",
167-
maxWidth: "100%",
168-
opacity: visuallyUnavailable ? 0.25 : 1,
169-
}}
170-
/>
171-
{tool.versionStatus === "alpha" && (
172-
<Badge
173-
size="xs"
174-
variant="light"
175-
color="orange"
176-
style={{ flexShrink: 0, opacity: visuallyUnavailable ? 0.25 : 1 }}
177-
>
178-
{t("toolPanel.alpha", "Alpha")}
179-
</Badge>
180-
)}
181-
{usesCloud && !visuallyUnavailable && <CloudBadge />}
182-
</div>
183-
{matchedSynonym && (
184-
<span
185-
style={{
186-
fontSize: "0.75rem",
187-
color: "var(--mantine-color-dimmed)",
188-
opacity: visuallyUnavailable ? 0.25 : 1,
189-
marginTop: "1px",
190-
overflow: "visible",
191-
whiteSpace: "nowrap",
192-
}}
193-
>
194-
{matchedSynonym}
195-
</span>
196-
)}
197-
</div>
198-
</>
199-
);
79+
const isUnavailable = toolAvailability?.[id] === "unavailable";
20080

20181
const handleExternalClick = (e: React.MouseEvent) => {
20282
handleUnlessSpecialClick(e, () => handleClick(id));
@@ -211,96 +91,119 @@ const ToolButton: React.FC<ToolButtonProps> = ({
21191
variant={isSelected ? "filled" : "subtle"}
21292
size="sm"
21393
radius="md"
214-
fullWidth
215-
justify="flex-start"
216-
className="tool-button"
217-
data-tour={`tool-button-${id}`}
218-
styles={{
219-
root: {
220-
borderRadius: 0,
221-
color: "var(--tools-text-and-icon-color)",
222-
overflow: "visible",
223-
},
224-
label: { overflow: "visible" },
225-
}}
94+
className={`tool-button ${rounded ? "tool-button--rounded" : ""} ${
95+
isSelected ? "tool-button--selected" : ""
96+
} ${isUnavailable ? "tool-button--unavailable" : ""}`}
97+
title={tool.name}
98+
data-tool-id={id}
99+
disabled={isUnavailable}
100+
aria-label={tool.name}
226101
>
227-
{buttonContent}
102+
<div className="tool-button-content">
103+
<ToolIcon icon={tool.icon} size={20} />
104+
<FitText
105+
text={matchedSynonym || tool.name}
106+
className="tool-button-label"
107+
maxLines={1}
108+
/>
109+
{hasStars && <FavoriteStar isFavorite={false} onToggle={() => {}} />}
110+
{usesCloud && <CloudBadge />}
111+
{isUnavailable && (
112+
<Badge
113+
size="xs"
114+
variant="light"
115+
color="gray"
116+
className="tool-button-unavailable-badge"
117+
>
118+
{t("common.unavailable", "Unavailable")}
119+
</Badge>
120+
)}
121+
</div>
228122
</Button>
229123
) : tool.link && !isUnavailable ? (
230124
// For external links, render Button as an anchor with proper href
231125
<Button
232126
component="a"
233127
href={tool.link}
234-
target="_blank"
235-
rel="noopener noreferrer"
236128
onClick={handleExternalClick}
237129
variant={isSelected ? "filled" : "subtle"}
238130
size="sm"
239131
radius="md"
240-
fullWidth
241-
justify="flex-start"
242-
className="tool-button"
243-
data-tour={`tool-button-${id}`}
244-
styles={{
245-
root: {
246-
borderRadius: 0,
247-
color: "var(--tools-text-and-icon-color)",
248-
overflow: "visible",
249-
},
250-
label: { overflow: "visible" },
251-
}}
132+
className={`tool-button ${rounded ? "tool-button--rounded" : ""} ${
133+
isSelected ? "tool-button--selected" : ""
134+
}`}
135+
title={tool.name}
136+
data-tool-id={id}
137+
target="_blank"
138+
rel="noopener noreferrer"
139+
aria-label={tool.name}
252140
>
253-
{buttonContent}
141+
<div className="tool-button-content">
142+
<ToolIcon icon={tool.icon} size={20} />
143+
<FitText
144+
text={matchedSynonym || tool.name}
145+
className="tool-button-label"
146+
maxLines={1}
147+
/>
148+
{hasStars && <FavoriteStar isFavorite={false} onToggle={() => {}} />}
149+
</div>
254150
</Button>
255151
) : (
256-
// For unavailable tools, use regular button
152+
// For normal tools without URLs
257153
<Button
258154
variant={isSelected ? "filled" : "subtle"}
259155
onClick={() => handleClick(id)}
260156
size="sm"
261157
radius="md"
262-
fullWidth
263-
justify="flex-start"
264-
className="tool-button"
265-
aria-disabled={isUnavailable}
266-
data-tour={`tool-button-${id}`}
267-
styles={{
268-
root: {
269-
borderRadius: 0,
270-
color: "var(--tools-text-and-icon-color)",
271-
cursor: visuallyUnavailable ? "not-allowed" : undefined,
272-
overflow: "visible",
273-
},
274-
label: { overflow: "visible" },
275-
}}
158+
className={`tool-button ${rounded ? "tool-button--rounded" : ""} ${
159+
isSelected ? "tool-button--selected" : ""
160+
} ${isUnavailable ? "tool-button--unavailable" : ""}`}
161+
title={tool.name}
162+
data-tool-id={id}
163+
disabled={isUnavailable}
164+
aria-label={tool.name}
276165
>
277-
{buttonContent}
166+
<div className="tool-button-content">
167+
<ToolIcon icon={tool.icon} size={20} />
168+
<FitText
169+
text={matchedSynonym || tool.name}
170+
className="tool-button-label"
171+
maxLines={1}
172+
/>
173+
{hasStars && <FavoriteStar isFavorite={false} onToggle={() => {}} />}
174+
{usesCloud && <CloudBadge />}
175+
{isUnavailable && (
176+
<Badge
177+
size="xs"
178+
variant="light"
179+
color="gray"
180+
className="tool-button-unavailable-badge"
181+
>
182+
{t("common.unavailable", "Unavailable")}
183+
</Badge>
184+
)}
185+
</div>
278186
</Button>
279187
);
280188

281-
const star =
282-
hasStars && !visuallyUnavailable ? (
283-
<FavoriteStar
284-
isFavorite={fav}
285-
onToggle={() => toggleFavorite(id as ToolId)}
286-
className="tool-button-star"
287-
size="xs"
288-
/>
289-
) : null;
189+
const unavailableReason = isUnavailable
190+
? getToolDisabledReason(tool, premiumEnabled)
191+
: null;
192+
const disabledLabel = unavailableReason
193+
? getDisabledLabel(unavailableReason)
194+
: null;
290195

291196
return (
292-
<div className="tool-button-container">
293-
{star}
294-
<Tooltip
295-
content={tooltipContent}
296-
position="right"
297-
arrow={true}
298-
delay={500}
299-
>
300-
{buttonElement}
301-
</Tooltip>
302-
</div>
197+
<Tooltip
198+
label={disabledLabel || tool.description}
199+
disabled={!disabledLabel && !tool.description}
200+
position="right"
201+
withArrow
202+
openDelay={500}
203+
>
204+
{buttonElement}
205+
</Tooltip>
303206
);
304207
};
305208

306-
export default ToolButton;
209+
export default ToolButton;

frontend/src/core/components/viewer/BookmarkSidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
import LocalIcon from "@app/components/shared/LocalIcon";
1313
import { useViewer } from "@app/contexts/ViewerContext";
1414
import { PdfBookmarkObject, PdfActionType } from "@embedpdf/models";
15-
import { openUrl } from "@app/utils/urlUtils";
15+
import { openUrl } from "@app/utils/urlExtensions";
1616
import BookmarksIcon from "@mui/icons-material/BookmarksRounded";
1717
import "@app/components/viewer/SidebarBase.css";
1818
import "@app/components/viewer/BookmarkSidebar.css";

frontend/src/core/components/viewer/LinkLayer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
PdfActionType,
88
type PdfLinkAnnoObject,
99
} from "@embedpdf/models";
10-
import { openUrl, isSafeUrlProtocol } from "@app/utils/urlUtils";
10+
import { openUrl, isSafeUrlProtocol } from "@app/utils/urlExtensions";
1111

1212
// ---------------------------------------------------------------------------
1313
// Inline SVG icons (thin-stroke, modern)

frontend/src/core/tools/formFill/FormFieldOverlay.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
useFieldValue,
2525
} from "@app/tools/formFill/FormFillContext";
2626
import { useViewer } from "@app/contexts/ViewerContext";
27-
import { openUrl } from "@app/utils/urlUtils";
27+
import { openUrl } from "@app/utils/urlExtensions";
2828
import type {
2929
FormField,
3030
WidgetCoordinates,

0 commit comments

Comments
 (0)