增加页面切换状态保存

This commit is contained in:
2026-05-09 10:20:36 +08:00
parent 706940bf93
commit e9a29d6006
6 changed files with 358 additions and 12 deletions
+123 -4
View File
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue'; import { ref, computed, nextTick, onBeforeUnmount, onMounted, watch } from 'vue';
import { usePromptStore } from '../stores/promptStore'; import { usePromptStore } from '../stores/promptStore';
import type { ExtendedPreset, PresetFolder, PresetType } from '../types'; import type { ExtendedPreset, PresetFolder, PresetType } from '../types';
import NotificationToast from './NotificationToast.vue'; import NotificationToast from './NotificationToast.vue';
@@ -9,6 +9,7 @@ import FolderSelector from './preset/FolderSelector.vue';
import TypeSelector from './preset/TypeSelector.vue'; import TypeSelector from './preset/TypeSelector.vue';
const store = usePromptStore(); const store = usePromptStore();
const PRESET_MANAGER_VIEW_STATE_KEY = 'preset-manager-view-state';
// State // State
const activeTab = ref<'presets' | 'folders'>('presets'); // Kept for compatibility if needed, but UI will be unified const activeTab = ref<'presets' | 'folders'>('presets'); // Kept for compatibility if needed, but UI will be unified
@@ -16,6 +17,9 @@ const selectedType = ref<PresetType | 'all'>('all');
const searchQuery = ref(''); const searchQuery = ref('');
const selectedFolderId = ref<string | null>(null); const selectedFolderId = ref<string | null>(null);
const expandedFolderIds = ref<Set<string>>(new Set()); const expandedFolderIds = ref<Set<string>>(new Set());
const presetSidebarRef = ref<InstanceType<typeof PresetSidebar> | null>(null);
const presetListRef = ref<InstanceType<typeof PresetList> | null>(null);
const isRestoringViewState = ref(false);
// Dialog State // Dialog State
const showMobileSidebar = ref(false); const showMobileSidebar = ref(false);
@@ -88,7 +92,7 @@ const filterOptions = computed<{ value: PresetType | 'all'; label: string }[]>((
]); ]);
const filteredPresets = computed(() => { const filteredPresets = computed(() => {
let presets = store.extendedPresets || []; let presets = [...(store.extendedPresets || [])];
// Filter by Type // Filter by Type
if (selectedType.value !== 'all') { if (selectedType.value !== 'all') {
@@ -117,9 +121,20 @@ const filteredPresets = computed(() => {
); );
} }
return presets.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()); return presets.sort((a, b) => {
const ao = typeof a.sortOrder === 'number' ? a.sortOrder : Number.POSITIVE_INFINITY;
const bo = typeof b.sortOrder === 'number' ? b.sortOrder : Number.POSITIVE_INFINITY;
if (ao !== bo) return ao - bo;
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();
});
}); });
const presetListResetKey = computed(() => JSON.stringify({
selectedType: selectedType.value,
searchQuery: searchQuery.value,
selectedFolderId: selectedFolderId.value,
}));
const flattenedFolders = computed(() => { const flattenedFolders = computed(() => {
type FlatItem = { id: string; name: string; label: string; level: number; presetCount: number; hasChildren: boolean }; type FlatItem = { id: string; name: string; label: string; level: number; presetCount: number; hasChildren: boolean };
const res: FlatItem[] = []; const res: FlatItem[] = [];
@@ -147,6 +162,62 @@ const allPresetsCount = computed(() => (store.extendedPresets || []).length);
const uncategorizedCount = computed(() => (store.extendedPresets || []).filter(p => !p.folderId).length); const uncategorizedCount = computed(() => (store.extendedPresets || []).filter(p => !p.folderId).length);
const favoritesCount = computed(() => (store.extendedPresets || []).filter(p => p.isFavorite).length); const favoritesCount = computed(() => (store.extendedPresets || []).filter(p => p.isFavorite).length);
function persistPresetManagerViewState() {
if (isRestoringViewState.value) return;
const payload = {
activeTab: activeTab.value,
selectedType: selectedType.value,
searchQuery: searchQuery.value,
selectedFolderId: selectedFolderId.value,
expandedFolderIds: Array.from(expandedFolderIds.value),
sidebarScrollTop: presetSidebarRef.value?.contentRef?.scrollTop ?? 0,
listScrollTop: presetListRef.value?.containerRef?.scrollTop ?? 0,
currentPage: presetListRef.value?.getCurrentPage?.() ?? 1,
};
window.sessionStorage.setItem(PRESET_MANAGER_VIEW_STATE_KEY, JSON.stringify(payload));
}
async function restorePresetManagerViewState() {
const raw = window.sessionStorage.getItem(PRESET_MANAGER_VIEW_STATE_KEY);
if (!raw) return;
try {
isRestoringViewState.value = true;
const state = JSON.parse(raw) as {
activeTab?: 'presets' | 'folders';
selectedType?: PresetType | 'all';
searchQuery?: string;
selectedFolderId?: string | null;
expandedFolderIds?: string[];
sidebarScrollTop?: number;
listScrollTop?: number;
currentPage?: number;
};
if (state.activeTab === 'presets' || state.activeTab === 'folders') {
activeTab.value = state.activeTab;
}
selectedType.value = state.selectedType ?? 'all';
searchQuery.value = state.searchQuery ?? '';
selectedFolderId.value = state.selectedFolderId ?? null;
expandedFolderIds.value = new Set(state.expandedFolderIds ?? []);
await nextTick();
if (typeof state.currentPage === 'number') {
presetListRef.value?.setCurrentPage?.(state.currentPage, false);
}
await nextTick();
if (presetSidebarRef.value?.contentRef) {
presetSidebarRef.value.contentRef.scrollTop = Math.max(0, state.sidebarScrollTop ?? 0);
}
if (presetListRef.value?.containerRef) {
presetListRef.value.containerRef.scrollTop = Math.max(0, state.listScrollTop ?? 0);
}
} catch {
// Ignore broken persisted state and continue with defaults.
} finally {
isRestoringViewState.value = false;
persistPresetManagerViewState();
}
}
// Actions // Actions
function handleFolderSelect(id: string | null) { function handleFolderSelect(id: string | null) {
selectedFolderId.value = id; selectedFolderId.value = id;
@@ -278,6 +349,28 @@ function toggleFavorite(preset: ExtendedPreset) {
} }
} }
function handleReorderPresets(payload: { draggedId: string; targetId: string; side: 'before' | 'after' }) {
const visibleIds = filteredPresets.value.map(preset => preset.id);
const from = visibleIds.indexOf(payload.draggedId);
const target = visibleIds.indexOf(payload.targetId);
if (from === -1 || target === -1) return;
const reordered = [...visibleIds];
const [draggedId] = reordered.splice(from, 1);
if (!draggedId) return;
let insertAt = target;
if (payload.side === 'after') {
insertAt += from < target ? 0 : 1;
} else if (from < target) {
insertAt -= 1;
}
insertAt = Math.max(0, Math.min(insertAt, reordered.length));
reordered.splice(insertAt, 0, draggedId);
if (store.reorderExtendedPresets(reordered)) {
persistPresetManagerViewState();
showNotification('已调整预设顺序', 'success');
}
}
async function copyPresetContent(preset: ExtendedPreset) { async function copyPresetContent(preset: ExtendedPreset) {
try { try {
await navigator.clipboard.writeText(preset.content); await navigator.clipboard.writeText(preset.content);
@@ -681,6 +774,26 @@ function closeFolderDialog() {
onMounted(() => { onMounted(() => {
store.initializeExtendedPresets(); store.initializeExtendedPresets();
nextTick(() => {
restorePresetManagerViewState();
});
});
watch(
() => [
activeTab.value,
selectedType.value,
searchQuery.value,
selectedFolderId.value,
Array.from(expandedFolderIds.value).sort().join('|'),
],
() => {
persistPresetManagerViewState();
}
);
onBeforeUnmount(() => {
persistPresetManagerViewState();
}); });
</script> </script>
@@ -693,6 +806,7 @@ onMounted(() => {
<div class="pm-sidebar" :class="{ 'mobile-open': showMobileSidebar }"> <div class="pm-sidebar" :class="{ 'mobile-open': showMobileSidebar }">
<button v-if="showMobileSidebar" class="mobile-sidebar-close" @click="showMobileSidebar = false">×</button> <button v-if="showMobileSidebar" class="mobile-sidebar-close" @click="showMobileSidebar = false">×</button>
<PresetSidebar <PresetSidebar
ref="presetSidebarRef"
:folder-tree="folderTree" :folder-tree="folderTree"
:selected-folder-id="selectedFolderId" :selected-folder-id="selectedFolderId"
:expanded-ids="expandedFolderIds" :expanded-ids="expandedFolderIds"
@@ -706,6 +820,7 @@ onMounted(() => {
@edit-folder="editFolder" @edit-folder="editFolder"
@delete-folder="deleteFolder" @delete-folder="deleteFolder"
@share-folder="handleShareFolder" @share-folder="handleShareFolder"
@view-state-change="persistPresetManagerViewState"
/> />
</div> </div>
@@ -771,20 +886,24 @@ onMounted(() => {
<div class="pm-content-area"> <div class="pm-content-area">
<PresetList <PresetList
ref="presetListRef"
:presets="filteredPresets" :presets="filteredPresets"
:search-query="searchQuery" :search-query="searchQuery"
:reset-key="presetListResetKey"
@apply="applyPreset" @apply="applyPreset"
@edit="editPreset" @edit="editPreset"
@delete="deletePreset" @delete="deletePreset"
@copy="copyPresetContent" @copy="copyPresetContent"
@share="handleShare" @share="handleShare"
@toggle-favorite="toggleFavorite" @toggle-favorite="toggleFavorite"
@reorder="handleReorderPresets"
@view-state-change="persistPresetManagerViewState"
/> />
</div> </div>
</div> </div>
<!-- Create/Edit Preset Modal --> <!-- Create/Edit Preset Modal -->
<div v-if="showCreateDialog" class="modal-overlay" @click.self="closePresetDialog"> <div v-if="showCreateDialog" class="modal-overlay">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h3>{{ editingPreset ? '编辑预设' : '新建预设' }}</h3> <h3>{{ editingPreset ? '编辑预设' : '新建预设' }}</h3>
+65 -1
View File
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watch } from 'vue'; import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
import { usePromptStore } from '../stores/promptStore'; import { usePromptStore } from '../stores/promptStore';
import type { PromptTag } from '../types'; import type { PromptTag } from '../types';
@@ -17,19 +17,23 @@ const filteredTags = computed(() => store.filteredTags);
const selectedLang = computed(() => store.selectedLang); const selectedLang = computed(() => store.selectedLang);
const PAGE_SIZE = 50; const PAGE_SIZE = 50;
const QUICK_ADD_STATE_KEY = 'prompt-quick-add-view-state';
const visibleCount = ref(PAGE_SIZE); const visibleCount = ref(PAGE_SIZE);
const tagsContainer = ref<HTMLElement | null>(null); const tagsContainer = ref<HTMLElement | null>(null);
const draggedTagKey = ref<string | null>(null); const draggedTagKey = ref<string | null>(null);
const isRestoringState = ref(false);
const visibleTags = computed(() => { const visibleTags = computed(() => {
return filteredTags.value.slice(0, visibleCount.value); return filteredTags.value.slice(0, visibleCount.value);
}); });
watch(() => filteredTags.value, () => { watch(() => filteredTags.value, () => {
if (isRestoringState.value) return;
visibleCount.value = PAGE_SIZE; visibleCount.value = PAGE_SIZE;
if (tagsContainer.value) { if (tagsContainer.value) {
tagsContainer.value.scrollTop = 0; tagsContainer.value.scrollTop = 0;
} }
persistQuickAddState();
}); });
function onScroll() { function onScroll() {
@@ -41,14 +45,17 @@ function onScroll() {
visibleCount.value += PAGE_SIZE; visibleCount.value += PAGE_SIZE;
} }
} }
persistQuickAddState();
} }
function selectCategory(index: number) { function selectCategory(index: number) {
store.selectCategory(index); store.selectCategory(index);
persistQuickAddState();
} }
function selectGroup(index: number) { function selectGroup(index: number) {
store.selectGroup(index); store.selectGroup(index);
persistQuickAddState();
} }
function onTagClick(tag: PromptTag) { function onTagClick(tag: PromptTag) {
@@ -75,6 +82,63 @@ function onTagDragEnd() {
function displayTrans(tag: PromptTag) { function displayTrans(tag: PromptTag) {
return tag.translation?.[selectedLang.value] ?? tag.key; return tag.translation?.[selectedLang.value] ?? tag.key;
} }
function persistQuickAddState() {
const payload = {
categoryIndex: store.selectedCategoryIndex,
groupIndex: store.selectedGroupIndex,
visibleCount: visibleCount.value,
scrollTop: tagsContainer.value?.scrollTop ?? 0,
};
window.sessionStorage.setItem(QUICK_ADD_STATE_KEY, JSON.stringify(payload));
}
async function restoreQuickAddState() {
const raw = window.sessionStorage.getItem(QUICK_ADD_STATE_KEY);
if (!raw) return;
try {
const state = JSON.parse(raw) as {
categoryIndex?: number;
groupIndex?: number;
visibleCount?: number;
scrollTop?: number;
};
isRestoringState.value = true;
const categoryCount = categories.value.length;
const nextCategoryIndex = Math.min(Math.max(state.categoryIndex ?? 0, 0), Math.max(categoryCount - 1, 0));
store.selectCategory(nextCategoryIndex);
const groupCount = currentCategory.value?.groups.length ?? 0;
const nextGroupIndex = Math.min(Math.max(state.groupIndex ?? 0, 0), Math.max(groupCount - 1, 0));
store.selectGroup(nextGroupIndex);
visibleCount.value = Math.max(PAGE_SIZE, state.visibleCount ?? PAGE_SIZE);
await nextTick();
if (tagsContainer.value) {
tagsContainer.value.scrollTop = Math.max(0, state.scrollTop ?? 0);
}
} catch {
// Ignore broken persisted state and continue with defaults.
} finally {
isRestoringState.value = false;
persistQuickAddState();
}
}
watch(
() => [store.selectedCategoryIndex, store.selectedGroupIndex, visibleCount.value],
() => {
if (!isRestoringState.value) {
persistQuickAddState();
}
}
);
onMounted(() => {
restoreQuickAddState();
});
onUnmounted(() => {
persistQuickAddState();
});
</script> </script>
<template> <template>
+99 -3
View File
@@ -8,6 +8,7 @@ import IconArrowRight from '../icons/IconArrowRight.vue';
const props = defineProps<{ const props = defineProps<{
presets: ExtendedPreset[]; presets: ExtendedPreset[];
searchQuery: string; searchQuery: string;
resetKey: string;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
@@ -17,12 +18,17 @@ const emit = defineEmits<{
(e: 'copy', preset: ExtendedPreset): void; (e: 'copy', preset: ExtendedPreset): void;
(e: 'share', preset: ExtendedPreset): void; (e: 'share', preset: ExtendedPreset): void;
(e: 'toggle-favorite', preset: ExtendedPreset): void; (e: 'toggle-favorite', preset: ExtendedPreset): void;
(e: 'reorder', payload: { draggedId: string; targetId: string; side: 'before' | 'after' }): void;
(e: 'view-state-change'): void;
}>(); }>();
// Pagination Logic // Pagination Logic
const PAGE_SIZE = 24; const PAGE_SIZE = 24;
const currentPage = ref(1); const currentPage = ref(1);
const containerRef = ref<HTMLElement | null>(null); const containerRef = ref<HTMLElement | null>(null);
const draggingPresetId = ref<string | null>(null);
const overPresetId = ref<string | null>(null);
const dropSide = ref<'before' | 'after' | null>(null);
const totalPages = computed(() => Math.ceil(props.presets.length / PAGE_SIZE)); const totalPages = computed(() => Math.ceil(props.presets.length / PAGE_SIZE));
@@ -32,11 +38,19 @@ const displayedPresets = computed(() => {
return props.presets.slice(start, end); return props.presets.slice(start, end);
}); });
watch(() => props.presets, () => { watch(() => props.resetKey, () => {
currentPage.value = 1; currentPage.value = 1;
if (containerRef.value) { if (containerRef.value) {
containerRef.value.scrollTop = 0; containerRef.value.scrollTop = 0;
} }
emit('view-state-change');
});
watch(() => props.presets.length, () => {
const fallbackMax = totalPages.value || 1;
if (currentPage.value > fallbackMax) {
currentPage.value = fallbackMax;
}
}); });
function changePage(page: number) { function changePage(page: number) {
@@ -45,6 +59,67 @@ function changePage(page: number) {
if (containerRef.value) { if (containerRef.value) {
containerRef.value.scrollTop = 0; containerRef.value.scrollTop = 0;
} }
emit('view-state-change');
}
function setCurrentPage(page: number, resetScroll = false) {
const fallbackMax = totalPages.value || 1;
currentPage.value = Math.min(Math.max(page, 1), fallbackMax);
if (resetScroll && containerRef.value) {
containerRef.value.scrollTop = 0;
}
}
function getCurrentPage() {
return currentPage.value;
}
defineExpose({
containerRef,
setCurrentPage,
getCurrentPage,
});
function onDragStart(preset: ExtendedPreset, event: DragEvent) {
draggingPresetId.value = preset.id;
if (event.dataTransfer) {
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', preset.id);
}
}
function onDragOver(preset: ExtendedPreset, event: DragEvent) {
if (!draggingPresetId.value || draggingPresetId.value === preset.id) return;
event.preventDefault();
const target = event.currentTarget as HTMLElement | null;
if (!target) return;
const rect = target.getBoundingClientRect();
const side = event.clientY < rect.top + rect.height / 2 ? 'before' : 'after';
overPresetId.value = preset.id;
dropSide.value = side;
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'move';
}
}
function onDrop(preset: ExtendedPreset, event: DragEvent) {
if (!draggingPresetId.value || draggingPresetId.value === preset.id || !dropSide.value) {
cleanupDragState();
return;
}
event.preventDefault();
emit('reorder', {
draggedId: draggingPresetId.value,
targetId: preset.id,
side: dropSide.value,
});
cleanupDragState();
}
function cleanupDragState() {
draggingPresetId.value = null;
overPresetId.value = null;
dropSide.value = null;
} }
function getTypeLabel(type: PresetType) { function getTypeLabel(type: PresetType) {
@@ -66,7 +141,7 @@ function formatDate(dateStr: string) {
</script> </script>
<template> <template>
<div class="preset-list-container" ref="containerRef"> <div class="preset-list-container" ref="containerRef" @scroll="emit('view-state-change')">
<div v-if="presets.length === 0" class="empty-state"> <div v-if="presets.length === 0" class="empty-state">
<div class="empty-icon">📭</div> <div class="empty-icon">📭</div>
<p class="empty-text">暂无预设</p> <p class="empty-text">暂无预设</p>
@@ -74,7 +149,14 @@ function formatDate(dateStr: string) {
<template v-else> <template v-else>
<div class="preset-grid"> <div class="preset-grid">
<div v-for="preset in displayedPresets" :key="preset.id" class="preset-card nav-btn"> <div v-for="preset in displayedPresets" :key="preset.id" class="preset-card nav-btn" draggable="true"
:class="{
dragging: draggingPresetId === preset.id,
'insert-before': overPresetId === preset.id && dropSide === 'before' && draggingPresetId !== preset.id,
'insert-after': overPresetId === preset.id && dropSide === 'after' && draggingPresetId !== preset.id
}"
@dragstart="onDragStart(preset, $event)" @dragover="onDragOver(preset, $event)"
@drop="onDrop(preset, $event)" @dragend="cleanupDragState">
<div class="card-header"> <div class="card-header">
<div class="preset-type" :title="getTypeLabel(preset.type)"> <div class="preset-type" :title="getTypeLabel(preset.type)">
<IconPresetType :type="preset.type" width="24" height="24" /> <IconPresetType :type="preset.type" width="24" height="24" />
@@ -221,6 +303,7 @@ function formatDate(dateStr: string) {
transition: all 0.2s ease; transition: all 0.2s ease;
position: relative; position: relative;
text-align: left; text-align: left;
cursor: grab;
} }
.preset-card:hover { .preset-card:hover {
@@ -229,6 +312,19 @@ function formatDate(dateStr: string) {
border-color: var(--color-border-hover); border-color: var(--color-border-hover);
} }
.preset-card.dragging {
opacity: 0.55;
cursor: grabbing;
}
.preset-card.insert-before {
border-top: 3px solid var(--color-accent);
}
.preset-card.insert-after {
border-bottom: 3px solid var(--color-accent);
}
.card-header { .card-header {
display: flex; display: flex;
align-items: center; align-items: center;
+9 -2
View File
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue'; import { ref } from 'vue';
import FolderTreeItem from './FolderTreeItem.vue'; import FolderTreeItem from './FolderTreeItem.vue';
import type { PresetFolder } from '../../types'; import type { PresetFolder } from '../../types';
@@ -25,11 +25,18 @@ const emit = defineEmits<{
(e: 'edit-folder', folder: PresetFolder): void; (e: 'edit-folder', folder: PresetFolder): void;
(e: 'delete-folder', folder: PresetFolder): void; (e: 'delete-folder', folder: PresetFolder): void;
(e: 'share-folder', folder: PresetFolder): void; (e: 'share-folder', folder: PresetFolder): void;
(e: 'view-state-change'): void;
}>(); }>();
function selectFolder(id: string | null) { function selectFolder(id: string | null) {
emit('update:selectedFolderId', id); emit('update:selectedFolderId', id);
} }
const contentRef = ref<HTMLElement | null>(null);
defineExpose({
contentRef,
});
</script> </script>
<template> <template>
@@ -45,7 +52,7 @@ function selectFolder(id: string | null) {
</button> </button>
</div> </div>
<div class="sidebar-content"> <div class="sidebar-content" ref="contentRef" @scroll="emit('view-state-change')">
<!-- 固定选项 --> <!-- 固定选项 -->
<div class="system-folders"> <div class="system-folders">
<div <div
+59
View File
@@ -833,6 +833,20 @@ export const usePromptStore = defineStore('promptStore', {
}; };
} }
const normalized = [...this.extendedPresets].sort((a, b) => {
const ao = typeof a.sortOrder === 'number' ? a.sortOrder : Number.POSITIVE_INFINITY;
const bo = typeof b.sortOrder === 'number' ? b.sortOrder : Number.POSITIVE_INFINITY;
if (ao !== bo) return ao - bo;
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();
});
let changed = false;
normalized.forEach((preset, index) => {
if (preset.sortOrder !== index) {
preset.sortOrder = index;
changed = true;
}
});
// 确保有默认文件夹 // 确保有默认文件夹
if (this.presetFolders.length === 0) { if (this.presetFolders.length === 0) {
const defaultFolder = this.createPresetFolder({ const defaultFolder = this.createPresetFolder({
@@ -845,15 +859,25 @@ export const usePromptStore = defineStore('promptStore', {
if (this.presetManagement.settings) { if (this.presetManagement.settings) {
this.presetManagement.settings.defaultFolder = defaultFolder.id; this.presetManagement.settings.defaultFolder = defaultFolder.id;
} }
changed = true;
}
if (changed) {
this.save();
} }
}, },
createExtendedPreset(data: Omit<ExtendedPreset, 'id' | 'createdAt' | 'updatedAt'>) { createExtendedPreset(data: Omit<ExtendedPreset, 'id' | 'createdAt' | 'updatedAt'>) {
const now = new Date().toISOString(); const now = new Date().toISOString();
const nextSortOrder = this.extendedPresets.reduce((max, preset) => {
const value = typeof preset.sortOrder === 'number' ? preset.sortOrder : -1;
return Math.max(max, value);
}, -1) + 1;
const preset: ExtendedPreset = { const preset: ExtendedPreset = {
id: `preset_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`, id: `preset_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
createdAt: now, createdAt: now,
updatedAt: now, updatedAt: now,
sortOrder: nextSortOrder,
...data ...data
}; };
@@ -876,6 +900,41 @@ export const usePromptStore = defineStore('promptStore', {
if (index === -1) return false; if (index === -1) return false;
this.extendedPresets.splice(index, 1); this.extendedPresets.splice(index, 1);
this.extendedPresets
.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
.forEach((preset, idx) => {
preset.sortOrder = idx;
});
this.save();
return true;
},
reorderExtendedPresets(orderedIds: string[]) {
if (!orderedIds.length) return false;
const idSet = new Set(orderedIds);
const ordered = [...this.extendedPresets].sort((a, b) => {
const ao = typeof a.sortOrder === 'number' ? a.sortOrder : Number.POSITIVE_INFINITY;
const bo = typeof b.sortOrder === 'number' ? b.sortOrder : Number.POSITIVE_INFINITY;
if (ao !== bo) return ao - bo;
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();
});
const replacements = ordered.filter(preset => idSet.has(preset.id));
if (replacements.length !== orderedIds.length) return false;
const replacementMap = new Map(replacements.map(preset => [preset.id, preset]));
const reorderedVisible = orderedIds
.map(id => replacementMap.get(id))
.filter((preset): preset is ExtendedPreset => !!preset);
let visibleIndex = 0;
const merged = ordered.map(preset => {
if (!idSet.has(preset.id)) return preset;
const nextPreset = reorderedVisible[visibleIndex];
visibleIndex += 1;
return nextPreset ?? preset;
});
merged.forEach((preset, index2) => {
preset.sortOrder = index2;
});
this.save(); this.save();
return true; return true;
}, },
+1
View File
@@ -84,6 +84,7 @@ export interface ExtendedPreset {
folderId?: string; folderId?: string;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
sortOrder?: number;
isPublic?: boolean; isPublic?: boolean;
author?: string; author?: string;
isFavorite?: boolean; isFavorite?: boolean;