Feat: 上下移动条目功能(非拖拽
This commit is contained in:
parent
71424c8770
commit
cd4dd441a4
|
|
@ -118,7 +118,11 @@ export function insertEntrySibling(
|
|||
): StructureNode {
|
||||
const cloned = cloneNode(root);
|
||||
const info = findParentAndIndex(cloned, targetPath);
|
||||
if (!info) return cloned;
|
||||
if (!info) {
|
||||
console.log("未能找到父节点", targetPath);
|
||||
return cloned;
|
||||
}
|
||||
|
||||
const { parent, index } = info;
|
||||
if (!parent.children) parent.children = [];
|
||||
const exists = parent.children.some((c) => c.key === newKey);
|
||||
|
|
@ -153,4 +157,22 @@ export function renameEntryAtPath(root: StructureNode, path: string, newKey: str
|
|||
return { root: cloned, newPath: segments.join(".") };
|
||||
}
|
||||
|
||||
export function moveEntryByOffset(root: StructureNode, path: string, offset: number): StructureNode {
|
||||
const cloned = cloneNode(root);
|
||||
if (!offset) return cloned;
|
||||
const info = findParentAndIndex(cloned, path);
|
||||
if (!info) return cloned;
|
||||
const { parent, index } = info;
|
||||
const siblings = parent.children ?? [];
|
||||
if (siblings.length <= 1) return cloned;
|
||||
const nextIndex = Math.max(0, Math.min(siblings.length - 1, index + offset));
|
||||
if (nextIndex === index) return cloned;
|
||||
const [node] = siblings.splice(index, 1);
|
||||
siblings.splice(nextIndex, 0, node);
|
||||
|
||||
console.log("cloned", cloned);
|
||||
|
||||
return cloned;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import {
|
|||
} from "@/lib/db";
|
||||
import { ArrowBigDownDash, ArrowBigUpDash, ArrowLeft, Brackets, CaseSensitive, Download, Filter, Languages, LocateFixed, MoreVertical, PencilLine, Reply, Save, Settings, Trash2 } from "lucide-react";
|
||||
import { useTranslationInlineEdit } from "@/hooks/biz/use-translation-inline-edit";
|
||||
import { flattenEntries, type FlatEntry, insertEntrySibling, removeEntryAtPath, renameEntryAtPath } from "@/lib/i18n-structure";
|
||||
import { flattenEntries, type FlatEntry, insertEntrySibling, removeEntryAtPath, renameEntryAtPath, moveEntryByOffset } from "@/lib/i18n-structure";
|
||||
import { ImportLanguageModal } from "@/components/biz/import-language-modal";
|
||||
import { ExportLanguageModal } from "@/components/biz/export-language-modal";
|
||||
import { EntryNameModal } from "@/components/biz/entry-name-modal";
|
||||
|
|
@ -64,6 +64,8 @@ export default function Editor() {
|
|||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
const [visibleLangs, setVisibleLangs] = useState<Set<string>>(new Set());
|
||||
const [moveCountUp, setMoveCountUp] = useState(1);
|
||||
const [moveCountDown, setMoveCountDown] = useState(1);
|
||||
const [savingAll, setSavingAll] = useState(false);
|
||||
|
||||
const { copy } = useClipboard();
|
||||
|
|
@ -94,6 +96,22 @@ export default function Editor() {
|
|||
requestAnimationFrame(() => tryFindAndAnimate(0));
|
||||
}
|
||||
|
||||
const handleMove = useCallback(async (path: string, offset: number) => {
|
||||
if (!projectId || !structure) return;
|
||||
try {
|
||||
const nextRoot = moveEntryByOffset(structure.root, path, offset);
|
||||
await upsertStructure({ projectId, root: nextRoot });
|
||||
setStructure({ projectId, root: nextRoot });
|
||||
const nextEntries = flattenEntries(nextRoot);
|
||||
const idx = nextEntries.findIndex((e) => e.path === path);
|
||||
if (idx >= 0) {
|
||||
virtuosoRef.current?.scrollIntoView({ index: idx, align: "center", done: () => highlightRow(idx) });
|
||||
}
|
||||
} catch (e) {
|
||||
setPageError((e as Error)?.message ?? "移动失败");
|
||||
}
|
||||
}, [projectId, structure]);
|
||||
|
||||
function scrollToQuery(ev: React.FormEvent<HTMLFormElement>) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
|
@ -317,13 +335,13 @@ export default function Editor() {
|
|||
);
|
||||
})}
|
||||
<td className="sticky right-0 px-3 py-2 align-top bg-white">
|
||||
<DropdownMenu>
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="sm" variant="outline">
|
||||
<MoreVertical />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-44">
|
||||
<DropdownMenuContent align="end" className="w-64">
|
||||
<DropdownMenuItem onClick={() => setAddModal({ open: true, path: entry.path, position: "below" })}>
|
||||
<ArrowBigDownDash />
|
||||
在下面新增
|
||||
|
|
@ -342,6 +360,52 @@ export default function Editor() {
|
|||
<PencilLine />
|
||||
重命名
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
handleMove(entry.path, -moveCountUp)
|
||||
}}
|
||||
>
|
||||
<ArrowBigUpDash />
|
||||
<span className="whitespace-nowrap">向上移动</span>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={moveCountUp}
|
||||
onChange={(e) => {
|
||||
const n = parseInt(e.target.value, 10);
|
||||
setMoveCountUp(Number.isFinite(n) && n > 0 ? n : 1);
|
||||
}}
|
||||
className="h-7 w-16"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
个条目
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onSelect={() => handleMove(entry.path, moveCountDown)}
|
||||
>
|
||||
<ArrowBigDownDash />
|
||||
<span className="whitespace-nowrap">向下移动</span>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={moveCountDown}
|
||||
onChange={(e) => {
|
||||
const n = parseInt(e.target.value, 10);
|
||||
setMoveCountDown(Number.isFinite(n) && n > 0 ? n : 1);
|
||||
}}
|
||||
className="h-7 w-16"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
个条目
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
onClick={async () => {
|
||||
|
|
@ -374,7 +438,7 @@ export default function Editor() {
|
|||
</td>
|
||||
</>
|
||||
);
|
||||
}, [displayedLanguages, inline, projectId, structure, setStructure, setValuesByLang, copy, selected]);
|
||||
}, [displayedLanguages, inline, projectId, structure, setStructure, setValuesByLang, copy, selected, moveCountUp, moveCountDown, handleMove]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectId || languages.length === 0) return;
|
||||
|
|
|
|||
Loading…
Reference in New Issue