feat: comments fully functional

This commit is contained in:
Alex 2022-04-20 02:03:46 +12:00
parent 291f961186
commit 3337a08095
10 changed files with 317 additions and 125 deletions

View File

@ -1,87 +0,0 @@
<template>
<div id="comment">
<a
:href="this.$rendererUtils.getNavigationEndpoints(comment.authorEndpoint)"
class="avatar-link pt-2"
>
<v-img
class="avatar-thumbnail"
:src="
comment.authorThumbnail.thumbnails[
comment.authorThumbnail.thumbnails.length - 1
].url
"
/>
</a>
<v-card-text class="comment-info pt-2">
<div
v-for="title in comment.title.runs"
:key="title.text"
style="margin-top: 0.5em"
class="vid-title"
>
{{ title.text }}
</div>
<div
class="caption background--text"
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
v-text="parseBottom(comment)"
/>
</v-card-text>
</div>
</template>
<style scoped>
.entry {
width: 100%; /* Prevent Loading Weirdness */
}
.vid-title {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
overflow: hidden;
}
.avatar-thumbnail {
margin-top: 0.5rem;
margin-left: 0.5rem;
border-radius: 50%;
width: 50px;
height: 50px;
}
#details {
display: flex;
flex-direction: row;
flex-basis: auto;
padding: 10px;
}
@media screen and (orientation: landscape) {
.entry {
margin-bottom: 8px;
}
#details {
flex-direction: column-reverse;
}
}
</style>
<script>
export default {
props: ["comment"],
methods: {
parseBottom(comment) {
const bottomText = [
comment.subscriberCountText?.runs[0].text,
comment.videoCountText?.runs.map((run) => run.text).join(" "),
];
return bottomText.join(" · ");
},
},
};
</script>

View File

@ -0,0 +1,90 @@
<template>
<div class="comment-thread" v-if="commentRenderer">
<a
:href="
this.$rendererUtils.getNavigationEndpoints(
commentRenderer.authorEndpoint
)
"
class="avatar-link"
>
<v-img
class="avatar-thumbnail"
:src="
commentRenderer.authorThumbnail.thumbnails[
commentRenderer.authorThumbnail.thumbnails.length - 1
].url
"
/>
</a>
<div class="comment-content">
<div class="comment-content--header">
<span class="font-weight-bold subtitle-2 pr-1">
{{ commentRenderer.authorText.runs[0].text }}
</span>
<span
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
class="background--text subtitle-2"
>
{{ commentRenderer.publishedTimeText.runs[0].text }}
</span>
</div>
<p class="comment-text">
<template v-for="text in commentRenderer.contentText.runs">{{
text.text
}}</template>
</p>
</div>
</div>
</template>
<style scoped>
.entry {
width: 100%; /* Prevent Loading Weirdness */
}
.avatar-thumbnail {
margin-top: 0.5rem;
margin-right: 0.5rem;
border-radius: 50%;
width: 48px;
height: 48px;
}
.comment-thread {
display: flex;
flex-direction: row;
flex-basis: auto;
width: 100%;
}
.comment-content {
display: flex;
flex-direction: column;
flex-basis: auto;
}
.comment-content--header {
display: flex;
}
.comment-text {
white-space: pre-line;
}
</style>
<script>
export default {
props: ["comment"],
data() {
return {
commentRenderer: null,
};
},
mounted() {
this.commentRenderer = this.comment?.comment?.commentRenderer;
},
};
</script>

View File

