mirror of
https://github.com/Teamlinker/Teamlinker.git
synced 2025-06-03 03:00:17 +00:00
add
This commit is contained in:
parent
f7858d1c77
commit
f485a45ac0
@ -16,7 +16,13 @@ Teamlinker是一个集成了多功能模块的团队协作平台,涵盖团队
|
||||
该平台解决了团队内高效协作的难题,避免了公司使用多个工具分别处理项目进程、同事交流和客户会议的问题。相较于传统工具,Teamlinker不仅提供基本而全面的协同办公需求,而且成本极低,更符合当前经济环境。
|
||||
|
||||
Teamlinker基于TeamOS系统开发,是一种web操作系统,用户可以并行处理不同任务,类似于Win和Mac等操作系统。主要包含六个功能模块:项目、Wiki、日历、会议、聊天和网盘,这些功能之间无缝整合,使团队协作更加顺畅。
|
||||
### 用心做开源,我们也很需要你的鼓励!右上角Star🌟,等你点亮!
|
||||
|
||||
**我们同时也提供了相关的开源组件**
|
||||
|
||||
[TLCalendar](https://github.com/Teamlinker/Teamlinker/tree/dev/code/client/src/business/common/component/calendar)
|
||||
[TLEditor](https://github.com/Teamlinker/Teamlinker/tree/dev/code/client/src/business/common/component/richEditorCore)
|
||||
[TLMeetingClient](https://github.com/Teamlinker/Teamlinker/tree/dev/code/client/src/business/common/component/meeting)
|
||||
[TLMeetingServer](https://github.com/Teamlinker/Teamlinker/tree/dev/code/server/common/meeting)
|
||||
|
||||
## 📋 官网
|
||||
|
||||
|
@ -19,6 +19,13 @@ The platform solves the problem of efficient collaboration within the team and a
|
||||
|
||||
Teamlinker is developed based on the TeamOS system. It is a web operating system that allows users to process different tasks in parallel, similar to operating systems such as Win and Mac. It mainly contains six functional modules: project, wiki, calendar, meeting, chat and network disk. These functions are seamlessly integrated to make team collaboration smoother.
|
||||
|
||||
**We also support the relevant open-source components**
|
||||
|
||||
[TLCalendar](https://github.com/Teamlinker/Teamlinker/tree/dev/code/client/src/business/common/component/calendar)
|
||||
[TLEditor](https://github.com/Teamlinker/Teamlinker/tree/dev/code/client/src/business/common/component/richEditorCore)
|
||||
[TLMeetingClient](https://github.com/Teamlinker/Teamlinker/tree/dev/code/client/src/business/common/component/meeting)
|
||||
[TLMeetingServer](https://github.com/Teamlinker/Teamlinker/tree/dev/code/server/common/meeting)
|
||||
|
||||
## 📋 Official website
|
||||
|
||||
https://team-linker.com
|
||||
|
@ -1,144 +0,0 @@
|
||||
<h1 align="center">
|
||||
TLCalendar
|
||||
</h1>
|
||||
<p align="center">
|
||||
A simple calendar component based on <b>vue3</b> and <b>typescript</b>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/tlcalendar">
|
||||
<img src="https://flat.badgen.net/npm/v/tlcalendar?icon=npm" alt="npm"/>
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/tlcalendar">
|
||||
<img src="https://flat.badgen.net/bundlephobia/minzip/tlcalendar?color=green" alt="Minzipped size"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## About
|
||||
It is an open-source calendar of [Teamlinker](https://team-linker.com). It provides a variety of features to help users to build their own calendar components like these below:
|
||||
|
||||

|
||||

|
||||
|
||||
## Features
|
||||
1. Customize the calendar event show dialog
|
||||
2. Switch between day,week and month
|
||||
3. Support timezone switch
|
||||
4. Calendar event freely drag and move
|
||||
5. Support cross-day event and all-day event
|
||||
6. Support multiply calendars
|
||||
|
||||
|
||||
## Demo
|
||||
[Teamlinker](https://team-linker.com)
|
||||
|
||||
Teamlinker provides a full experience of this package.Have a try!
|
||||
|
||||
## Installation
|
||||
```shell
|
||||
npm i tlcalendar
|
||||
```
|
||||
## Usage
|
||||
|
||||
### Global
|
||||
main.ts
|
||||
```typescript
|
||||
import Calendar from "tlcalendar"
|
||||
import "tlcalendar/style.css"
|
||||
|
||||
app.use(Calendar)
|
||||
```
|
||||
.vue
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import {computed, ref} from "vue";
|
||||
import moment from "moment";
|
||||
import "moment-timezone"
|
||||
const startDay=ref(moment().startOf("weeks").format("YYYY-MM-DD"))
|
||||
const endDay=ref(moment().endOf("weeks").format("YYYY-MM-DD"))
|
||||
const month=ref(moment().format("YYYY-MM"))
|
||||
const timezone=moment.tz.guess(true)
|
||||
const content=ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="width: 600px;height: 800px">
|
||||
<TLCalendar mode="day" :start-date="startDay" :end-date="endDay" :month="month" :event-list="[]" :utc-offset="8" :time-zone="timezone"></TLCalendar>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Sfc
|
||||
.vue
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
|
||||
import {computed, ref} from "vue";
|
||||
import moment from "moment";
|
||||
import "moment-timezone"
|
||||
import {TLCalendar} from "tlcalendar";
|
||||
import "tlcalendar/style.css"
|
||||
const startDay=ref(moment().startOf("weeks").format("YYYY-MM-DD"))
|
||||
const endDay=ref(moment().endOf("weeks").format("YYYY-MM-DD"))
|
||||
const month=ref(moment().format("YYYY-MM"))
|
||||
const timezone=moment.tz.guess(true)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="width: 600px;height: 800px">
|
||||
<TLCalendar mode="day" :start-date="startDay" :end-date="endDay" :month="month" :event-list="[]" :utc-offset="8" :time-zone="timezone"></TLCalendar>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Customization
|
||||
### Props
|
||||
```typescript
|
||||
eventList: {
|
||||
type: PropType<IClient_Calendar_Info[]>;
|
||||
required: true;
|
||||
};
|
||||
startDate: {
|
||||
type: PropType<string>;
|
||||
};
|
||||
endDate: {
|
||||
type: PropType<string>;
|
||||
};
|
||||
mode: {
|
||||
type: PropType<"day" | "month">;
|
||||
required: true;
|
||||
};
|
||||
month: {
|
||||
type: PropType<string>;
|
||||
};
|
||||
utcOffset: {
|
||||
type: PropType<number>;
|
||||
};
|
||||
timeZone: {
|
||||
type: PropType<string>;
|
||||
required: true;
|
||||
};
|
||||
```
|
||||
|
||||
### emits
|
||||
```typescript
|
||||
changeEventDate: (event: IClient_Calendar_Info, originalDateRange: {
|
||||
start: IClient_Calendar_Date;
|
||||
end: IClient_Calendar_Date;
|
||||
}, type: "resize" | "move") => void;
|
||||
//when user drag event or adjust event ,this event will be triggered
|
||||
blankClick: (date: moment_2.Moment, point: {
|
||||
x: number;
|
||||
y: number;
|
||||
}) => void;
|
||||
//when user click blank area of calendar,this event will be triggered
|
||||
```
|
||||
|
||||
### Slots
|
||||
```vue
|
||||
<template #shortView="{timeZone,selectedEvent,maskInfoTop,maskInfoLeft,onClose}">
|
||||
</template>
|
||||
```
|
||||
|
||||
## About Teamlinker
|
||||
Teamlinker is a cooperation platform that integrates different kind of modules.You can contact your teammates,assign your tasks,start a meeting,schedule your events,manage your files and so on with Teamlinker.
|
@ -1,15 +1,13 @@
|
||||
{
|
||||
"name": "tlcalendar",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.7",
|
||||
"description": "A simple calendar component based on vue3 and typescript",
|
||||
"main": "TLCalendar.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Teamlinker/TLCalendar.git"
|
||||
},
|
||||
"repository": "https://github.com/Teamlinker/Teamlinker",
|
||||
"homepage": "https://github.com/Teamlinker/Teamlinker/blob/dev/code/client/src/business/common/component/calendar/npm/README.md",
|
||||
"keywords": ["vue","calendar","date","date-picker","time-picker","teamlinker"],
|
||||
"author": "teamlinker",
|
||||
"license": "ISC",
|
||||
|
@ -1,303 +0,0 @@
|
||||
<h1 align="center">
|
||||
TLMeetingClient
|
||||
</h1>
|
||||
<p align="center">
|
||||
A simple video meeting library based on <b>node.js</b> and <b>typescript</b>
|
||||
</p>
|
||||
<p align="center">
|
||||
This is <b>client</b> package,you can retrieve server package from <a href="https://github.com/Teamlinker/TLMeetingServer">here</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/tlmeetingclient">
|
||||
<img src="https://flat.badgen.net/npm/v/tlmeetingclient?icon=npm" alt="npm"/>
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/tlmeetingclient">
|
||||
<img src="https://flat.badgen.net/bundlephobia/minzip/tlmeetingclient?color=green" alt="Minzipped size"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## About
|
||||
It is an open-source video meeting package of [Teamlinker](https://team-linker.com). It provides a variety of features to help users to build their own meeting rooms like these below:
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
1. Very easy to complete video meeting functionality
|
||||
2. Support screen share,presenter management
|
||||
3. mute & unmute
|
||||
4. meeting chat
|
||||
5. Free and open-source based on mediasoup
|
||||
|
||||
|
||||
## Demo
|
||||
[Teamlinker](https://team-linker.com)
|
||||
|
||||
Teamlinker provides a full experience of this package.Have a try!
|
||||
|
||||
## Installation
|
||||
```shell
|
||||
npm i tlmeetingclient
|
||||
```
|
||||
## Usage
|
||||
|
||||
TLMeetingClient is based on socket.io,you should build a socket.io connection from backend and pass the socket instance to the TLMeetingClient construct function.
|
||||
|
||||
.vue
|
||||
```typescript
|
||||
const props=defineProps<{
|
||||
meetingId:string,
|
||||
password?:string,
|
||||
inviteBusinessIds?:{
|
||||
id:string,
|
||||
type:ECommon_Model_Organization_Member_Type
|
||||
}[]
|
||||
}>()
|
||||
const loading=ref(true)
|
||||
const unReadCount=ref(0)
|
||||
const organizationUserList=ref<OrganizationUserItem[]>([])
|
||||
const tabValue=ref("participant")
|
||||
const speaker=ref<OrganizationUserItem>()
|
||||
const myOrganizationUserId=SessionStorage.get("organizationUserId")
|
||||
const me=ref<OrganizationUserItem>({
|
||||
organizationUserId:myOrganizationUserId,
|
||||
name:"",
|
||||
permission:ECommon_Meeting_Room_Permission.NORMAL,
|
||||
audioStream:null,
|
||||
videoStream:null,
|
||||
video:true,
|
||||
audio:true
|
||||
})
|
||||
const {t}=useI18n()
|
||||
const meetingChat=ref<InstanceType<typeof MeetingChat>>(null)
|
||||
const socket=SocketIOClient.get(ECommon_Socket_Type.MEETING)
|
||||
const navigator=getCurrentNavigator()
|
||||
const root=getRootNavigatorRef()
|
||||
const appContext=getCurrentInstance().appContext
|
||||
const currentMeeting=ref<DCSType<ICommon_Model_Meeting_Room>>()
|
||||
const screenShareInfo=ref<{
|
||||
video:MediaStream,
|
||||
audio:MediaStream,
|
||||
organizationUserId:string
|
||||
}>()
|
||||
let meetingClient=new MeetingClient(socket.getSocket())
|
||||
watch(tabValue,()=>{
|
||||
if(tabValue.value==="chat") {
|
||||
unReadCount.value=0
|
||||
}
|
||||
})
|
||||
meetingClient.onProducerStateChange=async (state, kind, businessId,type, stream, producerId) => {
|
||||
let objOrganizationUser=organizationUserList.value.find(value => value.organizationUserId===businessId)
|
||||
if(state=="new") {
|
||||
if(type==="camera" || type==="data") {
|
||||
if(!objOrganizationUser) {
|
||||
let obj=userTeamInfoPick.getInfos([{
|
||||
id:businessId,
|
||||
type:ECommon_IM_Message_EntityType.USER
|
||||
}])
|
||||
organizationUserList.value.push({
|
||||
organizationUserId:businessId,
|
||||
name:obj[businessId]?obj[businessId].name:"",
|
||||
permission:ECommon_Meeting_Room_Permission.NORMAL,
|
||||
audioStream:kind==="audio"?stream:null,
|
||||
videoStream:kind==="video"?stream:null,
|
||||
audio:kind==="audio"?true:false,
|
||||
video:kind==="video"?true:false,
|
||||
})
|
||||
objOrganizationUser=organizationUserList.value.at(-1);
|
||||
} else {
|
||||
if(kind=="video") {
|
||||
objOrganizationUser.videoStream=stream
|
||||
objOrganizationUser.video=true
|
||||
} else if(kind=="audio") {
|
||||
objOrganizationUser.audioStream=stream
|
||||
objOrganizationUser.audio=true
|
||||
}
|
||||
}
|
||||
if(!speaker.value) {
|
||||
speaker.value=objOrganizationUser
|
||||
}
|
||||
} else if(type==="screen") {
|
||||
if(!screenShareInfo.value) {
|
||||
screenShareInfo.value={
|
||||
organizationUserId:businessId,
|
||||
audio:kind==="audio"?stream:null,
|
||||
video:kind==="video"?stream:null,
|
||||
}
|
||||
} else {
|
||||
if(kind==="video") {
|
||||
screenShareInfo.value.video=stream
|
||||
} else if(kind==="audio") {
|
||||
screenShareInfo.value.audio=stream
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(state=="close") {
|
||||
if(type==="camera" || type==="data") {
|
||||
if(objOrganizationUser) {
|
||||
let index=organizationUserList.value.findIndex(value => value.organizationUserId===businessId)
|
||||
organizationUserList.value.splice(index,1)
|
||||
if(objOrganizationUser===speaker.value) {
|
||||
speaker.value=null
|
||||
}
|
||||
}
|
||||
} else if(type==="screen") {
|
||||
screenShareInfo.value=null;
|
||||
}
|
||||
} else if(state=="pause") {
|
||||
if(objOrganizationUser) {
|
||||
if(kind=="video") {
|
||||
objOrganizationUser.video=false
|
||||
} else if(kind=="audio") {
|
||||
objOrganizationUser.audio=false
|
||||
}
|
||||
}
|
||||
} else if(state=="resume") {
|
||||
if(objOrganizationUser) {
|
||||
if(kind=="video") {
|
||||
objOrganizationUser.video=true
|
||||
} else if(kind=="audio") {
|
||||
objOrganizationUser.audio=true
|
||||
}
|
||||
}
|
||||
}
|
||||
handleState()
|
||||
}
|
||||
meetingClient.onKick=() => {
|
||||
navigator.pop()
|
||||
}
|
||||
meetingClient.onJoinedRoom=async roomInfo => {
|
||||
getCurrentMeeting()
|
||||
meetingChat.value.getMessage()
|
||||
if(props.inviteBusinessIds) {
|
||||
socket.getSocket().emit("meeting_invite",props.inviteBusinessIds)
|
||||
}
|
||||
}
|
||||
meetingClient.onLeavedRoom=roomInfo => {
|
||||
|
||||
}
|
||||
meetingClient.onSpeaker=async businessId => {
|
||||
let obj=organizationUserList.value.find(item=>item.organizationUserId===businessId)
|
||||
if(obj && obj.organizationUserId!==myOrganizationUserId) {
|
||||
speaker.value=obj
|
||||
}
|
||||
}
|
||||
meetingClient.onLocalProducerInit=async stream => {
|
||||
let obj=userTeamInfoPick.getInfos([{
|
||||
id:myOrganizationUserId,
|
||||
type:ECommon_IM_Message_EntityType.USER
|
||||
}])
|
||||
me.value={
|
||||
organizationUserId:myOrganizationUserId,
|
||||
name:obj[myOrganizationUserId]?obj[myOrganizationUserId].name:"",
|
||||
permission:ECommon_Meeting_Room_Permission.NORMAL,
|
||||
audioStream:stream,
|
||||
videoStream:stream,
|
||||
video:true,
|
||||
audio:true
|
||||
}
|
||||
organizationUserList.value.unshift(me.value)
|
||||
}
|
||||
meetingClient.onLocalProducerStart=kind => {
|
||||
if(kind=="video" || kind=="audio") {
|
||||
handleState()
|
||||
loading.value=false
|
||||
}
|
||||
}
|
||||
const initMeeting=async ()=>{
|
||||
let password=props.password
|
||||
if(password==null) {
|
||||
password=await Dialog.input(root.value,appContext,t("placeholder.typeMeetingPassword"))
|
||||
if(!password) {
|
||||
Message.error(t("tip.joinMeetingFailed"))
|
||||
return
|
||||
}
|
||||
}
|
||||
let preview:any=await Dialog.open(root.value,appContext,t("controller.app.meeting.meetingProfile.meetingPreview"),markRaw(MeetingPreview))
|
||||
if(preview) {
|
||||
let ret=await meetingClient.join(props.meetingId,password,preview.enableVideo,preview.enableAudio,preview.cameraId)
|
||||
if(!ret?.success) {
|
||||
Message.error(ret.msg)
|
||||
navigator.pop()
|
||||
}
|
||||
} else {
|
||||
navigator.pop()
|
||||
}
|
||||
}
|
||||
|
||||
const handleState=async ()=>{
|
||||
let [retState,retPermission]=await Promise.all([
|
||||
meetingClient.states(),
|
||||
socket.getSocket().emitWithAck("meeting_get_presenters")
|
||||
])
|
||||
for(let obj of organizationUserList.value) {
|
||||
if(retPermission[obj.organizationUserId]) {
|
||||
obj.permission=retPermission[obj.organizationUserId]
|
||||
}
|
||||
}
|
||||
for(let objState of retState) {
|
||||
for(let objOrganizationUser of organizationUserList.value) {
|
||||
if (objState.businessId===objOrganizationUser.organizationUserId) {
|
||||
objOrganizationUser.video=objState.kinds["video"]
|
||||
objOrganizationUser.audio=objState.kinds["audio"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleUserInfo = (id: string, info: {
|
||||
id: string,
|
||||
name: string,
|
||||
photo: string
|
||||
}) => {
|
||||
for(let obj of organizationUserList.value) {
|
||||
if(obj.organizationUserId==id) {
|
||||
obj.name=info.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onPresenterChange=async (organizationUserId, permission) => {
|
||||
let obj=organizationUserList.value.find(item=>item.organizationUserId===organizationUserId)
|
||||
if(obj) {
|
||||
obj.permission=permission
|
||||
}
|
||||
}
|
||||
|
||||
const onNewMessage=()=>{
|
||||
if(tabValue.value!=="chat") {
|
||||
unReadCount.value=1
|
||||
}
|
||||
}
|
||||
|
||||
const getCurrentMeeting=async ()=>{
|
||||
let res=await apiMeeting.getCurrentRoom()
|
||||
if(res?.code==0) {
|
||||
currentMeeting.value=res.data
|
||||
}
|
||||
}
|
||||
|
||||
const handleLeaveMeeting=async ()=>{
|
||||
await meetingClient.leave()
|
||||
navigator.pop()
|
||||
}
|
||||
|
||||
onBeforeMount(()=>{
|
||||
eventBus.on(EClient_EVENTBUS_TYPE.UPDATE_USER_INFO, handleUserInfo)
|
||||
eventBus.on(EClient_EVENTBUS_TYPE.LEAVE_MEETING, handleLeaveMeeting)
|
||||
socket.getSocket().on("meeting_presenter_change", onPresenterChange)
|
||||
initMeeting()
|
||||
})
|
||||
|
||||
onBeforeUnmount(()=>{
|
||||
eventBus.off(EClient_EVENTBUS_TYPE.UPDATE_USER_INFO, handleUserInfo)
|
||||
eventBus.off(EClient_EVENTBUS_TYPE.LEAVE_MEETING, handleLeaveMeeting)
|
||||
socket.getSocket().off("meeting_presenter_change", onPresenterChange)
|
||||
if(meetingClient.getRoomInfo()) {
|
||||
meetingClient.leave()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## About Teamlinker
|
||||
Teamlinker is a cooperation platform that integrates different kind of modules.You can contact your teammates,assign your tasks,start a meeting,schedule your events,manage your files and so on with Teamlinker.
|
@ -1,15 +1,13 @@
|
||||
{
|
||||
"name": "tlmeetingclient",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"description": "A simple video meeting library based on node.js and typescript",
|
||||
"main": "client.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Teamlinker/TLMeetingClient.git"
|
||||
},
|
||||
"repository": "https://github.com/Teamlinker/Teamlinker",
|
||||
"homepage": "https://github.com/Teamlinker/Teamlinker/blob/dev/code/client/src/business/common/component/meeting/npm/README.md",
|
||||
"keywords": ["teamlinker","meeting","video","nodejs","typescript","meeting client"],
|
||||
"author": "teamlinker",
|
||||
"license": "ISC",
|
||||
|
@ -1,221 +0,0 @@
|
||||
<h1 align="center">
|
||||
TLEditor
|
||||
</h1>
|
||||
<p align="center">
|
||||
A simple block-style editor based on <b>vue3</b> and <b>typescript</b>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/tleditor">
|
||||
<img src="https://flat.badgen.net/npm/v/tleditor?icon=npm" alt="npm"/>
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/tleditor">
|
||||
<img src="https://flat.badgen.net/bundlephobia/minzip/tleditor?color=green" alt="Minzipped size"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## About
|
||||
It is an open-source editor of [Teamlinker](https://team-linker.com). It provides a variety of features to help users to build their own text editors like these below:
|
||||
|
||||

|
||||

|
||||
|
||||
## Features
|
||||
1. Output a clean json
|
||||
2. Customize the style and behavior of pop menu,quote menu
|
||||
3. Insert your own block style content
|
||||
4. type "/" to get pop menu and "@" to get quote menu
|
||||
5. Well-designed API
|
||||
6. Free and open source
|
||||
|
||||
**It solves the cross-line selection issue that many other components can't support**
|
||||
|
||||
## Demo
|
||||
[Teamlinker](https://team-linker.com)
|
||||
|
||||
Teamlinker provides a full experience of this package.Have a try!
|
||||
|
||||
## Installation
|
||||
```shell
|
||||
npm i tleditor
|
||||
```
|
||||
## Usage
|
||||
|
||||
### Global
|
||||
main.ts
|
||||
```typescript
|
||||
import Editor from "tleditor"
|
||||
import "tleditor/style.css"
|
||||
|
||||
app.use(Editor)
|
||||
```
|
||||
.vue
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import {ref} from "vue";
|
||||
const content=ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="width: 500px">
|
||||
<TLEditor v-model="content"></TLEditor>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Sfc
|
||||
.vue
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import {ref} from "vue";
|
||||
import {TLEditor} from "tleditor"
|
||||
import "tleditor/style.css"
|
||||
const content=ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="width: 500px">
|
||||
<TLEditor v-model="content"></TLEditor>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Customization
|
||||
### Props
|
||||
```typescript
|
||||
readonly: {
|
||||
type: PropType<boolean>;
|
||||
};
|
||||
border: {
|
||||
type: PropType<boolean>;
|
||||
};
|
||||
popMenuList: { //the pop menu list when user type "/"
|
||||
type: PropType<{
|
||||
type: any;
|
||||
title: string;
|
||||
}[]>;
|
||||
};
|
||||
placeholder: {
|
||||
type: PropType<string>;
|
||||
};
|
||||
quoteType: { //a quote type should be specified when user type "@"
|
||||
type: PropType<any>;
|
||||
};
|
||||
```
|
||||
|
||||
### emits
|
||||
```typescript
|
||||
onQuoteList: (keyword: string, handleFunc: (list: {
|
||||
value: string;
|
||||
label: string;
|
||||
photo: string;
|
||||
}[]) => void) => any;
|
||||
//users use keyword to call user api and return a users' list,the call handleFunc to complete this search
|
||||
onUploadFile: (file: File, handleFunc: (fileId: string, path: string) => void) => any;
|
||||
//users use File object to process upload business,get a file id and path ,then call handleFunc to complete this upload
|
||||
onPopMenuClick: (type: any, handleFunc: (item: IEditor_Content_Line_Config) => void) => any;
|
||||
//the pop menu item click
|
||||
onCustomAnchorClick: (type: any, value: string, link: string, label: string) => any;
|
||||
//the anchor click of customized content
|
||||
onMetaEnter: () => any;
|
||||
//use press meta+enter button
|
||||
onLinkClick: (type: any, value: string, x: number, y: number) => any;
|
||||
//all anchors click
|
||||
onSetLineConfigType: (linkElement: HTMLElement, objConfig: IEditor_Content_Line_Config) => any;
|
||||
//customize the link and image to the html element
|
||||
onGetLineConfigType: (config: IEditor_Content_Line_Config, linkElement: HTMLElement) => any;
|
||||
//parse the link and image from html element
|
||||
```
|
||||
here is an example about onSetLineConfigType and onGetLineConfigType
|
||||
```typescript
|
||||
const onSetLineConfigType=(ele:HTMLElement,obj:IEditor_Content_Line_Config)=> {
|
||||
if (obj.type == ECommon_Content_Line_Config_Type.LINK) {
|
||||
ele.setAttribute("href", obj.link)
|
||||
ele.setAttribute("target", "_blank")
|
||||
ele.style.cursor = "pointer"
|
||||
ele.innerText = obj.value
|
||||
if (obj.style) {
|
||||
for (let key in obj.style) {
|
||||
ele.style[key] = obj.style[key]
|
||||
}
|
||||
}
|
||||
} else if (obj.type == ECommon_Content_Line_Config_Type.IMAGE) {
|
||||
ele.setAttribute("src", obj.link)
|
||||
ele.setAttribute("width", String(obj.width ?? 200))
|
||||
ele.setAttribute("height", "auto")
|
||||
ele.setAttribute("fileId", obj.value)
|
||||
} else if (obj.type === ECommon_Content_Line_Config_Type.FILE) {
|
||||
ele.setAttribute("href", obj.link)
|
||||
ele.setAttribute("download", obj.label)
|
||||
ele.setAttribute("fileId", obj.value)
|
||||
ele.style.margin = "0 2px 0 2px"
|
||||
ele.style.cursor = "pointer"
|
||||
ele.contentEditable = "false"
|
||||
ele.innerText = obj.label
|
||||
ele.style.color = "black"
|
||||
let icon = document.createElement("i")
|
||||
icon.className = "svg svg-file"
|
||||
icon.style.marginRight = "5px"
|
||||
icon.style.color = "gray"
|
||||
ele.prepend(icon)
|
||||
}
|
||||
}
|
||||
|
||||
const onGetLineConfigType=(obj:IEditor_Content_Line_Config,ele:HTMLElement)=>{
|
||||
if(ele.tagName=="A") {
|
||||
let fileId=ele.getAttribute("fileId")
|
||||
if(fileId) {
|
||||
obj.type=ECommon_Content_Line_Config_Type.FILE
|
||||
obj.link=ele.getAttribute("href")
|
||||
obj.value=fileId
|
||||
obj.label=ele.innerText??""
|
||||
} else {
|
||||
obj.type=ECommon_Content_Line_Config_Type.LINK
|
||||
obj.link=ele.getAttribute("href")
|
||||
obj.value=ele.innerText??""
|
||||
}
|
||||
} else if(ele.tagName=="IMG") {
|
||||
obj.type=EEditor_Content_Line_Config_Type.IMAGE
|
||||
obj.link=ele.getAttribute("src")
|
||||
obj.width=parseInt(ele.getAttribute("width"))
|
||||
obj.value=ele.getAttribute("fileId")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Methods
|
||||
```typescript
|
||||
insertConfig: (itemList: IEditor_Content_Line_Config[]) => void;
|
||||
```
|
||||
here is an example:
|
||||
```typescript
|
||||
let arrPromise=await Promise.allSettled((data.data as File[]).map(file=>{
|
||||
return apiFile.upload({
|
||||
file:file as any
|
||||
}).then(res=>{
|
||||
let ret:ICommon_Content_Line_Config
|
||||
if(res?.code==0) {
|
||||
ret={
|
||||
type:ECommon_Content_Line_Config_Type.FILE,
|
||||
value:res.data.id,
|
||||
link:res.data.path,
|
||||
label:file.name
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
})
|
||||
}))
|
||||
if(loading) {
|
||||
loading.value=false
|
||||
}
|
||||
let itemList=arrPromise.filter(item=>{
|
||||
if(item.status==="fulfilled" && item.value) {
|
||||
return true
|
||||
}
|
||||
}).map(item=>{
|
||||
return (item as any).value
|
||||
})
|
||||
objEditor.value.insertConfig(itemList)
|
||||
```
|
||||
## About Teamlinker
|
||||
Teamlinker is a cooperation platform that integrates different kind of modules.You can contact your teammates,assign your tasks,start a meeting,schedule your events,manage your files and so on with Teamlinker.
|
@ -1,12 +1,10 @@
|
||||
{
|
||||
"name": "tleditor",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.5",
|
||||
"description": "A simple block-style editor based on vue3 and typescript",
|
||||
"main": "TLEditor.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Teamlinker/TLEditor.git"
|
||||
},
|
||||
"repository": "https://github.com/Teamlinker/Teamlinker",
|
||||
"homepage": "https://github.com/Teamlinker/Teamlinker/blob/dev/code/client/src/business/common/component/richEditorCore/npm/README.md",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
|
@ -1,217 +0,0 @@
|
||||
<h1 align="center">
|
||||
TLMeetingServer
|
||||
</h1>
|
||||
<p align="center">
|
||||
A simple video meeting library based on <b>node.js</b> and <b>typescript</b>
|
||||
</p>
|
||||
<p align="center">
|
||||
This is <b>server</b> package,you can retrieve client package from <a href="https://github.com/Teamlinker/TLMeetingClient">here</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/tlmeetingserver">
|
||||
<img src="https://flat.badgen.net/npm/v/tlmeetingserver?icon=npm" alt="npm"/>
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/tlmeetingserver">
|
||||
<img src="https://flat.badgen.net/bundlephobia/minzip/tlmeetingserver?color=green" alt="Minzipped size"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## About
|
||||
It is an open-source video meeting package of [Teamlinker](https://team-linker.com). It provides a variety of features to help users to build their own meeting rooms like these below:
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
1. Very easy to complete video meeting functionality
|
||||
2. Support screen share,presenter management
|
||||
3. mute & unmute
|
||||
4. meeting chat
|
||||
5. Free and open-source based on mediasoup
|
||||
|
||||
|
||||
## Demo
|
||||
[Teamlinker](https://team-linker.com)
|
||||
|
||||
Teamlinker provides a full experience of this package.Have a try!
|
||||
|
||||
## Installation
|
||||
```shell
|
||||
npm i tlmeetingserver
|
||||
```
|
||||
## Usage
|
||||
|
||||
TLMeetingServer is based on socket.io,you should build a socket.io connection from client and pass the io instance and meeting config to the TLMeetingServer construct function.
|
||||
|
||||
```typescript
|
||||
let objMeeting=new MeetingServer(io,meetingConfig as any)
|
||||
objMeeting.onJoinRoom=async (roomId,extraData, socketData, socketId) => {
|
||||
try {
|
||||
let objRoom=await MeetingRoomService.getItemById(roomId)
|
||||
if(!objRoom) {
|
||||
return {
|
||||
businessId:null,
|
||||
roomName:null,
|
||||
error:"room not found"
|
||||
}
|
||||
} else if(objRoom.getItem().password!==extraData){
|
||||
return {
|
||||
businessId:null,
|
||||
roomName:null,
|
||||
error:"password wrong"
|
||||
}
|
||||
} else if(objRoom.getItem().organization_id!==socketData.organizationId) {
|
||||
return {
|
||||
businessId:null,
|
||||
roomName:null,
|
||||
error:"access forbidden"
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
businessId:socketData.organizationUserId,
|
||||
roomName:objRoom.getItem().name
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
objMeeting.onJoinedRoom=async (roomId, businessId, socketId) => {
|
||||
try {
|
||||
let objRoom=await MeetingRoomService.getItemById(roomId)
|
||||
if(objRoom) {
|
||||
await objRoom.addMember(businessId)
|
||||
emit.in(businessId).socketsJoin(roomId)
|
||||
}
|
||||
} catch(err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
objMeeting.onLeavedRoom=async (type, roomId, businessId) => {
|
||||
try {
|
||||
let objRoom=await MeetingRoomService.getItemById(roomId)
|
||||
if(objRoom) {
|
||||
await objRoom.removeMember(businessId)
|
||||
emit.in(businessId).socketsLeave(roomId)
|
||||
}
|
||||
} catch(err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
objMeeting.onHandleOperation=async (type, roomId, fromBusinessId, toBusinessId, kind) => {
|
||||
try {
|
||||
let objRoom=await MeetingRoomService.getItemById(roomId)
|
||||
if(objRoom) {
|
||||
let ret=await objRoom.getPermission(fromBusinessId)
|
||||
if(ret===ECommon_Meeting_Room_Permission.PRESENTER) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} catch(err) {
|
||||
console.error(err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
objMeeting.onDeleteRoom=async roomId => {
|
||||
try {
|
||||
await rpcContentApi.clearByRefId(roomId)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
}
|
||||
objMeeting.onMessageSend=async (roomId, fromBusinessId, message) => {
|
||||
try {
|
||||
await rpcContentApi.add(roomId,ECommon_Model_Content_Type.MEETING_CHAT, fromBusinessId,message as string)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
}
|
||||
await objMeeting.start()
|
||||
```
|
||||
|
||||
### Meeting Config
|
||||
```typescript
|
||||
export default {
|
||||
"worker": {
|
||||
"logLevel": "warn",
|
||||
"logTags": [
|
||||
"info",
|
||||
"ice",
|
||||
"dtls",
|
||||
"rtp",
|
||||
"srtp",
|
||||
"rtcp"
|
||||
],
|
||||
"rtcMinPort": 40000,
|
||||
"rtcMaxPort": 49999
|
||||
},
|
||||
"codecs": [
|
||||
{
|
||||
"kind": "audio",
|
||||
"mimeType": "audio/opus",
|
||||
"clockRate": 48000,
|
||||
"channels": 2
|
||||
},
|
||||
{
|
||||
"kind": "video",
|
||||
"mimeType": "video/VP8",
|
||||
"clockRate": 90000,
|
||||
"parameters": {
|
||||
"x-google-start-bitrate": 1000
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "video",
|
||||
"mimeType": "video/VP9",
|
||||
"clockRate": 90000,
|
||||
"parameters": {
|
||||
"profile-id": 2,
|
||||
"x-google-start-bitrate": 1000
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "video",
|
||||
"mimeType": "video/h264",
|
||||
"clockRate": 90000,
|
||||
"parameters": {
|
||||
"packetization-mode": 1,
|
||||
"profile-level-id": "4d0032",
|
||||
"level-asymmetry-allowed": 1,
|
||||
"x-google-start-bitrate": 1000
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "video",
|
||||
"mimeType": "video/h264",
|
||||
"clockRate": 90000,
|
||||
"parameters": {
|
||||
"packetization-mode": 1,
|
||||
"profile-level-id": "42e01f",
|
||||
"level-asymmetry-allowed": 1,
|
||||
"x-google-start-bitrate": 1000
|
||||
}
|
||||
}
|
||||
],
|
||||
"webRtcTransport": {
|
||||
"listenIps": [
|
||||
{
|
||||
"ip": "0.0.0.0",
|
||||
"announcedIp": "192.168.110.6" //this ip should be the public ip
|
||||
}
|
||||
],
|
||||
"enableUdp": true,
|
||||
"enableTcp": true,
|
||||
"preferUdp": true,
|
||||
"enableSctp": false,
|
||||
"initialAvailableOutgoingBitrate": 1000000,
|
||||
"maxSctpMessageSize": 262144
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## About Teamlinker
|
||||
Teamlinker is a cooperation platform that integrates different kind of modules.You can contact your teammates,assign your tasks,start a meeting,schedule your events,manage your files and so on with Teamlinker.
|
@ -1,15 +1,13 @@
|
||||
{
|
||||
"name": "tlmeetingserver",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"description": "A simple video meeting library based on node.js and typescript",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Teamlinker/TLMeetingServer.git"
|
||||
},
|
||||
"repository": "https://github.com/Teamlinker/Teamlinker",
|
||||
"homepage": "https://github.com/Teamlinker/Teamlinker/blob/dev/code/server/common/meeting/npm/README.md",
|
||||
"keywords": ["teamlinker","meeting","video","nodejs","typescript","meeting server"],
|
||||
"author": "teamlinker",
|
||||
"license": "ISC",
|
||||
|
Loading…
Reference in New Issue
Block a user