This commit is contained in:
tangtaoit 2024-07-23 19:48:05 +08:00
commit cbf9f5907a
20 changed files with 825 additions and 692 deletions

View File

@ -1,3 +1,2 @@
registry "https://registry.npmmirror.com/"
electron_mirror "https://registry.npmmirror.com/-/binary/electron/"
electron_mirror "https://npmmirror.com/mirrors/electron/"

View File

@ -2,34 +2,34 @@
<a href="https://zh-hans.react.dev/" target="_blank" rel="noopener" style="display:inline-block;">
<img src="https://img.shields.io/badge/React-17.0.2-%236CB52D.svg?logo=React" alt="React" />
</a> &nbsp
</a> &nbsp;
<a href="https://ts.nodejs.cn/" target="_blank" rel="noopener" style="display:inline-block;">
<img src="https://img.shields.io/badge/TypeScript-5.0.4-%236CB52D.svg?logo=TypeScript&logoColor=FFF" alt="TypeScript" />
</a> &nbsp
</a> &nbsp;
<a href="https://yarn.bootcss.com/" target="_blank" rel="noopener" style="display:inline-block;">
<img src="https://img.shields.io/badge/Yarn-1.22.17-%236CB52D.svg?logo=Yarn&logoColor=FFF" alt="Yarn" />
</a> &nbsp
</a> &nbsp;
<a href="https://nodejs.org/" target="_blank" rel="noopener" style="display:inline-block;">
<img src="https://img.shields.io/badge/Node-18.17.1-%236CB52D.svg?logo=Node&logoColor=FFF" alt="Node">
</a> &nbsp
</a> &nbsp;
<a href="https://webpack.docschina.org/" target="_blank" rel="noopener" style="display:inline-block;">
<img src="https://img.shields.io/badge/Webpack-5.88.2-%236CB52D.svg?logo=Webpack" alt="Webpack" />
</a> &nbsp
</a> &nbsp;
<a href="https://www.electronjs.org/zh/" target="_blank" rel="noopener" style="display:inline-block;">
<img src="https://img.shields.io/badge/Electron-26.0.0-%236CB52D.svg?logo=Electron&logoColor=FFF" alt="Electron" />
</a> &nbsp
</a> &nbsp;
<a href="https://www.electron.build/" target="_blank" rel="noopener" style="display:inline-block;">
<img src="https://img.shields.io/badge/ElectronBuilder-24.9.1-%236CB52D.svg?logo=ElectronBuilder&logoColor=FFF" alt="ElectronBuilder" />
</a> &nbsp
</a> &nbsp;
<a href="https://semi.design/zh-CN/" target="_blank" rel="noopener" style="display:inline-block;">
<img src="https://img.shields.io/badge/Semi UI-2.24.2-%236CB52D.svg?logo=SemiUI" alt="SemiUI">
</a> &nbsp
</a> &nbsp;
<a href="https://turbo.build/repo" target="_blank" rel="noopener" style="display:inline-block;">
<img src="https://img.shields.io/badge/turbo-1.4.7-%236CB52D.svg?logo=Turbo&logoColor=FFF" alt="Turbo" />
</a> &nbsp
</a> &nbsp;
<a href="https://githubim.com/" target="_blank" rel="noopener" style="display:inline-block;">
<img src="https://img.shields.io/badge/WukongIm-1.2.10-%236CB52D.svg?logo=WukonIm" alt="Wukongim" />
</a> &nbsp
</a> &nbsp;
📚 [在线文档](https://tsdaodao.com/) | 🚀 [演示地址](https://web.botgate.cn/)(账号/密码15900000002/a1234567

View File

@ -1,70 +1,40 @@
var path = require('path')
const { override, babelInclude, addWebpackPlugin } = require('customize-cra')
const { override, babelInclude, addWebpackPlugin, overrideDevServer } = require('customize-cra')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const TerserPlugin = require("terser-webpack-plugin");
// module.exports = override(
// // 注意是production环境启动该plugin
// process.env.NODE_ENV === 'production' && addWebpackPlugin(
// new UglifyJsPlugin({
// // 开启打包缓存
// cache: true,
// // 开启多线程打包
// parallel: true,
// uglifyOptions: {
// // 删除警告
// warnings: false,
// // 压缩
// compress: {
// // 移除console
// drop_console: true,
// // 移除debugger
// drop_debugger: true
// }
// }
// })
// )
// )
const addDevServerConfig = () => config => {
return {
...config,
client: {
overlay: false
}
};
}
module.exports = function (config, env) {
if (process.env.NODE_ENV === 'production') {
config.devtool = false;
}
if (env === 'production') {
config.optimization = {
minimize: true,
minimizer: [new TerserPlugin()],
};
}
return Object.assign(
config,
override(
// 判断环境变量ANALYZER参数的值
process.env.ANALYZER && addWebpackPlugin(new BundleAnalyzerPlugin()),
// process.env.NODE_ENV === 'production' && addWebpackPlugin(
// new UglifyJsPlugin({
// cache: true,
// // 开启多线程打包
// parallel: true,
// uglifyOptions: {
// // 删除警告
// warnings: false,
// // 压缩
// compress: {
// // 移除console
// drop_console: true,
// // 移除debugger
// drop_debugger: true
// }
// },
// })
// ),
babelInclude([
/* transpile (converting to es5) code in src/ and shared component library */
path.resolve('src'),
path.resolve('../../packages'),
])
)(config, env)
)
module.exports = {
webpack: function (config, env) {
if (process.env.NODE_ENV === 'production') {
config.devtool = false;
}
if (env === 'production') {
config.optimization = {
minimize: true,
minimizer: [new TerserPlugin()],
};
}
return Object.assign(
config,
override(
// 判断环境变量ANALYZER参数的值
process.env.ANALYZER && addWebpackPlugin(new BundleAnalyzerPlugin()),
babelInclude([
/* transpile (converting to es5) code in src/ and shared component library */
path.resolve('src'),
path.resolve('../../packages'),
])
)(config, env)
)
},
devServer: overrideDevServer(addDevServerConfig())
}

View File

@ -1,60 +0,0 @@
module.exports = {
productName: "tangsengdaodao", //项目名
appId: "com.tsdaodao.im",
copyright: "Copyright © tsdaodao", //版权
directories: {
output: "dist-ele", // 输出文件夹
},
npmRebuild: false,
asar: false,
buildDependenciesFromSource: true,
electronDownload: {
mirror: "https://registry.npmmirror.com/-/binary/electron/",
},
files: ["resources/**/*","out-election/**/*", "build/**/*"], // 需要打包的文件
extraMetadata: {
main: "out-election/main/index.js",
},
mac: {
category: "public.app-category.instant-messaging",
target: [
{
target: 'dmg',
arch: ['x64', 'arm64']
}
],
artifactName: '${productName}-${os}-${version}-${arch}.${ext}',
icon: "resources/icons/icon.icns"
},
dmg: {
// background: 'build/appdmg.png', // dmg安装窗口背景图
icon: "resources/icons/icon.icns", // 客户端图标
iconSize: 100, // 安装图标大小
// 安装窗口中包含的项目和配置
contents: [
{ x: 380, y: 280, type: "link", path: "/Applications" },
{ x: 110, y: 280, type: "file" },
],
window: { width: 500, height: 500 }, // 安装窗口大小
},
win: {
icon: "resources/icons/icon.ico",
verifyUpdateCodeSignature: false,
target: ["nsis", "zip"],
artifactName: "${productName}-Setup-${version}.${ext}"
},
nsis: {
oneClick: false, // 是否一键安装
allowElevation: true, // 允许请求提升。 如果为false则用户必须使用提升的权限重新启动安装程序。
allowToChangeInstallationDirectory: true, // 允许修改安装目录
// installerIcon: "./build/icon.ico",// 安装图标
// uninstallerIcon: "./build/icons/bbb.ico",//卸载图标
// installerHeaderIcon: "./build/icon.ico", // 安装时头部图标
createDesktopShortcut: true, // 创建桌面图标
createStartMenuShortcut: true, // 创建开始菜单图标
},
linux: {
target: ["AppImage", "deb"],
icon: "resources/icons/icon.icns",
},
};

View File

@ -0,0 +1,58 @@
productName: tangsengdaodao
appId: com.tsdaodao.im
copyright: Copyright © tsdaodao
directories:
output: dist-ele
npmRebuild: false
asar: false
buildDependenciesFromSource: true
electronDownload:
mirror: https://npmmirror.com/mirrors/electron/
files:
- resources/**/*
- out-election/**/*
- build/**/*
extraMetadata:
main: out-election/main/index.js
mac:
category: public.app-category.instant-messaging
target:
- target: dmg
arch:
- x64
- arm64
artifactName: ${productName}-${os}-${version}-${arch}.${ext}
icon: resources/icons/icon.icns
dmg:
icon: resources/icons/icon.icns
iconSize: 100
contents:
- x: 380
y: 280
type: link
path: /Applications
- x: 110
y: 280
type: link
window:
width: 500
height: 500
win:
icon: resources/icons/icon.ico
verifyUpdateCodeSignature: false
target:
- nsis
- zip
artifactName: ${productName}-Setup-${version}.${ext}
nsis:
oneClick: false
allowElevation: true
allowToChangeInstallationDirectory: true
createDesktopShortcut: true
createStartMenuShortcut: true
linux:
target:
- AppImage
- deb
icon: resources/icons/icon.icns

View File

@ -55,7 +55,6 @@
"customize-cra": "^1.0.0",
"electron": "26.0.0",
"electron-builder": "^24.9.1",
"electron-log": "^5.0.1",
"eslint-config-react-app": "^7.0.1",
"kill-port": "^2.0.1",
"npm-run-all": "^4.1.5",
@ -64,7 +63,7 @@
"react-scripts": "5.0.0",
"terser-webpack-plugin": "^5.3.9",
"tsc-watch": "^6.0.4",
"typescript": "^5.3.3",
"typescript": "^4.5.5",
"wait-on": "^7.2.0",
"webpack-bundle-analyzer": "^4.5.0"
},
@ -73,7 +72,7 @@
},
"browserslist": {
"production": [
">0.2%",
"> 1%",
"not dead",
"not op_mini all"
],

Binary file not shown.

View File

@ -1,15 +1,15 @@
{
"name": "tsdd",
"private": true,
"version": "0.0.0",
"workspaces": [
"apps/*",
"packages/*"
],
"resolutions": {
"//": "See https://github.com/facebook/create-react-app/issues/11773",
"react-error-overlay": "6.0.11"
},
"scripts": {
"bootstrap": "yarn install",
"dev": "turbo run dev --parallel",
"dev-ele": "turbo run dev-ele --parallel",
"build": "turbo run build",
@ -18,7 +18,8 @@
"build-ele:win": "turbo run build-ele:win",
"build-ele:linux": "turbo run build-ele:linux",
"build-ele:linux-arm64": "turbo run build-ele:linux-arm64",
"clean": "turbo run clean && rimraf node_modules",
"bootstrap": "yarn install",
"clean": "turbo run clean && rimraf node_modules .turbo",
"lint": "turbo run lint",
"format": "prettier --write \"**/*.{ts,tsx,md}\""
},
@ -27,7 +28,11 @@
"eslint-config-custom": "*",
"prettier": "^2.5.1",
"rimraf": "^5.0.5",
"turbo": "latest"
"turbo": "2.0.9"
},
"version": "0.0.0"
"packageManager": "yarn@1.22.19",
"engines": {
"node": ">=18.0.0",
"yarn": ">=1.22.19"
}
}