@ -0,0 +1,59 @@
<template>
<div class="comment-header" v-if="boxRenderer">
<div class="avatar-container">
<v-img
class="avatar-thumbnail"
:src="
boxRenderer.authorThumbnail.thumbnails[
boxRenderer.authorThumbnail.thumbnails.length - 1
].url
"
/>
</div>
<v-text-field disabled>
{{
boxRenderer.placeholderText.runs[
boxRenderer.placeholderText.runs.length - 1
].text
}}
</v-text-field>
</div>
</template>
<style scoped>
.entry {
width: 100%; /* Prevent Loading Weirdness */
}
.avatar-thumbnail {
margin-top: 0.5rem;
margin-right: 0.5rem;
border-radius: 50%;
width: 48px;
height: 48px;
}
.comment-header {
display: flex;
flex-direction: row;
flex-basis: auto;
padding: 10px;
width: 100%;
}
</style>
<script>
export default {
props: ["comment"],
data() {
return {
boxRenderer: null,
};
},
mounted() {
this.boxRenderer = this.comment?.createRenderer?.commentSimpleboxRenderer;
},
};
</script>

View File

@ -0,0 +1,88 @@
<template>
<dialog-base>
<template v-slot:header>
<v-toolbar-title>
<template v-for="text in commentData.headerText.runs">
<template v-if="text.bold">
<strong :key="text.text">{{ text.text }}</strong>
</template>
<template v-else>{{ text.text }}</template>
</template>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon dark @click="$emit('changeState', false)">
<v-icon>mdi-close</v-icon>
</v-btn>
</template>
<div class="commentList">
<template v-for="commentItems in comments">
<v-list-item
v-for="(comment, index) in commentItems.reloadContinuationItemsCommand
.continuationItems"
:key="index"
>
<component
v-if="getComponents()[Object.keys(comment)[0]]"
:is="Object.keys(comment)[0]"
:comment="comment[Object.keys(comment)[0]]"
></component>
</v-list-item>
</template>
</div>
<div class="loading" v-if="loading">
<v-sheet color="background" v-for="i in !comments ? 10 : 3" :key="i">
<v-skeleton-loader type="list-item-avatar-three-line, actions" />
</v-sheet>
</div>
</dialog-base>
</template>
<script>
import dialogBase from "~/components/dialogBase.vue";
import commentsHeaderRenderer from "~/components/Comments/commentsHeaderRenderer.vue";
import commentThreadRenderer from "~/components/Comments/commentThreadRenderer.vue";
export default {
props: ["continuation", "commentData", "showComments"],
model: {
prop: "showComments",
event: "changeState",
},
components: {
dialogBase,
commentsHeaderRenderer,
commentThreadRenderer,
},
data: () => ({
loading: true,
comments: [],
}),
mounted() {
this.paginate();
},
methods: {
getComponents() {
return this.$options.components;
},
paginate() {
this.loading = true;
this.$youtube
.getContinuation(this.continuation, "next", "web")
.then((result) => {
this.comments = this.comments.concat(
result.data.onResponseReceivedEndpoints
);
console.log("comments", this.comments);
if (this.comments) this.loading = false;
});
},
},
};
</script>

View File

