feat: edit entry classes

This commit is contained in:
hypercross 2026-03-01 10:33:55 +08:00
parent 80a6b4526c
commit abaee79198
6 changed files with 112 additions and 1 deletions

View File

@ -4,18 +4,21 @@ import type { TrackerAttribute } from "./types";
export interface AttributeTooltipProps {
position: { x: number; y: number };
attributes: Record<string, TrackerAttribute>;
classes: string[];
onUpdate: (attrName: string, attr: TrackerAttribute) => void;
onClassesChange: (classes: string[]) => void;
onClose: () => void;
}
export const AttributeTooltip: Component<AttributeTooltipProps> = (props) => {
const [position, setPosition] = createSignal(props.position);
const [newClass, setNewClass] = createSignal("");
// 点击外部关闭
createEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
const target = e.target as HTMLElement;
if (!target.closest(".attribute-tooltip")) {
if (target.closest('body') && !target.closest(".attribute-tooltip")) {
props.onClose();
}
};
@ -29,6 +32,22 @@ export const AttributeTooltip: Component<AttributeTooltipProps> = (props) => {
props.onUpdate(attrName, { ...attr, value });
};
// 添加 class
const handleAddClass = () => {
const className = newClass().trim();
if (className && !props.classes.includes(className)) {
props.onClassesChange([...props.classes, className]);
setNewClass("");
}
};
// 移除 class
const handleRemoveClass = (e: MouseEvent, className: string) => {
e.preventDefault();
e.stopPropagation();
props.onClassesChange(props.classes.filter((c) => c !== className));
};
// 渲染不同类型的属性编辑器
const renderEditor = (attrName: string, attr: TrackerAttribute) => {
if (attr.type === "progress") {
@ -125,6 +144,49 @@ export const AttributeTooltip: Component<AttributeTooltipProps> = (props) => {
</button>
</div>
<div class="space-y-3">
{/* Class 列表编辑器 */}
<div class="flex flex-col gap-1">
<label class="text-xs text-gray-500 font-medium">Classes</label>
<div class="flex flex-wrap gap-1 mb-1">
<For each={props.classes}>
{(className) => (
<span class="inline-flex items-center gap-1 px-2 py-0.5 bg-blue-100 text-blue-700 rounded text-xs">
.{className}
<button
class="text-blue-400 hover:text-blue-600 font-bold leading-none"
onClick={(e) => handleRemoveClass(e, className)}
>
×
</button>
</span>
)}
</For>
</div>
<div class="flex items-center gap-1">
<input
type="text"
value={newClass()}
onInput={(e) => setNewClass((e.target as HTMLInputElement).value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
e.stopPropagation();
handleAddClass();
}
}}
placeholder="添加 class..."
class="flex-1 px-2 py-1 border border-gray-300 rounded text-sm"
/>
<button
class="px-2 py-1 bg-gray-200 hover:bg-gray-300 rounded text-sm font-medium transition-colors"
onClick={handleAddClass}
>
</button>
</div>
</div>
{/* 属性编辑器 */}
<For each={Object.entries(props.attributes)}>
{([name, attr]) => (
<div class="flex flex-col gap-1">

View File

@ -5,6 +5,7 @@ import { AttributeTooltip } from "./AttributeTooltip";
export interface TrackerViewProps {
items: () => TrackerItem[];
onEditAttribute?: (index: number, attrName: string, attr: TrackerAttribute) => void;
onClassesChange?: (index: number, classes: string[]) => void;
onRemoveClass?: (index: number, className: string) => void;
onMoveUp?: (index: number) => void;
onMoveDown?: (index: number) => void;
@ -53,6 +54,13 @@ export const TrackerView: Component<TrackerViewProps> = (props) => {
}
};
const handleClassesChange = (classes: string[]) => {
const data = editingItem();
if (data) {
props.onClassesChange?.(data.index, classes);
}
};
return (
<div class="tracker-view flex-1 overflow-auto p-3 bg-white">
<Show
@ -132,7 +140,9 @@ export const TrackerView: Component<TrackerViewProps> = (props) => {
<AttributeTooltip
position={editingItem()!.position}
attributes={props.items()[editingItem()!.index]?.attributes || {}}
classes={props.items()[editingItem()!.index]?.classes || []}
onUpdate={handleUpdateAttribute}
onClassesChange={handleClassesChange}
onClose={() => setEditingItem(null)}
/>
</Show>

View File

@ -15,6 +15,7 @@ import {
addTrackerItem as addTracker,
removeTrackerItem as removeTracker,
updateTrackerAttribute as updateTrackerAttr,
updateTrackerClasses as updateTrackerClassesStore,
moveTrackerItem as moveTracker,
removeTrackerClass as removeClassFromTracker,
getTrackerItems,
@ -255,6 +256,7 @@ export interface UseCommanderReturn {
removeTrackerItemByIndex: (index: number) => void;
updateTrackerAttribute: (emmet: string, attrName: string, attr: TrackerAttribute) => boolean;
updateTrackerAttributeByIndex: (index: number, attrName: string, attr: TrackerAttribute) => void;
updateTrackerClassesByIndex: (index: number, classes: string[]) => void;
moveTrackerItem: (emmet: string, direction: 'up' | 'down') => boolean;
moveTrackerItemByIndex: (index: number, direction: 'up' | 'down') => void;
removeTrackerItemClass: (emmet: string, className: string) => boolean;
@ -462,6 +464,15 @@ export function useCommander(
}
};
const updateTrackerClassesByIndex = (index: number, classes: 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('.') : ''}`;
updateTrackerClassesStore(emmet, classes);
}
};
const moveTrackerItem = (emmet: string, direction: 'up' | 'down') => {
return moveTracker(emmet, direction);
};
@ -535,6 +546,7 @@ export function useCommander(
removeTrackerItemByIndex,
updateTrackerAttribute,
updateTrackerAttributeByIndex,
updateTrackerClassesByIndex,
moveTrackerItem,
moveTrackerItemByIndex,
removeTrackerItemClass,

View File

@ -92,6 +92,9 @@ customElement<MdCommanderProps>(
onEditAttribute={(index, attrName, attr) =>
commander.updateTrackerAttributeByIndex(index, attrName, attr)
}
onClassesChange={(index, classes) =>
commander.updateTrackerClassesByIndex(index, classes)
}
onRemoveClass={commander.removeTrackerItemClassByIndex}
onMoveUp={(index) => commander.moveTrackerItemByIndex(index, "up")}
onMoveDown={(index) => commander.moveTrackerItemByIndex(index, "down")}

View File

@ -2,6 +2,7 @@ export {
addTrackerItem,
removeTrackerItem,
updateTrackerAttribute,
updateTrackerClasses,
moveTrackerItem,
removeTrackerClass,
getTrackerItems,

View File

@ -161,6 +161,29 @@ export function removeTrackerClass(emmet: string, className: string): boolean {
return true;
}
export function updateTrackerClasses(emmet: string, classes: string[]): boolean {
const index = findTrackerIndex(emmet);
if (index === -1) return false;
const item = tracker.items[index];
setTracker("items", (prev) =>
prev.map((i, idx) =>
idx === index
? { ...i, classes: [...classes] }
: i
)
);
setTracker("history", (prev) => [
...prev,
{
type: "update",
itemId: `${item.tag}${item.id ? '#' + item.id : ''}`,
timestamp: new Date(),
},
]);
return true;
}
export function getTrackerItems(): TrackerItem[] {
return tracker.items;
}