Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(search): add search shortcuts, highlight search box and search r… #464

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 28 additions & 14 deletions src/components/card.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,33 @@
<badge :status="status" v-if="info.uname"></badge>
</div>

<div class="column is-12-mobile is-6-tablet is-6-desktop is-6-widescreen is-6-fullhd content smallBottomMarginTopBottomPadding">
<h4 class="noMargin">
<a :href="`https://live.bilibili.com/${roomid}`" class="tag is-link" v-if="liveStatus" target="_blank">
<div class="content column is-12-mobile is-6-tablet is-6-desktop is-6-widescreen is-6-fullhd smallBottomMarginTopBottomPadding">
<h4 class="is-fullwidth is-flex is-align-items-center">
<a :href="`https://live.bilibili.com/${roomid}`" class="tag card-badge is-link" v-if="liveStatus" target="_blank">
直播中
</a>
<a :href="`https://live.bilibili.com/${roomid}`" class="tag" v-else-if="roomid && livePage" target="_blank">
<a :href="`https://live.bilibili.com/${roomid}`" class="tag card-badge" v-else-if="roomid && livePage" target="_blank">
没播
</a>
{{uname}}
<router-link v-if="worm" to="about" class="tag" title="如何扩充名单: 关于">
<text-highlight style="margin: 0 2px;" :keyword="search" :text="uname.toString()" />
<!-- <router-link v-if="worm" to="about" class="tag" title="如何扩充名单: 关于">
未收录
</router-link> -->
<!-- <span>
<a :href="`https://space.bilibili.com/${mid}`" target="_blank" class="space tag ml-auto">
{{mid}}
</a></span> -->
</h4>
<h4 class="is-fullwidth is-flex is-align-items-center is-justify-content-flex-start" >
<router-link v-if="worm" to="about" class="tag card-badge" title="如何扩充名单: 关于">
未收录
</router-link>
<a :href="`https://space.bilibili.com/${mid}`" target="_blank" class="space tag">
<a :href="`https://space.bilibili.com/${mid}`" target="_blank" class="tag card-badge ml-auto">
{{mid}}
</a>
</h4>
<span v-if="liveStatus" class="el-icon-ship">{{title}}</span>
<p>{{sign}}</p>
<text-highlight :keyword="search" :text="sign.toString()" />
<hr class="is-hidden-tablet">
</div>

Expand All @@ -46,15 +55,21 @@

<script>
import badge from '@/components/badge'
import textHighlight from '@/components/textHighlight'
import moment from 'moment'

