diff --git a/src/components/NavMenu.vue b/src/components/NavMenu.vue index 805f18f8..9ce96251 100644 --- a/src/components/NavMenu.vue +++ b/src/components/NavMenu.vue @@ -8,7 +8,7 @@ {{ item.titleIsCustom ? item.title : t(item.title || '') }} + @@ -333,9 +337,10 @@ DropdownMenuSubTrigger, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; - import { computed, defineAsyncComponent, onMounted, ref, watch } from 'vue'; + import { computed, defineAsyncComponent, h, onMounted, ref, watch } from 'vue'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { ChevronRight, Heart } from 'lucide-vue-next'; + import { Kbd } from '@/components/ui/kbd'; import { TooltipWrapper } from '@/components/ui/tooltip'; import { storeToRefs } from 'pinia'; import { useI18n } from 'vue-i18n'; @@ -361,6 +366,21 @@ const { t, locale } = useI18n(); const router = useRouter(); + const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; + + const getItemTooltip = (item) => { + const label = item.titleIsCustom ? item.title : t(item.title || ''); + if (item.action !== 'direct-access') { + return label; + } + return () => + h('span', { class: 'inline-flex items-center gap-1' }, [ + label, + h(Kbd, () => (isMac ? '⌘' : 'Ctrl')), + h(Kbd, () => 'D') + ]); + }; + const createDefaultNavLayout = () => [ { type: 'item', key: 'feed' }, { type: 'item', key: 'friends-locations' }, @@ -804,6 +824,10 @@ router.push({ name: routeName }); }; + /** + * + * @param layout + */ function getFirstNavRoute(layout) { for (const entry of layout) { if (entry.type === 'item') { diff --git a/src/components/dialogs/UserDialog/UserDialog.vue b/src/components/dialogs/UserDialog/UserDialog.vue index b0b721db..2d3cde70 100644 --- a/src/components/dialogs/UserDialog/UserDialog.vue +++ b/src/components/dialogs/UserDialog/UserDialog.vue @@ -1501,6 +1501,10 @@ } ); + /** + * + * @param visibility + */ function userFavoriteWorldsStatus(visibility) { const style = {}; if (visibility === 'public') { @@ -1513,6 +1517,10 @@ return style; } + /** + * + * @param user + */ function getUserStateText(user) { let state = ''; if (user.state === 'active') { @@ -1528,6 +1536,10 @@ return state; } + /** + * + * @param status + */ function getUserStatusText(status) { if (status === 'active') { return t('dialog.user.status.active'); @@ -1544,6 +1556,9 @@ return t('dialog.user.status.offline'); } + /** + * + */ function refreshUserDialogTreeData() { const D = userDialog.value; if (D.id === currentUser.value.id) { @@ -1556,6 +1571,10 @@ treeData.value = formatJsonVars(D.ref); } + /** + * + * @param tabName + */ function handleUserDialogTab(tabName) { userDialog.value.lastActiveTab = tabName; const userId = userDialog.value.id; @@ -1604,10 +1623,17 @@ } } + /** + * + */ function loadLastActiveTab() { handleUserDialogTab(userDialog.value.lastActiveTab); } + /** + * + * @param tabName + */ function userDialogTabClick(tabName) { if (tabName === userDialog.value.lastActiveTab) { if (tabName === 'JSON') { @@ -1618,17 +1644,26 @@ handleUserDialogTab(tabName); } + /** + * + */ function showPronounsDialog() { const D = pronounsDialog.value; D.pronouns = currentUser.value.pronouns; D.visible = true; } + /** + * + */ function showLanguageDialog() { const D = languageDialog.value; D.visible = true; } + /** + * + */ function showSocialStatusDialog() { const D = socialStatusDialog.value; const { statusHistory } = currentUser.value; @@ -1646,6 +1681,10 @@ D.visible = true; } + /** + * + * @param userId + */ async function setUserDialogAvatarsRemote(userId) { if (avatarRemoteDatabase.value && userId !== currentUser.value.id) { userDialog.value.isAvatarsLoading = true; @@ -1672,6 +1711,10 @@ sortUserDialogAvatars(userDialog.value.avatars); } + /** + * + * @param badge + */ async function toggleBadgeVisibility(badge) { if (badge.hidden) { badge.showcased = false; @@ -1684,6 +1727,10 @@ handleBadgeUpdate(args); } + /** + * + * @param badge + */ async function toggleBadgeShowcased(badge) { if (badge.showcased) { badge.hidden = false; @@ -1696,12 +1743,21 @@ handleBadgeUpdate(args); } + /** + * + * @param args + */ function handleBadgeUpdate(args) { if (args.json) { toast.success(t('message.badge.updated')); } } + /** + * + * @param userId + * @param type + */ function setPlayerModeration(userId, type) { const D = userDialog.value; AppApi.SetVRChatUserModeration(currentUser.value.id, userId, type).then((result) => { @@ -1722,6 +1778,11 @@ }); } + /** + * + * @param params + * @param userId + */ function showSendInviteDialog(params, userId) { sendInviteDialog.value = { params, @@ -1733,6 +1794,11 @@ sendInviteDialogVisible.value = true; } + /** + * + * @param params + * @param userId + */ function showSendInviteRequestDialog(params, userId) { sendInviteDialog.value = { params, @@ -1744,12 +1810,21 @@ sendInviteRequestDialogVisible.value = true; } + /** + * + * @param groupId + * @param userId + */ function showInviteGroupDialog(groupId, userId) { inviteGroupDialog.value.groupId = groupId; inviteGroupDialog.value.userId = userId; inviteGroupDialog.value.visible = true; } + /** + * + * @param command + */ function userDialogCommand(command) { let L; const D = userDialog.value; @@ -1892,6 +1967,10 @@ } } + /** + * + * @param args + */ function handleSendFriendRequest(args) { const ref = cachedUsers.get(args.params.userId); if (typeof ref === 'undefined') { @@ -1917,6 +1996,10 @@ } } + /** + * + * @param args + */ function handleCancelFriendRequest(args) { const ref = cachedUsers.get(args.params.userId); if (typeof ref === 'undefined') { @@ -1937,6 +2020,10 @@ D.outgoingRequest = false; } + /** + * + * @param args + */ function handleSendPlayerModeration(args) { const ref = applyPlayerModeration(args.json); const D = userDialog.value; @@ -1955,6 +2042,11 @@ toast.success(t('message.user.moderated')); } + /** + * + * @param command + * @param userId + */ async function performUserDialogCommand(command, userId) { let args; let key; @@ -2092,6 +2184,10 @@ } } + /** + * + * @param userId + */ function reportUserForHacking(userId) { miscRequest.reportUser({ userId, @@ -2101,6 +2197,10 @@ }); } + /** + * + * @param userId + */ async function getUserGroups(userId) { exitEditModeCurrentUserGroups(); userDialog.value.isGroupsLoading = true; @@ -2161,6 +2261,10 @@ userDialog.value.isGroupsLoading = false; } + /** + * + * @param userId + */ async function getUserMutualFriends(userId) { userDialog.value.mutualFriends = []; if (currentUser.value.hasSharedConnectionsOptOut) { @@ -2200,6 +2304,11 @@ }); } + /** + * + * @param a + * @param b + */ function sortGroupsByInGame(a, b) { const aIndex = inGameGroupOrder.value.indexOf(a?.id); const bIndex = inGameGroupOrder.value.indexOf(b?.id); @@ -2215,6 +2324,9 @@ return aIndex - bIndex; } + /** + * + */ async function sortCurrentUserGroups() { const D = userDialog.value; let sortMethod = () => 0; @@ -2237,6 +2349,10 @@ userDialog.value.userGroups.remainingGroups.sort(sortMethod); } + /** + * + * @param userId + */ function setUserDialogAvatars(userId) { const avatars = new Set(); userDialogAvatars.value.forEach((avatar) => { @@ -2250,6 +2366,10 @@ sortUserDialogAvatars(userDialog.value.avatars); } + /** + * + * @param userId + */ function setUserDialogWorlds(userId) { const worlds = []; for (const ref of cachedWorlds.values()) { @@ -2260,6 +2380,9 @@ userDialog.value.worlds = worlds; } + /** + * + */ function refreshUserDialogWorlds() { const D = userDialog.value; if (D.isWorldsLoading) { @@ -2306,6 +2429,10 @@ }); } + /** + * + * @param userId + */ async function getUserFavoriteWorlds(userId) { userDialog.value.isFavoriteWorldsLoading = true; favoriteWorldsTab.value = '0'; @@ -2344,6 +2471,9 @@ userDialog.value.isFavoriteWorldsLoading = false; } + /** + * + */ function showBioDialog() { const D = bioDialog.value; D.bio = currentUser.value.bio; @@ -2351,6 +2481,9 @@ D.visible = true; } + /** + * + */ async function translateBio() { if (translateLoading.value) { return; @@ -2388,22 +2521,35 @@ } } + /** + * + * @param userRef + */ function showPreviousInstancesListDialog(userRef) { instanceStore.showPreviousInstancesListDialog('user', userRef); } + /** + * + */ function toggleAvatarCopying() { userRequest.saveCurrentUser({ allowAvatarCopying: !currentUser.value.allowAvatarCopying }); } + /** + * + */ function toggleAllowBooping() { userRequest.saveCurrentUser({ isBoopingEnabled: !currentUser.value.isBoopingEnabled }); } + /** + * + */ function resetHome() { modalStore .confirm({ @@ -2426,14 +2572,26 @@ .catch(() => {}); } + /** + * + * @param userId + */ function copyUserId(userId) { copyToClipboard(userId, 'User ID copied to clipboard'); } + /** + * + * @param userId + */ function copyUserURL(userId) { copyToClipboard(`https://vrchat.com/home/user/${userId}`, 'User URL copied to clipboard'); } + /** + * + * @param displayName + */ function copyUserDisplayName(displayName) { copyToClipboard(displayName, 'User DisplayName copied to clipboard'); } @@ -2449,6 +2607,10 @@ return found ? String(found[0]) : ''; }); + /** + * + * @param key + */ function setUserDialogGroupSortingByKey(key) { const option = userDialogGroupSortingOptions[key]; if (!option) { @@ -2457,6 +2619,10 @@ setUserDialogGroupSorting(option); } + /** + * + * @param sortOrder + */ async function setUserDialogGroupSorting(sortOrder) { const D = userDialog.value; if (D.groupSorting.value === sortOrder.value) { @@ -2477,6 +2643,10 @@ return found ? String(found[0]) : ''; }); + /** + * + * @param key + */ function setUserDialogMutualFriendSortingByKey(key) { const option = userDialogMutualFriendSortingOptions[key]; if (!option) { @@ -2485,6 +2655,10 @@ setUserDialogMutualFriendSorting(option); } + /** + * + * @param sortOrder + */ async function setUserDialogMutualFriendSorting(sortOrder) { const D = userDialog.value; D.mutualFriendSorting = sortOrder; @@ -2501,6 +2675,9 @@ } } + /** + * + */ async function exitEditModeCurrentUserGroups() { userDialogGroupEditMode.value = false; userDialogGroupEditGroups.value = []; @@ -2509,6 +2686,9 @@ await sortCurrentUserGroups(); } + /** + * + */ async function editModeCurrentUserGroups() { await updateInGameGroupOrder(); userDialogGroupEditGroups.value = Array.from(currentUserGroups.value.values()); @@ -2516,6 +2696,9 @@ userDialogGroupEditMode.value = true; } + /** + * + */ async function saveInGameGroupOrder() { userDialogGroupEditGroups.value.sort(sortGroupsByInGame); try { @@ -2531,6 +2714,9 @@ } // Select all groups currently in the editable list by collecting their IDs + /** + * + */ function selectAllGroups() { const allSelected = userDialogGroupEditSelectedGroupIds.value.length === userDialogGroupEditGroups.value.length; @@ -2547,6 +2733,10 @@ const bulkGroupActionValue = ref(''); + /** + * + * @param value + */ function handleBulkGroupAction(value) { bulkGroupActionValue.value = value; @@ -2563,6 +2753,10 @@ } // Apply the given visibility to all selected groups + /** + * + * @param newVisibility + */ async function bulkSetVisibility(newVisibility) { for (const groupId of userDialogGroupEditSelectedGroupIds.value) { setGroupVisibility(groupId, newVisibility); @@ -2570,6 +2764,9 @@ } // Leave (remove user from) all selected groups + /** + * + */ function bulkLeaveGroups() { for (const groupId of userDialogGroupEditSelectedGroupIds.value) { leaveGroup(groupId); @@ -2577,6 +2774,10 @@ } // Toggle individual group selection for bulk actions + /** + * + * @param groupId + */ function toggleGroupSelection(groupId) { const index = userDialogGroupEditSelectedGroupIds.value.indexOf(groupId); if (index === -1) { @@ -2586,6 +2787,10 @@ } } + /** + * + * @param groupId + */ function moveGroupUp(groupId) { const index = inGameGroupOrder.value.indexOf(groupId); if (index > 0) { @@ -2595,6 +2800,10 @@ } } + /** + * + * @param groupId + */ function moveGroupDown(groupId) { const index = inGameGroupOrder.value.indexOf(groupId); if (index < inGameGroupOrder.value.length - 1) { @@ -2604,6 +2813,10 @@ } } + /** + * + * @param groupId + */ function moveGroupTop(groupId) { const index = inGameGroupOrder.value.indexOf(groupId); if (index > 0) { @@ -2613,6 +2826,10 @@ } } + /** + * + * @param groupId + */ function moveGroupBottom(groupId) { const index = inGameGroupOrder.value.indexOf(groupId); if (index < inGameGroupOrder.value.length - 1) { @@ -2622,6 +2839,10 @@ } } + /** + * + * @param sortOrder + */ async function setUserDialogWorldSorting(sortOrder) { const D = userDialog.value; if (D.worldSorting.value === sortOrder.value) { @@ -2642,6 +2863,10 @@ return found ? String(found[0]) : ''; }); + /** + * + * @param key + */ function setUserDialogWorldSortingByKey(key) { const option = userDialogWorldSortingOptions[key]; if (!option) { @@ -2650,6 +2875,10 @@ setUserDialogWorldSorting(option); } + /** + * + * @param order + */ async function setUserDialogWorldOrder(order) { const D = userDialog.value; if (D.worldOrder.value === order.value) { @@ -2670,6 +2899,10 @@ return found ? String(found[0]) : ''; }); + /** + * + * @param key + */ function setUserDialogWorldOrderByKey(key) { const option = userDialogWorldOrderOptions[key]; if (!option) { @@ -2678,16 +2911,26 @@ setUserDialogWorldOrder(option); } + /** + * + * @param sortOption + */ function changeUserDialogAvatarSorting(sortOption) { const D = userDialog.value; D.avatarSorting = sortOption; sortUserDialogAvatars(D.avatars); } + /** + * + */ function closeInviteDialog() { clearInviteImageUpload(); } + /** + * + */ function getVRChatCredits() { miscRequest.getVRChatCredits().then((args) => (vrchatCredit.value = args.json?.balance)); } diff --git a/src/components/ui/input-group/InputGroupTextarea.vue b/src/components/ui/input-group/InputGroupTextarea.vue index a2083668..a0b6f9a7 100644 --- a/src/components/ui/input-group/InputGroupTextarea.vue +++ b/src/components/ui/input-group/InputGroupTextarea.vue @@ -12,7 +12,7 @@ data-slot="input-group-control" :class=" cn( - 'flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent', + 'flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent break-all', props.class ) " /> diff --git a/src/localization/en.json b/src/localization/en.json index dff4cc2a..155e9284 100644 --- a/src/localization/en.json +++ b/src/localization/en.json @@ -76,7 +76,7 @@ } }, "side_panel": { - "search_placeholder": "Search...", + "search_placeholder": "Quick Search...", "search_no_results": "No results found", "search_min_chars": "Type at least 2 characters", "search_categories": "Search for...", @@ -1935,7 +1935,7 @@ "export_bans": "Export Bans", "import_bans": "Import Bans", "import_bans_description": "Paste CSV or user IDs below. The userId column will be auto-detected from CSV headers, or all usr_ IDs will be extracted.", - "import_bans_warning": "Do not import too many users at once. The API is rate-limited and may fail. You are responsible for any consequences.", + "import_bans_warning": "Do not import too many at once. This is a sensitive operation actively monitored and enforced by the official, use at your own risk!", "import_bans_placeholder": "Paste CSV or user IDs here (one per line)\nusr_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "import_bans_start": "Import & Ban", "import_bans_clear_errors": "Clear Errors" diff --git a/src/stores/ui.js b/src/stores/ui.js index 02858c33..c7a68640 100644 --- a/src/stores/ui.js +++ b/src/stores/ui.js @@ -30,6 +30,7 @@ export const useUiStore = defineStore('Ui', () => { const ctrlR = keys['Ctrl+R']; const ctrlD = keys['Ctrl+D']; + const metaD = keys['Meta+D']; const shift = keys['Shift']; const ctrlShiftI = keys['Ctrl+Shift+I']; const altShiftR = keys['Alt+Shift+R']; @@ -51,6 +52,12 @@ export const useUiStore = defineStore('Ui', () => { } }); + watch(metaD, (isPressed) => { + if (isPressed) { + directAccessPaste(); + } + }); + watch(shift, (isHeld) => { shiftHeld.value = isHeld; }); diff --git a/src/views/Sidebar/Sidebar.vue b/src/views/Sidebar/Sidebar.vue index 15875217..9e6f9f50 100644 --- a/src/views/Sidebar/Sidebar.vue +++ b/src/views/Sidebar/Sidebar.vue @@ -4,7 +4,7 @@