diff --git a/client/src/components/Collections/common/ClickToEdit.vue b/client/src/components/Collections/common/ClickToEdit.vue index a4aa3c117ee..ad9fc0d8fe6 100644 --- a/client/src/components/Collections/common/ClickToEdit.vue +++ b/client/src/components/Collections/common/ClickToEdit.vue @@ -83,10 +83,12 @@ function revertToOriginal() { {{ computedValue }} diff --git a/client/src/components/Common/GCard.vue b/client/src/components/Common/GCard.vue index 2557892f3aa..e53114289de 100644 --- a/client/src/components/Common/GCard.vue +++ b/client/src/components/Common/GCard.vue @@ -268,6 +268,17 @@ const emit = defineEmits<{ const bookmarkLoading = ref(false); +// Track open state of the extra-actions dropdown so the card can raise its +// stacking context while open. Without this, the open menu can visually drop +// over the next card row but get pixel-intercepted by that card's primary +// action buttons (see issue surfaced by Selenium test_delete_and_undelete_history). +const extraActionsOpen = ref(false); + +function onExtraDropdown(open: boolean) { + extraActionsOpen.value = open; + emit("dropdown", open); +} + /** * Toggles bookmark status with loading state */ @@ -316,6 +327,7 @@ function onKeyDown(event: KeyboardEvent) { { 'g-card-published': published }, { 'g-card-clickable': props.clickable }, { 'g-card-dim': props.dimWhenUnselected && !props.selected }, + { 'g-card-dropdown-open': extraActionsOpen }, containerClass, ]" :tabindex="props.clickable ? 0 : undefined" @@ -329,7 +341,7 @@ function onKeyDown(event: KeyboardEvent) {
+ class="d-flex flex-gapy-1 flex-gapx-1 justify-content-between">
@@ -349,7 +361,6 @@ function onKeyDown(event: KeyboardEvent) { :id="getElementId(props.id, 'title')" bold inline - class="d-block" :size="props.titleSize"> {{ title }} @@ -465,8 +476,8 @@ function onKeyDown(event: KeyboardEvent) { title="More options" toggle-class="inline-icon-button" variant="link" - @show="() => emit('dropdown', true)" - @hide="() => emit('dropdown', false)"> + @show="() => onExtraDropdown(true)" + @hide="() => onExtraDropdown(false)"> @@ -694,6 +705,20 @@ function onKeyDown(event: KeyboardEvent) { container: g-card / inline-size; width: 100%; + // While the extra-actions dropdown is open, raise this card above its + // siblings so the menu does not get pixel-intercepted by a neighboring + // card's primary action buttons drawn at the same screen coordinates, + // and relax the content's overflow:hidden so popper-positioned menu + // items (especially when flipped above the toggle) are not clipped. + &.g-card-dropdown-open { + position: relative; + z-index: 2; + + .g-card-content { + overflow: visible; + } + } + &.g-card-grid-view { width: calc(100% / 3); @@ -757,14 +782,7 @@ function onKeyDown(event: KeyboardEvent) { } .g-card-rename { - visibility: hidden; - } - - &:hover, - &:focus-within { - .g-card-rename { - visibility: visible; - } + align-self: flex-start; } .g-card-content { diff --git a/client/src/components/History/HistoryCard.vue b/client/src/components/History/HistoryCard.vue index fa1a9b4e4ec..15ecd794b6a 100644 --- a/client/src/components/History/HistoryCard.vue +++ b/client/src/components/History/HistoryCard.vue @@ -29,7 +29,6 @@ import { storeToRefs } from "pinia"; import { computed } from "vue"; -import { useRouter } from "vue-router/composables"; import { userOwnsHistory } from "@/api"; import type { AnyHistoryEntry } from "@/api/histories"; @@ -120,8 +119,6 @@ const props = withDefaults(defineProps(), { highlighted: false, }); -const router = useRouter(); - const historyStore = useHistoryStore(); const userStore = useUserStore(); @@ -143,6 +140,12 @@ const emit = defineEmits<{ */ (e: "titleClick", history: AnyHistoryEntry["id"]): void; + /** + * Emitted when the rename action is triggered + * @event rename + */ + (e: "rename", id: string, name: string): void; + /** * Emitted when a tag is clicked for filtering * @event tagClick @@ -178,8 +181,8 @@ const emit = defineEmits<{ * Handles clicking on the history title to navigate to the history view * @function onTitleClick */ -function onTitleClick() { - router.push(`/histories/view?id=${props.history.id}`); +async function onTitleClick() { + await historyStore.setCurrentHistory(props.history.id); } /** @@ -189,7 +192,7 @@ function onTitleClick() { const historyCardTitle = computed(() => { return { label: props.history.name, - title: localize("Click to view this history"), + title: localize("Click to set as current"), handler: onTitleClick, }; }); @@ -270,7 +273,7 @@ function onKeyDown(event: KeyboardEvent) { :clickable="props.clickable" :highlighted="props.highlighted" @titleClick="onTitleClick" - @rename="() => router.push(`/histories/rename?id=${history.id}`)" + @rename="emit('rename', history.id, history.name)" @select="isMyHistory(history) && emit('select', history)" @tagsUpdate="(tags) => onTagsUpdate(history.id, tags)" @tagClick="(tag) => emit('tagClick', tag)" diff --git a/client/src/components/History/HistoryCardList.vue b/client/src/components/History/HistoryCardList.vue index ff3e0e60550..413a53b8637 100644 --- a/client/src/components/History/HistoryCardList.vue +++ b/client/src/components/History/HistoryCardList.vue @@ -23,11 +23,13 @@ * @tagClick="onTagClick" /> */ -import type { Ref } from "vue"; +import { reactive, type Ref, ref } from "vue"; import type { AnyHistoryEntry, MyHistory } from "@/api/histories"; import { isMyHistory } from "@/api/histories"; +import { useHistoryStore } from "@/stores/historyStore"; +import RenameModal from "@/components/Common/RenameModal.vue"; import HistoryCard from "@/components/History/HistoryCard.vue"; interface Props { @@ -150,6 +152,28 @@ const emit = defineEmits<{ */ (e: "on-history-card-click", history: AnyHistoryEntry, event: Event): void; }>(); + +const historyStore = useHistoryStore(); + +const modalOptions = reactive({ + rename: { + id: "", + name: "", + }, +}); + +const showRename = ref(false); + +function onRenameClose() { + showRename.value = false; + emit("refreshList", true, true); +} + +function onRename(id: string, name: string) { + modalOptions.rename.id = id; + modalOptions.rename.name = name; + showRename.value = true; +} diff --git a/client/src/components/History/Layout/DetailsLayout.vue b/client/src/components/History/Layout/DetailsLayout.vue index 3d758cd0f81..509095d42d5 100644 --- a/client/src/components/History/Layout/DetailsLayout.vue +++ b/client/src/components/History/Layout/DetailsLayout.vue @@ -3,7 +3,7 @@ import { faPen, faSave, faUndo } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; import { BButton, BFormInput, BFormTextarea } from "bootstrap-vue"; import { storeToRefs } from "pinia"; -import { computed, nextTick, onMounted, ref, watch } from "vue"; +import { computed, ref } from "vue"; import { useUserStore } from "@/stores/userStore"; import l from "@/utils/localization"; @@ -41,8 +41,6 @@ const userStore = useUserStore(); const { isAnonymous } = storeToRefs(userStore); const nameRef = ref(null); -const clickToEditRef = ref | null>(null); -const clickToEditClamped = ref(false); const editing = ref(false); const textSelected = ref(false); @@ -107,19 +105,6 @@ function onToggle() { } } -function checkClickToEditClamped() { - const el = clickToEditRef.value?.$el; - if (el) { - clickToEditClamped.value = el.scrollHeight > el.clientHeight; - } -} - -onMounted(checkClickToEditClamped); -watch( - () => props.name, - () => nextTick(checkClickToEditClamped), -); - function selectText() { if (!textSelected.value) { nameRef.value?.select(); @@ -137,11 +122,8 @@ function selectText() {