This commit is contained in:
@@ -13,8 +13,11 @@
|
|||||||
"@onecomme.com/onesdk": "^5.2.1",
|
"@onecomme.com/onesdk": "^5.2.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.1",
|
"@sveltejs/vite-plugin-svelte": "^3.0.1",
|
||||||
"@tsconfig/svelte": "^5.0.2",
|
"@tsconfig/svelte": "^5.0.2",
|
||||||
|
"@types/color": "^3.0.6",
|
||||||
"@types/node": "^20.14.2",
|
"@types/node": "^20.14.2",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
|
"color": "^4.2.3",
|
||||||
|
"husky": "^8.0.0",
|
||||||
"postcss": "^8.4.32",
|
"postcss": "^8.4.32",
|
||||||
"postcss-load-config": "^5.0.2",
|
"postcss-load-config": "^5.0.2",
|
||||||
"prettier": "^3.3.2",
|
"prettier": "^3.3.2",
|
||||||
@@ -25,7 +28,6 @@
|
|||||||
"tailwindcss": "^3.3.6",
|
"tailwindcss": "^3.3.6",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^5.1.4",
|
"vite": "^5.1.4"
|
||||||
"husky": "^8.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
preview.html
Normal file
12
preview.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Chat</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/preview/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Comment } from '@onecomme.com/onesdk/types/Comment';
|
|
||||||
import { chats } from './chats';
|
import { chats } from './chats';
|
||||||
import { type TransitionConfig } from 'svelte/transition';
|
import { type TransitionConfig } from 'svelte/transition';
|
||||||
import { quadOut as easing } from 'svelte/easing';
|
import { quadOut as easing } from 'svelte/easing';
|
||||||
|
import CommentRenderer from './components/CommentRenderer.svelte';
|
||||||
|
|
||||||
const child = new WeakMap<Node, number>();
|
const child = new WeakMap<Node, number>();
|
||||||
let container: HTMLDivElement;
|
let container: HTMLDivElement;
|
||||||
@@ -35,14 +35,6 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function authorColor(comment: Comment) {
|
|
||||||
if (comment.service === 'youtube') {
|
|
||||||
if (comment.data.isModerator) return 'text-blue-300';
|
|
||||||
if (comment.data.isMember) return 'text-green-300';
|
|
||||||
}
|
|
||||||
return 'text-zinc-200';
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -50,57 +42,13 @@
|
|||||||
bind:this={container}
|
bind:this={container}
|
||||||
>
|
>
|
||||||
{#each [...$chats].reverse() as chat (`${chat.service}/${chat.data.id}`)}
|
{#each [...$chats].reverse() as chat (`${chat.service}/${chat.data.id}`)}
|
||||||
{@const author = chat.data.displayName ?? chat.data.name}
|
<div class="origin-top-left" in:scrollEffect>
|
||||||
<div class="flex flex-row gap-4 pt-3 pb-1 box-border origin-top-left" in:scrollEffect>
|
<CommentRenderer comment={chat} />
|
||||||
<div class="w-10 min-w-10 relative">
|
|
||||||
<img
|
|
||||||
src={chat.data.originalProfileImage}
|
|
||||||
alt={author}
|
|
||||||
class="w-10 h-10 rounded-full absolute -top-3 bg-slate-200 border-2 border-slate-400 shadow"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bubble shadow relative flex-grow flex flex-col gap-1 bg-slate-800 bg-opacity-95 text-gray-50 px-4 py-2 rounded-b-xl rounded-tr-xl"
|
|
||||||
>
|
|
||||||
<div class="text-xs font-bold gap-0.5 items-center {authorColor(chat)}">
|
|
||||||
{author}
|
|
||||||
{#each chat.data.badges as badge}
|
|
||||||
<img
|
|
||||||
class="h-3 w-3 inline-block mx-0.5"
|
|
||||||
src={badge.url}
|
|
||||||
alt={badge.label}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
<div class="comment-container">
|
|
||||||
{@html chat.data.comment}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
.comment-container {
|
|
||||||
:global(img) {
|
|
||||||
@apply mx-0.5 h-6 w-6;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.bubble {
|
|
||||||
&::before {
|
|
||||||
@apply -left-2 top-0 block h-2 w-2 bg-slate-800 bg-opacity-95;
|
|
||||||
content: ' ';
|
|
||||||
position: absolute;
|
|
||||||
mask-image: linear-gradient(
|
|
||||||
45deg,
|
|
||||||
rgba(0, 0, 0, 0) 0%,
|
|
||||||
rgba(0, 0, 0, 0) 50%,
|
|
||||||
rgba(0, 0, 0, 1) 50%,
|
|
||||||
rgba(0, 0, 0, 1) 100%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.main {
|
.main {
|
||||||
mask-image: linear-gradient(
|
mask-image: linear-gradient(
|
||||||
180deg,
|
180deg,
|
||||||
|
|||||||
@@ -8,3 +8,12 @@
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.comment-container {
|
||||||
|
img {
|
||||||
|
@apply mx-0.5 h-6 w-6;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
48
src/components/BaseComment.svelte
Normal file
48
src/components/BaseComment.svelte
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Comment } from '@onecomme.com/onesdk/types/Comment';
|
||||||
|
import { authorColor } from '../lib/utils';
|
||||||
|
|
||||||
|
export let comment: Comment;
|
||||||
|
|
||||||
|
$: author = comment.data.displayName ?? comment.data.name;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-row gap-4 pt-3 pb-1">
|
||||||
|
<div class="w-10 min-w-10 relative">
|
||||||
|
<img
|
||||||
|
src={comment.data.originalProfileImage}
|
||||||
|
alt={author}
|
||||||
|
class="w-10 h-10 rounded-full absolute -top-3 bg-slate-200 border-2 border-slate-400 shadow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bubble shadow relative flex-grow flex flex-col gap-1 bg-slate-800 bg-opacity-95 text-gray-50 px-4 py-2 rounded-b-xl rounded-tr-xl"
|
||||||
|
>
|
||||||
|
<div class="text-xs font-bold gap-0.5 items-center {authorColor(comment)}">
|
||||||
|
{author}
|
||||||
|
{#each comment.data.badges as badge}
|
||||||
|
<img class="h-3 w-3 inline-block mx-0.5" src={badge.url} alt={badge.label} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div class="comment-container">
|
||||||
|
{@html comment.data.comment}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
.bubble {
|
||||||
|
&::before {
|
||||||
|
@apply -left-2 top-0 block h-2 w-2 bg-inherit;
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
mask-image: linear-gradient(
|
||||||
|
45deg,
|
||||||
|
rgba(0, 0, 0, 0) 0%,
|
||||||
|
rgba(0, 0, 0, 0) 50%,
|
||||||
|
rgba(0, 0, 0, 1) 50%,
|
||||||
|
rgba(0, 0, 0, 1) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
24
src/components/CommentRenderer.svelte
Normal file
24
src/components/CommentRenderer.svelte
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Comment } from '@onecomme.com/onesdk/types/Comment';
|
||||||
|
import BaseComment from './BaseComment.svelte';
|
||||||
|
import ReceivedMember from './youtube/ReceivedMember.svelte';
|
||||||
|
import Gift from './youtube/Gift.svelte';
|
||||||
|
|
||||||
|
export let comment: Comment;
|
||||||
|
|
||||||
|
function determineComponent(comment: Comment) {
|
||||||
|
if (comment.service === 'youtube') {
|
||||||
|
if (comment.data.hasGift) {
|
||||||
|
switch (comment.data.giftType) {
|
||||||
|
case 'giftreceived':
|
||||||
|
return ReceivedMember;
|
||||||
|
default:
|
||||||
|
return Gift;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return BaseComment;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:component this={determineComponent(comment)} {comment} />
|
||||||
78
src/components/youtube/Gift.svelte
Normal file
78
src/components/youtube/Gift.svelte
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { YouTubeComment } from '@onecomme.com/onesdk/types/Comment';
|
||||||
|
import { authorColor } from '../../lib/utils';
|
||||||
|
import Color from 'color';
|
||||||
|
|
||||||
|
export let comment: YouTubeComment;
|
||||||
|
|
||||||
|
$: author = comment.data.displayName ?? comment.data.name;
|
||||||
|
|
||||||
|
function bgColor(comment: YouTubeComment) {
|
||||||
|
const color = Color(comment.data.colors?.bodyBackgroundColor)
|
||||||
|
.darken(0.6)
|
||||||
|
.saturate(0.4)
|
||||||
|
.alpha(0.95);
|
||||||
|
return color.rgb();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-row gap-4 pt-3 pb-1" style="--sc-bg-color: {bgColor(comment)}">
|
||||||
|
<div class="w-10 min-w-10 relative">
|
||||||
|
<img
|
||||||
|
src={comment.data.originalProfileImage}
|
||||||
|
alt={author}
|
||||||
|
class="w-10 h-10 rounded-full absolute -top-3 bg-slate-200 border-2 border-slate-400 shadow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bubble shadow relative flex-grow flex flex-col gap-1 bg-slate-800 bg-opacity-95 text-gray-50 px-4 py-2 rounded-b-xl rounded-tr-xl"
|
||||||
|
>
|
||||||
|
<div class="flex flex-row items-center gap-1">
|
||||||
|
<div class="text-xs font-bold items-center {authorColor(comment)}">
|
||||||
|
{author}
|
||||||
|
{#each comment.data.badges as badge}
|
||||||
|
<img class="h-3 w-3 inline-block mx-0.5" src={badge.url} alt={badge.label} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{#if comment.data.giftType === 'milestonechat'}
|
||||||
|
<div class="text-xs font-bold ml-auto">
|
||||||
|
{comment.data.membership?.primary}
|
||||||
|
</div>
|
||||||
|
{:else if comment.data.paidText}
|
||||||
|
<div class="text-xs font-bold ml-auto">
|
||||||
|
{comment.data.paidText}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if comment.data.giftType === 'supersticker'}
|
||||||
|
{@html comment.data.comment}
|
||||||
|
{:else if comment.data.giftType === 'sponsorgift'}
|
||||||
|
<div class="text-sm font-semibold italic">
|
||||||
|
{@html comment.data.comment}
|
||||||
|
</div>
|
||||||
|
{:else if comment.data.comment}
|
||||||
|
<div class="comment-container">
|
||||||
|
{@html comment.data.comment}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
.bubble {
|
||||||
|
background-color: var(--sc-bg-color);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
@apply -left-2 top-0 block h-2 w-2 bg-inherit;
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
mask-image: linear-gradient(
|
||||||
|
45deg,
|
||||||
|
rgba(0, 0, 0, 0) 0%,
|
||||||
|
rgba(0, 0, 0, 0) 50%,
|
||||||
|
rgba(0, 0, 0, 1) 50%,
|
||||||
|
rgba(0, 0, 0, 1) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
31
src/components/youtube/ReceivedMember.svelte
Normal file
31
src/components/youtube/ReceivedMember.svelte
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { YouTubeComment } from '@onecomme.com/onesdk/types/Comment';
|
||||||
|
|
||||||
|
export let comment: YouTubeComment;
|
||||||
|
|
||||||
|
$: name = comment.data.displayName ?? comment.data.name;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="text-center text-white font-semibold shadow-slate-600 py-1 text-sm">
|
||||||
|
<span class="text-green-300">{name}</span>
|
||||||
|
{comment.data.comment.replace(comment.data.name, '')}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
div {
|
||||||
|
text-shadow:
|
||||||
|
var(--tw-shadow-color) 2px 0px 0px,
|
||||||
|
var(--tw-shadow-color) 1.75517px 0.958851px 0px,
|
||||||
|
var(--tw-shadow-color) 1.0806px 1.68294px 0px,
|
||||||
|
var(--tw-shadow-color) 0.141474px 1.99499px 0px,
|
||||||
|
var(--tw-shadow-color) -0.832294px 1.81859px 0px,
|
||||||
|
var(--tw-shadow-color) -1.60229px 1.19694px 0px,
|
||||||
|
var(--tw-shadow-color) -1.97998px 0.28224px 0px,
|
||||||
|
var(--tw-shadow-color) -1.87291px -0.701566px 0px,
|
||||||
|
var(--tw-shadow-color) -1.30729px -1.5136px 0px,
|
||||||
|
var(--tw-shadow-color) -0.421592px -1.95506px 0px,
|
||||||
|
var(--tw-shadow-color) 0.567324px -1.91785px 0px,
|
||||||
|
var(--tw-shadow-color) 1.41734px -1.41108px 0px,
|
||||||
|
var(--tw-shadow-color) 1.92034px -0.558831px 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
10
src/lib/utils.ts
Normal file
10
src/lib/utils.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { Comment } from '@onecomme.com/onesdk/types/Comment';
|
||||||
|
|
||||||
|
export function authorColor(comment: Comment) {
|
||||||
|
if (comment.service === 'youtube') {
|
||||||
|
if (comment.data.isOwner) return 'text-yellow-300';
|
||||||
|
if (comment.data.isModerator) return 'text-blue-300';
|
||||||
|
if (comment.data.isMember) return 'text-green-300';
|
||||||
|
}
|
||||||
|
return 'text-zinc-200';
|
||||||
|
}
|
||||||
46
src/preview/Preview.svelte
Normal file
46
src/preview/Preview.svelte
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Comment } from '@onecomme.com/onesdk/types/Comment';
|
||||||
|
import CommentRenderer from '../components/CommentRenderer.svelte';
|
||||||
|
import data from './chats.json';
|
||||||
|
import { state } from './states';
|
||||||
|
|
||||||
|
$: filtered = ($state.giftOnly ? data.filter((c) => c.data.hasGift) : data) as Comment[];
|
||||||
|
$: chats = filtered.slice($state.skip, $state.skip + $state.commentLimit);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="bg-white sticky top-0 z-50 flex flex-col gap-2 py-2">
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" bind:checked={$state.giftOnly} />
|
||||||
|
Gift Only
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" bind:checked={$state.showJson} />
|
||||||
|
Show JSON
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label class="flex flex-row items-center gap-1">
|
||||||
|
Limit ({$state.commentLimit})
|
||||||
|
<input type="range" bind:value={$state.commentLimit} class="flex-grow" min="10" />
|
||||||
|
</label>
|
||||||
|
<label class="flex flex-row items-center gap-1">
|
||||||
|
Skip ({$state.skip})
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
bind:value={$state.skip}
|
||||||
|
class="flex-grow"
|
||||||
|
min={0}
|
||||||
|
max={filtered.length - $state.commentLimit}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col py-3">
|
||||||
|
{#each chats as comment}
|
||||||
|
{#if $state.showJson}
|
||||||
|
<pre class="text-xs whitespace-pre-wrap">{JSON.stringify(comment, null, 2)}</pre>
|
||||||
|
{/if}
|
||||||
|
<CommentRenderer {comment} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
62765
src/preview/chats.json
Normal file
62765
src/preview/chats.json
Normal file
File diff suppressed because it is too large
Load Diff
8
src/preview/main.ts
Normal file
8
src/preview/main.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import '../app.pcss';
|
||||||
|
import Preview from './Preview.svelte';
|
||||||
|
|
||||||
|
const app = new Preview({
|
||||||
|
target: document.getElementById('app')!
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
||||||
8
src/preview/states.ts
Normal file
8
src/preview/states.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export const state = writable({
|
||||||
|
giftOnly: false,
|
||||||
|
commentLimit: 30,
|
||||||
|
skip: 0,
|
||||||
|
showJson: false
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user