View File

@ -4,7 +4,7 @@
"main": "index.js",
"license": "MIT",
"scripts": {
"clean": "rimraf node_modules out-election"
"clean": "rimraf node_modules .turbo"
},
"dependencies": {
"@typescript-eslint/parser": "^6.16.0",

View File

@ -0,0 +1,25 @@
@keyframes loading {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.loading-box{
width: 32px;
height: 32px;
position:absolute;
top:50%;
left:50%;
transform:translate(-16px,-16px);
}
.loading-icon{
width: 32px;
height: 32px;
animation: loading 1s linear infinite;
}
.loading-text{
font-size: 14px;
white-space: nowrap;
}

View File

@ -1,124 +1,172 @@
import { MediaMessageContent } from "wukongimjssdk"
import React from "react"
import WKApp from "../../App"
import { MessageContentTypeConst } from "../../Service/Const"
import MessageBase from "../Base"
import { MessageCell } from "../MessageCell"
import Viewer from 'react-viewer';
import WKSDK, { MediaMessageContent, Task } from "wukongimjssdk";
import React from "react";
import WKApp from "../../App";
import { MessageContentTypeConst } from "../../Service/Const";
import MessageBase from "../Base";
import { MessageCell } from "../MessageCell";
import Viewer from "react-viewer";
import "./index.css";
export class ImageContent extends MediaMessageContent {
width!: number
height!: number
url!: string
imgData?: string
constructor(file?: File, imgData?: string, width?: number, height?: number) {
super()
this.file = file
this.imgData = imgData
this.width = width || 0
this.height = height || 0
}
decodeJSON(content: any) {
this.width = content["width"] || 0
this.height = content["height"] || 0
this.url = content["url"] || ''
this.remoteUrl = this.url
}
encodeJSON() {
return { "width": this.width || 0, "height": this.height || 0, "url": this.remoteUrl || "" }
}
get contentType() {
return MessageContentTypeConst.image
}
get conversationDigest() {
return "[图片]"
}
width!: number;
height!: number;
url!: string;
imgData?: string;
constructor(file?: File, imgData?: string, width?: number, height?: number) {
super();
this.file = file;
this.imgData = imgData;
this.width = width || 0;
this.height = height || 0;
}
decodeJSON(content: any) {
this.width = content["width"] || 0;
this.height = content["height"] || 0;
this.url = content["url"] || "";
this.remoteUrl = this.url;
}
encodeJSON() {
return { width: this.width || 0, height: this.height || 0, url: this.remoteUrl || "" };
}
get contentType() {
return MessageContentTypeConst.image;
}
get conversationDigest() {
return "[图片]";
}
}
interface ImageCellState {
showPreview: boolean
showPreview: boolean;
loading: boolean;
progress: number;
}
export class ImageCell extends MessageCell<any, ImageCellState> {
taskListener!: any;
constructor(props: any) {
super(props);
this.state = {
showPreview: false,
loading: false,
progress: 0,
};
}
constructor(props: any) {
super(props)
this.state = {
showPreview: false,
imageScale(orgWidth: number, orgHeight: number, maxWidth = 250, maxHeight = 250) {
let actSize = { width: orgWidth, height: orgHeight };
if (orgWidth > orgHeight) {
//横图
if (orgWidth > maxWidth) {
// 横图超过最大宽度
let rate = maxWidth / orgWidth; // 缩放比例
actSize.width = maxWidth;
actSize.height = orgHeight * rate;
}
} else if (orgWidth < orgHeight) {
//竖图
if (orgHeight > maxHeight) {
let rate = maxHeight / orgHeight; // 缩放比例
actSize.width = orgWidth * rate;
actSize.height = maxHeight;
}
} else if (orgWidth === orgHeight) {
if (orgWidth > maxWidth) {
let rate = maxWidth / orgWidth; // 缩放比例
actSize.width = maxWidth;
actSize.height = orgHeight * rate;
}
}
return actSize;
}
getImageSrc(content: ImageContent) {
if (content.url && content.url !== "") {
// 等待发送的消息
let downloadURL = WKApp.dataSource.commonDataSource.getImageURL(content.url, {
width: content.width,
height: content.height,
});
if (downloadURL.indexOf("?") != -1) {
downloadURL += "&filename=image.png";
} else {
downloadURL += "?filename=image.png";
}
return downloadURL;
}
return content.imgData;
}
getImageElement() {
const { message } = this.props;
const content = message.content as ImageContent;
let scaleSize = this.imageScale(content.width, content.height);
if (!this.taskListener && !content?.url) {
this.taskListener = (task: Task) => {
this.setState({
loading: true,
progress: Math.trunc(task.progress() * 100),
});
if (task.progress() === 1) {
this.setState({
loading: false,
progress: 100,
});
WKSDK.shared().taskManager.removeListener(this.taskListener);
}
};
WKSDK.shared().taskManager.addListener(this.taskListener);
}
return (
<div style={{ position: "relative" }}>
<img
alt=""
src={this.getImageSrc(content)}
style={{ borderRadius: "5px", width: scaleSize.width, height: scaleSize.height }}
/>
{this.state.loading && scaleSize.width > 32 && (
<i className="loading-box">
<img className="loading-icon" src={require("../../assets/loading.png")} alt="" />
{scaleSize.height > 46 && <p className="loading-text">{this.state.progress}%</p>}
</i>
)}
</div>
);
}
imageScale(orgWidth: number, orgHeight: number, maxWidth = 250, maxHeight = 250) {
let actSize = { width: orgWidth, height: orgHeight };
if (orgWidth > orgHeight) {//横图
if (orgWidth > maxWidth) { // 横图超过最大宽度
let rate = maxWidth / orgWidth; // 缩放比例
actSize.width = maxWidth;
actSize.height = orgHeight * rate;
}
} else if (orgWidth < orgHeight) { //竖图
if (orgHeight > maxHeight) {
let rate = maxHeight / orgHeight; // 缩放比例
actSize.width = orgWidth * rate;
actSize.height = maxHeight;
}
} else if (orgWidth === orgHeight) {
if (orgWidth > maxWidth) {
let rate = maxWidth / orgWidth; // 缩放比例
actSize.width = maxWidth;
actSize.height = orgHeight * rate;
}
}
return actSize;
}
getImageSrc(content: ImageContent) {
if (content.url && content.url !== "") { // 等待发送的消息
let downloadURL = WKApp.dataSource.commonDataSource.getImageURL(content.url, { width: content.width, height: content.height })
if (downloadURL.indexOf("?") != -1) {
downloadURL += "&filename=image.png"
} else {
downloadURL += "?filename=image.png"
}
return downloadURL
}
return content.imgData
}
getImageElement() {
const { message } = this.props
const content = message.content as ImageContent
let scaleSize = this.imageScale(content.width, content.height);
return <img alt="" src={this.getImageSrc(content)} style={{ borderRadius: '5px', width: scaleSize.width, height: scaleSize.height }} />
}
render() {
const { message, context } = this.props
const { showPreview } = this.state
const content = message.content as ImageContent
let scaleSize = this.imageScale(content.width, content.height);
const imageURL = this.getImageSrc(content) || ""
return <MessageBase context={context} message={message}>
<div style={{ width: scaleSize.width, height: scaleSize.height, cursor: "pointer" }} onClick={() => {
this.setState({
showPreview: !this.state.showPreview,
})
}}>
{this.getImageElement()}
</div>
<Viewer
visible={showPreview}
noImgDetails={true}
downloadable={true}
rotatable={false}
changeable={false}
showTotal={false}
onMaskClick={() => { this.setState({ showPreview: false }); }}
onClose={() => { this.setState({ showPreview: false }); }}
images={[{ src: imageURL, alt: '', downloadUrl: imageURL }]}
/>
</MessageBase>
}
}
render() {
const { message, context } = this.props;
const { showPreview } = this.state;
const content = message.content as ImageContent;
let scaleSize = this.imageScale(content.width, content.height);
const imageURL = this.getImageSrc(content) || "";
return (
<MessageBase context={context} message={message}>
<div
style={{ width: scaleSize.width, height: scaleSize.height, cursor: "pointer" }}
onClick={() => {
this.setState({
showPreview: !this.state.showPreview,
});
}}
>
{this.getImageElement()}
</div>
<Viewer
visible={showPreview}
noImgDetails={true}
downloadable={true}
rotatable={false}
changeable={false}
showTotal={false}
onMaskClick={() => {
this.setState({ showPreview: false });
}}
onClose={() => {
this.setState({ showPreview: false });
}}
images={[{ src: imageURL, alt: "", downloadUrl: imageURL }]}
/>
</MessageBase>
);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -5,6 +5,7 @@
"dependencies": {
"@tsdaodao/base": "*",
"axios": "^0.25.0",
"classnames": "^2.3.1"
"classnames": "^2.3.1",
"react-infinite-scroller": "^1.2.6"
}
}

View File

@ -1,246 +1,287 @@
import React from "react";
import { Component } from "react";
import { Contacts, ContactsChangeListener, ContextMenus, ContextMenusContext, WKApp, WKBase, WKBaseContext, WKNavMainHeader, Search, UserRelation } from "@tsdaodao/base"
import "./index.css"
import {
Contacts,
ContactsChangeListener,
ContextMenus,
ContextMenusContext,
WKApp,
WKBase,
WKBaseContext,
WKNavMainHeader,
Search,
UserRelation,
} from "@tsdaodao/base";
import "./index.css";
import { toSimplized } from "@tsdaodao/base";
import { getPinyin } from "@tsdaodao/base";
import classnames from "classnames";
import { Toast } from "@douyinfe/semi-ui";
import { Channel, ChannelTypePerson, WKSDK,ChannelInfoListener,ChannelInfo } from "wukongimjssdk";
import {
Channel,
ChannelTypePerson,
WKSDK,
ChannelInfoListener,
ChannelInfo,
} from "wukongimjssdk";
import { ContactsListManager } from "../Service/ContactsListManager";
import { Card } from "@tsdaodao/base/src/Messages/Card";
import WKAvatar from "@tsdaodao/base/src/Components/WKAvatar";
export class ContactsState {
indexList: string[] = []
indexItemMap: Map<string, Contacts[]> = new Map()
keyword?: string
selectedItem?: Contacts // 被选中的联系人
indexList: string[] = [];
indexItemMap: Map<string, Contacts[]> = new Map();
keyword?: string;
selectedItem?: Contacts; // 被选中的联系人
}
export default class ContactsList extends Component<any, ContactsState> {
contactsChangeListener!: ContactsChangeListener
channelInfoListener!: ChannelInfoListener
contextMenusContext!: ContextMenusContext
baseContext!: WKBaseContext
constructor(props: any) {
super(props)
contactsChangeListener!: ContactsChangeListener;
channelInfoListener!: ChannelInfoListener;
contextMenusContext!: ContextMenusContext;
baseContext!: WKBaseContext;
constructor(props: any) {
super(props);
this.state = {
indexList: [],
indexItemMap: new Map()
this.state = {
indexList: [],
indexItemMap: new Map(),
};
}
componentDidMount() {
this.contactsChangeListener = () => {
this.rebuildIndex();
};
this.channelInfoListener = (channelInfo: ChannelInfo) => {
if (channelInfo.channel.channelType !== ChannelTypePerson) {
return;
}
//是否包含
let exist = false;
WKApp.dataSource.contactsList.forEach((v) => {
if (v.uid === channelInfo.channel.channelID) {
exist = true;
v.name = channelInfo.title;
v.remark = channelInfo?.orgData.remark;
return;
}
}
componentDidMount() {
});
if (exist) {
this.rebuildIndex();
}
};
this.contactsChangeListener = () => {
this.rebuildIndex()
WKApp.dataSource.addContactsChangeListener(this.contactsChangeListener);
this.rebuildIndex();
WKSDK.shared().channelManager.addListener(this.channelInfoListener);
ContactsListManager.shared.setRefreshList = () => {
this.setState({});
};
}
componentWillUnmount() {
ContactsListManager.shared.setRefreshList = undefined;
WKApp.dataSource.removeContactsChangeListener(this.contactsChangeListener);
WKSDK.shared().channelManager.removeListener(this.channelInfoListener);
}
rebuildIndex() {
console.log("rebuildIndex---->");
this.buildIndex(this.contactsList());
}
contactsList() {
const { keyword } = this.state;
return WKApp.dataSource.contactsList.filter((v) => {
if (v.status === UserRelation.blacklist) {
return false;
}
if (v.follow !== 1) {
return false;
}
if (!keyword || keyword === "") {
return true;
}
if (v.remark && v.remark !== "") {
if (v.remark.indexOf(keyword) !== -1) {
return true;
}
}
this.channelInfoListener = (channelInfo:ChannelInfo)=>{
if(channelInfo.channel.channelType !== ChannelTypePerson) {
return
}
//是否包含
let exist = false
WKApp.dataSource.contactsList.forEach((v)=>{
if(v.uid === channelInfo.channel.channelID) {
exist = true
v.name = channelInfo.title
v.remark = channelInfo?.orgData.remark
return
}
})
if(exist) {
this.rebuildIndex()
}
}
return v.name.indexOf(keyword) !== -1;
});
}
WKApp.dataSource.addContactsChangeListener(this.contactsChangeListener)
buildIndex(contacts: Contacts[]) {
const indexItemMap = new Map<string, Contacts[]>();
let indexList = [];
for (const item of contacts) {
let name = item.name;
if (item.remark && item.remark !== "") {
name = item.remark;
}
this.rebuildIndex()
let pinyinNick = getPinyin(toSimplized(name)).toUpperCase();
let indexName =
!pinyinNick || /[^a-z]/i.test(pinyinNick[0]) ? "#" : pinyinNick[0];
WKSDK.shared().channelManager.addListener(this.channelInfoListener)
ContactsListManager.shared.setRefreshList = () => {
this.setState({})
}
let existItems = indexItemMap.get(indexName);
if (!existItems) {
existItems = [];
indexList.push(indexName);
}
existItems.push(item);
indexItemMap.set(indexName, existItems);
}
indexList = indexList.sort((a, b) => {
if (a === "#") {
return -1;
}
if (b === "#") {
return 1;
}
return a.localeCompare(b);
});
this.setState({
indexList: indexList,
indexItemMap: indexItemMap,
});
}
componentWillUnmount() {
ContactsListManager.shared.setRefreshList = undefined
WKApp.dataSource.removeContactsChangeListener(this.contactsChangeListener)
WKSDK.shared().channelManager.removeListener(this.channelInfoListener)
}
_handleContextMenu(item: Contacts, event: React.MouseEvent) {
this.contextMenusContext.show(event);
this.setState({
selectedItem: item,
});
}
sectionUI(indexName: string) {
const { indexItemMap } = this.state;
const { canSelect } = this.props;
const items = indexItemMap.get(indexName);
rebuildIndex() {
console.log("rebuildIndex---->")
this.buildIndex(this.contactsList())
}
contactsList() {
const { keyword } = this.state
return WKApp.dataSource.contactsList.filter((v) => {
if (v.status === UserRelation.blacklist) {
return false
}
if (v.follow !== 1) {
return false
}
if (!keyword || keyword === "") {
return true
}
if (v.remark && v.remark !== "") {
if (v.remark.indexOf(keyword) !== -1) {
return true
}
}
return v.name.indexOf(keyword) !== -1
})
}
buildIndex(contacts: Contacts[]) {
const indexItemMap = new Map<string, Contacts[]>()
let indexList = []
for (const item of contacts) {
let name = item.name
return (
<div key={indexName} className="wk-contacts-section">
<div className="wk-contacts-section-list">
{items?.map((item, i) => {
let name = item.name;
if (item.remark && item.remark !== "") {
name = item.remark
name = item.remark;
}
let pinyinNick = getPinyin(toSimplized(name)).toUpperCase();
let indexName = !pinyinNick || /[^a-z]/i.test(pinyinNick[0]) ? "#" : pinyinNick[0];
let existItems = indexItemMap.get(indexName)
if (!existItems) {
existItems = []
indexList.push(indexName)
}
existItems.push(item)
indexItemMap.set(indexName, existItems)
}
indexList = indexList.sort((a, b) => {
if (a === "#") {
return -1
}
if (b === "#") {
return 1
}
return a.localeCompare(b)
})
this.setState({
indexList: indexList,
indexItemMap: indexItemMap,
})
}
_handleContextMenu(item: Contacts, event: React.MouseEvent) {
this.contextMenusContext.show(event)
this.setState({
selectedItem: item,
})
}
sectionUI(indexName: string) {
const { indexItemMap } = this.state
const { canSelect } = this.props
const items = indexItemMap.get(indexName)
return <div key={indexName} className="wk-contacts-section">
<div className="wk-contacts-section-list">
{
items?.map((item, i) => {
let name = item.name
if (item.remark && item.remark !== "") {
name = item.remark
}
return <div key={item.uid} className={classnames("wk-contacts-section-item", WKApp.shared.openChannel?.channelType === ChannelTypePerson && WKApp.shared.openChannel?.channelID === item.uid ? "wk-contacts-section-item-selected" : undefined)} onClick={() => {
const channel = new Channel(item.uid, ChannelTypePerson)
WKApp.endpoints.showConversation(channel)
this.setState({})
}} onContextMenu={(e) => {
this._handleContextMenu(item, e)
}}>
<div className="wk-contacts-section-item-index">
{i === 0 ? indexName : ""}
</div>
<div className="wk-contacts-section-item-avatar">
<WKAvatar channel={new Channel(item.uid, ChannelTypePerson)}></WKAvatar>
</div>
<div className="wk-contacts-section-item-name">
{name}
</div>
</div>
})
}
</div>
</div>
}
render() {
const { indexList } = this.state
return <WKBase onContext={(baseCtx) => {
this.baseContext = baseCtx
}}>
<div className="wk-contacts">
<WKNavMainHeader title="联系人"></WKNavMainHeader>
<div className="wk-contacts-content">
<div className="wk-contacts-content-header">
<Search placeholder="搜索" onChange={(v) => {
this.setState({
keyword: v
}, () => {
this.rebuildIndex()
})
}}></Search>
</div>
<div className="wk-contacts-content-fnc">
{
WKApp.endpoints.contactsHeaders().map((view, i) => {
return <div key={i}>{view}</div>
})
}
</div>
<div className="wk-contacts-content-contacts">
{
indexList.map((indexName) => {
return this.sectionUI(indexName)
})
}
</div>
return (
<div
key={item.uid}
className={classnames(
"wk-contacts-section-item",
WKApp.shared.openChannel?.channelType === ChannelTypePerson &&
WKApp.shared.openChannel?.channelID === item.uid
? "wk-contacts-section-item-selected"
: undefined
)}
onClick={() => {
const channel = new Channel(item.uid, ChannelTypePerson);
WKApp.endpoints.showConversation(channel);
this.setState({});
}}
onContextMenu={(e) => {
this._handleContextMenu(item, e);
}}
>
<div className="wk-contacts-section-item-index">
{i === 0 ? indexName : ""}
</div>
<ContextMenus onContext={(context: ContextMenusContext) => {
this.contextMenusContext = context
}} menus={[{
title: "查看资料", onClick: () => {
const { selectedItem } = this.state
this.baseContext.showUserInfo(selectedItem?.uid || "")
<div className="wk-contacts-section-item-avatar">
<WKAvatar
channel={new Channel(item.uid, ChannelTypePerson)}
></WKAvatar>
</div>
<div className="wk-contacts-section-item-name">{name}</div>
</div>
);
})}
</div>
</div>
);
}
render() {
const { indexList } = this.state;
return (
<WKBase
onContext={(baseCtx) => {
this.baseContext = baseCtx;
}}
>
<div className="wk-contacts">
<WKNavMainHeader title="联系人"></WKNavMainHeader>
<div className="wk-contacts-content">
<div className="wk-contacts-content-header">
<Search
placeholder="搜索"
onChange={(v) => {
this.setState(
{
keyword: v,
},
() => {
this.rebuildIndex();
}
}, {
title: "分享给朋友...", onClick: () => {
WKApp.shared.baseContext.showConversationSelect((channels: Channel[]) => {
const { selectedItem } = this.state
if (channels && channels.length > 0) {
for (const channel of channels) {
const card = new Card()
card.uid = selectedItem?.uid || ""
card.name = selectedItem?.name || ""
card.vercode = selectedItem?.vercode||""
WKSDK.shared().chatManager.send(card, channel)
}
Toast.success("分享成功!")
}
}, "分享名片")
}
}]} />
);
}}
></Search>
</div>
</WKBase>
}
}
<div className="wk-contacts-content-fnc">
{WKApp.endpoints.contactsHeaders().map((view, i) => {
return <div key={i}>{view}</div>;
})}
</div>
<div className="wk-contacts-content-contacts">
{indexList.map((indexName) => {
return this.sectionUI(indexName);
})}
</div>
</div>
<ContextMenus
onContext={(context: ContextMenusContext) => {
this.contextMenusContext = context;
}}
menus={[
{
title: "查看资料",
onClick: () => {
const { selectedItem } = this.state;
this.baseContext.showUserInfo(selectedItem?.uid || "");
},
},
{
title: "分享给朋友...",
onClick: () => {
WKApp.shared.baseContext.showConversationSelect(
(channels: Channel[]) => {
const { selectedItem } = this.state;
if (channels && channels.length > 0) {
for (const channel of channels) {
const card = new Card();
card.uid = selectedItem?.uid || "";
card.name = selectedItem?.name || "";
card.vercode = selectedItem?.vercode || "";
WKSDK.shared().chatManager.send(card, channel);
}
Toast.success("分享成功!");
}
},
"分享名片"
);
},
},
]}
/>
</div>
</WKBase>
);
}
}

View File

@ -1,81 +1,98 @@
import { WKApp, WKViewQueueHeader, QRCodeMy, Search } from "@tsdaodao/base";
import {WKBase, WKBaseContext } from "@tsdaodao/base";
import { WKBase, WKBaseContext } from "@tsdaodao/base";
import React from "react";
import { Component, ReactNode } from "react";
import { Spin,Toast } from '@douyinfe/semi-ui';
import "./index.css"
import { Spin, Toast } from "@douyinfe/semi-ui";
import "./index.css";
export interface FriendAddProps {
onBack?: () => void
onBack?: () => void;
}
export class FriendAddState {
spinning!:boolean
keyword?:string
result?:any
spinning!: boolean;
keyword?: string;
result?: any;
}
export class FriendAdd extends Component<FriendAddProps,FriendAddState> {
baseContext!:WKBaseContext
constructor(props:any) {
super(props)
export class FriendAdd extends Component<FriendAddProps, FriendAddState> {
baseContext!: WKBaseContext;
constructor(props: any) {
super(props);
this.state = {
spinning: false,
}
this.state = {
spinning: false,
};
}
async searchUser() {
const { keyword } = this.state;
if (!keyword) {
return;
}
async searchUser() {
const { keyword } = this.state
if(!keyword) {
return
}
this.setState({
spinning: true,
})
const result = await WKApp.dataSource.commonDataSource.searchUser(keyword).catch((err)=>{
Toast.error(err.msg)
})
if(result) {
this.setState({
result: result,
spinning: false,
})
if(result.exist !== 1) {
Toast.error("用户不存在!")
}else {
WKApp.shared.baseContext.showUserInfo(result.data.uid,undefined,result.data.vercode)
}
this.setState({
spinning: true,
});
const result = await WKApp.dataSource.commonDataSource
.searchUser(keyword)
.catch((err) => {
Toast.error(err.msg);
});
if (result) {
this.setState({
result: result,
spinning: false,
});
if (result.exist !== 1) {
Toast.error("用户不存在!");
} else {
WKApp.shared.baseContext.showUserInfo(
result.data.uid,
undefined,
result.data.vercode
);
}
}
render(): ReactNode {
const { onBack } = this.props
const { spinning } = this.state
return <WKBase onContext={(ctx)=>{
this.baseContext = ctx
}}>
<div className="wk-friendadd">
<WKViewQueueHeader title="添加好友" onBack={onBack} />
<div className="wk-friendadd-content">
<Spin spinning={spinning}>
<Search placeholder={`${WKApp.config.appName}号/手机号`} onChange={(v)=>{
this.setState({
keyword: v
})
}} onEnterPress={()=>{
this.searchUser()
}}></Search>
</Spin>
<div className="wk-friendadd-content-qrcode">
{WKApp.config.appName}{WKApp.loginInfo.shortNo} <img onClick={()=>{
WKApp.routeLeft.push(<QRCodeMy></QRCodeMy>)
}} src={require("./assets/icon_qrcode.png")}></img>
</div>
</div>
</div>
</WKBase>
}
}
}
render(): ReactNode {
const { onBack } = this.props;
const { spinning } = this.state;
return (
<WKBase
onContext={(ctx) => {
this.baseContext = ctx;
}}
>
<div className="wk-friendadd">
<WKViewQueueHeader title="添加好友" onBack={onBack} />
<div className="wk-friendadd-content">
<Spin spinning={spinning}>
<Search
placeholder={`${WKApp.config.appName}号/手机号`}
onChange={(v) => {
this.setState({
keyword: v,
});
}}
onEnterPress={() => {
this.searchUser();
}}
></Search>
</Spin>
<div className="wk-friendadd-content-qrcode">
{WKApp.config.appName}{WKApp.loginInfo.shortNo}{" "}
<img
onClick={() => {
WKApp.routeLeft.push(<QRCodeMy></QRCodeMy>);
}}
src={require("./assets/icon_qrcode.png")}
></img>
</div>
</div>
</div>
</WKBase>
);
}
}

View File

@ -9,7 +9,7 @@
.wk-newfriend-content {
height: calc(100% - var( --wk-height-viewqueueheader));
width: 100%;
overflow-y: auto;
}
.wk-newfriend-content li {

View File

@ -1,58 +1,130 @@
import { FriendApplyState, WKApp, WKViewQueueHeader, Provider } from "@tsdaodao/base";
import React from "react";
import {
FriendApply,
FriendApplyState,
WKApp,
ContextMenus,
ContextMenusContext,
WKViewQueueHeader,
Provider,
} from "@tsdaodao/base";
import { Component, ReactNode } from "react";
import { Button } from '@douyinfe/semi-ui';
import "./index.css"
import { Button } from "@douyinfe/semi-ui";
import { NewFriendVM } from "./vm";
import "./index.css"
import { FriendAdd } from "../FriendAdd";
import "./index.css";
export class NewFriend extends Component {
export class NewFriendState {
selectedItem?: FriendApply; // 被选中的好友
}
render(): ReactNode {
return <Provider create={() => {
return new NewFriendVM()
}} render={(vm: NewFriendVM) => {
export class NewFriend extends Component<any, NewFriendState> {
contextMenusContext!: ContextMenusContext;
return <div className="wk-newfriend">
<WKViewQueueHeader title="新朋友" onBack={() => {
WKApp.routeLeft.pop()
}} action={<div className="wk-viewqueueheader-content-action">
<Button size="small" onClick={()=>{
WKApp.routeLeft.push(<FriendAdd onBack={()=>{
WKApp.routeLeft.pop()
}}></FriendAdd>)
}} ></Button>
</div>}></WKViewQueueHeader>
<div className="wk-newfriend-content">
<ul>
{
vm.friendApplys.map((f) => {
return <li key={f.to_uid} >
<div className="wk-newfriend-content-avatar">
<img src={WKApp.shared.avatarUser(f.to_uid)}></img>
</div>
<div className="wk-newfriend-content-title">
<div className="wk-newfriend-content-title-name">
{f.to_name}
</div>
<div className="wk-newfriend-content-title-remark">
{f.remark}
</div>
</div>
<div className="wk-newfriend-content-action">
<Button loading={vm.currentFriendApply?.to_uid === f.to_uid && vm.sureLoading } disabled={f.status == FriendApplyState.accepted} onClick={()=>{
vm.friendSure(f)
}}>{f.status == FriendApplyState.accepted ? "已添加" : "确认"}</Button>
</div>
</li>
})
}
</ul>
</div>
constructor(props: any) {
super(props);
}
_handleContextMenu(item: FriendApply, event: React.MouseEvent) {
console.log(item);
this.contextMenusContext.show(event);
this.setState({
selectedItem: item,
});
}
render(): ReactNode {
return (
<Provider
create={() => {
return new NewFriendVM();
}}
render={(vm: NewFriendVM) => {
return (
<div className="wk-newfriend">
<WKViewQueueHeader
title="新朋友"
onBack={() => {
WKApp.routeLeft.pop();
}}
action={
<div className="wk-viewqueueheader-content-action">
<Button
size="small"
onClick={() => {
WKApp.routeLeft.push(
<FriendAdd
onBack={() => {
WKApp.routeLeft.pop();
}}
></FriendAdd>
);
}}
>
</Button>
</div>
}
></WKViewQueueHeader>
<div className="wk-newfriend-content">
<ul>
{vm.friendApplys.map((f) => {
return (
<li
key={f.to_uid}
onContextMenu={(e) => {
this._handleContextMenu(f, e);
}}
>
<div className="wk-newfriend-content-avatar">
<img src={WKApp.shared.avatarUser(f.to_uid)}></img>
</div>
<div className="wk-newfriend-content-title">
<div className="wk-newfriend-content-title-name">
{f.to_name}
</div>
<div className="wk-newfriend-content-title-remark">
{f.remark}
</div>
</div>
<div className="wk-newfriend-content-action">
<Button
loading={
vm.currentFriendApply?.to_uid === f.to_uid &&
vm.sureLoading
}
disabled={f.status == FriendApplyState.accepted}
onClick={() => {
vm.friendSure(f);
}}
>
{f.status == FriendApplyState.accepted
? "已添加"
: "确认"}
</Button>
</div>
</li>
);
})}
</ul>
</div>
<ContextMenus
onContext={(context: ContextMenusContext) => {
this.contextMenusContext = context;
}}
menus={[
{
title: "删除",
onClick: () => {
const { selectedItem } = this.state;
selectedItem && vm.delFriendApply(selectedItem);
},
},
]}
/>
</div>
}}>
</Provider>
}
}
);
}}
></Provider>
);
}
}

View File

@ -18,10 +18,13 @@ export class MediaMessageUploadTask extends MessageTask {
async start(): Promise<void> {
const mediaContent = this.message.content as MediaMessageContent
if(mediaContent.file) {
const fileNameSuffix = mediaContent.file.name.substring(
mediaContent.file.name.lastIndexOf(".")
);
const param = new FormData();
param.append("file", mediaContent.file);
const fileName = this.getUUID();
const path = `/${this.message.channel.channelType}/${this.message.channel.channelID}/${fileName}${mediaContent.extension??""}`
const path = `/${this.message.channel.channelType}/${this.message.channel.channelID}/${fileName}${fileNameSuffix ?? ""}`;
const uploadURL = await this.getUploadURL(path)
if(uploadURL) {
this.uploadFile(mediaContent.file,uploadURL)
@ -52,7 +55,7 @@ export class MediaMessageUploadTask extends MessageTask {
this.canceler = c
}),
onUploadProgress: e => {
var completeProgress = ((e.loaded / e.total) | 0);
var completeProgress = ((e.loaded / e.total) || 0);
this._progress = completeProgress
this.update()
}

View File

@ -1,6 +1,6 @@
{
"$schema": "https://turborepo.org/schema.json",
"pipeline": {
"tasks": {
"dev": {
"cache": false
},
@ -37,5 +37,6 @@
"clean": {
"dependsOn": ["^clean"]
}
}
},
"ui": "stream"
}

148
yarn.lock
View File

@ -5046,20 +5046,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001400:
version "1.0.30001495"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001495.tgz"
integrity sha512-F6x5IEuigtUfU5ZMQK2jsy5JqUUlEFRVZq8bO2a+ysq5K7jD6PPc9YXZj78xDNS3uNchesp1Jw47YXEqr+Viyg==
caniuse-lite@^1.0.30001517:
version "1.0.30001533"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001533.tgz#1180daeb2518b93c82f19b904d1fefcf82197707"
integrity sha512-9aY/b05NKU4Yl2sbcJhn4A7MsGwR1EPfW/nrqsnqVA0Oq50wpmPaGI+R1Z0UKlUl96oxUkGEOILWtOHck0eCWw==
caniuse-lite@^1.0.30001520:
version "1.0.30001534"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001534.tgz#f24a9b2a6d39630bac5c132b5dff89b39a12e7dd"
integrity sha512-vlPVrhsCS7XaSh2VvWluIQEzVhefrUQcEsQWSS5A5V+dM07uv1qHeQzAOTGIMy9i3e9bH15+muvI/UHojVgS/Q==
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001520:
version "1.0.30001643"
resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz"
integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==
case-sensitive-paths-webpack-plugin@^2.4.0:
version "2.4.0"
@ -11009,9 +10999,16 @@ react-draggable@^4.0.3:
react-error-overlay@6.0.11, react-error-overlay@^6.0.11:
version "6.0.11"
resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
resolved "https://registry.npmmirror.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
react-infinite-scroller@^1.2.6:
version "1.2.6"
resolved "https://registry.npmmirror.com/react-infinite-scroller/-/react-infinite-scroller-1.2.6.tgz#8b80233226dc753a597a0eb52621247f49b15f18"
integrity sha512-mGdMyOD00YArJ1S1F3TVU9y4fGSfVVl6p5gh/Vt4u99CJOptfVu/q5V/Wlle72TMgYlBwIhbxK5wF0C/R33PXQ==
dependencies:
prop-types "^15.5.8"
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.4:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -12753,95 +12750,47 @@ tsutils@^3.21.0:
dependencies:
tslib "^1.8.1"
turbo-android-arm64@1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/turbo-android-arm64/-/turbo-android-arm64-1.4.7.tgz#553f68a1f475495549fb8a260461239c90f40410"
integrity sha512-BtWtH8e8w1GhtYpGQmkcDS/AUzVZhQ4ZZN+qtUFei1wZD7VAdtJ9Wcsfi3WD+mXA6vtpIpRJVfQMcShr8l8ErA==
turbo-darwin-64@2.0.9:
version "2.0.9"
resolved "https://registry.npmmirror.com/turbo-darwin-64/-/turbo-darwin-64-2.0.9.tgz#dc7bb92060a41b92155195dba5850c9669fa765a"
integrity sha512-owlGsOaExuVGBUfrnJwjkL1BWlvefjSKczEAcpLx4BI7Oh6ttakOi+JyomkPkFlYElRpjbvlR2gP8WIn6M/+xQ==
turbo-darwin-64@1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.4.7.tgz#c23e2452a01115136e631dc06aa6a9bb379d2d32"
integrity sha512-bMvZaAz5diec9feZ0XpQosYI8U0kiOQM2tj2sv0Y2WZbe227wodVMCQMyUowmcotO8nr6NF76Xo5E+H+dnY6LQ==
turbo-darwin-arm64@2.0.9:
version "2.0.9"
resolved "https://registry.npmmirror.com/turbo-darwin-arm64/-/turbo-darwin-arm64-2.0.9.tgz#6e5ce2c0f03999c6ec0116d5525841107da3078b"
integrity sha512-XAXkKkePth5ZPPE/9G9tTnPQx0C8UTkGWmNGYkpmGgRr8NedW+HrPsi9N0HcjzzIH9A4TpNYvtiV+WcwdaEjKA==
turbo-darwin-arm64@1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.4.7.tgz#4212917f4892159033cfa88bafa662f6e865fe49"
integrity sha512-AyfxYfKgh1EigQKjypbnDoMLuy4e/n/go+KYiWKKIpOaWXWLBokrBWzYN/aI3NMDRUJWK5ExdlWI9Nleelq8uQ==
turbo-linux-64@2.0.9:
version "2.0.9"
resolved "https://registry.npmmirror.com/turbo-linux-64/-/turbo-linux-64-2.0.9.tgz#e00e5e1b1cffab23c58888e7c397e108dc24fe2f"
integrity sha512-l9wSgEjrCFM1aG16zItBsZ206ZlhSSx1owB8Cgskfv0XyIXRGHRkluihiaxkp+UeU5WoEfz4EN5toc+ICA0q0w==
turbo-freebsd-64@1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/turbo-freebsd-64/-/turbo-freebsd-64-1.4.7.tgz#7d60d44a623bd000f53673a0db0205a16d8815e6"
integrity sha512-T5/osfbCh0rL53MFS5byFFfsR3vPMHIKIJ4fMMCNkoHsmFj2R0Pv53nqhEItogt0FJwCDHPyt7oBqO83H/AWQQ==
turbo-linux-arm64@2.0.9:
version "2.0.9"
resolved "https://registry.npmmirror.com/turbo-linux-arm64/-/turbo-linux-arm64-2.0.9.tgz#d240e4f0a784d03f1a79fd9e6c4e83abd9aa57c7"
integrity sha512-gRnjxXRne18B27SwxXMqL3fJu7jw/8kBrOBTBNRSmZZiG1Uu3nbnP7b4lgrA/bCku6C0Wligwqurvtpq6+nFHA==
turbo-freebsd-arm64@1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/turbo-freebsd-arm64/-/turbo-freebsd-arm64-1.4.7.tgz#35e5b23313d42aab074e69c1d1c44cab17f438ac"
integrity sha512-PL+SaO78AUCas+YKj01UiS2rpmGcxz8XPmLdFWmq6PYjPX6GL5UBAc3pkBphIm0aTLZtsikoEul+JrwAuAy6UA==
turbo-windows-64@2.0.9:
version "2.0.9"
resolved "https://registry.npmmirror.com/turbo-windows-64/-/turbo-windows-64-2.0.9.tgz#d52835302e722cc7de670b90aca55ce2b3516879"
integrity sha512-ZVo0apxUvaRq4Vm1qhsfqKKhtRgReYlBVf9MQvVU1O9AoyydEQvLDO1ryqpXDZWpcHoFxHAQc9msjAMtE5K2lA==
turbo-linux-32@1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/turbo-linux-32/-/turbo-linux-32-1.4.7.tgz#2861539d767cdfca058224f284991d918d7b1965"
integrity sha512-dK94UwDzySMALoQtjBVVPbWJZP6xw3yHGuytM3q5p4kfMZPSA+rgNBn5T5Af2Rc7jxlLAsu5ODJ0SgIbWSF5Hg==
turbo-windows-arm64@2.0.9:
version "2.0.9"
resolved "https://registry.npmmirror.com/turbo-windows-arm64/-/turbo-windows-arm64-2.0.9.tgz#45f0aa685514ec1cc753a559924e003b22b24bb7"
integrity sha512-sGRz7c5Pey6y7y9OKi8ypbWNuIRPF9y8xcMqL56OZifSUSo+X2EOsOleR9MKxQXVaqHPGOUKWsE6y8hxBi9pag==
turbo-linux-64@1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.4.7.tgz#5ff0e648a1d0b0053ad2f3c6a4fc735744cadc16"
integrity sha512-F6IM23zgTYo9gYJaNp17gVvQBt0hMIvz52OF91DpPYSLpV2h9OSlzPJ3j5TGaWueS/bc/YCV23+VojXX/MauGQ==
turbo-linux-arm64@1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.4.7.tgz#c5cb63db0ab59dd2ea37be4e44efef119a289ad1"
integrity sha512-kFe5jzj3FoY6jAEwyNEswndj1t/fPl0qyxfcQv6aNPz7Nb2Lh7mY/EEse+CG3ydIo5RZKba7ppQoBSDmHx7JsA==
turbo-linux-arm@1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/turbo-linux-arm/-/turbo-linux-arm-1.4.7.tgz#93ed9e43a95760e660a42dbf65cbfd430e558e89"
integrity sha512-FTh4itdMNZ7IxGKknFnQ6iPO9vGGKqyySkCYLR01lnw6BTnKL9KuM9XUCBRyn7dNmHhAnqu1ZtVsBkH7CE7DEw==
turbo-linux-mips64le@1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/turbo-linux-mips64le/-/turbo-linux-mips64le-1.4.7.tgz#77357165c3ae0102fd5924dd85229676938dd717"
integrity sha512-756nG8dnPQVcnl9s70S4NQ43aJjpsnc2h0toktPO+9u2ayv9XTbIPvZLFsS55bDeYhodDGvxoB96W6Xnx01hyQ==
turbo-linux-ppc64le@1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/turbo-linux-ppc64le/-/turbo-linux-ppc64le-1.4.7.tgz#2a7399d6ee31d894d48aea393ed1f9addc4a310c"
integrity sha512-VS2ofGN/XsafNGJdZ21UguURHb7KRG879yWLj59hO1d+0xXXQbx7ljsmEPOhiE4UjEdx4Ur6P44BhztTgDx9Og==
turbo-windows-32@1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/turbo-windows-32/-/turbo-windows-32-1.4.7.tgz#e93233d8681886dc0b609c897447991afa378340"
integrity sha512-M5GkZdA0CbJAOcR8SScM63CBV+NtX7qjhoNNOl0F99nGJ+rO3dH71CcM/rbhlz9SQzKQoX8rcuwZHe4r2HZAug==
turbo-windows-64@1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.4.7.tgz#da9fe6b6bf2b6fd1f85ecdc186fc7a49b8411d22"
integrity sha512-ftZUtZ1BX1vi8MbxKr+a7riIkhwvGnNTtWGprVu+aDJ8PnV+lNqbkrLJGvKP7Cn22hGTfzcjNNPcJ5PBZpQEQw==
turbo-windows-arm64@1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.4.7.tgz#c60a772aab36c45ce461e3305e35d1c22a00e516"
integrity sha512-mZ79XeJFfaeVKdBV3w0eoGaqAxFnwxrme0jZtSWemAbeDSCF/13wcbLGwtq0+Lu0LxEGweeQ5AqsCIc9t9i6sA==
turbo@latest:
version "1.4.7"
resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.4.7.tgz#859989a5dce2a7b1fa51f17ac4fb1e34dbfd455d"
integrity sha512-oIk7PAISPidDOkTM5M+ydEe5GDQ/+TahDgIbaYKeAAy2Qpmur4s0HybSDWHIdxLqI96OPD/mOKymRLrjh3Mdhg==
turbo@2.0.9:
version "2.0.9"
resolved "https://registry.npmmirror.com/turbo/-/turbo-2.0.9.tgz#fa0ab576c4cb9a8fc9db648e9ac9adfe10a22ae5"
integrity sha512-QaLaUL1CqblSKKPgLrFW3lZWkWG4pGBQNW+q1ScJB5v1D/nFWtsrD/yZljW/bdawg90ihi4/ftQJ3h6fz1FamA==
optionalDependencies:
turbo-android-arm64 "1.4.7"
turbo-darwin-64 "1.4.7"
turbo-darwin-arm64 "1.4.7"
turbo-freebsd-64 "1.4.7"
turbo-freebsd-arm64 "1.4.7"
turbo-linux-32 "1.4.7"
turbo-linux-64 "1.4.7"
turbo-linux-arm "1.4.7"
turbo-linux-arm64 "1.4.7"
turbo-linux-mips64le "1.4.7"
turbo-linux-ppc64le "1.4.7"
turbo-windows-32 "1.4.7"
turbo-windows-64 "1.4.7"
turbo-windows-arm64 "1.4.7"
turbo-darwin-64 "2.0.9"
turbo-darwin-arm64 "2.0.9"
turbo-linux-64 "2.0.9"
turbo-linux-arm64 "2.0.9"
turbo-windows-64 "2.0.9"
turbo-windows-arm64 "2.0.9"
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
@ -12936,6 +12885,11 @@ typedarray-to-buffer@^3.1.5:
dependencies:
is-typedarray "^1.0.0"
typescript@^4.5.5:
version "4.9.5"
resolved "https://registry.npmmirror.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
typescript@^5.3.3:
version "5.3.3"
resolved "https://registry.npmmirror.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"