This commit is contained in:
2026-04-25 16:36:34 +08:00
commit db90e7579b
1876 changed files with 189777 additions and 0 deletions

1
apps/widget/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
lib/

3
apps/widget/.yarnrc Normal file
View File

@@ -0,0 +1,3 @@
registry "https://registry.npmjs.org"
version-tag-prefix "widget-v"
version-git-message "widget-v%s"

1
apps/widget/demo/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.parcel-cache/

View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tailchat Widget Demo</title>
</head>
<body>
<script type="module">
import { createTailchatWidget } from '../lib/index.js';
createTailchatWidget({
groupId: '611e4e18d3e846001bd0223a',
panelId: '611e4e18d3e846001bd02239',
});
</script>
</body>
</html>

View File

@@ -0,0 +1,19 @@
{
"name": "tailchat-widget-demo",
"version": "1.0.0",
"source": "index.html",
"main": "dist/index.js",
"repository": "https://github.com/msgbyte/tailchat.git",
"author": "moonrailgun <moonrailgun@gmail.com>",
"license": "MIT",
"private": true,
"scripts": {
"start": "parcel serve index.html --no-cache"
},
"devDependencies": {
"typescript": "^4.4.4"
},
"dependencies": {
"parcel": "^2.0.0"
}
}

4535
apps/widget/demo/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

20
apps/widget/package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "tailchat-widget",
"version": "0.0.8",
"main": "lib/index.js",
"repository": "https://github.com/msgbyte/tailchat.git",
"author": "moonrailgun <moonrailgun@gmail.com>",
"license": "MIT",
"files": [
"lib/index.js",
"lib/index.d.ts"
],
"scripts": {
"watch": "tsc --watch",
"build": "tsc",
"release": "yarn publish --patch"
},
"devDependencies": {
"typescript": "^4.8.2"
}
}

136
apps/widget/src/index.ts Normal file
View File

@@ -0,0 +1,136 @@
export interface TailchatWidgetOptions {
/**
* @default https://nightly.paw.msgbyte.com/
*/
host?: string;
groupId: string;
panelId: string;
widgetStyle?: Partial<CSSStyleDeclaration>;
iconStyle?: Partial<CSSStyleDeclaration>;
frameStyle?: Partial<CSSStyleDeclaration>;
}
const defaultTailchatWidgetOptions: Partial<TailchatWidgetOptions> = {
host: 'https://nightly.paw.msgbyte.com',
};
const defaultWidgetStyle: Partial<CSSStyleDeclaration> = {
position: 'absolute',
right: '20px',
bottom: '20px',
};
const iconContainerSize = 48;
const defaultIconContainerStyle: Partial<CSSStyleDeclaration> = {
width: `${iconContainerSize}px`,
height: `${iconContainerSize}px`,
boxShadow: '0 1px 4px rgba(0, 0, 0, 0.2)',
borderRadius: '50%',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
};
const defaultFrameStyle: Partial<CSSStyleDeclaration> = {
width: '414px',
height: '736px',
border: '0',
borderRadius: '3px',
boxShadow: '0 1px 4px rgba(0, 0, 0, 0.2)',
};
const iconSize = 32;
const iconSvg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="${iconSize}" height="${iconSize}" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path d="M12 3C6.5 3 2 6.58 2 11a7.218 7.218 0 0 0 2.75 5.5c0 .6-.42 2.17-2.75 4.5c2.37-.11 4.64-1 6.47-2.5c1.14.33 2.34.5 3.53.5c5.5 0 10-3.58 10-8s-4.5-8-10-8m0 14c-4.42 0-8-2.69-8-6s3.58-6 8-6s8 2.69 8 6s-3.58 6-8 6m5-5v-2h-2v2h2m-4 0v-2h-2v2h2m-4 0v-2H7v2h2z" fill="currentColor"/></svg>`;
const closeIconSvg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="${iconSize}" height="${iconSize}" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" style="display: block;"><path d="M11 4h2v12l5.5-5.5l1.42 1.42L12 19.84l-7.92-7.92L5.5 10.5L11 16V4z" fill="currentColor"/></svg>`;
/**
* 创建聊天小部件
*/
export function createTailchatWidget(_options: TailchatWidgetOptions) {
const options = { ...defaultTailchatWidgetOptions, ..._options };
const url = `${options.host}/panel/group/${options.groupId}/${options.panelId}`;
// 容器
const container = document.createElement('div');
applyStyle(container, {
...defaultWidgetStyle,
..._options.widgetStyle,
});
// 图标
const iconContainer = document.createElement('div');
applyStyle(iconContainer, {
...defaultIconContainerStyle,
..._options.iconStyle,
});
iconContainer.innerHTML = iconSvg;
container.appendChild(iconContainer);
// Iframe 容器
let frameContainer: HTMLDivElement | null = null;
iconContainer.addEventListener('click', () => {
// 展开iframe
if (!frameContainer) {
// 元素不存在
// 容器
const _frameContainer = document.createElement('div');
frameContainer = _frameContainer;
// Iframe
const frameEl = document.createElement('iframe');
frameEl.src = url;
applyStyle(frameEl, {
...defaultFrameStyle,
..._options.frameStyle,
});
// closeBtn
const closeBtnEl = document.createElement('div');
closeBtnEl.innerHTML = closeIconSvg;
applyStyle(closeBtnEl, {
position: 'absolute',
right: '0',
top: `-${iconSize}px`,
backgroundColor: 'white',
cursor: 'pointer',
boxShadow: '0px -1px 4px rgba(0, 0, 0, 0.2)',
borderRadius: '50% 50% 0 0',
});
closeBtnEl.addEventListener('click', () => {
// 关闭操作
iconContainer.style.display = 'flex';
if (frameContainer) {
frameContainer.style.display = 'none';
}
});
_frameContainer.appendChild(frameEl);
_frameContainer.appendChild(closeBtnEl);
container.appendChild(_frameContainer);
} else {
// 已创建
frameContainer.style.display = 'block';
}
iconContainer.style.display = 'none';
});
document.body.appendChild(container);
}
/**
* 应用样式到元素
* @param el 元素
* @param styles 样式
*/
function applyStyle(el: HTMLElement, styles: Partial<CSSStyleDeclaration>) {
for (const key in styles) {
const val = styles[key];
if (typeof val === 'string') {
el.style[key] = val;
}
}
}

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "ES5",
"declaration": true,
"lib": ["DOM", "ESNext"],
"rootDir": "./src",
"outDir": "lib"
}
}