misskey/packages/frontend/src/components/global/SearchMarker.vue

117 lines
2.3 KiB
Vue

<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div ref="root" :class="[$style.root, { [$style.highlighted]: highlighted }]">
<slot></slot>
</div>
</template>
<script lang="ts" setup>
import {
onActivated,
onDeactivated,
onMounted,
onBeforeUnmount,
watch,
computed,
ref,
useTemplateRef,
inject,
} from 'vue';
import type { Ref } from 'vue';
const props = defineProps<{
markerId?: string;
label?: string;
icon?: string;
keywords?: string[];
children?: string[];
inlining?: string[];
}>();
const rootEl = useTemplateRef('root');
const rootElMutationObserver = new MutationObserver(() => {
checkChildren();
});
const injectedSearchMarkerId = inject<Ref<string | null> | null>('inAppSearchMarkerId', null);
const searchMarkerId = computed(() => injectedSearchMarkerId?.value ?? window.location.hash.slice(1));
const highlighted = ref(props.markerId === searchMarkerId.value);
function checkChildren() {
if (props.children?.includes(searchMarkerId.value)) {
const el = window.document.querySelector(`[data-in-app-search-marker-id="${searchMarkerId.value}"]`);
highlighted.value = el == null;
}
}
watch([
searchMarkerId,
() => props.children,
], () => {
if (props.children != null && props.children.length > 0) {
checkChildren();
}
}, { flush: 'post' });
function init() {
checkChildren();
if (highlighted.value) {
rootEl.value?.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
if (rootEl.value != null) {
rootElMutationObserver.observe(rootEl.value, {
childList: true,
subtree: true,
});
}
}
function dispose() {
rootElMutationObserver.disconnect();
}
onMounted(init);
onActivated(init);
onDeactivated(dispose);
onBeforeUnmount(dispose);
</script>
<style lang="scss" module>
.root {
position: relative;
}
.highlighted {
&::after {
content: '';
position: absolute;
top: -8px;
left: -8px;
width: calc(100% + 16px);
height: calc(100% + 16px);
border-radius: 6px;
animation: blink 1s 3.5;
pointer-events: none;
}
}
@keyframes blink {
0%, 100% {
background: color(from var(--MI_THEME-accent) srgb r g b / 0.05);
border: 1px solid color(from var(--MI_THEME-accent) srgb r g b / 0.7);
}
50% {
background: transparent;
border: 1px solid transparent;
}
}
</style>