feat: highlight link to other quest or copy item name in quest details

This commit is contained in:
2026-04-25 16:06:06 +02:00
parent a780dd7051
commit 3068b3e352
7 changed files with 186 additions and 44 deletions

View File

@ -53,6 +53,7 @@ export default function GuideView() {
questUrl={selectedQuest.url}
profileId={activeProfileId}
onClose={() => setSelectedQuest(null)}
onSelectQuest={setSelectedQuest}
/>
</div>
);
@ -380,11 +381,6 @@ function QuestRow({ quest, completed, onToggle, onSelect, indent }: {
>
{quest.name}
</span>
{quest.combat_indicators.map((ci, i) => (
<span key={i} style={{ fontSize: "11px", color: "#94a3b8", whiteSpace: "nowrap", flexShrink: 0 }}>
{combatIcon(ci.combat_type)} x{ci.count}
</span>
))}
</div>
{hasPreviewSection && (

View File

@ -1,17 +1,20 @@
import { useState, useEffect } from "react";
import { invoke } from "@tauri-apps/api/core";
import { openUrl } from "@tauri-apps/plugin-opener";
import { QuestStep } from "../types";
import { QuestStep, RichSegment } from "../types";
import { TextWithCoords } from "./TextWithCoords";
const DPLN_BASE = "https://www.dofuspourlesnoobs.com";
interface Props {
questName: string;
questUrl: string | null;
profileId: string;
onClose: () => void;
onSelectQuest?: (quest: { name: string; url: string | null }) => void;
}
export default function QuestDetailPanel({ questName, questUrl, profileId, onClose }: Props) {
export default function QuestDetailPanel({ questName, questUrl, profileId, onClose, onSelectQuest }: Props) {
const [steps, setSteps] = useState<QuestStep[]>([]);
const [completedSteps, setCompletedSteps] = useState<Set<number>>(new Set());
const [expandedSteps, setExpandedSteps] = useState<Set<number>>(new Set());
@ -187,9 +190,6 @@ export default function QuestDetailPanel({ questName, questUrl, profileId, onClo
const lines = step.text.split('\n').filter(l => l.trim().length > 0);
const needsTruncate = lines.length > 4;
const displayText = needsTruncate && !expanded
? lines.slice(0, 4).join('\n')
: step.text;
return (
<div key={step.index} onClick={() => toggleStep(step.index)} style={{
@ -213,7 +213,10 @@ export default function QuestDetailPanel({ questName, questUrl, profileId, onClo
fontSize: "12px", color: "#94a3b8",
lineHeight: 1.6, whiteSpace: "pre-wrap", wordBreak: "break-word",
}}>
<TextWithCoords text={displayText} />
{step.rich_text.length > 0 && (expanded || !needsTruncate)
? <RichText segments={step.rich_text} onSelectQuest={onSelectQuest} />
: <TextWithCoords text={needsTruncate && !expanded ? lines.slice(0, 4).join('\n') : step.text} />
}
</div>
{needsTruncate && (
@ -255,6 +258,42 @@ export default function QuestDetailPanel({ questName, questUrl, profileId, onClo
);
}
function RichText({
segments,
onSelectQuest,
}: {
segments: RichSegment[];
onSelectQuest?: (quest: { name: string; url: string | null }) => void;
}) {
return (
<>
{segments.map((seg, i) => {
if (seg.type === "QuestLink") {
return (
<span
key={i}
onClick={e => {
e.stopPropagation();
onSelectQuest?.({ name: seg.text, url: DPLN_BASE + seg.href });
}}
style={{
color: "#4a9eff",
textDecoration: "underline",
cursor: "pointer",
}}
onMouseEnter={e => (e.currentTarget.style.color = "#93c5fd")}
onMouseLeave={e => (e.currentTarget.style.color = "#4a9eff")}
>
{seg.text}
</span>
);
}
return <TextWithCoords key={i} text={seg.text} />;
})}
</>
);
}
function QuestHeader({ step }: { step: QuestStep }) {
return (
<div style={{