export default {
components: {
badge,
'text-highlight': textHighlight
},
props: {
vtb: Object,
hover: Boolean,
search: {
type: String,
default: ''
}
},
computed: {
info: function() {
Expand Down Expand Up @@ -147,8 +162,11 @@ export default {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
}

.noMargin {
margin: 0;
.content h4:first-of-type{
margin-bottom: 4px;
}
.card-badge {
margin: 0 2px;
}

.smallBottomMarginTopBottomPadding {
Expand All @@ -161,10 +179,6 @@ export default {
margin-bottom: 8px;
}

.space {
float: right;
}

.discover {
position: absolute;
z-index: 20;
Expand Down
75 changes: 75 additions & 0 deletions src/components/textHighlight.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<template>
<span class="searchHighlight">
<span v-if="keyword != ''" class="searchHighlightRow">
<span v-for="fragment in highlightedText" :key="fragment.id"
:class="{ searchHighlightLighted: fragment.highlight }" v-text="fragment.text">
</span>
</span>
<p v-else v-text="text" class="searchHighlightRow">
</p>
</span>
</template>

<script>
export default {
props: {
text: {
type: String,
default: ''
},
keyword: {
type: String,
default: ''
}
},
computed: {
highlightedText() {
// 正则匹配切分文本中的高亮词
if (this.text && this.keyword && this.text != '' && this.keyword != ' ') {
const fragments = [];
let remainingText = this.text;
const keywordRegex = new RegExp(this.keyword, "gi");
while (remainingText.length > 0) {
const match = keywordRegex.exec(remainingText);
if (match) {
const startIndex = match.index;
const endIndex = match.index + match[0].length;
if (startIndex > 0) {
fragments.push({
text: remainingText.substring(0, startIndex),
highlight: false
});
}
fragments.push({
text: remainingText.substring(startIndex, endIndex),
highlight: true
});
remainingText = remainingText.substring(endIndex);
} else {
fragments.push({
text: remainingText,
highlight: false
});
break;
}
}
return fragments;
}
}
}
}
</script>

<style scoped>
.searchHighlight{
display: inline;
width: auto;
}
.searchHighlightRow {
display: inline;
}

.searchHighlightLighted {
color: #409eff
}
</style>
90 changes: 82 additions & 8 deletions src/views/Home.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
<template>
<div class="column is-gapless gap">
<input class="input search is-rounded vtb-search" v-model="search" type="text" placeholder="查找主播~">
<div :class="`vtb-search ${searchFocusStatus ? 'search-focus' : ''}`">
<div class="is-relative">
<input ref="searchInput" :class="`input search is-rounded`" v-model="search" type="text" placeholder="查找主播~">
<div v-if="!searchFocusStatus && (!search || search.length === 0)" class="search-shortcut">
<span>/</span>
</div>
</div>
</div>
<div class="columns">
<div class="column vtb-column"></div>
<div class="column is-full-mobile is-11-tablet is-10-desktop is-three-fifths-widescreen is-7-fullhd" :style="{height: sumHeight}" ref="container">
<p v-if="cacheAge">数据缓存于: <span class="tag is-rounded is-info smallMargin">{{cacheAge}}</span></p>
<progress class="progress" max="100" v-if="!currentVtbs.length"></progress>
<card v-if="mountCard" :vtb="mountCard" hover class="aboveTop" ref="mountCard"></card>
<transition-group name="flip-list">
<card v-for="{vtb, h} in rankLimit" :vtb="vtb" hover :key="vtb.mid" class="card" :style="{top: h, width: unitWidth}"></card>
<card :search="search" v-for="{vtb, h} in rankLimit" :vtb="vtb" hover :key="vtb.mid" class="card" :style="{top: h, width: unitWidth}"></card>
</transition-group>
</div>
<div class="column"></div>
Expand All @@ -32,6 +39,26 @@ export default {
...mapActions([
'fetchSecretList',
]),
handleKeyDown(event) {
const isSearchShortcut = (event.key === '/')
const isSearchEsc = (event.key === 'Escape')
if (isSearchShortcut) {
this.searchFocusStatus ? '' : event.preventDefault()
this.searchFocusStatus = true
this.$refs.searchInput.focus();
}
if (isSearchEsc) {
if (this.searchFocusStatus) {
this.$refs.searchInput.blur();
this.searchFocusStatus = false
}
}
},
handleSearchBlur() {
if (this.searchFocusStatus) {
this.searchFocusStatus = false
}
}
},

data() {
Expand All @@ -43,6 +70,7 @@ export default {
unitHeight: 172,
unitWidth: undefined,
ratio: 0,
searchFocusStatus: false,
}
},
components: {
Expand Down Expand Up @@ -117,7 +145,13 @@ export default {
preRank() {
const keys = this.search.toLowerCase().split(' ').filter(Boolean)
if (keys.length) {
return this.rank.filter(i => keys.every(key => ((this.$store.getters.info[i.mid] || {}).uname || []).toLowerCase().includes(key)))
return this.rank.filter(i => keys.every(key => {
const user = this.$store.getters.info[i.mid];
if (user && user.uname && typeof user.uname === 'string') {
return user.uname.toLowerCase().includes(key);
}
return false;
}));
}
return this.rank
},
Expand All @@ -142,6 +176,16 @@ export default {
},
mounted() {
this.intersectionObserver.observe(this.$refs.container)
// 挂载搜索相关监听
window.addEventListener('keydown', this.handleKeyDown)
this.$refs.searchInput.addEventListener('keydown', this.handleKeyDown)
this.$refs.searchInput.addEventListener('blur', this.handleSearchBlur)
},
beforeDestroy(){
// 移除搜索相关监听
window.removeEventListener('keydown', this.handleKeyDown)
this.$refs.searchInput.removeEventListener('keydown', this.handleKeyDown)
this.$refs.searchInput.removeEventListener('blur', this.handleSearchBlur)
},
destroyed() {
this.resizeObserver.disconnect()
Expand All @@ -154,18 +198,48 @@ export default {
.vtb-search {
right: 10px;
bottom: 10px;
box-shadow: inset 0 0.0625em 1em rgba(10, 10, 10, 0.05);
outline: none;
/* border: none; */
width: 260px;
height: 40px;
position: fixed;
z-index: 99;
}

.vtb-search>div>input {
box-shadow: inset 0 0.0625em 1em rgba(10, 10, 10, 0.05);
outline: none;
backdrop-filter: blur(6px) grayscale(50%);
background-color: rgba(255, 255, 255, 0.3);
}

.vtb-search:focus {
box-shadow: inset 0 0.0625em 1em rgba(10, 10, 10, 0.05);
.search-shortcut {
width: 24px;
color: #dbdbdb;
background-color: rgba(150, 150, 150, 0.06);
border: 1px solid #dbdbdb;
border-radius: 4px;
right: 24px;
top: 8px;
height: 24px;
position: absolute;
display: inline-flex;
align-items: center;
justify-content: center;
}

.search-shortcut>span {
font-size: 14px;
line-height: 16px;
}

.search-focus {
top: calc(50% - 12px);
right: calc(50vw - 130px);
transition: .5s cubic-bezier(0.39, 0.575, 0.565, 1);
}
.search-focus>div>input {
box-shadow: inset 0 0.0625em 1em rgba(10, 10, 10, 0.05),
0 0 3000px 3000px rgba(10, 10, 10, 0.5);
transition: .5s cubic-bezier(0.39, 0.575, 0.565, 1);
}

.vtb-column {
Expand Down
Loading