publish
This commit is contained in:
commit
a79c1c599c
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "msedge",
|
||||
"request": "launch",
|
||||
"name": "Launch Edge against localhost",
|
||||
"url": "http://localhost:5173",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
34
.vscode/tasks.json
vendored
Normal file
34
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "server",
|
||||
"type": "shell",
|
||||
"command": "${workspaceFolder}/scripts/server.sh"
|
||||
},
|
||||
{
|
||||
"label": "vite",
|
||||
"type": "shell",
|
||||
"command": "${workspaceFolder}/scripts/vite.sh",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "caddy",
|
||||
"type": "npm",
|
||||
"script": "caddy-dev"
|
||||
},
|
||||
{
|
||||
"label": "vite-build",
|
||||
"type": "shell",
|
||||
"command": "${workspaceFolder}/scripts/vite-build.sh"
|
||||
},
|
||||
{
|
||||
"label": "dev-startup",
|
||||
"dependsOn": [
|
||||
"server",
|
||||
"vite"
|
||||
],
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
2
README.md
Normal file
2
README.md
Normal file
@ -0,0 +1,2 @@
|
||||
# YouTube Chat Designer V1.0
|
||||
A simple css editor for YouTube live chat.
|
49
dist/assets/index-C9J66gnR.js
vendored
Normal file
49
dist/assets/index-C9J66gnR.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/index-mL8GJvpZ.css
vendored
Normal file
1
dist/assets/index-mL8GJvpZ.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
dist/avatar.png
vendored
Normal file
BIN
dist/avatar.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 126 KiB |
13
dist/index.html
vendored
Normal file
13
dist/index.html
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>YouTube Chat Designer</title>
|
||||
<script type="module" crossorigin src="/assets/index-C9J66gnR.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-mL8GJvpZ.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
13
index.html
Normal file
13
index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="/src/index.scss">
|
||||
<title>YouTube Chat Designer</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
17
package.json
Normal file
17
package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "ytchat",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"sass": "^1.72.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"colortranslator": "^4.1.0",
|
||||
"fluentx": "../fluentx",
|
||||
"vite": "^5.2.6"
|
||||
}
|
||||
}
|
BIN
public/avatar.png
Normal file
BIN
public/avatar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 126 KiB |
3
scripts/vite-build.sh
Executable file
3
scripts/vite-build.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
cd web
|
||||
bunx --bun vite build
|
3
scripts/vite.sh
Executable file
3
scripts/vite.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
cd web
|
||||
bunx --bun vite --host 127.0.0.1
|
30
src/component/ColorInput.ts
Normal file
30
src/component/ColorInput.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { InputComponent } from "./InputComponent";
|
||||
|
||||
export class ColorInput extends InputComponent {
|
||||
$color = $('input').type('color').class('color');
|
||||
constructor(id: string) {
|
||||
super(id);
|
||||
this.addStaticClass('color');
|
||||
this.$color.id(id);
|
||||
this.layout();
|
||||
}
|
||||
|
||||
layout() {
|
||||
this.content([
|
||||
this.$label,
|
||||
$('div').content([
|
||||
this.$value,
|
||||
this.$color,
|
||||
])
|
||||
])
|
||||
|
||||
this.$value.on('input', e => this.$color.value(this.$value.value()))
|
||||
this.$color.on('input', e => this.$value.value(this.$color.value()))
|
||||
}
|
||||
|
||||
value(value: string | undefined) {
|
||||
super.value(value);
|
||||
this.$color.value(value);
|
||||
return this;
|
||||
}
|
||||
}
|
40
src/component/InputComponent.ts
Normal file
40
src/component/InputComponent.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { $Container } from "fluentx";
|
||||
|
||||
export class InputComponent extends $Container {
|
||||
$value = $('input').class('value');
|
||||
$unit = $('span').staticClass('unit');
|
||||
$label = $('label').hide(true);
|
||||
constructor(id: string) {
|
||||
super('div');
|
||||
this.staticClass('input-component', id);
|
||||
this.$value.id(id);
|
||||
this.$label.for(id);
|
||||
}
|
||||
|
||||
unit(unit: string) {
|
||||
this.$unit.content(unit);
|
||||
return this;
|
||||
}
|
||||
|
||||
value(number: number | string | undefined) {
|
||||
if (number === undefined) return this;
|
||||
this.$value.value(number.toString());
|
||||
return this;
|
||||
}
|
||||
|
||||
label(label: string) {
|
||||
if (label) this.$label.hide(false);
|
||||
this.$label.content(label);
|
||||
return this;
|
||||
}
|
||||
|
||||
min(number: number) {
|
||||
this.$value.min(number);
|
||||
return this;
|
||||
}
|
||||
|
||||
max(number: number) {
|
||||
this.$value.max(number);
|
||||
return this;
|
||||
}
|
||||
}
|
54
src/component/RangeInput.ts
Normal file
54
src/component/RangeInput.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { InputComponent } from "./InputComponent";
|
||||
|
||||
export class RangeInput extends InputComponent {
|
||||
$range = $('input').type('range').class('range');
|
||||
constructor(id: string) {
|
||||
super(id);
|
||||
this.addStaticClass('range');
|
||||
this.$range.id(id);
|
||||
this.$value;
|
||||
this.layout();
|
||||
}
|
||||
|
||||
layout() {
|
||||
this.content([
|
||||
this.$label,
|
||||
$('div').content([
|
||||
this.$value,
|
||||
this.$range,
|
||||
this.$unit
|
||||
])
|
||||
])
|
||||
|
||||
this.$range.on('input', e => {
|
||||
this.$value.value(`${this.$range.value()}`)
|
||||
})
|
||||
|
||||
this.$value.on('input', e => {
|
||||
this.$range.value(this.$value.value())
|
||||
})
|
||||
}
|
||||
|
||||
value(): string;
|
||||
value(value: string | undefined): this;
|
||||
value(value?: string) {
|
||||
if (!arguments.length) return this.value();
|
||||
if (value === undefined) return this;
|
||||
if (value.match(/[a-zA-Z]/)) value = value.replaceAll(/[a-zA-Z]/g, '')
|
||||
super.value(value);
|
||||
this.$range.value(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
min(number: number) {
|
||||
super.min(number);
|
||||
this.$range.min(number);
|
||||
return this;
|
||||
}
|
||||
|
||||
max(number: number) {
|
||||
super.max(number);
|
||||
this.$range.max(number);
|
||||
return this;
|
||||
}
|
||||
}
|
31
src/component/SelectInput.ts
Normal file
31
src/component/SelectInput.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import type { $SelectContentType } from "fluentx";
|
||||
import { InputComponent } from "./InputComponent";
|
||||
|
||||
export class SelectInput extends InputComponent {
|
||||
$select = $('select');
|
||||
constructor(id: string) {
|
||||
super(id);
|
||||
this.addStaticClass('select');
|
||||
this.$select.id(id);
|
||||
this.layout();
|
||||
}
|
||||
|
||||
layout() {
|
||||
this.content([
|
||||
this.$label,
|
||||
this.$select
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
add(option: $SelectContentType | OrMatrix<$SelectContentType>) {
|
||||
this.$select.add(option);
|
||||
return this;
|
||||
}
|
||||
|
||||
value(value: string | undefined) {
|
||||
super.value(value);
|
||||
this.$select.value(value);
|
||||
return this;
|
||||
}
|
||||
}
|
86
src/component/_$InputComponent.scss
Normal file
86
src/component/_$InputComponent.scss
Normal file
@ -0,0 +1,86 @@
|
||||
.input-component {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1rem;
|
||||
background-color: #ffffff15;
|
||||
border-radius: 5px;
|
||||
input.value {
|
||||
font-family: Noto Sans Mono;
|
||||
}
|
||||
|
||||
.unit {
|
||||
display: none;
|
||||
}
|
||||
|
||||
select {
|
||||
height: 2rem;
|
||||
width: 100%;
|
||||
max-width: 100px;
|
||||
border: none;
|
||||
padding: 0.4rem 0.6rem;
|
||||
background-color: #ffffff20;
|
||||
border-radius: 5px;
|
||||
color: white;
|
||||
|
||||
option {
|
||||
background-color: #ffffff20;
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
&.range {
|
||||
& > div {
|
||||
display: flex;
|
||||
gap: 0.2rem;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
input.value {
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
border: none;
|
||||
text-align: right;
|
||||
padding-top: 0.2rem;
|
||||
width: 2rem;
|
||||
}
|
||||
|
||||
input.range {
|
||||
appearance: none;
|
||||
height: 5px;
|
||||
background-color: #ffffff50;
|
||||
border-radius: 10px;
|
||||
outline: none;
|
||||
|
||||
&::-webkit-slider-thumb, &::-moz-range-thumb {
|
||||
background-color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.color {
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
gap: 0.2rem;
|
||||
justify-content: end;
|
||||
}
|
||||
input.value {
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
border: none;
|
||||
text-align: right;
|
||||
padding-top: 0.2rem;
|
||||
width: 4rem;
|
||||
}
|
||||
|
||||
input.color {
|
||||
padding: 0;
|
||||
appearance: none;
|
||||
border: none;
|
||||
background-color: #00000000;
|
||||
width: 30px;
|
||||
}
|
||||
}
|
||||
}
|
944
src/data/defaultStyle.ts
Normal file
944
src/data/defaultStyle.ts
Normal file
@ -0,0 +1,944 @@
|
||||
const element = {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "0px",
|
||||
"borderTopRightRadius": "0px",
|
||||
"borderBottomLeftRadius": "0px",
|
||||
"borderBottomRightRadius": "0px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "block"
|
||||
}
|
||||
|
||||
const image = {
|
||||
...element,
|
||||
height: '60px',
|
||||
width: '60px'
|
||||
}
|
||||
|
||||
const style : {[key:string]:Partial<CSSStyleDeclaration>} = {
|
||||
"Message": element,
|
||||
"Name": element,
|
||||
"Badge": element,
|
||||
"Avatar": image,
|
||||
"Author Area": element,
|
||||
"Content Area": element,
|
||||
"Outer Area": element,
|
||||
}
|
||||
export const defaultStyle: {
|
||||
[key: string]: {[key: string]: Partial<CSSStyleDeclaration>}
|
||||
} = {
|
||||
"Normal": {
|
||||
"Message": {
|
||||
"fontSize": "16px",
|
||||
"fontWeight": "400",
|
||||
"color": "#F0F0F0FF",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "0px",
|
||||
"borderTopRightRadius": "0px",
|
||||
"borderBottomLeftRadius": "0px",
|
||||
"borderBottomRightRadius": "0px",
|
||||
"marginTop": "4px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "2px",
|
||||
"marginRight": "2px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "block"
|
||||
},
|
||||
"Name": {
|
||||
"fontSize": "16px",
|
||||
"fontWeight": "400",
|
||||
"color": "#FFFFFFFF",
|
||||
"backgroundColor": "#00000099",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "10px",
|
||||
"borderTopRightRadius": "10px",
|
||||
"borderBottomLeftRadius": "10px",
|
||||
"borderBottomRightRadius": "10px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "4px",
|
||||
"paddingBottom": "4px",
|
||||
"paddingLeft": "8px",
|
||||
"paddingRight": "8px",
|
||||
"opacity": "1",
|
||||
"display": "block"
|
||||
},
|
||||
"Badge": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "0px",
|
||||
"borderTopRightRadius": "0px",
|
||||
"borderBottomLeftRadius": "0px",
|
||||
"borderBottomRightRadius": "0px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "block"
|
||||
},
|
||||
"Avatar": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000FF",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "10px",
|
||||
"borderTopRightRadius": "10px",
|
||||
"borderBottomLeftRadius": "10px",
|
||||
"borderBottomRightRadius": "10px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "block",
|
||||
"height": "60px",
|
||||
"width": "60px"
|
||||
},
|
||||
"Author Area": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000FF",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "0px",
|
||||
"borderTopRightRadius": "0px",
|
||||
"borderBottomLeftRadius": "0px",
|
||||
"borderBottomRightRadius": "0px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "flex"
|
||||
},
|
||||
"Content Area": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000FF",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "0px",
|
||||
"borderTopRightRadius": "0px",
|
||||
"borderBottomLeftRadius": "0px",
|
||||
"borderBottomRightRadius": "0px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "10px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "block"
|
||||
},
|
||||
"Outer Area": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000FF",
|
||||
"backgroundColor": "#3D3D3D80",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "10px",
|
||||
"borderTopRightRadius": "10px",
|
||||
"borderBottomLeftRadius": "10px",
|
||||
"borderBottomRightRadius": "10px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "10px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "8px",
|
||||
"paddingBottom": "8px",
|
||||
"paddingLeft": "8px",
|
||||
"paddingRight": "8px",
|
||||
"opacity": "1",
|
||||
"display": "flex"
|
||||
}
|
||||
},
|
||||
"Member": {
|
||||
"Message": {
|
||||
"fontSize": "16px",
|
||||
"fontWeight": "400",
|
||||
"color": "#F0F0F0FF",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "0px",
|
||||
"borderTopRightRadius": "0px",
|
||||
"borderBottomLeftRadius": "0px",
|
||||
"borderBottomRightRadius": "0px",
|
||||
"marginTop": "4px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "2px",
|
||||
"marginRight": "2px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "block"
|
||||
},
|
||||
"Name": {
|
||||
"fontSize": "16px",
|
||||
"fontWeight": "400",
|
||||
"color": "#0AFFFBFF",
|
||||
"backgroundColor": "#527F8099",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "10px",
|
||||
"borderTopRightRadius": "10px",
|
||||
"borderBottomLeftRadius": "10px",
|
||||
"borderBottomRightRadius": "10px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "4px",
|
||||
"paddingBottom": "4px",
|
||||
"paddingLeft": "8px",
|
||||
"paddingRight": "8px",
|
||||
"opacity": "1",
|
||||
"display": "block"
|
||||
},
|
||||
"Badge": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "0px",
|
||||
"borderTopRightRadius": "0px",
|
||||
"borderBottomLeftRadius": "0px",
|
||||
"borderBottomRightRadius": "0px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "block"
|
||||
},
|
||||
"Avatar": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000FF",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "10px",
|
||||
"borderTopRightRadius": "10px",
|
||||
"borderBottomLeftRadius": "10px",
|
||||
"borderBottomRightRadius": "10px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "block",
|
||||
"height": "60px",
|
||||
"width": "60px"
|
||||
},
|
||||
"Author Area": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000FF",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "0px",
|
||||
"borderTopRightRadius": "0px",
|
||||
"borderBottomLeftRadius": "0px",
|
||||
"borderBottomRightRadius": "0px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "flex"
|
||||
},
|
||||
"Content Area": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000FF",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "0px",
|
||||
"borderTopRightRadius": "0px",
|
||||
"borderBottomLeftRadius": "0px",
|
||||
"borderBottomRightRadius": "0px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "10px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "block"
|
||||
},
|
||||
"Outer Area": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000FF",
|
||||
"backgroundColor": "#3D3D3D80",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "10px",
|
||||
"borderTopRightRadius": "10px",
|
||||
"borderBottomLeftRadius": "10px",
|
||||
"borderBottomRightRadius": "10px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "10px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "8px",
|
||||
"paddingBottom": "8px",
|
||||
"paddingLeft": "8px",
|
||||
"paddingRight": "8px",
|
||||
"opacity": "1",
|
||||
"display": "flex"
|
||||
}
|
||||
},
|
||||
"Moderator": {
|
||||
"Message": {
|
||||
"fontSize": "16px",
|
||||
"fontWeight": "400",
|
||||
"color": "#F0F0F0FF",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "0px",
|
||||
"borderTopRightRadius": "0px",
|
||||
"borderBottomLeftRadius": "0px",
|
||||
"borderBottomRightRadius": "0px",
|
||||
"marginTop": "4px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "2px",
|
||||
"marginRight": "2px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "block"
|
||||
},
|
||||
"Name": {
|
||||
"fontSize": "16px",
|
||||
"fontWeight": "400",
|
||||
"color": "#B8CFFFFF",
|
||||
"backgroundColor": "#2E58FF99",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "10px",
|
||||
"borderTopRightRadius": "10px",
|
||||
"borderBottomLeftRadius": "10px",
|
||||
"borderBottomRightRadius": "10px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "4px",
|
||||
"paddingBottom": "4px",
|
||||
"paddingLeft": "8px",
|
||||
"paddingRight": "8px",
|
||||
"opacity": "1",
|
||||
"display": "block"
|
||||
},
|
||||
"Badge": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "0px",
|
||||
"borderTopRightRadius": "0px",
|
||||
"borderBottomLeftRadius": "0px",
|
||||
"borderBottomRightRadius": "0px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "block"
|
||||
},
|
||||
"Avatar": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000FF",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "10px",
|
||||
"borderTopRightRadius": "10px",
|
||||
"borderBottomLeftRadius": "10px",
|
||||
"borderBottomRightRadius": "10px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "block",
|
||||
"height": "60px",
|
||||
"width": "60px"
|
||||
},
|
||||
"Author Area": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000FF",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "0px",
|
||||
"borderTopRightRadius": "0px",
|
||||
"borderBottomLeftRadius": "0px",
|
||||
"borderBottomRightRadius": "0px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "flex"
|
||||
},
|
||||
"Content Area": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000FF",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "0px",
|
||||
"borderTopRightRadius": "0px",
|
||||
"borderBottomLeftRadius": "0px",
|
||||
"borderBottomRightRadius": "0px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "10px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "block"
|
||||
},
|
||||
"Outer Area": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000FF",
|
||||
"backgroundColor": "#3D3D3D80",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "10px",
|
||||
"borderTopRightRadius": "10px",
|
||||
"borderBottomLeftRadius": "10px",
|
||||
"borderBottomRightRadius": "10px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "10px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "8px",
|
||||
"paddingBottom": "8px",
|
||||
"paddingLeft": "8px",
|
||||
"paddingRight": "8px",
|
||||
"opacity": "1",
|
||||
"display": "flex"
|
||||
}
|
||||
},
|
||||
"Owner": {
|
||||
"Message": {
|
||||
"fontSize": "16px",
|
||||
"fontWeight": "400",
|
||||
"color": "#F0F0F0FF",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "0px",
|
||||
"borderTopRightRadius": "0px",
|
||||
"borderBottomLeftRadius": "0px",
|
||||
"borderBottomRightRadius": "0px",
|
||||
"marginTop": "4px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "2px",
|
||||
"marginRight": "2px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "block"
|
||||
},
|
||||
"Name": {
|
||||
"fontSize": "16px",
|
||||
"fontWeight": "400",
|
||||
"color": "#FFEB6BFF",
|
||||
"backgroundColor": "#00000099",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "10px",
|
||||
"borderTopRightRadius": "10px",
|
||||
"borderBottomLeftRadius": "10px",
|
||||
"borderBottomRightRadius": "10px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "4px",
|
||||
"paddingBottom": "4px",
|
||||
"paddingLeft": "8px",
|
||||
"paddingRight": "8px",
|
||||
"opacity": "1",
|
||||
"display": "block"
|
||||
},
|
||||
"Badge": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "0px",
|
||||
"borderTopRightRadius": "0px",
|
||||
"borderBottomLeftRadius": "0px",
|
||||
"borderBottomRightRadius": "0px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "block"
|
||||
},
|
||||
"Avatar": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000FF",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "10px",
|
||||
"borderTopRightRadius": "10px",
|
||||
"borderBottomLeftRadius": "10px",
|
||||
"borderBottomRightRadius": "10px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "block",
|
||||
"height": "60px",
|
||||
"width": "60px"
|
||||
},
|
||||
"Author Area": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000FF",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "0px",
|
||||
"borderTopRightRadius": "0px",
|
||||
"borderBottomLeftRadius": "0px",
|
||||
"borderBottomRightRadius": "0px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "flex"
|
||||
},
|
||||
"Content Area": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000FF",
|
||||
"backgroundColor": "#00000000",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "0px",
|
||||
"borderTopRightRadius": "0px",
|
||||
"borderBottomLeftRadius": "0px",
|
||||
"borderBottomRightRadius": "0px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "0px",
|
||||
"marginLeft": "10px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "0px",
|
||||
"paddingBottom": "0px",
|
||||
"paddingLeft": "0px",
|
||||
"paddingRight": "0px",
|
||||
"opacity": "1",
|
||||
"display": "block"
|
||||
},
|
||||
"Outer Area": {
|
||||
"fontSize": "16px",
|
||||
"color": "#000000FF",
|
||||
"backgroundColor": "#3D3D3D80",
|
||||
"borderTopStyle": "solid",
|
||||
"borderTopColor": "#000000",
|
||||
"borderTopWidth": "0px",
|
||||
"borderBottomStyle": "solid",
|
||||
"borderBottomColor": "#000000",
|
||||
"borderBottomWidth": "0px",
|
||||
"borderLeftStyle": "solid",
|
||||
"borderLeftColor": "#000000",
|
||||
"borderLeftWidth": "0px",
|
||||
"borderRightStyle": "solid",
|
||||
"borderRightColor": "#000000",
|
||||
"borderRightWidth": "0px",
|
||||
"borderTopLeftRadius": "10px",
|
||||
"borderTopRightRadius": "10px",
|
||||
"borderBottomLeftRadius": "10px",
|
||||
"borderBottomRightRadius": "10px",
|
||||
"marginTop": "0px",
|
||||
"marginBottom": "10px",
|
||||
"marginLeft": "0px",
|
||||
"marginRight": "0px",
|
||||
"paddingTop": "8px",
|
||||
"paddingBottom": "8px",
|
||||
"paddingLeft": "8px",
|
||||
"paddingRight": "8px",
|
||||
"opacity": "1",
|
||||
"display": "flex"
|
||||
}
|
||||
}
|
||||
}
|
||||
//{"Normal": style,"Member": style,"Moderator": style,"Owner": style}
|
44
src/data/ytchat.ts
Normal file
44
src/data/ytchat.ts
Normal file
@ -0,0 +1,44 @@
|
||||
export const ytchat_css = `yt-live-chat-renderer yt-live-chat-header-renderer,
|
||||
yt-live-chat-renderer yt-live-chat-ticker-renderer,
|
||||
yt-live-chat-renderer yt-live-chat-message-input-renderer,
|
||||
yt-live-chat-renderer yt-reaction-control-panel-overlay-view-model,
|
||||
yt-live-chat-viewer-engagement-message-renderer,
|
||||
yt-live-chat-banner-manager,
|
||||
yt-live-chat-docked-message {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
yt-live-chat-text-message-renderer #author-photo {
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
yt-live-chat-text-message-renderer #author-photo img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
yt-live-chat-text-message-renderer #content {
|
||||
width: 100%;
|
||||
}
|
||||
yt-live-chat-text-message-renderer #menu {
|
||||
display: none;
|
||||
}
|
||||
yt-live-chat-text-message-renderer #chat-badges {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 0.2rem !important;
|
||||
}
|
||||
yt-live-chat-text-message-renderer yt-live-chat-author-chip {
|
||||
align-items: unset !important;
|
||||
}
|
||||
yt-live-chat-text-message-renderer yt-live-chat-author-badge-renderer[type="moderator"] {
|
||||
display: block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
fill: #5e84f1;
|
||||
}
|
||||
|
||||
`
|
370
src/index.scss
Normal file
370
src/index.scss
Normal file
@ -0,0 +1,370 @@
|
||||
@import './component/$InputComponent';
|
||||
:root {
|
||||
--background-color: #131313;
|
||||
--font-family : system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-family: var(--font-family);
|
||||
overflow-y: scroll;
|
||||
background-color: var(--background-color);
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
|
||||
}
|
||||
|
||||
html {
|
||||
::-webkit-scrollbar {
|
||||
background-color: var(--background-color);
|
||||
width: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #ffffff20;
|
||||
}
|
||||
::-webkit-scrollbar-button {
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px;
|
||||
background-color: #00000000;
|
||||
border: 1px solid #ffffff20;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #ffffff10;
|
||||
}
|
||||
}
|
||||
|
||||
app {
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
|
||||
h1 {
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #ffffff90;
|
||||
font-weight: 100;
|
||||
letter-spacing: 0.1rem;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
|
||||
.console {
|
||||
width: 100%;
|
||||
max-width: 60%;
|
||||
|
||||
label, button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: #13131390;
|
||||
backdrop-filter: blur(10px);
|
||||
padding-block: 1rem;
|
||||
|
||||
.action-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
.role-list {
|
||||
display: flex;
|
||||
border: 1px solid #ffffff20;
|
||||
border-radius: 10px;
|
||||
width: fit-content;
|
||||
overflow: hidden;
|
||||
span {
|
||||
padding: 10px;
|
||||
color: #ffffffaa;
|
||||
font-weight: 700;
|
||||
}
|
||||
& > div {
|
||||
display: flex;
|
||||
.role {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
label {
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
}
|
||||
&:has(input:checked) {
|
||||
background-color: #ffffff20;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #ffffff10;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button-list {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.element-list {
|
||||
display: flex;
|
||||
gap: 0.1rem;
|
||||
button {
|
||||
flex: 1;
|
||||
background-color: transparent;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
border: none;
|
||||
font-size: 16px;
|
||||
padding-block: 0.6rem 0.4rem;
|
||||
padding-inline: 1rem;
|
||||
cursor: pointer;
|
||||
color: #ffffff66;
|
||||
border-radius: 5px;
|
||||
|
||||
&.active {
|
||||
background-color: #ffffff20;
|
||||
color: white;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #ffffff10;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.style-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
padding-block: 1rem;
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3rem;
|
||||
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
margin-block: 0.5em;
|
||||
font-weight: 200;
|
||||
letter-spacing: 0.2rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
& > div {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
border: 2px solid #ffffff15;
|
||||
border-radius: 5px;
|
||||
padding: 1rem;
|
||||
h4 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #ffffff15;
|
||||
border-radius: 5px;
|
||||
.input-component {
|
||||
background-color: #00000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview {
|
||||
border: 1px solid #ffffff20;
|
||||
border-radius: 10px;
|
||||
padding: 1rem;
|
||||
width: 400px;
|
||||
height: 80%;
|
||||
position: fixed;
|
||||
left: calc(50vw + 150px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
header {
|
||||
margin-block: 0.6rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
|
||||
h2 {
|
||||
margin-block: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ytchat {
|
||||
flex: 5;
|
||||
overflow-y: scroll;
|
||||
padding-right: 2px;
|
||||
}
|
||||
.input-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
padding-top: 1rem;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
align-items: center;
|
||||
}
|
||||
select {
|
||||
height: 2rem;
|
||||
width: 100%;
|
||||
max-width: 100px;
|
||||
border: none;
|
||||
padding: 0.4rem 0.6rem;
|
||||
background-color: #ffffff20;
|
||||
border-radius: 5px;
|
||||
color: white;
|
||||
|
||||
option {
|
||||
background-color: #ffffff20;
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
textarea, input {
|
||||
background-color: #ffffff20;
|
||||
border: none;
|
||||
padding: 10px;
|
||||
height: 1rem;
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
resize: none;
|
||||
flex: 5;
|
||||
font-family: var(--font-family);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
button {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
yt-live-chat-renderer {
|
||||
|
||||
yt-live-chat-header-renderer,
|
||||
yt-live-chat-ticker-renderer,
|
||||
yt-live-chat-message-input-renderer,
|
||||
yt-reaction-control-panel-overlay-view-model {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
&:hover {
|
||||
.overlay {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
.overlay {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(270deg, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0.2) 100%);
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
color: white;
|
||||
padding-right: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#author-photo {
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#content {
|
||||
width: 100%;
|
||||
#timestamp {}
|
||||
|
||||
yt-live-chat-author-chip {
|
||||
align-items: unset !important;
|
||||
|
||||
#prepend-chat-badges {}
|
||||
#author-name {
|
||||
#chip-badges {}
|
||||
}
|
||||
|
||||
#chat-badges {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.2rem;
|
||||
|
||||
yt-live-chat-author-badge-renderer[type="moderator"] {
|
||||
display: block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
fill: #5e84f1;
|
||||
|
||||
svg {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#message {
|
||||
}
|
||||
|
||||
#deleted-state {}
|
||||
}
|
||||
|
||||
#menu {
|
||||
display: none;
|
||||
}
|
||||
}
|
188
src/main.ts
Normal file
188
src/main.ts
Normal file
@ -0,0 +1,188 @@
|
||||
import { $, $Button, $Select, $Textarea } from 'fluentx';
|
||||
import { StylePanel } from './structure/StylePanel';
|
||||
import { StyleModel } from './structure/StyleModel';
|
||||
import { defaultStyle } from './data/defaultStyle';
|
||||
import { YouTubeChat } from './structure/YouTubeChat';
|
||||
import { $Input } from 'fluentx/lib/$Input';
|
||||
import { ColorInput } from './component/ColorInput';
|
||||
import { ytchat_css } from './data/ytchat';
|
||||
|
||||
export const ROLE_MODEL_MAP = new Map<string, Map<string, StyleModel>>();
|
||||
const ROLE_LIST = ['Normal', 'Member', 'Moderator', 'Owner'];
|
||||
const ELEMENT_LIST = ['Message', 'Name', 'Badge', 'Avatar', 'Author Area', 'Content Area', 'Outer Area'];
|
||||
const IS_TEXT_ELEMENT = ['Message', 'Name', 'Timestamp'];
|
||||
const IS_IMAGE_ELEMENT = ['Badge', 'Avatar'];
|
||||
const PANEL_MAP = new Map<string, StylePanel>();
|
||||
|
||||
const $view = $('view');
|
||||
export const $chat = new YouTubeChat().css({backgroundColor: '#131313'})
|
||||
.send({name: 'Normal User', message: 'Hover mouse on the message will show the author role info.', role: 'Normal'})
|
||||
.send({name: 'Member User', message: 'You can use Shift + Left Click on Role list to select multiple role!', role: 'Member'})
|
||||
.send({name: 'Moderator User', message: 'yoyo', role: 'Moderator'})
|
||||
.send({name: 'Owner User', message: 'Using the input panel to send message for testing.', role: 'Owner'})
|
||||
|
||||
init();
|
||||
function init() {
|
||||
ROLE_MODEL_MAP.clear();
|
||||
for (const role of ROLE_LIST) {
|
||||
const STYLE_MAP = new Map<string, StyleModel>()
|
||||
ROLE_MODEL_MAP.set(role, STYLE_MAP)
|
||||
for (const element of ELEMENT_LIST) {
|
||||
const model = new StyleModel(defaultStyle[role][element]);
|
||||
STYLE_MAP.set(element, model)
|
||||
$chat.updateStyle(element, model, [role])
|
||||
}
|
||||
}
|
||||
|
||||
for (const element of ELEMENT_LIST) PANEL_MAP.set(element, new StylePanel(element, IS_TEXT_ELEMENT.includes(element) ? 'text' : IS_IMAGE_ELEMENT.includes(element) ? 'image' : 'element'))
|
||||
}
|
||||
|
||||
function exportJson() {
|
||||
const json = {};
|
||||
for (const [role, element_model_map] of ROLE_MODEL_MAP.entries()) {
|
||||
const element_json = {};
|
||||
for (const [element, model]of element_model_map.entries()) {
|
||||
element_json[element] = model.data
|
||||
}
|
||||
json[role] = element_json;
|
||||
}
|
||||
console.debug(json);
|
||||
return json;
|
||||
}
|
||||
|
||||
|
||||
$('app').content([
|
||||
$('h1').content(['YouTube Chat Designer v1.0', $('span').content('DEFAULTKAVY')]),
|
||||
$('div').class('content').content([
|
||||
$('div').class('console').content([
|
||||
$('div').class('menu').content([
|
||||
$('div').class('action-row').content([
|
||||
$('div').class('role-list').content([
|
||||
$('span').content('Role'),
|
||||
$('div').content([
|
||||
ROLE_LIST.map(id => [
|
||||
$('div').class('role').content($div => [
|
||||
$('input').class('role-checkbox').type('checkbox').value(id).id(id.toLowerCase()).on('input', refreshPanel),
|
||||
$('label').content(id).for(id.toLowerCase())
|
||||
.on('click', (e) => {
|
||||
const checkboxes = $<$Input>('::.role-checkbox')
|
||||
if (e.shiftKey) return;
|
||||
checkboxes.forEach($input => {if ($input.id() !== id.toLowerCase()) $input.checked(false)})
|
||||
})
|
||||
])
|
||||
])
|
||||
])
|
||||
]),
|
||||
$('div').class('button-list').content([
|
||||
$('button').content('Select All').on('click', (e, $button) => {
|
||||
const $input_list = $<$Input>('::.role-checkbox');
|
||||
const IS_ALL_CHECKED = !$input_list.find($input => $input.checked() === false);
|
||||
$input_list
|
||||
.slice(IS_ALL_CHECKED ? 1 : 0)
|
||||
.forEach($input => $input.checked(!IS_ALL_CHECKED))
|
||||
refreshPanel();
|
||||
}),
|
||||
$('button').content('Export JSON').on('click', () => exportJson()),
|
||||
$('button').content('Export CSS').on('click', () => exportCSS())
|
||||
]),
|
||||
]),
|
||||
$('div').class('element-list').content($content => [
|
||||
ELEMENT_LIST.map(id => {
|
||||
$view.set(id, PANEL_MAP.get(id)!)
|
||||
return $('button').staticClass('element-button').content(id).on('click', (e, $button) => $view.switch(id))
|
||||
.self($button => {
|
||||
$view.event.on('switch', content_id => {
|
||||
if (content_id !== id) $button.removeClass('active');
|
||||
else $button.addClass('active')
|
||||
})
|
||||
})
|
||||
.on('mouseenter', e => {
|
||||
$chat.showHint(id)
|
||||
})
|
||||
})
|
||||
]).on('mouseleave', e => {
|
||||
$chat.hideHint();
|
||||
}),
|
||||
]),
|
||||
$view
|
||||
]),
|
||||
|
||||
$('div').class('preview').content([
|
||||
$('header').content([
|
||||
$('h2').content('YouTube Chat Preview'),
|
||||
new ColorInput('ytchat-background-color').label('Background Color').value('#131313').on('input', (e, $input) => {
|
||||
$chat.css({backgroundColor: $input.$color.value()})
|
||||
})
|
||||
]),
|
||||
$chat,
|
||||
$('div').class('input-panel').content([
|
||||
$('div').content([
|
||||
$('select').id('role-select').add([
|
||||
ROLE_LIST.map(role => $('option').content(role).value(role))
|
||||
]),
|
||||
$('input').id('username').placeholder('User Name')
|
||||
]),
|
||||
$('div').content([
|
||||
$('textarea').id('message-input').attribute('placeholder', 'Type here...').on('keydown', e => {
|
||||
if (e.key === 'Enter') {e.preventDefault(); send();}
|
||||
}),
|
||||
$('button').content('Send').on('click', (e) => {
|
||||
send();
|
||||
})
|
||||
])
|
||||
])
|
||||
])
|
||||
])
|
||||
]).self($app => document.body.append($app.dom))
|
||||
load();
|
||||
|
||||
exportCSS();
|
||||
function load() {
|
||||
$view.switch('Message');
|
||||
$<$Input>(':#normal')?.checked(true);
|
||||
refreshPanel();
|
||||
}
|
||||
|
||||
function refreshPanel() {
|
||||
PANEL_MAP.forEach(panel => panel.layout())
|
||||
}
|
||||
|
||||
function send() {
|
||||
const message = $<$Textarea>(':#message-input')!.value().trim();
|
||||
if (message === '') return;
|
||||
$chat.send({
|
||||
name: $<$Input>(':#username')!.value(),
|
||||
message: message,
|
||||
role: $<$Select>(':#role-select')!.value() as any
|
||||
})
|
||||
$chat.dom.scrollTop = $chat.dom.scrollHeight;
|
||||
}
|
||||
|
||||
function exportCSS() {
|
||||
let css = ytchat_css;
|
||||
for (const [ROLE, MODEL_MAP] of ROLE_MODEL_MAP) {
|
||||
for (const [ELEMENT, MODEL] of MODEL_MAP) {
|
||||
let selector = ROLE === 'Normal' ? `yt-live-chat-text-message-renderer` : `yt-live-chat-text-message-renderer[author-type="${ROLE.toLowerCase()}"]`
|
||||
switch (ELEMENT) {
|
||||
case 'Message': selector += ' #message'; break;
|
||||
case 'Name': selector += ' #author-name '; break;
|
||||
case 'Badge': selector += ' #chat-bagdes'; break;
|
||||
case 'Avatar': selector += ' #author-photo'; break;
|
||||
case 'Author Area': selector += ' yt-live-chat-author-clip'; break;
|
||||
case 'Content Area': selector += ' #content'; break;
|
||||
case 'Outer Area': break;
|
||||
}
|
||||
let stylesheet = '';
|
||||
for (const [key, value] of Object.entries(MODEL.data)) {
|
||||
stylesheet += ` ${toCssProp(key)}: ${value} !important;\n`
|
||||
}
|
||||
css += `${selector} {\n${stylesheet}\n}\n\n`
|
||||
}
|
||||
}
|
||||
|
||||
console.debug(css)
|
||||
|
||||
function toCssProp(str: string) {
|
||||
return str.replaceAll(/[A-Z]/g, $1 => `-${$1.toLowerCase()}`)
|
||||
}
|
||||
}
|
90
src/structure/StyleModel.ts
Normal file
90
src/structure/StyleModel.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { $Input } from "fluentx/lib/$Input";
|
||||
import { StylePanel } from "./StylePanel";
|
||||
import { ColorTranslator } from "colortranslator";
|
||||
import { $Select } from "fluentx";
|
||||
import { firstCap } from "./util";
|
||||
|
||||
export class StyleModel {
|
||||
data: Partial<CSSStyleDeclaration>;
|
||||
constructor(data: Partial<CSSStyleDeclaration>) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
update(panel: StylePanel) {
|
||||
const filterMultitype = (value: string | undefined, recover: any, unit = false) => {
|
||||
if (value === '??' || value === undefined) {
|
||||
return recover;
|
||||
} else {
|
||||
if (unit) return value + 'px';
|
||||
return value;
|
||||
}
|
||||
}
|
||||
const border = (dir: 'top' | 'right' | 'left' | 'bottom') => {
|
||||
return {
|
||||
[`border${firstCap(dir)}Style`]: filterMultitype(panel.$<$Select>(`#border-${dir}-style`)?.value(), this.data[`boder${firstCap(dir)}Style`]),
|
||||
[`border${firstCap(dir)}Color`]: filterMultitype(panel.$<$Input>(`#border-${dir}-color`)?.value(), this.data[`boder${firstCap(dir)}Color`]),
|
||||
[`border${firstCap(dir)}Width`]: filterMultitype(panel.$<$Input>(`#border-${dir}-width`)?.value(), this.data[`boder${firstCap(dir)}Color`], true)
|
||||
}
|
||||
}
|
||||
|
||||
const dir = (prop: 'margin' | 'padding') => {
|
||||
return {
|
||||
[`${prop}Top`]: filterMultitype(panel.$<$Input>(`#${prop}-top`)?.value(), this.data[`${prop}Top`], true),
|
||||
[`${prop}Bottom`]: filterMultitype(panel.$<$Input>(`#${prop}-bottom`)?.value(), this.data[`${prop}Bottom`], true),
|
||||
[`${prop}Left`]: filterMultitype(panel.$<$Input>(`#${prop}-left`)?.value(), this.data[`${prop}Left`], true),
|
||||
[`${prop}Right`]: filterMultitype(panel.$<$Input>(`#${prop}-right`)?.value(), this.data[`${prop}Right`], true),
|
||||
}
|
||||
}
|
||||
const data: Partial<CSSStyleDeclaration> = {
|
||||
fontSize: filterMultitype(panel.$<$Input>('#font-size')?.value(), this.data.fontSize, true),
|
||||
fontWeight: filterMultitype(panel.$<$Input>('#font-weight')?.value(), this.data.fontWeight),
|
||||
color:
|
||||
new ColorTranslator({
|
||||
...new ColorTranslator(filterMultitype(panel.$<$Input>('#font-color')?.value(), this.data.color)).RGBObject,
|
||||
A: filterMultitype(panel.$<$Input>('#font-color-transparent')?.value(), new ColorTranslator(this.data.color!).A)
|
||||
}).HEXA,
|
||||
backgroundColor:
|
||||
new ColorTranslator({
|
||||
...new ColorTranslator(filterMultitype(panel.$<$Input>('#background-color')?.value(), this.data.backgroundColor)).RGBObject,
|
||||
A: filterMultitype(panel.$<$Input>('#background-color-transparent')?.value(), new ColorTranslator(this.data.backgroundColor!).A)
|
||||
}).HEXA,
|
||||
...border('top'),
|
||||
...border('bottom'),
|
||||
...border('left'),
|
||||
...border('right'),
|
||||
borderTopLeftRadius: filterMultitype(panel.$<$Input>('#border-top-left-radius')?.value(), this.data.borderTopLeftRadius, true),
|
||||
borderTopRightRadius: filterMultitype(panel.$<$Input>('#border-top-right-radius')?.value(), this.data.borderTopRightRadius, true),
|
||||
borderBottomLeftRadius: filterMultitype(panel.$<$Input>('#border-bottom-left-radius')?.value(), this.data.borderBottomLeftRadius, true),
|
||||
borderBottomRightRadius: filterMultitype(panel.$<$Input>('#border-bottom-right-radius')?.value(), this.data.borderBottomRightRadius, true),
|
||||
...dir('margin'),
|
||||
...dir('padding'),
|
||||
opacity: filterMultitype(panel.$<$Input>('#opacity')?.value(), this.data.opacity),
|
||||
display: filterMultitype(panel.$<$Select>('#display')?.value(), this.data.display),
|
||||
height: filterMultitype(panel.$<$Input>('#height')?.value(), this.data.height, true),
|
||||
width: filterMultitype(panel.$<$Input>('#width')?.value(), this.data.width, true),
|
||||
}
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
cssObject() {
|
||||
const json = {};
|
||||
const convert = (passKey: string | null, data: Object) => {
|
||||
for (let [key, value] of Object.entries(data)) {
|
||||
key = passKey ? passKey + key.charAt(0).toUpperCase() + key.slice(1) : key;
|
||||
if (value instanceof Object === false) {
|
||||
if (typeof value === 'number' && key !== 'opacity') value = `${value}px`;
|
||||
Object.assign(json, {[`${key}`]: value});
|
||||
continue;
|
||||
}
|
||||
convert(key, value);
|
||||
}
|
||||
}
|
||||
convert(null, this.data);
|
||||
return json
|
||||
}
|
||||
|
||||
css() {
|
||||
|
||||
}
|
||||
}
|
152
src/structure/StylePanel.ts
Normal file
152
src/structure/StylePanel.ts
Normal file
@ -0,0 +1,152 @@
|
||||
import { $Container } from "fluentx";
|
||||
import { ColorInput } from "../component/ColorInput";
|
||||
import { RangeInput } from "../component/RangeInput";
|
||||
import { SelectInput } from "../component/SelectInput";
|
||||
import { $chat, ROLE_MODEL_MAP } from "../main";
|
||||
import { ColorTranslator } from 'colortranslator';
|
||||
import { $Input } from "fluentx/lib/$Input";
|
||||
import { firstCap, propCap } from "./util";
|
||||
|
||||
export class StylePanel extends $Container {
|
||||
type: StyleType;
|
||||
name: string;
|
||||
constructor(name: string, type: StyleType) {
|
||||
super('div');
|
||||
this.staticClass('style-panel');
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.layout();
|
||||
this.on('input', e => {
|
||||
this.role_model_list.forEach(([role, model]) => {
|
||||
model.update(this)
|
||||
$chat.updateStyle(this.name, model, [role]);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
layout() {
|
||||
if (!this.roles.length) return this.clear();
|
||||
const backgroundColor = this.data.backgroundColor === '??' ? {HEX: '??', A: '??'} : new ColorTranslator(this.data.backgroundColor!);
|
||||
const color = this.data.color === '??' ? {HEX: '??', A: '??'} : new ColorTranslator(this.data.color!);
|
||||
this.content([
|
||||
$('section').content([
|
||||
$('h3').content('Properties'),
|
||||
$('div').content([
|
||||
new SelectInput('display').label('Display').add([
|
||||
['block', 'inline', 'flex', 'none'].map(value => $('option').content(value).value(value))
|
||||
]),
|
||||
new RangeInput('opacity').value(this.data.opacity).unit('px').min(0).max(1).label('Opacity').self($input => {$input.$range.step(0.01); $input.$value.step(0.1)}),
|
||||
])
|
||||
]),
|
||||
|
||||
this.type === 'text' ? $('section').content([
|
||||
$('h3').content('Font'),
|
||||
$('div').content([
|
||||
new RangeInput('font-size').value(this.data.fontSize).unit('px').min(1).label('Size'),
|
||||
new RangeInput('font-weight').min(100).max(900).label('Weight').value(this.data.fontWeight).self($input => $input.$range.step(100)),
|
||||
new ColorInput('font-color').value(this.data.color).label('Color'),
|
||||
new RangeInput('font-color-transparent').value(color.A.toString()).unit('px').min(0).max(1).label('Transparent').self($input => {$input.$range.step(0.01); $input.$value.step(0.1)}),
|
||||
])
|
||||
]) : undefined,
|
||||
|
||||
this.type === 'image' ? $('section').content([
|
||||
$('h3').content('Dimension'),
|
||||
$('div').content([
|
||||
new RangeInput('height').value(this.data.height).unit('px').min(1).label('Height'),
|
||||
new RangeInput('width').value(this.data.width).unit('px').min(1).label('Width'),
|
||||
])
|
||||
]) : undefined,
|
||||
|
||||
$('section').content([
|
||||
$('h3').content('Background'),
|
||||
$('div').content([
|
||||
new ColorInput('background-color').value(backgroundColor.HEX).label('Color'),
|
||||
new RangeInput('background-color-transparent').value(backgroundColor.A.toString()).unit('px').min(0).max(1).label('Transparent').self($input => {$input.$range.step(0.01); $input.$value.step(0.1)}),
|
||||
])
|
||||
]),
|
||||
$('section').content([
|
||||
$('h3').content('Padding'),
|
||||
$('div').content([
|
||||
['left', 'top', 'right', 'bottom'].map(dir => new RangeInput(`padding-${dir}`).value(this.data[`padding${firstCap(dir)}`]).unit('px').label(firstCap(dir)))
|
||||
])
|
||||
]),
|
||||
$('section').content([
|
||||
$('h3').content('Margin'),
|
||||
$('div').content([
|
||||
['left', 'top', 'right', 'bottom'].map(dir => new RangeInput(`margin-${dir}`).value(this.data[`margin${firstCap(dir)}`]).unit('px').label(firstCap(dir)))
|
||||
])
|
||||
]),
|
||||
$('section').content([
|
||||
$('header').content([
|
||||
$('h3').content('Border'),
|
||||
$('div').content([
|
||||
$('label').content('Link').for('border-link'),
|
||||
$('input').id('border-link').type('checkbox').checked(true)
|
||||
])
|
||||
]),
|
||||
$('div').content([
|
||||
['left', 'top', 'right', 'bottom'].map(dir =>
|
||||
$('section').content([
|
||||
$('h4').content(firstCap(dir)),
|
||||
$('div').content([
|
||||
new RangeInput(`border-${dir}-width`).value(this.data[`border${firstCap(dir)}Width`]).unit('px').label('Width'),
|
||||
new SelectInput(`border-${dir}-style`).label('Style').add([
|
||||
['solid', 'dashed', 'doubled', 'dotted', 'groove', 'outset', 'inset', 'ridge', 'hidden'].map(value => $('option').value(value).content(value).id(value))
|
||||
]).value(this.data[`border${firstCap(dir)}Style`]),
|
||||
new ColorInput(`border-${dir}-color`).value(this.data[`border${firstCap(dir)}Color`]).label('Color')
|
||||
]).on('input', (e, $div) => {
|
||||
if (!$<$Input>(':#border-link')?.checked()) return;
|
||||
['left', 'top', 'right', 'bottom'].forEach(d => {
|
||||
if (d === dir) return;
|
||||
const id = $(e.target)?.id()
|
||||
if (id?.includes('width')) $<RangeInput>(`:div.border-${d}-width`)?.value($div.$<$Input>(`#border-${dir}-width`)?.value())
|
||||
if (id?.includes('style')) $<SelectInput>(`:div.border-${d}-style`)?.value($div.$<$Input>(`#border-${dir}-style`)?.value())
|
||||
if (id?.includes('color')) $<ColorInput>(`:div.border-${d}-color`)?.value($div.$<$Input>(`#border-${dir}-color`)?.value())
|
||||
})
|
||||
})
|
||||
])),
|
||||
]),
|
||||
]),
|
||||
$('section').content([
|
||||
$('h3').content('Border Radius'),
|
||||
['top-left', 'top-right', 'bottom-left', 'bottom-right'].map(corner =>
|
||||
new RangeInput(`border-${corner}-radius`).value(this.data[`border${propCap(corner)}Radius`]).unit('px').label(`${corner.split('-').map(str => str.charAt(0).toUpperCase() + str.slice(1)).toString().replace(',', ' ')}`) )
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
get models() {
|
||||
return this.roles.map(role => ROLE_MODEL_MAP.get(role)!.get(this.name)!);
|
||||
}
|
||||
|
||||
get role_model_list() {
|
||||
return this.roles.map(role => [role, ROLE_MODEL_MAP.get(role)!.get(this.name)!] as const);
|
||||
}
|
||||
|
||||
get data() {
|
||||
if (this.roles.length > 1) {
|
||||
function multidata<T extends Object>(object: T, list: T[]) {
|
||||
let data = {};
|
||||
for (const [key, value] of Object.entries(object)) {
|
||||
data[key] = value;
|
||||
for (const model of list) {
|
||||
if (model[key] !== value) {
|
||||
data[key] = '??';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
return multidata(this.models[0].data, this.models.map(model => model.data)) as Partial<CSSStyleDeclaration>
|
||||
} else return this.models[0].data
|
||||
}
|
||||
|
||||
get roles() {
|
||||
return $<$Input>('::.role-checkbox').map($input => {
|
||||
if ($input.checked()) return $input.value();
|
||||
}).detype()
|
||||
}
|
||||
}
|
||||
|
||||
export type StyleType = 'text' | 'element' | 'image'
|
42
src/structure/YouTubeChat.ts
Normal file
42
src/structure/YouTubeChat.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { $Container } from "fluentx";
|
||||
import { YouTubeMessage, type YouTubeMessageData } from "./YouTubeMessage";
|
||||
import { StyleModel } from "./StyleModel";
|
||||
import { $Input } from "fluentx/lib/$Input";
|
||||
|
||||
export class YouTubeChat extends $Container {
|
||||
messageList = new Set<YouTubeMessage>();
|
||||
constructor() {
|
||||
super('ytchat')
|
||||
}
|
||||
|
||||
send(data: YouTubeMessageData) {
|
||||
const message = new YouTubeMessage(data);
|
||||
this.messageList.add(message);
|
||||
this.insert(message);
|
||||
return this;
|
||||
}
|
||||
|
||||
updateStyle(element: string, model: StyleModel, roles: string[]) {
|
||||
this.messageList.forEach(message => {
|
||||
if (roles.includes(message.data.role)) message.updateStyle(element, model);
|
||||
})
|
||||
}
|
||||
|
||||
showHint(element: string) {
|
||||
this.messageList.forEach(message => {
|
||||
if (this.roles.includes(message.data.role)) message.hint(element);
|
||||
})
|
||||
}
|
||||
|
||||
hideHint() {
|
||||
this.messageList.forEach(message => {
|
||||
message.$hint.css({display: 'none'})
|
||||
})
|
||||
}
|
||||
|
||||
get roles() {
|
||||
return $<$Input>('::.role-checkbox').map($input => {
|
||||
if ($input.checked()) return $input.value();
|
||||
}).detype()
|
||||
}
|
||||
}
|
101
src/structure/YouTubeMessage.ts
Normal file
101
src/structure/YouTubeMessage.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { $Container, $Element } from "fluentx";
|
||||
import { StyleModel } from "./StyleModel";
|
||||
import { ROLE_MODEL_MAP } from "../main";
|
||||
|
||||
export interface YouTubeMessageData {
|
||||
name: string;
|
||||
message: string;
|
||||
role: 'Normal' | 'Member' | 'Moderator' | 'Owner';
|
||||
}
|
||||
export class YouTubeMessage extends $Container {
|
||||
data: YouTubeMessageData;
|
||||
$content = $('div').id('content');
|
||||
$message = $('span').id('message');
|
||||
$name = $('span').id('author-name');
|
||||
$author_area = $('yt-live-chat-author-chip');
|
||||
$timestamp = $('span').id('timestamp')
|
||||
$avatar = $('yt-img-shadow').id('author-photo');
|
||||
$overlay = $('div').class('overlay');
|
||||
$hint = $('div').class('hint').css({display: 'none'});
|
||||
constructor(data: YouTubeMessageData) {
|
||||
super('yt-live-chat-text-message-renderer')
|
||||
this.data = data;
|
||||
this.build();
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
ROLE_MODEL_MAP.get(this.data.role)?.forEach((model, element) => this.updateStyle(element, model))
|
||||
}
|
||||
|
||||
build() {
|
||||
this.content([
|
||||
this.$overlay.content([
|
||||
`Role: ${this.data.role}`
|
||||
]),
|
||||
this.$hint,
|
||||
this.$avatar.content([
|
||||
$('img').src('/avatar.png')
|
||||
]),
|
||||
this.$content.content([
|
||||
this.$timestamp,
|
||||
this.$author_area.content([
|
||||
this.$name.content(this.data.name),
|
||||
$('span').id('chat-badges').content([
|
||||
$('yt-live-chat-author-badge-renderer').content([
|
||||
$('div').id('image').content([
|
||||
$('img')
|
||||
])
|
||||
]),
|
||||
|
||||
this.data.role === 'Moderator' ?
|
||||
$('yt-live-chat-author-badge-renderer').attribute('type', 'moderator').content([
|
||||
$('div').id('image').self($div => {
|
||||
$div.dom.innerHTML =
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" focusable="false" style="pointer-events: none; display: inherit; width: 100%; height: 100%;"><path d="M9.64589146,7.05569719 C9.83346524,6.562372 9.93617022,6.02722257 9.93617022,5.46808511 C9.93617022,3.00042984 7.93574038,1 5.46808511,1 C4.90894765,1 4.37379823,1.10270499 3.88047304,1.29027875 L6.95744681,4.36725249 L4.36725255,6.95744681 L1.29027875,3.88047305 C1.10270498,4.37379824 1,4.90894766 1,5.46808511 C1,7.93574038 3.00042984,9.93617022 5.46808511,9.93617022 C6.02722256,9.93617022 6.56237198,9.83346524 7.05569716,9.64589147 L12.4098057,15 L15,12.4098057 L9.64589146,7.05569719 Z"></path></svg>`
|
||||
})
|
||||
]) : undefined
|
||||
]),
|
||||
]),
|
||||
this.$message.content(this.data.message),
|
||||
$('span').id('deleted-state')
|
||||
])
|
||||
])
|
||||
}
|
||||
|
||||
updateStyle(element: string, model: StyleModel) {
|
||||
switch (element) {
|
||||
case 'Message': this.$message.css(model.data); break;
|
||||
case 'Name': this.$name.css(model.data); break;
|
||||
case 'Avatar': this.$avatar.css(model.data); break;
|
||||
case 'Content Area': this.$content.css(model.data); break;
|
||||
case 'Author Area': this.$author_area.css(model.data); break;
|
||||
case 'Outer Area': this.css(model.data); break;
|
||||
}
|
||||
}
|
||||
|
||||
hint(element: string) {
|
||||
switch (element) {
|
||||
case 'Message': this.hintPosition(this.$message); break;
|
||||
case 'Name': this.hintPosition(this.$name); break;
|
||||
case 'Avatar': this.hintPosition(this.$avatar); break;
|
||||
case 'Content Area': this.hintPosition(this.$content); break;
|
||||
case 'Author Area': this.hintPosition(this.$author_area); break;
|
||||
case 'Outer Area': this.hintPosition(this); break;
|
||||
}
|
||||
}
|
||||
|
||||
private hintPosition($ele: $Element) {
|
||||
const rect = $ele.dom.getBoundingClientRect();
|
||||
const this_rect = this.dom.getBoundingClientRect();
|
||||
this.$hint.css({
|
||||
position: 'absolute',
|
||||
top: `${rect.top - this_rect.top}px`,
|
||||
left: `${rect.left - this_rect.left}px`,
|
||||
height: `${rect.height}px`,
|
||||
width: `${rect.width}px`,
|
||||
backgroundColor: '#ff000030',
|
||||
display: 'block'
|
||||
})
|
||||
}
|
||||
}
|
7
src/structure/util.ts
Normal file
7
src/structure/util.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export function firstCap(str: string) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||
}
|
||||
|
||||
export function propCap(str: string) {
|
||||
return str.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).toString().replaceAll(',', '');
|
||||
}
|
30
tsconfig.json
Normal file
30
tsconfig.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
// "strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"exclude": ["dist"]
|
||||
}
|
8
vite.config.ts
Normal file
8
vite.config.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
target: 'es2022'
|
||||
},
|
||||
|
||||
})
|
Loading…
Reference in New Issue
Block a user