mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-27 00:23:56 +00:00
# Pull Request Template ## Description This PR enables URL-based search and tab selection, allowing search queries and active tabs to persist in the URL for easy sharing. Fixes [CW-5766](https://linear.app/chatwoot/issue/CW-5766/cannot-impersonate-an-account), https://github.com/chatwoot/chatwoot/issues/12623 ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? ### Loom video https://www.loom.com/share/422a1d61f3fe4278a88e352ef98d2b78?sid=35fabee7-652f-4e17-83bd-e066a3bb804c ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules
105 lines
2.6 KiB
Vue
105 lines
2.6 KiB
Vue
<script setup>
|
|
import { ref, useTemplateRef, onMounted, onUnmounted, watch } from 'vue';
|
|
import { debounce } from '@chatwoot/utils';
|
|
|
|
const props = defineProps({
|
|
initialQuery: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
});
|
|
|
|
const emit = defineEmits(['search']);
|
|
|
|
const searchQuery = ref(props.initialQuery);
|
|
const isInputFocused = ref(false);
|
|
|
|
const searchInput = useTemplateRef('searchInput');
|
|
|
|
const handler = e => {
|
|
if (e.key === '/' && document.activeElement.tagName !== 'INPUT') {
|
|
e.preventDefault();
|
|
searchInput.value.focus();
|
|
} else if (e.key === 'Escape' && document.activeElement.tagName === 'INPUT') {
|
|
e.preventDefault();
|
|
searchInput.value.blur();
|
|
}
|
|
};
|
|
|
|
const debouncedEmit = debounce(
|
|
value =>
|
|
emit('search', value.length > 1 || value.match(/^[0-9]+$/) ? value : ''),
|
|
500
|
|
);
|
|
|
|
const onInput = e => {
|
|
searchQuery.value = e.target.value;
|
|
debouncedEmit(searchQuery.value);
|
|
};
|
|
|
|
const onFocus = () => {
|
|
isInputFocused.value = true;
|
|
};
|
|
|
|
const onBlur = () => {
|
|
isInputFocused.value = false;
|
|
};
|
|
|
|
watch(
|
|
() => props.initialQuery,
|
|
newValue => {
|
|
if (searchQuery.value !== newValue) {
|
|
searchQuery.value = newValue;
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
onMounted(() => {
|
|
searchInput.value.focus();
|
|
document.addEventListener('keydown', handler);
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
document.removeEventListener('keydown', handler);
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
class="input-container rounded-xl transition-[border-bottom] duration-[0.2s] ease-[ease-in-out] relative flex items-center py-2 px-4 h-14 gap-2 border border-solid bg-n-alpha-black2"
|
|
:class="{
|
|
'border-n-brand': isInputFocused,
|
|
'border-n-weak': !isInputFocused,
|
|
}"
|
|
>
|
|
<div class="flex items-center">
|
|
<fluent-icon
|
|
icon="search"
|
|
class="icon"
|
|
aria-hidden="true"
|
|
:class="{
|
|
'text-n-blue-text': isInputFocused,
|
|
'text-n-slate-10': !isInputFocused,
|
|
}"
|
|
/>
|
|
</div>
|
|
<input
|
|
ref="searchInput"
|
|
type="search"
|
|
class="reset-base outline-none w-full m-0 bg-transparent border-transparent shadow-none text-n-slate-12 dark:text-n-slate-12 active:border-transparent active:shadow-none hover:border-transparent hover:shadow-none focus:border-transparent focus:shadow-none"
|
|
:placeholder="$t('SEARCH.INPUT_PLACEHOLDER')"
|
|
:value="searchQuery"
|
|
@focus="onFocus"
|
|
@blur="onBlur"
|
|
@input="onInput"
|
|
/>
|
|
<woot-label
|
|
:title="$t('SEARCH.PLACEHOLDER_KEYBINDING')"
|
|
:show-close="false"
|
|
small
|
|
class="!m-0 whitespace-nowrap !bg-n-slate-3 dark:!bg-n-solid-3 !border-n-weak dark:!border-n-strong"
|
|
/>
|
|
</div>
|
|
</template>
|