fix: issues

This commit is contained in:
hypercross 2026-03-01 10:01:53 +08:00
parent c23bff5a89
commit fdaf79e7ed
7 changed files with 178 additions and 68 deletions

View File

@ -1,13 +1,13 @@
import { type Component, For, Show } from "solid-js";
import { type Component, For, Show, Index } from "solid-js";
import type { TrackerItem, TrackerAttribute } from "./types";
export interface TrackerViewProps {
items: () => TrackerItem[];
onEditAttribute?: (itemId: string, attrName: string, attr: TrackerAttribute) => void;
onRemoveClass?: (itemId: string, className: string) => void;
onMoveUp?: (itemId: string) => void;
onMoveDown?: (itemId: string) => void;
onRemove?: (itemId: string) => void;
onEditAttribute?: (index: number, attrName: string, attr: TrackerAttribute) => void;
onRemoveClass?: (index: number, className: string) => void;
onMoveUp?: (index: number) => void;
onMoveDown?: (index: number) => void;
onRemove?: (index: number) => void;
}
export const TrackerView: Component<TrackerViewProps> = (props) => {
@ -32,7 +32,7 @@ export const TrackerView: Component<TrackerViewProps> = (props) => {
>
<div class="space-y-4">
<For each={props.items()}>
{(item) => (
{(item, index) => (
<div class="border border-gray-200 rounded-lg p-3 bg-gray-50">
{/* 头部tag、id 和操作按钮 */}
<div class="flex items-center justify-between mb-2">
@ -41,27 +41,26 @@ export const TrackerView: Component<TrackerViewProps> = (props) => {
<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 class="flex items-center gap-1">
<button
class="p-1 text-gray-500 hover:text-blue-600 hover:bg-blue-50 rounded"
title="上移"
onClick={() => props.onMoveUp?.(item.id)}
onClick={() => props.onMoveUp?.(index())}
>
</button>
<button
class="p-1 text-gray-500 hover:text-blue-600 hover:bg-blue-50 rounded"
title="下移"
onClick={() => props.onMoveDown?.(item.id)}
onClick={() => props.onMoveDown?.(index())}
>
</button>
<button
class="p-1 text-gray-500 hover:text-red-600 hover:bg-red-50 rounded"
title="移除"
onClick={() => props.onRemove?.(item.id)}
onClick={() => props.onRemove?.(index())}
>
×
</button>
@ -71,20 +70,20 @@ export const TrackerView: Component<TrackerViewProps> = (props) => {
{/* Classes */}
<Show when={item.classes.length > 0}>
<div class="flex flex-wrap gap-1 mb-2">
<For each={item.classes}>
<Index each={item.classes}>
{(cls) => (
<span class="inline-flex items-center gap-1 px-2 py-0.5 bg-blue-100 text-blue-700 text-xs rounded">
{cls}
{cls()}
<button
class="hover:text-red-600"
onClick={() => props.onRemoveClass?.(item.id, cls)}
onClick={() => props.onRemoveClass?.(index(), cls())}
title="移除类"
>
×
</button>
</span>
)}
</For>
</Index>
</div>
</Show>
@ -94,7 +93,7 @@ export const TrackerView: Component<TrackerViewProps> = (props) => {
{(attr: TrackerAttribute) => (
<div
class="flex items-center justify-between px-2 py-1 bg-white rounded border border-gray-200 cursor-pointer hover:border-blue-400"
onClick={() => props.onEditAttribute?.(item.id, attr.name, attr)}
onClick={() => props.onEditAttribute?.(index(), attr.name, attr)}
>
<span class="text-xs text-gray-600">{attr.name}</span>
<span class="text-sm font-mono text-gray-800">

View File

@ -1,6 +1,6 @@
import type { MdCommanderCommand, MdCommanderCommandMap } from "../types";
import { parseEmmet } from "../utils/emmetParser";
import { addTrackerItem, removeTrackerItem, getTrackerItems } from "../stores";
import type { MdCommanderCommand } from "../types";
import { parseEmmet } from "../utils";
import { addTrackerItem, removeTrackerItem, getTrackerItems, findTrackerItem } from "../stores";
export const trackCommand: MdCommanderCommand = {
command: "track",
@ -42,24 +42,32 @@ export const trackCommand: MdCommanderCommand = {
export const untrackCommand: MdCommanderCommand = {
command: "untrack",
description: "移除一个追踪项目",
description: "移除一个追踪项目 - 支持 Emmet 语法untrack #g2 或 untrack goblin.warrior",
parameters: [
{
name: "id",
description: "追踪项目的 ID",
name: "emmet",
description: "Emmet 格式:#id 或 tag.class",
type: "string",
required: true,
},
],
handler: (args) => {
const id = args.params.id;
if (!id) {
return { message: "错误:缺少 id 参数", type: "error" };
const emmet = args.params.emmet;
if (!emmet) {
return { message: "错误:缺少 Emmet 参数", type: "error" };
}
removeTrackerItem(id);
const found = findTrackerItem(emmet);
if (!found) {
return { message: `未找到匹配的追踪项目:${emmet}`, type: "error" };
}
return { message: `已移除追踪项目:${id}`, type: "success" };
const success = removeTrackerItem(emmet);
if (!success) {
return { message: `移除失败:${emmet}`, type: "error" };
}
return { message: `已移除追踪项目:${found.tag}${found.id ? '#' + found.id : ''}`, type: "success" };
},
};

View File

@ -19,6 +19,7 @@ import {
removeTrackerClass as removeClassFromTracker,
getTrackerItems,
getTrackerHistory,
findTrackerIndex,
} from "../stores";
// ==================== 默认命令 ====================
@ -250,10 +251,14 @@ export interface UseCommanderReturn {
setTrackerItems: (updater: (prev: TrackerItem[]) => TrackerItem[]) => void;
trackerHistory: () => TrackerCommand[];
addTrackerItem: (item: Omit<TrackerItem, 'id'>) => void;
removeTrackerItem: (itemId: string) => void;
updateTrackerAttribute: (itemId: string, attrName: string, attr: TrackerAttribute) => void;
moveTrackerItem: (itemId: string, direction: 'up' | 'down') => void;
removeTrackerItemClass: (itemId: string, className: string) => void;
removeTrackerItem: (emmet: string) => boolean;
removeTrackerItemByIndex: (index: number) => void;
updateTrackerAttribute: (emmet: string, attrName: string, attr: TrackerAttribute) => boolean;
updateTrackerAttributeByIndex: (index: number, attrName: string, attr: TrackerAttribute) => void;
moveTrackerItem: (emmet: string, direction: 'up' | 'down') => boolean;
moveTrackerItemByIndex: (index: number, direction: 'up' | 'down') => void;
removeTrackerItemClass: (emmet: string, className: string) => boolean;
removeTrackerItemClassByIndex: (index: number, className: string) => void;
recordTrackerCommand: (cmd: Omit<TrackerCommand, 'timestamp'>) => void;
}
@ -431,20 +436,56 @@ export function useCommander(
return addTracker(item);
};
const removeTrackerItem = (itemId: string) => {
removeTracker(itemId);
const removeTrackerItem = (emmet: string) => {
return removeTracker(emmet);
};
const updateTrackerAttribute = (itemId: string, attrName: string, attr: TrackerAttribute) => {
updateTrackerAttr(itemId, attrName, attr);
const removeTrackerItemByIndex = (index: number) => {
const items = trackerItems();
if (index >= 0 && index < items.length) {
const item = items[index];
const emmet = `${item.tag}${item.id ? '#' + item.id : ''}${item.classes.length > 0 ? '.' + item.classes.join('.') : ''}`;
removeTracker(emmet);
}
};
const moveTrackerItem = (itemId: string, direction: 'up' | 'down') => {
moveTracker(itemId, direction);
const updateTrackerAttribute = (emmet: string, attrName: string, attr: TrackerAttribute) => {
return updateTrackerAttr(emmet, attrName, attr);
};
const removeTrackerItemClass = (itemId: string, className: string) => {
removeClassFromTracker(itemId, className);
const updateTrackerAttributeByIndex = (index: number, attrName: string, attr: TrackerAttribute) => {
const items = trackerItems();
if (index >= 0 && index < items.length) {
const item = items[index];
const emmet = `${item.tag}${item.id ? '#' + item.id : ''}${item.classes.length > 0 ? '.' + item.classes.join('.') : ''}`;
updateTrackerAttr(emmet, attrName, attr);
}
};
const moveTrackerItem = (emmet: string, direction: 'up' | 'down') => {
return moveTracker(emmet, direction);
};
const moveTrackerItemByIndex = (index: number, direction: 'up' | 'down') => {
const items = trackerItems();
if (index >= 0 && index < items.length) {
const item = items[index];
const emmet = `${item.tag}${item.id ? '#' + item.id : ''}${item.classes.length > 0 ? '.' + item.classes.join('.') : ''}`;
moveTracker(emmet, direction);
}
};
const removeTrackerItemClass = (emmet: string, className: string) => {
return removeClassFromTracker(emmet, className);
};
const removeTrackerItemClassByIndex = (index: number, className: string) => {
const items = trackerItems();
if (index >= 0 && index < items.length) {
const item = items[index];
const emmet = `${item.tag}${item.id ? '#' + item.id : ''}${item.classes.length > 0 ? '.' + item.classes.join('.') : ''}`;
removeClassFromTracker(emmet, className);
}
};
const trackerItems = () => getTrackerItems();
@ -491,9 +532,13 @@ export function useCommander(
trackerHistory,
addTrackerItem,
removeTrackerItem,
removeTrackerItemByIndex,
updateTrackerAttribute,
updateTrackerAttributeByIndex,
moveTrackerItem,
moveTrackerItemByIndex,
removeTrackerItemClass,
removeTrackerItemClassByIndex,
recordTrackerCommand,
};
}

View File

@ -16,7 +16,7 @@ customElement<MdCommanderProps>(
const commander = useCommander(props.commands);
const [editingAttr, setEditingAttr] = createSignal<{
itemId: string;
index: number;
attrName: string;
attr: TrackerAttribute;
} | null>(null);
@ -99,13 +99,13 @@ customElement<MdCommanderProps>(
fallback={
<TrackerView
items={commander.trackerItems}
onEditAttribute={(itemId, attrName, attr) =>
setEditingAttr({ itemId, attrName, attr })
onEditAttribute={(index, attrName, attr) =>
setEditingAttr({ index, attrName, attr })
}
onRemoveClass={commander.removeTrackerItemClass}
onMoveUp={(itemId) => commander.moveTrackerItem(itemId, "up")}
onMoveDown={(itemId) => commander.moveTrackerItem(itemId, "down")}
onRemove={commander.removeTrackerItem}
onRemoveClass={commander.removeTrackerItemClassByIndex}
onMoveUp={(index) => commander.moveTrackerItemByIndex(index, "up")}
onMoveDown={(index) => commander.moveTrackerItemByIndex(index, "down")}
onRemove={(index) => commander.removeTrackerItemByIndex(index)}
/>
}
>
@ -146,7 +146,7 @@ customElement<MdCommanderProps>(
onUpdate={(attr) => {
const data = editingAttr();
if (data) {
commander.updateTrackerAttribute(data.itemId, data.attrName, attr);
commander.updateTrackerAttributeByIndex(data.index, data.attrName, attr);
}
}}
onClose={() => setEditingAttr(null)}

View File

@ -7,5 +7,7 @@ export {
getTrackerItems,
getTrackerHistory,
clearTrackerHistory,
findTrackerIndex,
findTrackerItem,
} from "./trackerStore";
export type { TrackerStore } from "./trackerStore";

View File

@ -11,10 +11,51 @@ const [tracker, setTracker] = createStore<TrackerStore>({
history: [],
});
/**
* Emmet
*
* - #id - id
* - tag - tag
* - tag.class - tag class
* - .class - class
*/
export function findTrackerIndex(emmet: string): number {
const trimmed = emmet.trim();
// 解析 #id
if (trimmed.startsWith('#')) {
const id = trimmed.slice(1).split('.')[0];
return tracker.items.findIndex(item => item.id === id);
}
// 解析 tag 和 classes
const match = trimmed.match(/^([^.#]+)?(?:#([^.#]+))?(?:\.(.+))?$/);
if (!match) return -1;
const tag = match[1];
const classes = match[3] ? match[3].split('.') : [];
return tracker.items.findIndex(item => {
// 匹配 tag
if (tag && item.tag !== tag) return false;
// 匹配所有 classes
if (classes.length > 0) {
for (const cls of classes) {
if (!item.classes.includes(cls)) return false;
}
}
return true;
});
}
export function findTrackerItem(emmet: string): TrackerItem | undefined {
const index = findTrackerIndex(emmet);
return index >= 0 ? tracker.items[index] : undefined;
}
export function addTrackerItem(item: Omit<TrackerItem, "uuid">): TrackerItem {
const newItem: TrackerItem = {
...item,
uuid: Date.now().toString() + Math.random().toString(36).slice(2),
};
setTracker("items", (prev) => [...prev, newItem]);
@ -31,29 +72,38 @@ export function addTrackerItem(item: Omit<TrackerItem, "uuid">): TrackerItem {
return newItem;
}
export function removeTrackerItem(itemId: string) {
const item = tracker.items.find((i) => i.uuid === itemId);
export function removeTrackerItem(emmet: string): boolean {
const index = findTrackerIndex(emmet);
if (index === -1) return false;
setTracker("items", (prev) => prev.filter((i) => i.uuid !== itemId));
const item = tracker.items[index];
setTracker("items", (prev) => prev.filter((_, i) => i !== index));
setTracker("history", (prev) => [
...prev,
{
type: "remove",
itemId,
itemId: `${item.tag}${item.id ? '#' + item.id : ''}`,
data: item,
timestamp: new Date(),
},
]);
return true;
}
export function updateTrackerAttribute(
itemId: string,
emmet: string,
attrName: string,
attr: TrackerAttribute
) {
): boolean {
const index = findTrackerIndex(emmet);
if (index === -1) return false;
const item = tracker.items[index];
setTracker("items", (prev) =>
prev.map((i) =>
i.uuid === itemId
prev.map((i, idx) =>
idx === index
? { ...i, attributes: { ...i.attributes, [attrName]: attr } }
: i
)
@ -62,23 +112,24 @@ export function updateTrackerAttribute(
...prev,
{
type: "update",
itemId,
itemId: `${item.tag}${item.id ? '#' + item.id : ''}`,
attributeUpdates: { [attrName]: attr },
timestamp: new Date(),
},
]);
return true;
}
export function moveTrackerItem(itemId: string, direction: "up" | "down") {
const index = tracker.items.findIndex((i) => i.uuid === itemId);
if (index === -1) return;
export function moveTrackerItem(emmet: string, direction: "up" | "down"): boolean {
const index = findTrackerIndex(emmet);
if (index === -1) return false;
const newIndex = direction === "up" ? index - 1 : index + 1;
if (newIndex < 0 || newIndex >= tracker.items.length) return;
if (newIndex < 0 || newIndex >= tracker.items.length) return false;
const newItems = [...tracker.items];
[newItems[index], newItems[newIndex]] = [newItems[newIndex], newItems[index]];
setTracker("items", newItems);
setTracker("history", (prev) => [
...prev,
@ -88,12 +139,17 @@ export function moveTrackerItem(itemId: string, direction: "up" | "down") {
timestamp: new Date(),
},
]);
return true;
}
export function removeTrackerClass(itemId: string, className: string) {
export function removeTrackerClass(emmet: string, className: string): boolean {
const index = findTrackerIndex(emmet);
if (index === -1) return false;
const item = tracker.items[index];
setTracker("items", (prev) =>
prev.map((i) =>
i.uuid === itemId
prev.map((i, idx) =>
idx === index
? { ...i, classes: i.classes.filter((c) => c !== className) }
: i
)
@ -102,10 +158,11 @@ export function removeTrackerClass(itemId: string, className: string) {
...prev,
{
type: "update",
itemId,
itemId: `${item.tag}${item.id ? '#' + item.id : ''}`,
timestamp: new Date(),
},
]);
return true;
}
export function getTrackerItems(): TrackerItem[] {

View File

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