文件
chen 301194963b
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

155 行
5.3 KiB
TypeScript

import { useState, useCallback, useMemo } from 'react';
import { ToolbarButtonInfo } from '@joplin/lib/services/commands/ToolbarButtonUtils';
export interface ReorderableItem {
commandName: string;
buttonInfo: ToolbarButtonInfo;
}
interface UseToolbarEditorStateProps {
initialSelectedCommandNames: string[];
allCommandNames: string[];
allButtonInfos: ToolbarButtonInfo[];
}
interface UseToolbarEditorStateResult {
enabledItems: ReorderableItem[];
disabledItems: ReorderableItem[];
handleMoveUp: (index: number)=> void;
handleMoveDown: (index: number)=> void;
handleToggle: (commandName: string)=> void;
reinitialize: (selectedNames: string[])=> void;
}
type ItemsState = {
enabledItems: ReorderableItem[];
disabledItems: ReorderableItem[];
};
const useToolbarEditorState = (props: UseToolbarEditorStateProps): UseToolbarEditorStateResult => {
const { initialSelectedCommandNames, allCommandNames, allButtonInfos } = props;
// Build a lookup map from command name to button info
const buttonInfoMap = useMemo(() => {
const map = new Map<string, ToolbarButtonInfo>();
for (const info of allButtonInfos) {
if (info.type === 'button') {
map.set(info.name, info);
}
}
return map;
}, [allButtonInfos]);
// Filter out separators from allCommandNames for building the disabled list
const allCommandNamesWithoutSeparators = useMemo(() => {
return allCommandNames.filter(name => name !== '-');
}, [allCommandNames]);
// Build initial enabled items from selectedCommandNames (filtering separators)
const buildEnabledItems = useCallback((selectedNames: string[]): ReorderableItem[] => {
const items: ReorderableItem[] = [];
for (const name of selectedNames) {
if (name === '-') continue;
const buttonInfo = buttonInfoMap.get(name);
if (buttonInfo) {
items.push({ commandName: name, buttonInfo });
}
}
return items;
}, [buttonInfoMap]);
// Build disabled items: commands in allCommandNames but not in enabled, preserving default order
const buildDisabledItems = useCallback((enabledNames: Set<string>): ReorderableItem[] => {
const items: ReorderableItem[] = [];
for (const name of allCommandNamesWithoutSeparators) {
if (!enabledNames.has(name)) {
const buttonInfo = buttonInfoMap.get(name);
if (buttonInfo) {
items.push({ commandName: name, buttonInfo });
}
}
}
return items;
}, [allCommandNamesWithoutSeparators, buttonInfoMap]);
// Both lists are combined into one state object so that handleToggle can update them
// atomically in a single functional updater. This eliminates the stale-closure race
// that would occur if they were separate useState values (rapid double-taps could see
// an outdated snapshot of enabledItems and toggle in the wrong direction).
const [{ enabledItems, disabledItems }, setItems] = useState<ItemsState>(() => ({
enabledItems: buildEnabledItems(initialSelectedCommandNames),
disabledItems: buildDisabledItems(new Set(initialSelectedCommandNames.filter(n => n !== '-'))),
}));
const reinitialize = useCallback((selectedNames: string[]) => {
setItems({
enabledItems: buildEnabledItems(selectedNames),
disabledItems: buildDisabledItems(new Set(selectedNames.filter(n => n !== '-'))),
});
}, [buildEnabledItems, buildDisabledItems]);
const handleMoveUp = useCallback((index: number) => {
setItems(prev => {
if (index <= 0) return prev;
const newEnabled = [...prev.enabledItems];
[newEnabled[index - 1], newEnabled[index]] = [newEnabled[index], newEnabled[index - 1]];
return { ...prev, enabledItems: newEnabled };
});
}, []);
const handleMoveDown = useCallback((index: number) => {
setItems(prev => {
if (index >= prev.enabledItems.length - 1) return prev;
const newEnabled = [...prev.enabledItems];
[newEnabled[index], newEnabled[index + 1]] = [newEnabled[index + 1], newEnabled[index]];
return { ...prev, enabledItems: newEnabled };
});
}, []);
const handleToggle = useCallback((commandName: string) => {
setItems(prev => {
const isCurrentlyEnabled = prev.enabledItems.some(item => item.commandName === commandName);
if (isCurrentlyEnabled) {
const newEnabled = prev.enabledItems.filter(item => item.commandName !== commandName);
const buttonInfo = buttonInfoMap.get(commandName);
if (!buttonInfo) return prev;
// Insert in default-relative order
const newDisabled: ReorderableItem[] = [];
let inserted = false;
for (const name of allCommandNamesWithoutSeparators) {
if (name === commandName) {
newDisabled.push({ commandName, buttonInfo });
inserted = true;
} else {
const existing = prev.disabledItems.find(item => item.commandName === name);
if (existing) newDisabled.push(existing);
}
}
if (!inserted) newDisabled.push({ commandName, buttonInfo });
return { enabledItems: newEnabled, disabledItems: newDisabled };
} else {
const buttonInfo = buttonInfoMap.get(commandName);
if (!buttonInfo) return prev;
return {
enabledItems: [...prev.enabledItems, { commandName, buttonInfo }],
disabledItems: prev.disabledItems.filter(item => item.commandName !== commandName),
};
}
});
}, [buttonInfoMap, allCommandNamesWithoutSeparators]);
return {
enabledItems,
disabledItems,
handleMoveUp,
handleMoveDown,
handleToggle,
reinitialize,
};
};
export default useToolbarEditorState;