Fix rename flow: KeepAlive-cached view watchers cleared cursor on stale props
Deactivated FileExplorer/Gallery instances stay alive in KeepAlive with frozen, potentially empty document props. Their empty-folder watcher cleared store.cursor and yanked focus to the breadcrumb on every cursor change, breaking rename via gallery pen and keyboard entry into the file list, and hiding the explorer rename button. - Guard cursor watchers in FileExplorer/Gallery with an isActive flag (set on activated, cleared on deactivated) - Declare emits in GalleryFigure (rename/menu fell through to the root anchor as native listeners) - Show the explorer rename button on row hover with a delayed fade-in instead of only on the keyboard-focused row
This commit is contained in:
@@ -53,7 +53,7 @@
|
||||
<a :href="doc.text ? doc.editurl : doc.url" tabindex=-1 @contextmenu.stop @focus.stop="store.cursor = doc.key">
|
||||
{{ doc.name }}
|
||||
</a>
|
||||
<button tabindex=-1 v-if="store.cursor == doc.key" class="rename-button" @click="() => (editing = doc)">🖊️</button>
|
||||
<button tabindex=-1 class="rename-button" @click="() => (editing = doc)">🖊️</button>
|
||||
</template>
|
||||
</td>
|
||||
<FileModified :doc=doc :now=nowkey />
|
||||
@@ -84,6 +84,7 @@ import ContextMenu from '@imengyu/vue3-context-menu'
|
||||
import {
|
||||
computed,
|
||||
nextTick,
|
||||
onActivated,
|
||||
onDeactivated,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
@@ -336,9 +337,13 @@ const focusBreadcrumb = () => {
|
||||
const keyboardFollowScroll = createKeyboardFollowScroll()
|
||||
const markKeyboardFollow = keyboardFollowScroll.markKeyboardFollow
|
||||
const keepCursorVisibleSmooth = keyboardFollowScroll.keepVisible
|
||||
// Deactivated (KeepAlive-cached) instances stay alive with frozen, potentially
|
||||
// stale props - their watchers must not react to global store changes.
|
||||
let isActive = true
|
||||
watch(
|
||||
() => store.cursor,
|
||||
cursor => {
|
||||
if (!isActive) return
|
||||
if (cursor && editing.value && cursor !== editing.value.key) {
|
||||
exitEditing()
|
||||
}
|
||||
@@ -347,6 +352,7 @@ watch(
|
||||
watch(
|
||||
() => store.cursor,
|
||||
cursor => {
|
||||
if (!isActive) return
|
||||
if (cursor && !editing.value) {
|
||||
const a = document.querySelector(
|
||||
`#file-${cursor} .name a`
|
||||
@@ -359,6 +365,7 @@ watch(
|
||||
watch(
|
||||
() => [props.documents.length, store.cursor, store.query, editing.value] as const,
|
||||
([len, cursor, query, editingDoc]) => {
|
||||
if (!isActive) return
|
||||
if (!len && cursor && !query && !editingDoc) {
|
||||
store.cursor = ''
|
||||
focusBreadcrumb()
|
||||
@@ -378,7 +385,11 @@ onMounted(() => {
|
||||
active.focus({ preventScroll: true })
|
||||
}
|
||||
})
|
||||
onActivated(() => {
|
||||
isActive = true
|
||||
})
|
||||
onDeactivated(() => {
|
||||
isActive = false
|
||||
if (editing.value) exitEditing()
|
||||
})
|
||||
onUnmounted(() => {
|
||||
@@ -617,6 +628,12 @@ table td {
|
||||
.name .rename-button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
tbody tr:hover .name .rename-button {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
animation: appear calc(5 * var(--transition-time)) linear;
|
||||
}
|
||||
@keyframes appear {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:editing="editing === doc ? {rename, exit} : null"
|
||||
:style="{ '--gallery-figure-height': rowHeightsByKey[doc.key] ?? '15em' }"
|
||||
@menu="contextMenu($event, doc)"
|
||||
@rename="editing = doc; store.cursor = doc.key"
|
||||
@rename="onFigureRename(doc)"
|
||||
:class="{ 'folder-start': showFolderBreadcrumb(index) }"
|
||||
/>
|
||||
</template>
|
||||
@@ -64,6 +64,10 @@ const editing = shallowRef<Doc | null>(null)
|
||||
const exit = () => {
|
||||
editing.value = null
|
||||
}
|
||||
const onFigureRename = (doc: Doc) => {
|
||||
editing.value = doc
|
||||
store.cursor = doc.key
|
||||
}
|
||||
const rename = async (doc: Doc, newName: string) => {
|
||||
const oldName = doc.name
|
||||
doc.name = newName // We should get an update from watch but this is quicker
|
||||
@@ -396,9 +400,13 @@ const focusBreadcrumb = () => {
|
||||
const keyboardFollowScroll = createKeyboardFollowScroll()
|
||||
const markKeyboardFollow = keyboardFollowScroll.markKeyboardFollow
|
||||
const keepCursorVisibleSmooth = keyboardFollowScroll.keepVisible
|
||||
// Deactivated (KeepAlive-cached) instances stay alive with frozen, potentially
|
||||
// stale props - their watchers must not react to global store changes.
|
||||
let isActive = true
|
||||
watch(
|
||||
() => store.cursor,
|
||||
cursor => {
|
||||
if (!isActive) return
|
||||
if (cursor && editing.value && cursor !== editing.value.key) {
|
||||
exit()
|
||||
}
|
||||
@@ -407,6 +415,7 @@ watch(
|
||||
watch(
|
||||
() => store.cursor,
|
||||
cursor => {
|
||||
if (!isActive) return
|
||||
if (cursor && !editing.value) {
|
||||
const a = document.querySelector(`#file-${cursor}`) as HTMLAnchorElement | null
|
||||
if (a) {
|
||||
@@ -419,6 +428,7 @@ watch(
|
||||
watch(
|
||||
() => [props.documents.length, store.cursor, store.query, editing.value] as const,
|
||||
([len, cursor, query, editingDoc]) => {
|
||||
if (!isActive) return
|
||||
if (!len && cursor && !query && !editingDoc) {
|
||||
store.cursor = ''
|
||||
focusBreadcrumb()
|
||||
@@ -449,12 +459,14 @@ onMounted(() => {
|
||||
attachGalleryObservers()
|
||||
})
|
||||
onActivated(() => {
|
||||
isActive = true
|
||||
nextTick(() => {
|
||||
updateColumns()
|
||||
attachGalleryObservers()
|
||||
})
|
||||
})
|
||||
onDeactivated(() => {
|
||||
isActive = false
|
||||
detachGalleryObservers()
|
||||
if (editing.value) exit()
|
||||
})
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<span class="filename">{{ snap.displayName }}<SparseIndicator :doc="doc" class="after-name" /></span>
|
||||
<span v-if="snap.ext" class="file-ext">.{{ snap.ext }}</span>
|
||||
</span>
|
||||
<button class="rename-btn" @click="$emit('rename')" title="Rename">✏️</button>
|
||||
<button class="rename-btn" @click="emit('rename')" title="Rename">✏️</button>
|
||||
</div>
|
||||
<div class=namespacer></div>
|
||||
</template>
|
||||
@@ -64,6 +64,10 @@ const props = defineProps<{
|
||||
doc: Doc
|
||||
editing?: EditingProp
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'rename'): void
|
||||
(e: 'menu', ev: MouseEvent): void
|
||||
}>()
|
||||
const m = ref<typeof MediaPreview | null>(null)
|
||||
const tooltip = ref<InstanceType<typeof CursorTooltip> | null>(null)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user