@ -27,6 +27,7 @@ export default VDialog.extend({
contentClasses() {
return {
"swipeable-bottom-sheet__content": true,
"swipeable-bottom-sheet__content--active": this.isActive,
};
},
},

View File

@ -27,12 +27,18 @@
&.v-dialog:not(.v-dialog--fullscreen)
max-height: 100%
height: 100%
.swipeable-bottom-sheet__content
position: sticky !important
bottom: 0
top: unset
overflow-y: auto
align-items: center
display: flex
justify-content: center
pointer-events: none
position: absolute
transition: .2s map-get($transition, 'fast-in-fast-out'), z-index 1ms
width: 100%
height: 100%
outline: none
inset: 0

View File

@ -0,0 +1,24 @@
<template>
<v-card class="dialog-base">
<div class="toolbar-container">
<v-toolbar color="background" flat>
<slot name="header"></slot>
</v-toolbar>
<v-divider></v-divider>
</div>
<div class="dialog-body background">
<slot></slot>
</div>
</v-card>
</template>
<style lang="sass">
.dialog-base
display: flex
flex-direction: column
height: 100%
.dialog-body
overflow-y: auto
height: 100%
</style>

View File

@ -166,33 +166,11 @@
attach="#content-container"
v-if="loaded && video.commentData"
>
<v-card height="100%">
<div
class="toolbar-container"
style="position: sticky; inset: -1px; z-index: 2"
>
<v-toolbar color="background" flat>
<v-toolbar-title>
<template v-for="text in video.commentData.headerText.runs">
<template v-if="text.bold">
<strong :key="text.text">{{ text.text }}</strong>
</template>
<template v-else>{{ text.text }}</template>
</template>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon dark @click="showComments = !showComments">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-divider></v-divider>
</div>
<div scrollable>
<v-sheet color="background" v-for="i in 10" :key="i">
<v-skeleton-loader type="list-item-avatar-three-line, actions" />
</v-sheet>
</div>
</v-card>
<mainCommentRenderer
:continuation="video.commentContinuation"
:commentData="video.commentData"
v-model="showComments"
></mainCommentRenderer>
</swipeable-bottom-sheet>
<!-- <swipeable-bottom-sheet
@ -220,6 +198,7 @@ import SlimVideoDescriptionRenderer from "~/components/UtilRenderers/slimVideoDe
import ItemSectionRenderer from "~/components/SectionRenderers/itemSectionRenderer.vue";
import vuetubePlayer from "~/components/Player/index.vue";
import ShelfRenderer from "~/components/SectionRenderers/shelfRenderer.vue";
import mainCommentRenderer from "~/components/Comments/mainCommentRenderer.vue";
import SwipeableBottomSheet from "~/components/ExtendedComponents/swipeableBottomSheet";
export default {
@ -230,6 +209,7 @@ export default {
vuetubePlayer,
ItemSectionRenderer,
SwipeableBottomSheet,
mainCommentRenderer,
},
data: function () {
@ -254,13 +234,15 @@ export default {
},
},
},
mounted() {
this.mountedInit();
},
destroyed() {
beforeDestroy() {
clearInterval(this.interval);
},
methods: {
getVideo() {
this.loaded = false;

View File

@ -117,7 +117,7 @@ class Innertube {
continuation: continuation,
};
let url;
switch (type) {
switch (type.toLowerCase()) {
case "browse":
url = `${constants.URLS.YT_BASE_API}/browse?key=${this.key}`;
break;
@ -344,6 +344,19 @@ class Innertube {
(content) => content.commentsEntryPointHeaderRenderer
)?.commentsEntryPointHeaderRenderer,
playbackTracking: responseInfo.playbackTracking,
commentContinuation: responseNext.engagementPanels
.find(
(panel) =>
panel.engagementPanelSectionListRenderer.panelIdentifier ==
"engagement-panel-comments-section"
)
?.engagementPanelSectionListRenderer.content.sectionListRenderer.contents.find(
(content) => content.itemSectionRenderer
)
?.itemSectionRenderer.contents.find(
(content) => content.continuationItemRenderer
)?.continuationItemRenderer.continuationEndpoint.continuationCommand
.token,
};
return vidData;

View File

@ -133,15 +133,11 @@ const innertubeModule = {
},
async recommendContinuation(continuation, endpoint) {
const response = await InnertubeAPI.getContinuationsAsync(
continuation,
endpoint
);
const response = await this.getContinuation(continuation, endpoint);
const contents =
response.data.continuationContents.sectionListContinuation.contents;
const final = contents.map((shelves) => {
const video = shelves.shelfRenderer?.content?.horizontalListRenderer;
if (video) return video;
});
const continuations =
@ -149,6 +145,26 @@ const innertubeModule = {
return { continuations: continuations, contents: final };
},
async getContinuation(continuation, endpoint, mode = "android") {
let contextAdditional = {};
if (mode.toLowerCase() == "web") {
contextAdditional = {
...contextAdditional,
...{
client: {
clientName: constants.YT_API_VALUES.CLIENT_WEB,
clientVersion: constants.YT_API_VALUES.VERSION_WEB,
},
},
};
}
return await InnertubeAPI.getContinuationsAsync(
continuation,
endpoint,
contextAdditional
);
},
async search(query) {
try {
const response = await InnertubeAPI.getSearchAsync(query);