feat: less completions

This commit is contained in:
hypercross 2026-03-01 09:52:53 +08:00
parent a55f55505c
commit c23bff5a89
6 changed files with 45 additions and 17 deletions

View File

@ -39,8 +39,8 @@ export const CommanderInput: Component<CommanderInputProps> = (props) => {
{/* 自动补全下拉框 - 向上弹出 */} {/* 自动补全下拉框 - 向上弹出 */}
<Show when={props.showCompletions() && props.completions().length > 0}> <Show when={props.showCompletions() && props.completions().length > 0}>
<div class="absolute left-0 bottom-full w-full bg-white border border-gray-300 shadow-lg max-h-48 overflow-auto mb-1"> <div class="absolute left-0 bottom-full w-full bg-white border border-gray-300 shadow-lg mb-1">
<For each={props.completions()}>{(comp, idx) => ( <For each={props.completions().slice(0, 3)}>{(comp, idx) => (
<div <div
class={`px-3 py-2 cursor-pointer flex justify-between items-center ${ class={`px-3 py-2 cursor-pointer flex justify-between items-center ${
idx() === props.selectedCompletion() idx() === props.selectedCompletion()
@ -71,6 +71,11 @@ export const CommanderInput: Component<CommanderInputProps> = (props) => {
</Show> </Show>
</div> </div>
)}</For> )}</For>
<Show when={props.completions().length > 3}>
<div class="px-3 py-1 text-xs text-gray-500 bg-gray-50 border-t border-gray-200">
{props.completions().length - 3} ...
</div>
</Show>
</div> </div>
</Show> </Show>
</div> </div>

View File

@ -34,11 +34,14 @@ export const TrackerView: Component<TrackerViewProps> = (props) => {
<For each={props.items()}> <For each={props.items()}>
{(item) => ( {(item) => (
<div class="border border-gray-200 rounded-lg p-3 bg-gray-50"> <div class="border border-gray-200 rounded-lg p-3 bg-gray-50">
{/* 头部tag 和操作按钮 */} {/* 头部tag、id 和操作按钮 */}
<div class="flex items-center justify-between mb-2"> <div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="font-bold text-gray-800">{item.tag}</span> <span class="font-bold text-gray-800">{item.tag}</span>
<span class="text-xs text-gray-500 font-mono">#{item.id.slice(-6)}</span> <Show when={item.id}>
<span class="text-xs text-purple-600 font-mono">#{item.id}</span>
</Show>
<span class="text-xs text-gray-500 font-mono"> {item.uuid.slice(-6)}</span>
</div> </div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<button <button

View File

@ -28,12 +28,13 @@ export const trackCommand: MdCommanderCommand = {
// 直接添加追踪项目 // 直接添加追踪项目
addTrackerItem(parsed); addTrackerItem(parsed);
const idStr = parsed.id ? ` #${parsed.id}` : "";
const classStr = parsed.classes.length > 0 ? `.${parsed.classes.join(".")}` : ""; const classStr = parsed.classes.length > 0 ? `.${parsed.classes.join(".")}` : "";
const attrCount = Object.keys(parsed.attributes).length; const attrCount = Object.keys(parsed.attributes).length;
const attrStr = attrCount > 0 ? `[${attrCount} 个属性]` : ""; const attrStr = attrCount > 0 ? `[${attrCount} 个属性]` : "";
return { return {
message: `已添加追踪项目:${parsed.tag}${classStr}${attrStr}`, message: `已添加追踪项目:${parsed.tag}${idStr}${classStr}${attrStr}`,
type: "success", type: "success",
}; };
}, },
@ -71,7 +72,7 @@ export const listTrackCommand: MdCommanderCommand = {
return { message: "暂无追踪项目", type: "info" }; return { message: "暂无追踪项目", type: "info" };
} }
const list = items.map((i) => `${i.tag}${i.classes.length > 0 ? "." + i.classes.join(".") : ""}`).join("\n"); const list = items.map((i) => `${i.tag}${i.id ? "#" + i.id : ""}${i.classes.length > 0 ? "." + i.classes.join(".") : ""}`).join("\n");
return { message: `追踪项目:\n${list}`, type: "info" }; return { message: `追踪项目:\n${list}`, type: "info" };
}, },
}; };

View File

@ -264,7 +264,7 @@ export function useCommander(
const [entries, setEntries] = createSignal<CommanderEntry[]>([]); const [entries, setEntries] = createSignal<CommanderEntry[]>([]);
const [showCompletions, setShowCompletions] = createSignal(false); const [showCompletions, setShowCompletions] = createSignal(false);
const [completions, setCompletions] = createSignal<CompletionItem[]>([]); const [completions, setCompletions] = createSignal<CompletionItem[]>([]);
const [selectedCompletion, setSelectedCompletion] = createSignal(0); const [selectedCompletion, setSelectedCompletionState] = createSignal(0);
const [isFocused, setIsFocused] = createSignal(false); const [isFocused, setIsFocused] = createSignal(false);
// 命令历史 // 命令历史
@ -335,12 +335,30 @@ export function useCommander(
const comps = getCompletions(input, commands); const comps = getCompletions(input, commands);
setCompletions(comps); setCompletions(comps);
setShowCompletions(comps.length > 0 && isFocused()); setShowCompletions(comps.length > 0 && isFocused());
setSelectedCompletion(0); setSelectedCompletionState(0);
};
const setSelectedCompletion = (v: number | ((prev: number) => number)) => {
const comps = completions();
const maxVisible = 3;
const maxIdx = Math.min(comps.length - 1, maxVisible - 1);
if (typeof v === 'function') {
setSelectedCompletionState(prev => {
const next = v(prev);
return Math.max(0, Math.min(next, maxIdx));
});
} else {
setSelectedCompletionState(Math.max(0, Math.min(v, maxIdx)));
}
}; };
const acceptCompletion = () => { const acceptCompletion = () => {
const idx = selectedCompletion(); const idx = selectedCompletion();
const comp = completions()[idx]; const comps = completions();
// 确保索引在有效范围内
const validIdx = Math.min(idx, Math.min(comps.length - 1, 2));
const comp = comps[validIdx];
if (!comp) return; if (!comp) return;
const input = inputValue(); const input = inputValue();

View File

@ -11,10 +11,10 @@ const [tracker, setTracker] = createStore<TrackerStore>({
history: [], history: [],
}); });
export function addTrackerItem(item: Omit<TrackerItem, "id">): TrackerItem { export function addTrackerItem(item: Omit<TrackerItem, "uuid">): TrackerItem {
const newItem: TrackerItem = { const newItem: TrackerItem = {
...item, ...item,
id: Date.now().toString() + Math.random().toString(36).slice(2), uuid: Date.now().toString() + Math.random().toString(36).slice(2),
}; };
setTracker("items", (prev) => [...prev, newItem]); setTracker("items", (prev) => [...prev, newItem]);
@ -32,9 +32,9 @@ export function addTrackerItem(item: Omit<TrackerItem, "id">): TrackerItem {
} }
export function removeTrackerItem(itemId: string) { export function removeTrackerItem(itemId: string) {
const item = tracker.items.find((i) => i.id === itemId); const item = tracker.items.find((i) => i.uuid === itemId);
setTracker("items", (prev) => prev.filter((i) => i.id !== itemId)); setTracker("items", (prev) => prev.filter((i) => i.uuid !== itemId));
setTracker("history", (prev) => [ setTracker("history", (prev) => [
...prev, ...prev,
{ {
@ -53,7 +53,7 @@ export function updateTrackerAttribute(
) { ) {
setTracker("items", (prev) => setTracker("items", (prev) =>
prev.map((i) => prev.map((i) =>
i.id === itemId i.uuid === itemId
? { ...i, attributes: { ...i.attributes, [attrName]: attr } } ? { ...i, attributes: { ...i.attributes, [attrName]: attr } }
: i : i
) )
@ -70,7 +70,7 @@ export function updateTrackerAttribute(
} }
export function moveTrackerItem(itemId: string, direction: "up" | "down") { export function moveTrackerItem(itemId: string, direction: "up" | "down") {
const index = tracker.items.findIndex((i) => i.id === itemId); const index = tracker.items.findIndex((i) => i.uuid === itemId);
if (index === -1) return; if (index === -1) return;
const newIndex = direction === "up" ? index - 1 : index + 1; const newIndex = direction === "up" ? index - 1 : index + 1;
@ -93,7 +93,7 @@ export function moveTrackerItem(itemId: string, direction: "up" | "down") {
export function removeTrackerClass(itemId: string, className: string) { export function removeTrackerClass(itemId: string, className: string) {
setTracker("items", (prev) => setTracker("items", (prev) =>
prev.map((i) => prev.map((i) =>
i.id === itemId i.uuid === itemId
? { ...i, classes: i.classes.filter((c) => c !== className) } ? { ...i, classes: i.classes.filter((c) => c !== className) }
: i : i
) )

View File

@ -86,8 +86,9 @@ export interface TrackerAttribute {
} }
export interface TrackerItem { export interface TrackerItem {
id: string; uuid: string; // 内部唯一标识符
tag: string; tag: string;
id?: string; // Emmet ID (#id),可选的用户定义 ID
classes: string[]; classes: string[];
attributes: Record<string, TrackerAttribute>; attributes: Record<string, TrackerAttribute>;
} }