mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-10 20:36:55 +08:00
chore: format code style and update linting configuration
This commit is contained in:
@@ -1,3 +1,33 @@
|
||||
<script setup>
|
||||
import { NTooltip } from 'naive-ui'
|
||||
import { computed } from 'vue'
|
||||
import { FILE_BASE_URL } from '@/data/constants'
|
||||
import * as constants from './constants'
|
||||
|
||||
const props = defineProps({
|
||||
isAdmin: Boolean,
|
||||
privilegeType: Number,
|
||||
})
|
||||
|
||||
const authorTypeText = computed(() => {
|
||||
if (props.isAdmin) {
|
||||
return 'moderator'
|
||||
}
|
||||
return props.privilegeType > 0 ? 'member' : ''
|
||||
})
|
||||
|
||||
const readableAuthorTypeText = computed(() => {
|
||||
if (props.isAdmin) {
|
||||
return '管理员'
|
||||
}
|
||||
return constants.getShowGuardLevelText(props.privilegeType)
|
||||
})
|
||||
|
||||
const fileServerUrl = computed(() => {
|
||||
return FILE_BASE_URL
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<yt-live-chat-author-badge-renderer :type="authorTypeText">
|
||||
<NTooltip
|
||||
@@ -41,35 +71,6 @@
|
||||
</yt-live-chat-author-badge-renderer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { NTooltip } from 'naive-ui'
|
||||
import * as constants from './constants'
|
||||
import { FILE_BASE_URL } from '@/data/constants'
|
||||
|
||||
const props = defineProps({
|
||||
isAdmin: Boolean,
|
||||
privilegeType: Number
|
||||
})
|
||||
|
||||
const authorTypeText = computed(() => {
|
||||
if (props.isAdmin) {
|
||||
return 'moderator'
|
||||
}
|
||||
return props.privilegeType > 0 ? 'member' : ''
|
||||
})
|
||||
|
||||
const readableAuthorTypeText = computed(() => {
|
||||
if (props.isAdmin) {
|
||||
return '管理员'
|
||||
}
|
||||
return constants.getShowGuardLevelText(props.privilegeType)
|
||||
})
|
||||
|
||||
const fileServerUrl = computed(() => {
|
||||
return FILE_BASE_URL
|
||||
})
|
||||
</script>
|
||||
|
||||
<style src="@/assets/css/youtube/yt-live-chat-author-badge-renderer.css"></style>
|
||||
|
||||
<style src="@/assets/css/youtube/yt-icon.css"></style>
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import AuthorBadge from './AuthorBadge.vue'
|
||||
import * as constants from './constants'
|
||||
|
||||
const props = defineProps({
|
||||
isInMemberMessage: Boolean,
|
||||
authorName: String,
|
||||
authorType: Number,
|
||||
privilegeType: Number,
|
||||
})
|
||||
|
||||
const AUTHOR_TYPE_ADMIN = constants.AUTHOR_TYPE_ADMIN
|
||||
|
||||
const authorTypeText = computed(() => {
|
||||
return constants.AUTHOR_TYPE_TO_TEXT[props.authorType]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<yt-live-chat-author-chip>
|
||||
<span
|
||||
@@ -18,20 +37,20 @@
|
||||
id="chat-badges"
|
||||
class="style-scope yt-live-chat-author-chip"
|
||||
>
|
||||
<author-badge
|
||||
<AuthorBadge
|
||||
v-if="isInMemberMessage"
|
||||
class="style-scope yt-live-chat-author-chip"
|
||||
:is-admin="false"
|
||||
:privilege-type="privilegeType"
|
||||
/>
|
||||
<template v-else>
|
||||
<author-badge
|
||||
<AuthorBadge
|
||||
v-if="authorType === AUTHOR_TYPE_ADMIN"
|
||||
class="style-scope yt-live-chat-author-chip"
|
||||
is-admin
|
||||
:privilege-type="0"
|
||||
/>
|
||||
<author-badge
|
||||
<AuthorBadge
|
||||
v-if="privilegeType > 0"
|
||||
class="style-scope yt-live-chat-author-chip"
|
||||
:is-admin="false"
|
||||
@@ -42,23 +61,4 @@
|
||||
</yt-live-chat-author-chip>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import AuthorBadge from './AuthorBadge.vue'
|
||||
import * as constants from './constants'
|
||||
|
||||
const props = defineProps({
|
||||
isInMemberMessage: Boolean,
|
||||
authorName: String,
|
||||
authorType: Number,
|
||||
privilegeType: Number
|
||||
})
|
||||
|
||||
const AUTHOR_TYPE_ADMIN = constants.AUTHOR_TYPE_ADMIN
|
||||
|
||||
const authorTypeText = computed(() => {
|
||||
return constants.AUTHOR_TYPE_TO_TEXT[props.authorType]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style src="@/assets/css/youtube/yt-live-chat-author-chip.css"></style>
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import * as models from '../../../data/chat/models'
|
||||
|
||||
const props = defineProps({
|
||||
imgUrl: String,
|
||||
height: String,
|
||||
width: String,
|
||||
})
|
||||
|
||||
const showImgUrl = ref(props.imgUrl)
|
||||
|
||||
watch(() => props.imgUrl, (val) => {
|
||||
showImgUrl.value = val
|
||||
})
|
||||
|
||||
function onLoadError() {
|
||||
if (showImgUrl.value !== models.DEFAULT_AVATAR_URL) {
|
||||
showImgUrl.value = models.DEFAULT_AVATAR_URL
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<yt-img-shadow
|
||||
class="no-transition"
|
||||
@@ -19,27 +42,4 @@
|
||||
</yt-img-shadow>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import * as models from '../../../data/chat/models'
|
||||
|
||||
const props = defineProps({
|
||||
imgUrl: String,
|
||||
height: String,
|
||||
width: String
|
||||
})
|
||||
|
||||
const showImgUrl = ref(props.imgUrl)
|
||||
|
||||
watch(() => props.imgUrl, (val) => {
|
||||
showImgUrl.value = val
|
||||
})
|
||||
|
||||
function onLoadError() {
|
||||
if (showImgUrl.value !== models.DEFAULT_AVATAR_URL) {
|
||||
showImgUrl.value = models.DEFAULT_AVATAR_URL
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style src="@/assets/css/youtube/yt-img-shadow.css"></style>
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import AuthorChip from './AuthorChip.vue'
|
||||
import ImgShadow from './ImgShadow.vue'
|
||||
import * as utils from './utils'
|
||||
|
||||
const props = defineProps({
|
||||
avatarUrl: String,
|
||||
authorName: String,
|
||||
privilegeType: Number,
|
||||
title: String,
|
||||
time: Date,
|
||||
})
|
||||
|
||||
const timeText = computed(() => {
|
||||
return utils.getTimeTextHourMin(props.time)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<yt-live-chat-membership-item-renderer
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
@@ -12,7 +31,7 @@
|
||||
id="header"
|
||||
class="style-scope yt-live-chat-membership-item-renderer"
|
||||
>
|
||||
<img-shadow
|
||||
<ImgShadow
|
||||
id="author-photo"
|
||||
height="40"
|
||||
width="40"
|
||||
@@ -31,7 +50,7 @@
|
||||
id="header-content-inner-column"
|
||||
class="style-scope yt-live-chat-membership-item-renderer"
|
||||
>
|
||||
<author-chip
|
||||
<AuthorChip
|
||||
class="style-scope yt-live-chat-membership-item-renderer"
|
||||
is-in-member-message
|
||||
:author-name="authorName"
|
||||
@@ -58,23 +77,4 @@
|
||||
</yt-live-chat-membership-item-renderer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import ImgShadow from './ImgShadow.vue'
|
||||
import AuthorChip from './AuthorChip.vue'
|
||||
import * as utils from './utils'
|
||||
|
||||
const props = defineProps({
|
||||
avatarUrl: String,
|
||||
authorName: String,
|
||||
privilegeType: Number,
|
||||
title: String,
|
||||
time: Date
|
||||
})
|
||||
|
||||
const timeText = computed(() => {
|
||||
return utils.getTimeTextHourMin(props.time)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style src="@/assets/css/youtube/yt-live-chat-membership-item-renderer.css"></style>
|
||||
|
||||
@@ -1,97 +1,12 @@
|
||||
<template>
|
||||
<yt-live-chat-renderer
|
||||
class="style-scope yt-live-chat-app"
|
||||
style="--scrollbar-width:11px;"
|
||||
hide-timestamps
|
||||
@mousemove="refreshCantScrollStartTime"
|
||||
>
|
||||
<ticker
|
||||
v-model:messages="paidMessages"
|
||||
class="style-scope yt-live-chat-renderer"
|
||||
:show-gift-name="showGiftName || undefined"
|
||||
/>
|
||||
<yt-live-chat-item-list-renderer
|
||||
class="style-scope yt-live-chat-renderer"
|
||||
allow-scroll
|
||||
>
|
||||
<div
|
||||
id="item-scroller"
|
||||
ref="scroller"
|
||||
class="style-scope yt-live-chat-item-list-renderer animated"
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<div
|
||||
id="item-offset"
|
||||
ref="itemOffset"
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
>
|
||||
<div
|
||||
id="items"
|
||||
ref="items"
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
style="overflow: hidden"
|
||||
:style="{ transform: `translateY(${Math.floor(scrollPixelsRemaining)}px)` }"
|
||||
>
|
||||
<template
|
||||
v-for="message in messages"
|
||||
:key="message.id"
|
||||
>
|
||||
<text-message
|
||||
v-if="message.type === MESSAGE_TYPE_TEXT"
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
:time="message.time"
|
||||
:avatar-url="message.avatarUrl"
|
||||
:author-name="message.authorName"
|
||||
:author-type="message.authorType"
|
||||
:privilege-type="message.privilegeType"
|
||||
:content-parts="getShowContentParts(message)"
|
||||
:repeated="message.repeated"
|
||||
/>
|
||||
<paid-message
|
||||
v-else-if="message.type === MESSAGE_TYPE_GIFT"
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
:time="message.time"
|
||||
:avatar-url="message.avatarUrl"
|
||||
:author-name="getShowAuthorName(message)"
|
||||
:price="message.price"
|
||||
:price-text="message.price <= 0 ? getGiftShowNameAndNum(message) : ''"
|
||||
:content="message.price <= 0 ? '' : getGiftShowContent(message, showGiftName)"
|
||||
/>
|
||||
<membership-item
|
||||
v-else-if="message.type === MESSAGE_TYPE_MEMBER"
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
:time="message.time"
|
||||
:avatar-url="message.avatarUrl"
|
||||
:author-name="getShowAuthorName(message)"
|
||||
:privilege-type="message.privilegeType"
|
||||
:title="message.title"
|
||||
/>
|
||||
<paid-message
|
||||
v-else-if="message.type === MESSAGE_TYPE_SUPER_CHAT"
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
:time="message.time"
|
||||
:avatar-url="message.avatarUrl"
|
||||
:author-name="getShowAuthorName(message)"
|
||||
:price="message.price"
|
||||
:content="getShowContent(message)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</yt-live-chat-item-list-renderer>
|
||||
</yt-live-chat-renderer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import _ from 'lodash'
|
||||
import Ticker from './Ticker.vue'
|
||||
import TextMessage from './TextMessage.vue'
|
||||
import { defineComponent } from 'vue'
|
||||
import * as constants from './constants'
|
||||
import MembershipItem from './MembershipItem.vue'
|
||||
import PaidMessage from './PaidMessage.vue'
|
||||
import * as constants from './constants'
|
||||
import { defineComponent } from 'vue'
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import TextMessage from './TextMessage.vue'
|
||||
import Ticker from './Ticker.vue'
|
||||
|
||||
// 要添加的消息类型
|
||||
const ADD_MESSAGE_TYPES = [
|
||||
@@ -116,24 +31,24 @@ export default defineComponent({
|
||||
Ticker,
|
||||
TextMessage,
|
||||
MembershipItem,
|
||||
PaidMessage
|
||||
PaidMessage,
|
||||
},
|
||||
props: {
|
||||
maxNumber: {
|
||||
type: Number,
|
||||
default: 60
|
||||
default: 60,
|
||||
},
|
||||
showGiftName: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
customCss: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
let customStyleElement = document.createElement('style')
|
||||
const customStyleElement = document.createElement('style')
|
||||
document.head.appendChild(customStyleElement)
|
||||
const setCssDebounce = useDebounceFn(() => {
|
||||
customStyleElement.innerHTML = this.customCss ?? ''
|
||||
@@ -145,27 +60,27 @@ export default defineComponent({
|
||||
MESSAGE_TYPE_MEMBER: constants.MESSAGE_TYPE_MEMBER,
|
||||
MESSAGE_TYPE_SUPER_CHAT: constants.MESSAGE_TYPE_SUPER_CHAT,
|
||||
|
||||
messages: [], // 显示的消息
|
||||
paidMessages: [], // 固定在上方的消息
|
||||
messages: [], // 显示的消息
|
||||
paidMessages: [], // 固定在上方的消息
|
||||
|
||||
smoothedMessageQueue: [], // 平滑消息队列,由外部调用addMessages等方法添加
|
||||
emitSmoothedMessageTimerId: null, // 消费平滑消息队列的定时器ID
|
||||
enqueueIntervals: [], // 最近进队列的时间间隔,用来估计下次进队列的时间
|
||||
lastEnqueueTime: null, // 上次进队列的时间
|
||||
estimatedEnqueueInterval: null, // 估计的下次进队列时间间隔
|
||||
smoothedMessageQueue: [], // 平滑消息队列,由外部调用addMessages等方法添加
|
||||
emitSmoothedMessageTimerId: null, // 消费平滑消息队列的定时器ID
|
||||
enqueueIntervals: [], // 最近进队列的时间间隔,用来估计下次进队列的时间
|
||||
lastEnqueueTime: null, // 上次进队列的时间
|
||||
estimatedEnqueueInterval: null, // 估计的下次进队列时间间隔
|
||||
|
||||
messagesBuffer: [], // 暂时未显示的消息,当不能自动滚动时会积压在这
|
||||
preinsertHeight: 0, // 插入新消息之前items的高度
|
||||
isSmoothed: true, // 是否平滑滚动,当消息太快时不平滑滚动
|
||||
chatRateMs: 1000, // 用来计算消息速度
|
||||
scrollPixelsRemaining: 0, // 平滑滚动剩余像素
|
||||
scrollTimeRemainingMs: 0, // 平滑滚动剩余时间
|
||||
lastSmoothChatMessageAddMs: null, // 上次showNewMessages时间
|
||||
smoothScrollRafHandle: null, // 平滑滚动requestAnimationFrame句柄
|
||||
lastSmoothScrollUpdate: null, // 平滑滚动上一帧时间
|
||||
messagesBuffer: [], // 暂时未显示的消息,当不能自动滚动时会积压在这
|
||||
preinsertHeight: 0, // 插入新消息之前items的高度
|
||||
isSmoothed: true, // 是否平滑滚动,当消息太快时不平滑滚动
|
||||
chatRateMs: 1000, // 用来计算消息速度
|
||||
scrollPixelsRemaining: 0, // 平滑滚动剩余像素
|
||||
scrollTimeRemainingMs: 0, // 平滑滚动剩余时间
|
||||
lastSmoothChatMessageAddMs: null, // 上次showNewMessages时间
|
||||
smoothScrollRafHandle: null, // 平滑滚动requestAnimationFrame句柄
|
||||
lastSmoothScrollUpdate: null, // 平滑滚动上一帧时间
|
||||
|
||||
atBottom: true, // 滚动到底部,用来判断能否自动滚动
|
||||
cantScrollStartTime: null, // 开始不能自动滚动的时间,用来防止卡住
|
||||
atBottom: true, // 滚动到底部,用来判断能否自动滚动
|
||||
cantScrollStartTime: null, // 开始不能自动滚动的时间,用来防止卡住
|
||||
|
||||
customStyleElement,
|
||||
|
||||
@@ -175,7 +90,7 @@ export default defineComponent({
|
||||
computed: {
|
||||
canScrollToBottom() {
|
||||
return this.atBottom/* || this.allowScroll */
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
canScrollToBottom(val) {
|
||||
@@ -185,8 +100,8 @@ export default defineComponent({
|
||||
immediate: true,
|
||||
handler(val, oldVal) {
|
||||
this.setCssDebounce(val)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.scrollToBottom()
|
||||
@@ -220,12 +135,12 @@ export default defineComponent({
|
||||
// 后悔加这个功能了
|
||||
mergeSimilarText(content) {
|
||||
content = content.trim().toLowerCase()
|
||||
for (let message of this.iterRecentMessages(5)) {
|
||||
for (const message of this.iterRecentMessages(5)) {
|
||||
if (message.type !== constants.MESSAGE_TYPE_TEXT) {
|
||||
continue
|
||||
}
|
||||
|
||||
let messageContent = message.content.trim().toLowerCase()
|
||||
const messageContent = message.content.trim().toLowerCase()
|
||||
let longer, shorter
|
||||
if (messageContent.length > content.length) {
|
||||
longer = messageContent
|
||||
@@ -236,13 +151,13 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
if (
|
||||
longer.indexOf(shorter) !== -1 // 长的包含短的
|
||||
longer.includes(shorter) // 长的包含短的
|
||||
&& longer.length - shorter.length < shorter.length // 长度差较小
|
||||
) {
|
||||
this.updateMessage(message.id, {
|
||||
$add: {
|
||||
repeated: 1
|
||||
}
|
||||
repeated: 1,
|
||||
},
|
||||
})
|
||||
return true
|
||||
}
|
||||
@@ -250,7 +165,7 @@ export default defineComponent({
|
||||
return false
|
||||
},
|
||||
mergeSimilarGift(authorName, price, _freePrice, giftName, num) {
|
||||
for (let message of this.iterRecentMessages(5)) {
|
||||
for (const message of this.iterRecentMessages(5)) {
|
||||
if (
|
||||
message.type === constants.MESSAGE_TYPE_GIFT
|
||||
&& message.authorName === authorName
|
||||
@@ -258,10 +173,10 @@ export default defineComponent({
|
||||
) {
|
||||
this.updateMessage(message.id, {
|
||||
$add: {
|
||||
price: price,
|
||||
price,
|
||||
// freePrice: freePrice, // 暂时没用到
|
||||
num: num
|
||||
}
|
||||
num,
|
||||
},
|
||||
})
|
||||
return true
|
||||
}
|
||||
@@ -269,13 +184,13 @@ export default defineComponent({
|
||||
return false
|
||||
},
|
||||
// 从新到老迭代num条消息,注意会迭代smoothedMessageQueue,不会迭代paidMessages
|
||||
*iterRecentMessages(num, onlyCountAddMessages = true) {
|
||||
* iterRecentMessages(num, onlyCountAddMessages = true) {
|
||||
if (num <= 0) {
|
||||
return
|
||||
}
|
||||
for (let arr of this.iterMessageArrs()) {
|
||||
for (const arr of this.iterMessageArrs()) {
|
||||
for (let i = arr.length - 1; i >= 0 && num > 0; i--) {
|
||||
let message = arr[i]
|
||||
const message = arr[i]
|
||||
yield message
|
||||
if (!onlyCountAddMessages || this.isAddMessage(message)) {
|
||||
num--
|
||||
@@ -287,7 +202,7 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
// 从新到老迭代消息的数组
|
||||
*iterMessageArrs() {
|
||||
* iterMessageArrs() {
|
||||
for (let i = this.smoothedMessageQueue.length - 1; i >= 0; i--) {
|
||||
yield this.smoothedMessageQueue[i]
|
||||
}
|
||||
@@ -301,8 +216,8 @@ export default defineComponent({
|
||||
this.enqueueMessages(ids.map(
|
||||
id => ({
|
||||
type: constants.MESSAGE_TYPE_DEL,
|
||||
id
|
||||
})
|
||||
id,
|
||||
}),
|
||||
))
|
||||
},
|
||||
clearMessages() {
|
||||
@@ -326,7 +241,7 @@ export default defineComponent({
|
||||
this.enqueueMessages([{
|
||||
type: constants.MESSAGE_TYPE_UPDATE,
|
||||
id,
|
||||
newValuesObj
|
||||
newValuesObj,
|
||||
}])
|
||||
},
|
||||
|
||||
@@ -335,8 +250,8 @@ export default defineComponent({
|
||||
if (!this.lastEnqueueTime) {
|
||||
this.lastEnqueueTime = new Date()
|
||||
} else {
|
||||
let curTime = new Date()
|
||||
let interval = curTime - this.lastEnqueueTime
|
||||
const curTime = new Date()
|
||||
const interval = curTime - this.lastEnqueueTime
|
||||
// 真实的进队列时间间隔模式大概是这样:2500, 300, 300, 300, 2500, 300, ...
|
||||
// B站消息有缓冲,会一次发多条消息。这里把波峰视为发送了一次真实的WS消息,所以要过滤掉间隔太小的
|
||||
if (interval > 1000 || this.enqueueIntervals.length < 5) {
|
||||
@@ -354,7 +269,7 @@ export default defineComponent({
|
||||
|
||||
// 把messages分成messageGroup,每个组里最多有1个需要平滑的消息
|
||||
let messageGroup = []
|
||||
for (let message of messages) {
|
||||
for (const message of messages) {
|
||||
messageGroup.push(message)
|
||||
if (this.isAddMessage(message)) {
|
||||
this.smoothedMessageQueue.push(messageGroup)
|
||||
@@ -365,8 +280,8 @@ export default defineComponent({
|
||||
if (messageGroup.length > 0) {
|
||||
if (this.smoothedMessageQueue.length > 0) {
|
||||
// 和上一组合并
|
||||
let lastMessageGroup = this.smoothedMessageQueue[this.smoothedMessageQueue.length - 1]
|
||||
for (let message of messageGroup) {
|
||||
const lastMessageGroup = this.smoothedMessageQueue[this.smoothedMessageQueue.length - 1]
|
||||
for (const message of messageGroup) {
|
||||
lastMessageGroup.push(message)
|
||||
}
|
||||
} else {
|
||||
@@ -380,7 +295,7 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
isAddMessage({ type }) {
|
||||
return ADD_MESSAGE_TYPES.indexOf(type) !== -1
|
||||
return ADD_MESSAGE_TYPES.includes(type)
|
||||
},
|
||||
emitSmoothedMessages() {
|
||||
this.emitSmoothedMessageTimerId = null
|
||||
@@ -395,9 +310,9 @@ export default defineComponent({
|
||||
}
|
||||
// 计算发送的消息数,保证在下次进队列之前发完
|
||||
// 下次进队列之前应该发多少条消息
|
||||
let shouldEmitGroupNum = Math.max(this.smoothedMessageQueue.length, 0)
|
||||
const shouldEmitGroupNum = Math.max(this.smoothedMessageQueue.length, 0)
|
||||
// 下次进队列之前最多能发多少次
|
||||
let maxCanEmitCount = estimatedNextEnqueueRemainTime / MESSAGE_MIN_INTERVAL
|
||||
const maxCanEmitCount = estimatedNextEnqueueRemainTime / MESSAGE_MIN_INTERVAL
|
||||
// 这次发多少条消息
|
||||
let groupNumToEmit
|
||||
if (shouldEmitGroupNum < maxCanEmitCount) {
|
||||
@@ -409,10 +324,10 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
// 发消息
|
||||
let messageGroups = this.smoothedMessageQueue.splice(0, groupNumToEmit)
|
||||
let mergedGroup = []
|
||||
for (let messageGroup of messageGroups) {
|
||||
for (let message of messageGroup) {
|
||||
const messageGroups = this.smoothedMessageQueue.splice(0, groupNumToEmit)
|
||||
const mergedGroup = []
|
||||
for (const messageGroup of messageGroups) {
|
||||
for (const message of messageGroup) {
|
||||
mergedGroup.push(message)
|
||||
}
|
||||
}
|
||||
@@ -444,7 +359,7 @@ export default defineComponent({
|
||||
return
|
||||
}
|
||||
|
||||
for (let message of messageGroup) {
|
||||
for (const message of messageGroup) {
|
||||
switch (message.type) {
|
||||
case constants.MESSAGE_TYPE_TEXT:
|
||||
case constants.MESSAGE_TYPE_GIFT:
|
||||
@@ -482,9 +397,9 @@ export default defineComponent({
|
||||
this.messagesBuffer.push(message)
|
||||
},
|
||||
handleDelMessage({ id }) {
|
||||
let arrs = [this.messages, this.paidMessages, this.messagesBuffer]
|
||||
const arrs = [this.messages, this.paidMessages, this.messagesBuffer]
|
||||
let needResetSmoothScroll = false
|
||||
for (let arr of arrs) {
|
||||
for (const arr of arrs) {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i].id !== id) {
|
||||
continue
|
||||
@@ -501,10 +416,10 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
handleUpdateMessage({ id, newValuesObj }) {
|
||||
let arrs = [this.messages, this.paidMessages, this.messagesBuffer]
|
||||
const arrs = [this.messages, this.paidMessages, this.messagesBuffer]
|
||||
let needResetSmoothScroll = false
|
||||
for (let arr of arrs) {
|
||||
for (let message of arr) {
|
||||
for (const arr of arrs) {
|
||||
for (const message of arr) {
|
||||
if (message.id !== id) {
|
||||
continue
|
||||
}
|
||||
@@ -521,15 +436,15 @@ export default defineComponent({
|
||||
},
|
||||
doUpdateMessage(message, newValuesObj) {
|
||||
// +=
|
||||
let addValuesObj = newValuesObj.$add
|
||||
const addValuesObj = newValuesObj.$add
|
||||
if (addValuesObj !== undefined) {
|
||||
for (let name in addValuesObj) {
|
||||
for (const name in addValuesObj) {
|
||||
message[name] += addValuesObj[name]
|
||||
}
|
||||
}
|
||||
|
||||
// =
|
||||
for (let name in newValuesObj) {
|
||||
for (const name in newValuesObj) {
|
||||
if (!name.startsWith('$')) {
|
||||
message[name] = newValuesObj[name]
|
||||
}
|
||||
@@ -548,7 +463,7 @@ export default defineComponent({
|
||||
return
|
||||
}
|
||||
|
||||
let removeNum = Math.max(this.messages.length + this.messagesBuffer.length - this.maxNumber, 0)
|
||||
const removeNum = Math.max(this.messages.length + this.messagesBuffer.length - this.maxNumber, 0)
|
||||
if (removeNum > 0) {
|
||||
this.messages.splice(0, removeNum)
|
||||
// 防止同时添加和删除项目时所有的项目重新渲染 https://github.com/vuejs/vue/issues/6857
|
||||
@@ -556,7 +471,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
this.preinsertHeight = this.$refs.items.clientHeight
|
||||
for (let message of this.messagesBuffer) {
|
||||
for (const message of this.messagesBuffer) {
|
||||
this.messages.push(message)
|
||||
}
|
||||
this.messagesBuffer = []
|
||||
@@ -565,7 +480,7 @@ export default defineComponent({
|
||||
this.showNewMessages()
|
||||
},
|
||||
showNewMessages() {
|
||||
let hasScrollBar = this.$refs.items.clientHeight > this.$refs.scroller.clientHeight
|
||||
const hasScrollBar = this.$refs.items.clientHeight > this.$refs.scroller.clientHeight
|
||||
this.$refs.itemOffset.style.height = `${this.$refs.items.clientHeight}px`
|
||||
if (!this.canScrollToBottomOrTimedOut() || !hasScrollBar) {
|
||||
return
|
||||
@@ -579,7 +494,7 @@ export default defineComponent({
|
||||
if (!this.lastSmoothChatMessageAddMs) {
|
||||
this.lastSmoothChatMessageAddMs = performance.now()
|
||||
}
|
||||
let interval = performance.now() - this.lastSmoothChatMessageAddMs
|
||||
const interval = performance.now() - this.lastSmoothChatMessageAddMs
|
||||
this.chatRateMs = (0.9 * this.chatRateMs) + (0.1 * interval)
|
||||
if (this.isSmoothed) {
|
||||
if (this.chatRateMs < 400) {
|
||||
@@ -605,9 +520,9 @@ export default defineComponent({
|
||||
return
|
||||
}
|
||||
|
||||
let interval = time - this.lastSmoothScrollUpdate
|
||||
const interval = time - this.lastSmoothScrollUpdate
|
||||
if (
|
||||
this.scrollPixelsRemaining <= 0 || this.scrollPixelsRemaining >= 400 // 已经滚动到底部或者离底部太远则结束
|
||||
this.scrollPixelsRemaining <= 0 || this.scrollPixelsRemaining >= 400 // 已经滚动到底部或者离底部太远则结束
|
||||
|| interval >= 1000 // 离上一帧时间太久,可能用户切换到其他网页
|
||||
|| this.scrollTimeRemainingMs <= 0 // 时间已结束
|
||||
) {
|
||||
@@ -615,7 +530,7 @@ export default defineComponent({
|
||||
return
|
||||
}
|
||||
|
||||
let pixelsToScroll = interval / this.scrollTimeRemainingMs * this.scrollPixelsRemaining
|
||||
const pixelsToScroll = interval / this.scrollTimeRemainingMs * this.scrollPixelsRemaining
|
||||
this.scrollPixelsRemaining -= pixelsToScroll
|
||||
if (this.scrollPixelsRemaining < 0) {
|
||||
this.scrollPixelsRemaining = 0
|
||||
@@ -647,12 +562,12 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
scrollToBottom() {
|
||||
this.$refs.scroller.scrollTop = Math.pow(2, 24)
|
||||
this.$refs.scroller.scrollTop = 2 ** 24
|
||||
this.atBottom = true
|
||||
},
|
||||
onScroll() {
|
||||
this.refreshCantScrollStartTime()
|
||||
let scroller = this.$refs.scroller
|
||||
const scroller = this.$refs.scroller
|
||||
this.atBottom = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight < SCROLLED_TO_BOTTOM_EPSILON
|
||||
this.flushMessagesBuffer()
|
||||
},
|
||||
@@ -668,11 +583,98 @@ export default defineComponent({
|
||||
if (this.cantScrollStartTime) {
|
||||
this.cantScrollStartTime = new Date()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<yt-live-chat-renderer
|
||||
class="style-scope yt-live-chat-app"
|
||||
style="--scrollbar-width:11px;"
|
||||
hide-timestamps
|
||||
@mousemove="refreshCantScrollStartTime"
|
||||
>
|
||||
<Ticker
|
||||
v-model:messages="paidMessages"
|
||||
class="style-scope yt-live-chat-renderer"
|
||||
:show-gift-name="showGiftName || undefined"
|
||||
/>
|
||||
<yt-live-chat-item-list-renderer
|
||||
class="style-scope yt-live-chat-renderer"
|
||||
allow-scroll
|
||||
>
|
||||
<div
|
||||
id="item-scroller"
|
||||
ref="scroller"
|
||||
class="style-scope yt-live-chat-item-list-renderer animated"
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<div
|
||||
id="item-offset"
|
||||
ref="itemOffset"
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
>
|
||||
<div
|
||||
id="items"
|
||||
ref="items"
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
style="overflow: hidden"
|
||||
:style="{ transform: `translateY(${Math.floor(scrollPixelsRemaining)}px)` }"
|
||||
>
|
||||
<template
|
||||
v-for="message in messages"
|
||||
:key="message.id"
|
||||
>
|
||||
<TextMessage
|
||||
v-if="message.type === MESSAGE_TYPE_TEXT"
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
:time="message.time"
|
||||
:avatar-url="message.avatarUrl"
|
||||
:author-name="message.authorName"
|
||||
:author-type="message.authorType"
|
||||
:privilege-type="message.privilegeType"
|
||||
:content-parts="getShowContentParts(message)"
|
||||
:repeated="message.repeated"
|
||||
/>
|
||||
<PaidMessage
|
||||
v-else-if="message.type === MESSAGE_TYPE_GIFT"
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
:time="message.time"
|
||||
:avatar-url="message.avatarUrl"
|
||||
:author-name="getShowAuthorName(message)"
|
||||
:price="message.price"
|
||||
:price-text="message.price <= 0 ? getGiftShowNameAndNum(message) : ''"
|
||||
:content="message.price <= 0 ? '' : getGiftShowContent(message, showGiftName)"
|
||||
/>
|
||||
<MembershipItem
|
||||
v-else-if="message.type === MESSAGE_TYPE_MEMBER"
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
:time="message.time"
|
||||
:avatar-url="message.avatarUrl"
|
||||
:author-name="getShowAuthorName(message)"
|
||||
:privilege-type="message.privilegeType"
|
||||
:title="message.title"
|
||||
/>
|
||||
<PaidMessage
|
||||
v-else-if="message.type === MESSAGE_TYPE_SUPER_CHAT"
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
:time="message.time"
|
||||
:avatar-url="message.avatarUrl"
|
||||
:author-name="getShowAuthorName(message)"
|
||||
:price="message.price"
|
||||
:content="getShowContent(message)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</yt-live-chat-item-list-renderer>
|
||||
</yt-live-chat-renderer>
|
||||
</template>
|
||||
|
||||
<style src="@/assets/css/youtube/yt-html.css"></style>
|
||||
|
||||
<style src="@/assets/css/youtube/yt-live-chat-renderer.css"></style>
|
||||
<style src="@/assets/css/youtube/yt-live-chat-item-list-renderer.css"></style>
|
||||
|
||||
<style src="@/assets/css/youtube/yt-live-chat-item-list-renderer.css"></style>
|
||||
|
||||
@@ -1,3 +1,35 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import * as constants from './constants'
|
||||
import ImgShadow from './ImgShadow.vue'
|
||||
import * as utils from './utils'
|
||||
|
||||
const props = defineProps({
|
||||
avatarUrl: String,
|
||||
authorName: String,
|
||||
price: Number, // 价格,人民币
|
||||
priceText: String,
|
||||
time: Date,
|
||||
content: String,
|
||||
})
|
||||
|
||||
const priceConfig = computed(() => {
|
||||
return constants.getPriceConfig(props.price)
|
||||
})
|
||||
|
||||
const color = computed(() => {
|
||||
return priceConfig.value.colors
|
||||
})
|
||||
|
||||
const showPriceText = computed(() => {
|
||||
return props.priceText || `CN¥${utils.formatCurrency(props.price)}`
|
||||
})
|
||||
|
||||
const timeText = computed(() => {
|
||||
return utils.getTimeTextHourMin(props.time)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<yt-live-chat-paid-message-renderer
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
@@ -9,7 +41,7 @@
|
||||
'--yt-live-chat-paid-message-header-color': color.header,
|
||||
'--yt-live-chat-paid-message-author-name-color': color.authorName,
|
||||
'--yt-live-chat-paid-message-timestamp-color': color.time,
|
||||
'--yt-live-chat-paid-message-color': color.content
|
||||
'--yt-live-chat-paid-message-color': color.content,
|
||||
}"
|
||||
:blc-price-level="priceConfig.priceLevel"
|
||||
>
|
||||
@@ -21,7 +53,7 @@
|
||||
id="header"
|
||||
class="style-scope yt-live-chat-paid-message-renderer"
|
||||
>
|
||||
<img-shadow
|
||||
<ImgShadow
|
||||
id="author-photo"
|
||||
height="40"
|
||||
width="40"
|
||||
@@ -71,36 +103,4 @@
|
||||
</yt-live-chat-paid-message-renderer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import ImgShadow from './ImgShadow.vue'
|
||||
import * as constants from './constants'
|
||||
import * as utils from './utils'
|
||||
|
||||
const props = defineProps({
|
||||
avatarUrl: String,
|
||||
authorName: String,
|
||||
price: Number, // 价格,人民币
|
||||
priceText: String,
|
||||
time: Date,
|
||||
content: String
|
||||
})
|
||||
|
||||
const priceConfig = computed(() => {
|
||||
return constants.getPriceConfig(props.price)
|
||||
})
|
||||
|
||||
const color = computed(() => {
|
||||
return priceConfig.value.colors
|
||||
})
|
||||
|
||||
const showPriceText = computed(() => {
|
||||
return props.priceText || `CN¥${utils.formatCurrency(props.price)}`
|
||||
})
|
||||
|
||||
const timeText = computed(() => {
|
||||
return utils.getTimeTextHourMin(props.time)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style src="@/assets/css/youtube/yt-live-chat-paid-message-renderer.css"></style>
|
||||
|
||||
@@ -1,9 +1,58 @@
|
||||
<script setup>
|
||||
import { NBadge } from 'naive-ui'
|
||||
import { computed } from 'vue'
|
||||
import AuthorChip from './AuthorChip.vue'
|
||||
import * as constants from './constants'
|
||||
import ImgShadow from './ImgShadow.vue'
|
||||
import * as utils from './utils'
|
||||
|
||||
const props = defineProps({
|
||||
avatarUrl: String,
|
||||
time: Date,
|
||||
authorName: String,
|
||||
authorType: Number,
|
||||
contentParts: Array,
|
||||
privilegeType: Number,
|
||||
repeated: Number,
|
||||
})
|
||||
// HSL
|
||||
const REPEATED_MARK_COLOR_START = [210, 100.0, 62.5]
|
||||
const REPEATED_MARK_COLOR_END = [360, 87.3, 69.2]
|
||||
|
||||
const CONTENT_TYPE_TEXT = constants.CONTENT_TYPE_TEXT
|
||||
const CONTENT_TYPE_IMAGE = constants.CONTENT_TYPE_IMAGE
|
||||
|
||||
const timeText = computed(() => {
|
||||
return utils.getTimeTextHourMin(props.time)
|
||||
})
|
||||
|
||||
const authorTypeText = computed(() => {
|
||||
return constants.AUTHOR_TYPE_TO_TEXT[props.authorType]
|
||||
})
|
||||
|
||||
const repeatedMarkColor = computed(() => {
|
||||
let color
|
||||
if (props.repeated <= 2) {
|
||||
color = REPEATED_MARK_COLOR_START
|
||||
} else if (props.repeated >= 10) {
|
||||
color = REPEATED_MARK_COLOR_END
|
||||
} else {
|
||||
color = [0, 0, 0]
|
||||
const t = (props.repeated - 2) / (10 - 2)
|
||||
for (let i = 0; i < 3; i++) {
|
||||
color[i] = REPEATED_MARK_COLOR_START[i] + ((REPEATED_MARK_COLOR_END[i] - REPEATED_MARK_COLOR_START[i]) * t)
|
||||
}
|
||||
}
|
||||
return `hsl(${color[0]}, ${color[1]}%, ${color[2]}%)`
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<yt-live-chat-text-message-renderer
|
||||
:author-type="authorTypeText"
|
||||
:blc-guard-level="privilegeType"
|
||||
>
|
||||
<img-shadow
|
||||
<ImgShadow
|
||||
id="author-photo"
|
||||
height="24"
|
||||
width="24"
|
||||
@@ -18,7 +67,7 @@
|
||||
id="timestamp"
|
||||
class="style-scope yt-live-chat-text-message-renderer"
|
||||
>{{ timeText }}</span>
|
||||
<author-chip
|
||||
<AuthorChip
|
||||
class="style-scope yt-live-chat-text-message-renderer"
|
||||
:is-in-member-message="false"
|
||||
:author-name="authorName"
|
||||
@@ -38,7 +87,7 @@
|
||||
<img
|
||||
v-else-if="content.type === CONTENT_TYPE_IMAGE"
|
||||
:id="`emoji-${content.text}`"
|
||||
:key="'_' + index"
|
||||
:key="`_${index}`"
|
||||
class="emoji yt-formatted-string style-scope yt-live-chat-text-message-renderer"
|
||||
:src="content.url"
|
||||
:alt="content.text"
|
||||
@@ -61,56 +110,6 @@
|
||||
</yt-live-chat-text-message-renderer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import ImgShadow from './ImgShadow.vue'
|
||||
import AuthorChip from './AuthorChip.vue'
|
||||
import * as constants from './constants'
|
||||
import * as utils from './utils'
|
||||
import { NBadge } from 'naive-ui'
|
||||
|
||||
// HSL
|
||||
const REPEATED_MARK_COLOR_START = [210, 100.0, 62.5]
|
||||
const REPEATED_MARK_COLOR_END = [360, 87.3, 69.2]
|
||||
|
||||
const CONTENT_TYPE_TEXT = constants.CONTENT_TYPE_TEXT
|
||||
const CONTENT_TYPE_IMAGE = constants.CONTENT_TYPE_IMAGE
|
||||
|
||||
const props = defineProps({
|
||||
avatarUrl: String,
|
||||
time: Date,
|
||||
authorName: String,
|
||||
authorType: Number,
|
||||
contentParts: Array,
|
||||
privilegeType: Number,
|
||||
repeated: Number
|
||||
})
|
||||
|
||||
const timeText = computed(() => {
|
||||
return utils.getTimeTextHourMin(props.time)
|
||||
})
|
||||
|
||||
const authorTypeText = computed(() => {
|
||||
return constants.AUTHOR_TYPE_TO_TEXT[props.authorType]
|
||||
})
|
||||
|
||||
const repeatedMarkColor = computed(() => {
|
||||
let color
|
||||
if (props.repeated <= 2) {
|
||||
color = REPEATED_MARK_COLOR_START
|
||||
} else if (props.repeated >= 10) {
|
||||
color = REPEATED_MARK_COLOR_END
|
||||
} else {
|
||||
color = [0, 0, 0]
|
||||
let t = (props.repeated - 2) / (10 - 2)
|
||||
for (let i = 0; i < 3; i++) {
|
||||
color[i] = REPEATED_MARK_COLOR_START[i] + ((REPEATED_MARK_COLOR_END[i] - REPEATED_MARK_COLOR_START[i]) * t)
|
||||
}
|
||||
}
|
||||
return `hsl(${color[0]}, ${color[1]}%, ${color[2]}%)`
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
yt-live-chat-text-message-renderer>#content>#message>.el-badge {
|
||||
margin-left: 10px;
|
||||
|
||||
@@ -1,99 +1,19 @@
|
||||
<!-- eslint-disable vue/multi-word-component-names -->
|
||||
<template>
|
||||
<yt-live-chat-ticker-renderer :hidden="showMessages.length === 0">
|
||||
<div
|
||||
id="container"
|
||||
dir="ltr"
|
||||
class="style-scope yt-live-chat-ticker-renderer"
|
||||
>
|
||||
<transition-group
|
||||
id="items"
|
||||
tag="div"
|
||||
:css="false"
|
||||
class="style-scope yt-live-chat-ticker-renderer"
|
||||
@enter="onTickerItemEnter"
|
||||
@leave="onTickerItemLeave"
|
||||
>
|
||||
<yt-live-chat-ticker-paid-message-item-renderer
|
||||
v-for="message in showMessages"
|
||||
:key="message.raw.id"
|
||||
tabindex="0"
|
||||
class="style-scope yt-live-chat-ticker-renderer"
|
||||
style="overflow: hidden;"
|
||||
@click="onItemClick(message.raw)"
|
||||
>
|
||||
<div
|
||||
id="container"
|
||||
dir="ltr"
|
||||
class="style-scope yt-live-chat-ticker-paid-message-item-renderer"
|
||||
:style="{
|
||||
background: message.bgColor,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
id="content"
|
||||
class="style-scope yt-live-chat-ticker-paid-message-item-renderer"
|
||||
:style="{
|
||||
color: message.color
|
||||
}"
|
||||
>
|
||||
<img-shadow
|
||||
id="author-photo"
|
||||
height="24"
|
||||
width="24"
|
||||
class="style-scope yt-live-chat-ticker-paid-message-item-renderer"
|
||||
:img-url="message.raw.avatarUrl"
|
||||
/>
|
||||
<span
|
||||
id="text"
|
||||
dir="ltr"
|
||||
class="style-scope yt-live-chat-ticker-paid-message-item-renderer"
|
||||
>{{ message.text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</yt-live-chat-ticker-paid-message-item-renderer>
|
||||
</transition-group>
|
||||
</div>
|
||||
<template v-if="pinnedMessage">
|
||||
<membership-item
|
||||
v-if="pinnedMessage.type === MESSAGE_TYPE_MEMBER"
|
||||
:key="pinnedMessage.id"
|
||||
class="style-scope yt-live-chat-ticker-renderer"
|
||||
:avatar-url="pinnedMessage.avatarUrl"
|
||||
:author-name="getShowAuthorName(pinnedMessage)"
|
||||
:privilege-type="pinnedMessage.privilegeType"
|
||||
:title="pinnedMessage.title"
|
||||
:time="pinnedMessage.time"
|
||||
/>
|
||||
<paid-message
|
||||
v-else
|
||||
:key="pinnedMessage.id"
|
||||
class="style-scope yt-live-chat-ticker-renderer"
|
||||
:price="pinnedMessage.price"
|
||||
:avatar-url="pinnedMessage.avatarUrl"
|
||||
:author-name="getShowAuthorName(pinnedMessage)"
|
||||
:time="pinnedMessage.time"
|
||||
:content="pinnedMessageShowContent"
|
||||
/>
|
||||
</template>
|
||||
</yt-live-chat-ticker-renderer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// @ts-nocheck
|
||||
import { ref, computed, onBeforeUnmount, nextTick } from 'vue'
|
||||
import { formatCurrency } from './utils'
|
||||
import { computed, nextTick, onBeforeUnmount, ref } from 'vue'
|
||||
import * as constants from './constants'
|
||||
import ImgShadow from './ImgShadow.vue'
|
||||
import MembershipItem from './MembershipItem.vue'
|
||||
import PaidMessage from './PaidMessage.vue'
|
||||
import * as constants from './constants'
|
||||
import { formatCurrency } from './utils'
|
||||
|
||||
const props = defineProps({
|
||||
messages: Array,
|
||||
showGiftName: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:messages'])
|
||||
@@ -109,8 +29,8 @@ onBeforeUnmount(() => {
|
||||
})
|
||||
|
||||
const showMessages = computed(() => {
|
||||
let res = []
|
||||
for (let message of props.messages) {
|
||||
const res = []
|
||||
for (const message of props.messages) {
|
||||
if (!needToShow(message)) {
|
||||
continue
|
||||
}
|
||||
@@ -118,7 +38,7 @@ const showMessages = computed(() => {
|
||||
raw: message,
|
||||
bgColor: getBgColor(message),
|
||||
color: getColor(message),
|
||||
text: getText(message)
|
||||
text: getText(message),
|
||||
})
|
||||
}
|
||||
return res
|
||||
@@ -136,7 +56,7 @@ const pinnedMessageShowContent = computed(() => {
|
||||
})
|
||||
|
||||
async function onTickerItemEnter(el, done) {
|
||||
let width = el.clientWidth
|
||||
const width = el.clientWidth
|
||||
if (width === 0) {
|
||||
// CSS指定了不显示固定栏
|
||||
done()
|
||||
@@ -165,7 +85,7 @@ function onTickerItemLeave(el, done) {
|
||||
const getShowAuthorName = constants.getShowAuthorName
|
||||
|
||||
function needToShow(message) {
|
||||
let pinTime = getPinTime(message)
|
||||
const pinTime = getPinTime(message)
|
||||
return (new Date() - message.addTime) / (60 * 1000) < pinTime
|
||||
}
|
||||
|
||||
@@ -175,11 +95,11 @@ function getBgColor(message) {
|
||||
color1 = 'rgba(15,157,88,1)'
|
||||
color2 = 'rgba(11,128,67,1)'
|
||||
} else {
|
||||
let config = constants.getPriceConfig(message.price)
|
||||
const config = constants.getPriceConfig(message.price)
|
||||
color1 = config.colors.contentBg
|
||||
color2 = config.colors.headerBg
|
||||
}
|
||||
let pinTime = getPinTime(message)
|
||||
const pinTime = getPinTime(message)
|
||||
let progress = (1 - ((curTime.value - message.addTime) / (60 * 1000) / pinTime)) * 100
|
||||
if (progress < 0) {
|
||||
progress = 0
|
||||
@@ -215,10 +135,10 @@ function updateProgress() {
|
||||
curTime.value = new Date()
|
||||
|
||||
// 删除过期的消息
|
||||
let filteredMessages = []
|
||||
const filteredMessages = []
|
||||
let messagesChanged = false
|
||||
for (let message of props.messages) {
|
||||
let pinTime = getPinTime(message)
|
||||
for (const message of props.messages) {
|
||||
const pinTime = getPinTime(message)
|
||||
if ((curTime.value - message.addTime) / (60 * 1000) >= pinTime) {
|
||||
messagesChanged = true
|
||||
if (pinnedMessage.value === message) {
|
||||
@@ -242,5 +162,86 @@ function onItemClick(message) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<yt-live-chat-ticker-renderer :hidden="showMessages.length === 0">
|
||||
<div
|
||||
id="container"
|
||||
dir="ltr"
|
||||
class="style-scope yt-live-chat-ticker-renderer"
|
||||
>
|
||||
<transition-group
|
||||
id="items"
|
||||
tag="div"
|
||||
:css="false"
|
||||
class="style-scope yt-live-chat-ticker-renderer"
|
||||
@enter="onTickerItemEnter"
|
||||
@leave="onTickerItemLeave"
|
||||
>
|
||||
<yt-live-chat-ticker-paid-message-item-renderer
|
||||
v-for="message in showMessages"
|
||||
:key="message.raw.id"
|
||||
tabindex="0"
|
||||
class="style-scope yt-live-chat-ticker-renderer"
|
||||
style="overflow: hidden;"
|
||||
@click="onItemClick(message.raw)"
|
||||
>
|
||||
<div
|
||||
id="container"
|
||||
dir="ltr"
|
||||
class="style-scope yt-live-chat-ticker-paid-message-item-renderer"
|
||||
:style="{
|
||||
background: message.bgColor,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
id="content"
|
||||
class="style-scope yt-live-chat-ticker-paid-message-item-renderer"
|
||||
:style="{
|
||||
color: message.color,
|
||||
}"
|
||||
>
|
||||
<ImgShadow
|
||||
id="author-photo"
|
||||
height="24"
|
||||
width="24"
|
||||
class="style-scope yt-live-chat-ticker-paid-message-item-renderer"
|
||||
:img-url="message.raw.avatarUrl"
|
||||
/>
|
||||
<span
|
||||
id="text"
|
||||
dir="ltr"
|
||||
class="style-scope yt-live-chat-ticker-paid-message-item-renderer"
|
||||
>{{ message.text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</yt-live-chat-ticker-paid-message-item-renderer>
|
||||
</transition-group>
|
||||
</div>
|
||||
<template v-if="pinnedMessage">
|
||||
<MembershipItem
|
||||
v-if="pinnedMessage.type === MESSAGE_TYPE_MEMBER"
|
||||
:key="pinnedMessage.id"
|
||||
class="style-scope yt-live-chat-ticker-renderer"
|
||||
:avatar-url="pinnedMessage.avatarUrl"
|
||||
:author-name="getShowAuthorName(pinnedMessage)"
|
||||
:privilege-type="pinnedMessage.privilegeType"
|
||||
:title="pinnedMessage.title"
|
||||
:time="pinnedMessage.time"
|
||||
/>
|
||||
<PaidMessage
|
||||
v-else
|
||||
:key="pinnedMessage.id"
|
||||
class="style-scope yt-live-chat-ticker-renderer"
|
||||
:price="pinnedMessage.price"
|
||||
:avatar-url="pinnedMessage.avatarUrl"
|
||||
:author-name="getShowAuthorName(pinnedMessage)"
|
||||
:time="pinnedMessage.time"
|
||||
:content="pinnedMessageShowContent"
|
||||
/>
|
||||
</template>
|
||||
</yt-live-chat-ticker-renderer>
|
||||
</template>
|
||||
|
||||
<style src="@/assets/css/youtube/yt-live-chat-ticker-renderer.css"></style>
|
||||
|
||||
<style src="@/assets/css/youtube/yt-live-chat-ticker-paid-message-item-renderer.css"></style>
|
||||
|
||||
@@ -7,7 +7,7 @@ export const AUTHOR_TYPE_TO_TEXT = [
|
||||
'',
|
||||
'member', // 舰队
|
||||
'moderator', // 房管
|
||||
'owner' // 主播
|
||||
'owner', // 主播
|
||||
]
|
||||
|
||||
export function getShowGuardLevelText(guardLevel) {
|
||||
@@ -45,7 +45,7 @@ const PRICE_CONFIGS = [
|
||||
header: 'rgba(0,0,0,1)',
|
||||
authorName: 'rgba(0,0,0,0.701961)',
|
||||
time: 'rgba(0,0,0,0.501961)',
|
||||
content: 'rgba(0,0,0,1)'
|
||||
content: 'rgba(0,0,0,1)',
|
||||
},
|
||||
pinTime: 0,
|
||||
priceLevel: 0,
|
||||
@@ -59,7 +59,7 @@ const PRICE_CONFIGS = [
|
||||
header: 'rgba(255,255,255,1)',
|
||||
authorName: 'rgba(255,255,255,0.701961)',
|
||||
time: 'rgba(255,255,255,0.501961)',
|
||||
content: 'rgba(255,255,255,1)'
|
||||
content: 'rgba(255,255,255,1)',
|
||||
},
|
||||
pinTime: 0,
|
||||
priceLevel: 1,
|
||||
@@ -73,7 +73,7 @@ const PRICE_CONFIGS = [
|
||||
header: 'rgba(0,0,0,1)',
|
||||
authorName: 'rgba(0,0,0,0.701961)',
|
||||
time: 'rgba(0,0,0,0.501961)',
|
||||
content: 'rgba(0,0,0,1)'
|
||||
content: 'rgba(0,0,0,1)',
|
||||
},
|
||||
pinTime: 0,
|
||||
priceLevel: 2,
|
||||
@@ -87,7 +87,7 @@ const PRICE_CONFIGS = [
|
||||
header: 'rgba(0,0,0,1)',
|
||||
authorName: 'rgba(0,0,0,0.541176)',
|
||||
time: 'rgba(0,0,0,0.501961)',
|
||||
content: 'rgba(0,0,0,1)'
|
||||
content: 'rgba(0,0,0,1)',
|
||||
},
|
||||
pinTime: 2,
|
||||
priceLevel: 3,
|
||||
@@ -101,7 +101,7 @@ const PRICE_CONFIGS = [
|
||||
header: 'rgba(0,0,0,0.87451)',
|
||||
authorName: 'rgba(0,0,0,0.541176)',
|
||||
time: 'rgba(0,0,0,0.501961)',
|
||||
content: 'rgba(0,0,0,0.87451)'
|
||||
content: 'rgba(0,0,0,0.87451)',
|
||||
},
|
||||
pinTime: 5,
|
||||
priceLevel: 4,
|
||||
@@ -115,7 +115,7 @@ const PRICE_CONFIGS = [
|
||||
header: 'rgba(255,255,255,0.87451)',
|
||||
authorName: 'rgba(255,255,255,0.701961)',
|
||||
time: 'rgba(255,255,255,0.501961)',
|
||||
content: 'rgba(255,255,255,0.87451)'
|
||||
content: 'rgba(255,255,255,0.87451)',
|
||||
},
|
||||
pinTime: 10,
|
||||
priceLevel: 5,
|
||||
@@ -129,7 +129,7 @@ const PRICE_CONFIGS = [
|
||||
header: 'rgba(255,255,255,1)',
|
||||
authorName: 'rgba(255,255,255,0.701961)',
|
||||
time: 'rgba(255,255,255,0.501961)',
|
||||
content: 'rgba(255,255,255,1)'
|
||||
content: 'rgba(255,255,255,1)',
|
||||
},
|
||||
pinTime: 30,
|
||||
priceLevel: 6,
|
||||
@@ -143,7 +143,7 @@ const PRICE_CONFIGS = [
|
||||
header: 'rgba(255,255,255,1)',
|
||||
authorName: 'rgba(255,255,255,0.701961)',
|
||||
time: 'rgba(255,255,255,0.501961)',
|
||||
content: 'rgba(255,255,255,1)'
|
||||
content: 'rgba(255,255,255,1)',
|
||||
},
|
||||
pinTime: 60,
|
||||
priceLevel: 7,
|
||||
@@ -154,7 +154,7 @@ export function getPriceConfig(price) {
|
||||
let i = 0
|
||||
// 根据先验知识,从小找到大通常更快结束
|
||||
for (; i < PRICE_CONFIGS.length - 1; i++) {
|
||||
let nextConfig = PRICE_CONFIGS[i + 1]
|
||||
const nextConfig = PRICE_CONFIGS[i + 1]
|
||||
if (price < nextConfig.price) {
|
||||
return PRICE_CONFIGS[i]
|
||||
}
|
||||
@@ -170,22 +170,22 @@ export function getShowContent(message) {
|
||||
}
|
||||
|
||||
export function getShowRichContent(message) {
|
||||
let richContent = [...message.richContent]
|
||||
const richContent = [...message.richContent]
|
||||
if (message.translation) {
|
||||
richContent.push({
|
||||
type: CONTENT_TYPE_TEXT,
|
||||
text: `(${message.translation})`
|
||||
text: `(${message.translation})`,
|
||||
})
|
||||
}
|
||||
return richContent
|
||||
}
|
||||
|
||||
export function getShowContentParts(message) {
|
||||
let contentParts = [...message.contentParts || []]
|
||||
const contentParts = [...message.contentParts || []]
|
||||
if (message.translation) {
|
||||
contentParts.push({
|
||||
type: CONTENT_TYPE_TEXT,
|
||||
text: `(${message.translation})`
|
||||
text: `(${message.translation})`,
|
||||
})
|
||||
}
|
||||
return contentParts
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
|
||||
import { format } from 'date-fns'
|
||||
|
||||
export function mergeConfig(config, defaultConfig) {
|
||||
let res = {}
|
||||
for (let i in defaultConfig) {
|
||||
const res = {}
|
||||
for (const i in defaultConfig) {
|
||||
res[i] = i in config ? config[i] : defaultConfig[i]
|
||||
}
|
||||
return res
|
||||
@@ -11,13 +10,13 @@ export function mergeConfig(config, defaultConfig) {
|
||||
|
||||
export function toBool(val) {
|
||||
if (typeof val === 'string') {
|
||||
return ['false', 'no', 'off', '0', ''].indexOf(val.toLowerCase()) === -1
|
||||
return !['false', 'no', 'off', '0', ''].includes(val.toLowerCase())
|
||||
}
|
||||
return Boolean(val)
|
||||
}
|
||||
|
||||
export function toInt(val, _default) {
|
||||
let res = parseInt(val)
|
||||
let res = Number.parseInt(val)
|
||||
if (isNaN(res)) {
|
||||
res = _default
|
||||
}
|
||||
@@ -25,7 +24,7 @@ export function toInt(val, _default) {
|
||||
}
|
||||
|
||||
export function toFloat(val, _default) {
|
||||
let res = parseFloat(val)
|
||||
let res = Number.parseFloat(val)
|
||||
if (isNaN(res)) {
|
||||
res = _default
|
||||
}
|
||||
@@ -34,7 +33,7 @@ export function toFloat(val, _default) {
|
||||
|
||||
export function formatCurrency(price) {
|
||||
return new Intl.NumberFormat('zh-CN', {
|
||||
minimumFractionDigits: price < 100 ? 2 : 0
|
||||
minimumFractionDigits: price < 100 ? 2 : 0,
|
||||
}).format(price)
|
||||
}
|
||||
|
||||
@@ -43,9 +42,9 @@ export function getTimeTextHourMin(date) {
|
||||
}
|
||||
|
||||
export function getUuid4Hex() {
|
||||
let chars = []
|
||||
const chars = []
|
||||
for (let i = 0; i < 32; i++) {
|
||||
let char = Math.floor(Math.random() * 16).toString(16)
|
||||
const char = Math.floor(Math.random() * 16).toString(16)
|
||||
chars.push(char)
|
||||
}
|
||||
return chars.join('')
|
||||
|
||||
@@ -9,19 +9,19 @@ export class PronunciationConverter {
|
||||
async loadDict(dictName) {
|
||||
let promise
|
||||
switch (dictName) {
|
||||
case DICT_PINYIN:
|
||||
promise = import('./dictPinyin')
|
||||
break
|
||||
case DICT_KANA:
|
||||
promise = import('./dictKana')
|
||||
break
|
||||
default:
|
||||
return
|
||||
case DICT_PINYIN:
|
||||
promise = import('./dictPinyin')
|
||||
break
|
||||
case DICT_KANA:
|
||||
promise = import('./dictKana')
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
let dictTxt = (await promise).default
|
||||
let pronunciationMap = new Map()
|
||||
for (let item of dictTxt.split('\n')) {
|
||||
const dictTxt = (await promise).default
|
||||
const pronunciationMap = new Map()
|
||||
for (const item of dictTxt.split('\n')) {
|
||||
if (item.length === 0) {
|
||||
continue
|
||||
}
|
||||
@@ -31,10 +31,10 @@ export class PronunciationConverter {
|
||||
}
|
||||
|
||||
getPronunciation(text) {
|
||||
let res = []
|
||||
const res = []
|
||||
let lastHasPronunciation = null
|
||||
for (let char of text) {
|
||||
let pronunciation = this.pronunciationMap.get(char)
|
||||
for (const char of text) {
|
||||
const pronunciation = this.pronunciationMap.get(char)
|
||||
if (pronunciation === undefined) {
|
||||
if (lastHasPronunciation !== null && lastHasPronunciation) {
|
||||
res.push(' ')
|
||||
|
||||
@@ -6,7 +6,7 @@ export class Trie {
|
||||
_createNode() {
|
||||
return {
|
||||
children: {}, // char -> node
|
||||
value: null
|
||||
value: null,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export class Trie {
|
||||
throw new Error('key is empty')
|
||||
}
|
||||
let node = this._root
|
||||
for (let char of key) {
|
||||
for (const char of key) {
|
||||
let nextNode = node.children[char]
|
||||
if (nextNode === undefined) {
|
||||
nextNode = node.children[char] = this._createNode()
|
||||
@@ -27,8 +27,8 @@ export class Trie {
|
||||
|
||||
get(key) {
|
||||
let node = this._root
|
||||
for (let char of key) {
|
||||
let nextNode = node.children[char]
|
||||
for (const char of key) {
|
||||
const nextNode = node.children[char]
|
||||
if (nextNode === undefined) {
|
||||
return null
|
||||
}
|
||||
@@ -43,8 +43,8 @@ export class Trie {
|
||||
|
||||
lazyMatch(str) {
|
||||
let node = this._root
|
||||
for (let char of str) {
|
||||
let nextNode = node.children[char]
|
||||
for (const char of str) {
|
||||
const nextNode = node.children[char]
|
||||
if (nextNode === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user