mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:27:09 -05:00 
			
		
		
		
	refactor(web): list navigation with keyboard (#7987)
This commit is contained in:
		
							parent
							
								
									e21c586cc5
								
							
						
					
					
						commit
						997e9c5877
					
				@ -1,7 +1,7 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  import { createEventDispatcher } from 'svelte';
 | 
					  import { createEventDispatcher } from 'svelte';
 | 
				
			||||||
  import ConfirmDialogue from './confirm-dialogue.svelte';
 | 
					  import ConfirmDialogue from './confirm-dialogue.svelte';
 | 
				
			||||||
  import { maximumLengthSearchPeople, timeBeforeShowLoadingSpinner } from '$lib/constants';
 | 
					  import { timeBeforeShowLoadingSpinner } from '$lib/constants';
 | 
				
			||||||
  import { handleError } from '$lib/utils/handle-error';
 | 
					  import { handleError } from '$lib/utils/handle-error';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  import { clickOutside } from '$lib/utils/click-outside';
 | 
					  import { clickOutside } from '$lib/utils/click-outside';
 | 
				
			||||||
@ -10,6 +10,7 @@
 | 
				
			|||||||
  import { timeToLoadTheMap } from '$lib/constants';
 | 
					  import { timeToLoadTheMap } from '$lib/constants';
 | 
				
			||||||
  import { searchPlaces, type AssetResponseDto, type PlacesResponseDto } from '@immich/sdk';
 | 
					  import { searchPlaces, type AssetResponseDto, type PlacesResponseDto } from '@immich/sdk';
 | 
				
			||||||
  import SearchBar from '../elements/search-bar.svelte';
 | 
					  import SearchBar from '../elements/search-bar.svelte';
 | 
				
			||||||
 | 
					  import { listNavigation } from '$lib/utils/list-navigation';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export const title = 'Change Location';
 | 
					  export const title = 'Change Location';
 | 
				
			||||||
  export let asset: AssetResponseDto | undefined = undefined;
 | 
					  export let asset: AssetResponseDto | undefined = undefined;
 | 
				
			||||||
@ -24,8 +25,7 @@
 | 
				
			|||||||
  let searchWord: string;
 | 
					  let searchWord: string;
 | 
				
			||||||
  let isSearching = false;
 | 
					  let isSearching = false;
 | 
				
			||||||
  let showSpinner = false;
 | 
					  let showSpinner = false;
 | 
				
			||||||
  let focusedElements: (HTMLButtonElement | null)[] = Array.from({ length: maximumLengthSearchPeople }, () => null);
 | 
					  let suggestionContainer: HTMLDivElement;
 | 
				
			||||||
  let indexFocus: number | null = null;
 | 
					 | 
				
			||||||
  let hideSuggestion = false;
 | 
					  let hideSuggestion = false;
 | 
				
			||||||
  let addClipMapMarker: (long: number, lat: number) => void;
 | 
					  let addClipMapMarker: (long: number, lat: number) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -41,7 +41,6 @@
 | 
				
			|||||||
  $: {
 | 
					  $: {
 | 
				
			||||||
    if (places) {
 | 
					    if (places) {
 | 
				
			||||||
      suggestedPlaces = places.slice(0, 5);
 | 
					      suggestedPlaces = places.slice(0, 5);
 | 
				
			||||||
      indexFocus = null;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (searchWord === '') {
 | 
					    if (searchWord === '') {
 | 
				
			||||||
      suggestedPlaces = [];
 | 
					      suggestedPlaces = [];
 | 
				
			||||||
@ -93,52 +92,8 @@
 | 
				
			|||||||
    point = { lng: longitude, lat: latitude };
 | 
					    point = { lng: longitude, lat: latitude };
 | 
				
			||||||
    addClipMapMarker(longitude, latitude);
 | 
					    addClipMapMarker(longitude, latitude);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleKeyboardPress = (event: KeyboardEvent) => {
 | 
					 | 
				
			||||||
    if (suggestedPlaces.length === 0) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    event.stopPropagation();
 | 
					 | 
				
			||||||
    switch (event.key) {
 | 
					 | 
				
			||||||
      case 'ArrowDown': {
 | 
					 | 
				
			||||||
        event.preventDefault();
 | 
					 | 
				
			||||||
        if (indexFocus === null) {
 | 
					 | 
				
			||||||
          indexFocus = 0;
 | 
					 | 
				
			||||||
        } else if (indexFocus === suggestedPlaces.length - 1) {
 | 
					 | 
				
			||||||
          indexFocus = 0;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          indexFocus++;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        focusedElements[indexFocus]?.focus();
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      case 'ArrowUp': {
 | 
					 | 
				
			||||||
        if (indexFocus === null) {
 | 
					 | 
				
			||||||
          indexFocus = 0;
 | 
					 | 
				
			||||||
          return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (indexFocus === 0) {
 | 
					 | 
				
			||||||
          indexFocus = suggestedPlaces.length - 1;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          indexFocus--;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        focusedElements[indexFocus]?.focus();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      case 'Enter': {
 | 
					 | 
				
			||||||
        if (indexFocus !== null) {
 | 
					 | 
				
			||||||
          hideSuggestion = true;
 | 
					 | 
				
			||||||
          handleUseSuggested(suggestedPlaces[indexFocus].latitude, suggestedPlaces[indexFocus].longitude);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<svelte:document on:keydown={handleKeyboardPress} />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<ConfirmDialogue
 | 
					<ConfirmDialogue
 | 
				
			||||||
  confirmColor="primary"
 | 
					  confirmColor="primary"
 | 
				
			||||||
  cancelColor="secondary"
 | 
					  cancelColor="secondary"
 | 
				
			||||||
@ -148,7 +103,11 @@
 | 
				
			|||||||
  onClose={handleCancel}
 | 
					  onClose={handleCancel}
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
  <div slot="prompt" class="flex flex-col w-full h-full gap-2">
 | 
					  <div slot="prompt" class="flex flex-col w-full h-full gap-2">
 | 
				
			||||||
    <div class="relative w-64 sm:w-96" use:clickOutside on:outclick={() => (hideSuggestion = true)}>
 | 
					    <div
 | 
				
			||||||
 | 
					      class="relative w-64 sm:w-96"
 | 
				
			||||||
 | 
					      use:clickOutside={{ onOutclick: () => (hideSuggestion = true) }}
 | 
				
			||||||
 | 
					      use:listNavigation={suggestionContainer}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
      <button class="w-full" on:click={() => (hideSuggestion = false)}>
 | 
					      <button class="w-full" on:click={() => (hideSuggestion = false)}>
 | 
				
			||||||
        <SearchBar
 | 
					        <SearchBar
 | 
				
			||||||
          placeholder="Search places"
 | 
					          placeholder="Search places"
 | 
				
			||||||
@ -161,11 +120,10 @@
 | 
				
			|||||||
          roundedBottom={suggestedPlaces.length === 0 || hideSuggestion}
 | 
					          roundedBottom={suggestedPlaces.length === 0 || hideSuggestion}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </button>
 | 
					      </button>
 | 
				
			||||||
      <div class="absolute z-[99] w-full" id="suggestion">
 | 
					      <div class="absolute z-[99] w-full" id="suggestion" bind:this={suggestionContainer}>
 | 
				
			||||||
        {#if !hideSuggestion}
 | 
					        {#if !hideSuggestion}
 | 
				
			||||||
          {#each suggestedPlaces as place, index}
 | 
					          {#each suggestedPlaces as place, index}
 | 
				
			||||||
            <button
 | 
					            <button
 | 
				
			||||||
              bind:this={focusedElements[index]}
 | 
					 | 
				
			||||||
              class=" flex w-full border-t border-gray-400 dark:border-immich-dark-gray h-14 place-items-center bg-gray-200 p-2 dark:bg-gray-700 hover:bg-gray-300 hover:dark:bg-[#232932] focus:bg-gray-300 focus:dark:bg-[#232932] {index ===
 | 
					              class=" flex w-full border-t border-gray-400 dark:border-immich-dark-gray h-14 place-items-center bg-gray-200 p-2 dark:bg-gray-700 hover:bg-gray-300 hover:dark:bg-[#232932] focus:bg-gray-300 focus:dark:bg-[#232932] {index ===
 | 
				
			||||||
              suggestedPlaces.length - 1
 | 
					              suggestedPlaces.length - 1
 | 
				
			||||||
                ? 'rounded-b-lg border-b'
 | 
					                ? 'rounded-b-lg border-b'
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										32
									
								
								web/src/lib/utils/list-navigation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								web/src/lib/utils/list-navigation.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					import type { Action } from 'svelte/action';
 | 
				
			||||||
 | 
					import { shortcuts } from './shortcut';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const listNavigation: Action<HTMLElement, HTMLElement> = (node, container: HTMLElement) => {
 | 
				
			||||||
 | 
					  const moveFocus = (direction: 'up' | 'down') => {
 | 
				
			||||||
 | 
					    const children = Array.from(container?.children);
 | 
				
			||||||
 | 
					    if (children.length === 0) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const currentIndex = document.activeElement === null ? -1 : children.indexOf(document.activeElement);
 | 
				
			||||||
 | 
					    const directionFactor = (direction === 'up' ? -1 : 1) + (direction === 'up' && currentIndex === -1 ? 1 : 0);
 | 
				
			||||||
 | 
					    const newIndex = (currentIndex + directionFactor + children.length) % children.length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const element = children.at(newIndex);
 | 
				
			||||||
 | 
					    if (element instanceof HTMLElement) {
 | 
				
			||||||
 | 
					      element.focus();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { destroy } = shortcuts(node, [
 | 
				
			||||||
 | 
					    { shortcut: { key: 'ArrowUp' }, onShortcut: () => moveFocus('up'), ignoreInputFields: false },
 | 
				
			||||||
 | 
					    { shortcut: { key: 'ArrowDown' }, onShortcut: () => moveFocus('down'), ignoreInputFields: false },
 | 
				
			||||||
 | 
					  ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    update(newContainer) {
 | 
				
			||||||
 | 
					      container = newContainer;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    destroy,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -10,6 +10,7 @@ export type Shortcut = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export type ShortcutOptions<T = HTMLElement> = {
 | 
					export type ShortcutOptions<T = HTMLElement> = {
 | 
				
			||||||
  shortcut: Shortcut;
 | 
					  shortcut: Shortcut;
 | 
				
			||||||
 | 
					  ignoreInputFields?: boolean;
 | 
				
			||||||
  onShortcut: (event: KeyboardEvent & { currentTarget: T }) => unknown;
 | 
					  onShortcut: (event: KeyboardEvent & { currentTarget: T }) => unknown;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -50,11 +51,13 @@ export const shortcuts = <T extends HTMLElement>(
 | 
				
			|||||||
  options: ShortcutOptions<T>[],
 | 
					  options: ShortcutOptions<T>[],
 | 
				
			||||||
): ActionReturn<ShortcutOptions<T>[]> => {
 | 
					): ActionReturn<ShortcutOptions<T>[]> => {
 | 
				
			||||||
  function onKeydown(event: KeyboardEvent) {
 | 
					  function onKeydown(event: KeyboardEvent) {
 | 
				
			||||||
    if (shouldIgnoreShortcut(event)) {
 | 
					    const ignoreShortcut = shouldIgnoreShortcut(event);
 | 
				
			||||||
      return;
 | 
					
 | 
				
			||||||
 | 
					    for (const { shortcut, onShortcut, ignoreInputFields = true } of options) {
 | 
				
			||||||
 | 
					      if (ignoreInputFields && ignoreShortcut) {
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const { shortcut, onShortcut } of options) {
 | 
					 | 
				
			||||||
      if (matchesShortcut(event, shortcut)) {
 | 
					      if (matchesShortcut(event, shortcut)) {
 | 
				
			||||||
        event.preventDefault();
 | 
					        event.preventDefault();
 | 
				
			||||||
        onShortcut(event as KeyboardEvent & { currentTarget: T });
 | 
					        onShortcut(event as KeyboardEvent & { currentTarget: T });
 | 
				
			||||||
 | 
				
			|||||||
@ -47,6 +47,7 @@
 | 
				
			|||||||
  import { mdiArrowLeft, mdiDotsVertical, mdiPlus } from '@mdi/js';
 | 
					  import { mdiArrowLeft, mdiDotsVertical, mdiPlus } from '@mdi/js';
 | 
				
			||||||
  import { onMount } from 'svelte';
 | 
					  import { onMount } from 'svelte';
 | 
				
			||||||
  import type { PageData } from './$types';
 | 
					  import type { PageData } from './$types';
 | 
				
			||||||
 | 
					  import { listNavigation } from '$lib/utils/list-navigation';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let data: PageData;
 | 
					  export let data: PageData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -95,8 +96,7 @@
 | 
				
			|||||||
   **/
 | 
					   **/
 | 
				
			||||||
  let searchWord: string;
 | 
					  let searchWord: string;
 | 
				
			||||||
  let isSearchingPeople = false;
 | 
					  let isSearchingPeople = false;
 | 
				
			||||||
  let focusedElements: (HTMLButtonElement | null)[] = Array.from({ length: maximumLengthSearchPeople }, () => null);
 | 
					  let suggestionContainer: HTMLDivElement;
 | 
				
			||||||
  let indexFocus: number | null = null;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const searchPeople = async () => {
 | 
					  const searchPeople = async () => {
 | 
				
			||||||
    if ((people.length < maximumLengthSearchPeople && name.startsWith(searchWord)) || name === '') {
 | 
					    if ((people.length < maximumLengthSearchPeople && name.startsWith(searchWord)) || name === '') {
 | 
				
			||||||
@ -122,7 +122,6 @@
 | 
				
			|||||||
  $: {
 | 
					  $: {
 | 
				
			||||||
    if (people) {
 | 
					    if (people) {
 | 
				
			||||||
      suggestedPeople = name ? searchNameLocal(name, people, 5, data.person.id) : [];
 | 
					      suggestedPeople = name ? searchNameLocal(name, people, 5, data.person.id) : [];
 | 
				
			||||||
      indexFocus = null;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -143,48 +142,6 @@
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleKeyboardPress = (event: KeyboardEvent) => {
 | 
					 | 
				
			||||||
    if (suggestedPeople.length === 0) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!$showAssetViewer) {
 | 
					 | 
				
			||||||
      event.stopPropagation();
 | 
					 | 
				
			||||||
      switch (event.key) {
 | 
					 | 
				
			||||||
        case 'ArrowDown': {
 | 
					 | 
				
			||||||
          event.preventDefault();
 | 
					 | 
				
			||||||
          if (indexFocus === null) {
 | 
					 | 
				
			||||||
            indexFocus = 0;
 | 
					 | 
				
			||||||
          } else if (indexFocus === suggestedPeople.length - 1) {
 | 
					 | 
				
			||||||
            indexFocus = 0;
 | 
					 | 
				
			||||||
          } else {
 | 
					 | 
				
			||||||
            indexFocus++;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          focusedElements[indexFocus]?.focus();
 | 
					 | 
				
			||||||
          return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        case 'ArrowUp': {
 | 
					 | 
				
			||||||
          if (indexFocus === null) {
 | 
					 | 
				
			||||||
            indexFocus = 0;
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          if (indexFocus === 0) {
 | 
					 | 
				
			||||||
            indexFocus = suggestedPeople.length - 1;
 | 
					 | 
				
			||||||
          } else {
 | 
					 | 
				
			||||||
            indexFocus--;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          focusedElements[indexFocus]?.focus();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        case 'Enter': {
 | 
					 | 
				
			||||||
          if (indexFocus !== null) {
 | 
					 | 
				
			||||||
            handleSuggestPeople(suggestedPeople[indexFocus]);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleEscape = async () => {
 | 
					  const handleEscape = async () => {
 | 
				
			||||||
    if ($showAssetViewer || viewMode === ViewMode.SUGGEST_MERGE) {
 | 
					    if ($showAssetViewer || viewMode === ViewMode.SUGGEST_MERGE) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
@ -401,7 +358,6 @@
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<svelte:document on:keydown={handleKeyboardPress} />
 | 
					 | 
				
			||||||
{#if viewMode === ViewMode.UNASSIGN_ASSETS}
 | 
					{#if viewMode === ViewMode.UNASSIGN_ASSETS}
 | 
				
			||||||
  <UnMergeFaceSelector
 | 
					  <UnMergeFaceSelector
 | 
				
			||||||
    assetIds={[...$selectedAssets].map((a) => a.id)}
 | 
					    assetIds={[...$selectedAssets].map((a) => a.id)}
 | 
				
			||||||
@ -491,11 +447,12 @@
 | 
				
			|||||||
      {#if viewMode === ViewMode.VIEW_ASSETS || viewMode === ViewMode.SUGGEST_MERGE || viewMode === ViewMode.BIRTH_DATE}
 | 
					      {#if viewMode === ViewMode.VIEW_ASSETS || viewMode === ViewMode.SUGGEST_MERGE || viewMode === ViewMode.BIRTH_DATE}
 | 
				
			||||||
        <!-- Person information block -->
 | 
					        <!-- Person information block -->
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          role="button"
 | 
					 | 
				
			||||||
          class="relative w-fit p-4 sm:px-6"
 | 
					          class="relative w-fit p-4 sm:px-6"
 | 
				
			||||||
          use:clickOutside
 | 
					          use:clickOutside={{
 | 
				
			||||||
          on:outclick={handleCancelEditName}
 | 
					            onOutclick: handleCancelEditName,
 | 
				
			||||||
          on:escape={handleCancelEditName}
 | 
					            onEscape: handleCancelEditName,
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					          use:listNavigation={suggestionContainer}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <section class="flex w-64 sm:w-96 place-items-center border-black">
 | 
					          <section class="flex w-64 sm:w-96 place-items-center border-black">
 | 
				
			||||||
            {#if isEditingName}
 | 
					            {#if isEditingName}
 | 
				
			||||||
@ -550,9 +507,9 @@
 | 
				
			|||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
              {:else}
 | 
					              {:else}
 | 
				
			||||||
 | 
					                <div bind:this={suggestionContainer}>
 | 
				
			||||||
                  {#each suggestedPeople as person, index (person.id)}
 | 
					                  {#each suggestedPeople as person, index (person.id)}
 | 
				
			||||||
                    <button
 | 
					                    <button
 | 
				
			||||||
                    bind:this={focusedElements[index]}
 | 
					 | 
				
			||||||
                      class="flex w-full border-t border-gray-400 dark:border-immich-dark-gray h-14 place-items-center bg-gray-200 p-2 dark:bg-gray-700 hover:bg-gray-300 hover:dark:bg-[#232932] focus:bg-gray-300 focus:dark:bg-[#232932] {index ===
 | 
					                      class="flex w-full border-t border-gray-400 dark:border-immich-dark-gray h-14 place-items-center bg-gray-200 p-2 dark:bg-gray-700 hover:bg-gray-300 hover:dark:bg-[#232932] focus:bg-gray-300 focus:dark:bg-[#232932] {index ===
 | 
				
			||||||
                      suggestedPeople.length - 1
 | 
					                      suggestedPeople.length - 1
 | 
				
			||||||
                        ? 'rounded-b-lg border-b'
 | 
					                        ? 'rounded-b-lg border-b'
 | 
				
			||||||
@ -570,6 +527,7 @@
 | 
				
			|||||||
                      <p class="ml-4 text-gray-700 dark:text-gray-100">{person.name}</p>
 | 
					                      <p class="ml-4 text-gray-700 dark:text-gray-100">{person.name}</p>
 | 
				
			||||||
                    </button>
 | 
					                    </button>
 | 
				
			||||||
                  {/each}
 | 
					                  {/each}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
              {/if}
 | 
					              {/if}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          {/if}
 | 
					          {/if}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user