Merge tag 'v2.5.1' into merge25-26-jun16
This commit is contained in:
commit
edeb70de0d
@ -20,7 +20,7 @@ trait CreateDefaultPublicGroupChat {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
|
||||
val envelope = BbbCoreEnvelope(GroupChatCreatedEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(GroupChatCreatedEvtMsg.NAME, meetingId, userId)
|
||||
val body = GroupChatCreatedEvtMsgBody(correlationId, gc.id, gc.createdBy, gc.name, gc.access, gc.users, msgs)
|
||||
val body = GroupChatCreatedEvtMsgBody(correlationId, gc.id, gc.createdBy, gc.access, gc.users, msgs)
|
||||
val event = GroupChatCreatedEvtMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ trait CreateGroupChatReqMsgHdlr extends SystemConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
val gc = GroupChatApp.createGroupChat(msg.body.name, msg.body.access, createdBy, users, msgs)
|
||||
val gc = GroupChatApp.createGroupChat(msg.body.access, createdBy, users, msgs)
|
||||
sendMessages(msg, gc, liveMeeting, bus)
|
||||
|
||||
val groupChats = state.groupChats.add(gc)
|
||||
@ -84,12 +84,12 @@ trait CreateGroupChatReqMsgHdlr extends SystemConfiguration {
|
||||
BbbCoreEnvelope(name, routing)
|
||||
}
|
||||
|
||||
def makeBody(chatId: String, name: String,
|
||||
def makeBody(chatId: String,
|
||||
access: String, correlationId: String,
|
||||
createdBy: GroupChatUser, users: Vector[GroupChatUser],
|
||||
msgs: Vector[GroupChatMsgToUser]): GroupChatCreatedEvtMsgBody = {
|
||||
GroupChatCreatedEvtMsgBody(correlationId, chatId, createdBy,
|
||||
name, access, users, msgs)
|
||||
access, users, msgs)
|
||||
}
|
||||
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
@ -102,7 +102,7 @@ trait CreateGroupChatReqMsgHdlr extends SystemConfiguration {
|
||||
val envelope = makeEnvelope(MessageTypes.DIRECT, GroupChatCreatedEvtMsg.NAME, meetingId, userId)
|
||||
val header = makeHeader(GroupChatCreatedEvtMsg.NAME, meetingId, userId)
|
||||
|
||||
val body = makeBody(gc.id, gc.name, gc.access, correlationId, gc.createdBy, users, msgs)
|
||||
val body = makeBody(gc.id, gc.access, correlationId, gc.createdBy, users, msgs)
|
||||
val event = GroupChatCreatedEvtMsg(header, body)
|
||||
val outEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(outEvent)
|
||||
@ -117,7 +117,7 @@ trait CreateGroupChatReqMsgHdlr extends SystemConfiguration {
|
||||
meetingId, userId)
|
||||
val header = makeHeader(GroupChatCreatedEvtMsg.NAME, meetingId, userId)
|
||||
|
||||
val body = makeBody(gc.id, gc.name, gc.access, correlationId, gc.createdBy, users, msgs)
|
||||
val body = makeBody(gc.id, gc.access, correlationId, gc.createdBy, users, msgs)
|
||||
val event = GroupChatCreatedEvtMsg(header, body)
|
||||
|
||||
val outEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
|
@ -27,8 +27,8 @@ trait GetGroupChatsReqMsgHdlr {
|
||||
|
||||
val publicChats = state.groupChats.findAllPublicChats()
|
||||
val privateChats = state.groupChats.findAllPrivateChatsForUser(msg.header.userId)
|
||||
val pubChats = publicChats map (pc => GroupChatInfo(pc.id, pc.name, pc.access, pc.createdBy, pc.users))
|
||||
val privChats = privateChats map (pc => GroupChatInfo(pc.id, pc.name, pc.access, pc.createdBy, pc.users))
|
||||
val pubChats = publicChats map (pc => GroupChatInfo(pc.id, pc.access, pc.createdBy, pc.users))
|
||||
val privChats = privateChats map (pc => GroupChatInfo(pc.id, pc.access, pc.createdBy, pc.users))
|
||||
|
||||
val allChats = pubChats ++ privChats
|
||||
|
||||
|
@ -9,10 +9,10 @@ object GroupChatApp {
|
||||
|
||||
val MAIN_PUBLIC_CHAT = "MAIN-PUBLIC-GROUP-CHAT"
|
||||
|
||||
def createGroupChat(chatName: String, access: String, createBy: GroupChatUser,
|
||||
def createGroupChat(access: String, createBy: GroupChatUser,
|
||||
users: Vector[GroupChatUser], msgs: Vector[GroupChatMessage]): GroupChat = {
|
||||
val gcId = GroupChatFactory.genId()
|
||||
GroupChatFactory.create(gcId, chatName, access, createBy, users, msgs)
|
||||
GroupChatFactory.create(gcId, access, createBy, users, msgs)
|
||||
}
|
||||
|
||||
def toGroupChatMessage(sender: GroupChatUser, msg: GroupChatMsgFromUser): GroupChatMessage = {
|
||||
@ -46,13 +46,15 @@ object GroupChatApp {
|
||||
|
||||
def createDefaultPublicGroupChat(): GroupChat = {
|
||||
val createBy = GroupChatUser(SystemUser.ID)
|
||||
GroupChatFactory.create(MAIN_PUBLIC_CHAT, MAIN_PUBLIC_CHAT, GroupChatAccess.PUBLIC, createBy, Vector.empty, Vector.empty)
|
||||
GroupChatFactory.create(MAIN_PUBLIC_CHAT, GroupChatAccess.PUBLIC, createBy, Vector.empty, Vector.empty)
|
||||
}
|
||||
|
||||
def createTestPublicGroupChat(state: MeetingState2x): MeetingState2x = {
|
||||
val createBy = GroupChatUser(SystemUser.ID)
|
||||
val defaultPubGroupChat = GroupChatFactory.create("TEST_GROUP_CHAT", "TEST_GROUP_CHAT",
|
||||
GroupChatAccess.PUBLIC, createBy, Vector.empty, Vector.empty)
|
||||
val defaultPubGroupChat = GroupChatFactory.create(
|
||||
"TEST_GROUP_CHAT",
|
||||
GroupChatAccess.PUBLIC, createBy, Vector.empty, Vector.empty
|
||||
)
|
||||
val groupChats = state.groupChats.add(defaultPubGroupChat)
|
||||
state.update(groupChats)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.bigbluebutton.core.apps.groupchats
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.PermissionCheck
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting }
|
||||
@ -43,17 +44,27 @@ trait SendGroupChatMessageMsgHdlr extends HandlerHelpers {
|
||||
sender <- GroupChatApp.findGroupChatUser(msg.header.userId, liveMeeting.users2x)
|
||||
chat <- state.groupChats.find(msg.body.chatId)
|
||||
} yield {
|
||||
val gcm = GroupChatApp.toGroupChatMessage(sender, msg.body.msg)
|
||||
val gcs = GroupChatApp.addGroupChatMessage(chat, state.groupChats, gcm)
|
||||
val chatIsPrivate = chat.access == GroupChatAccess.PRIVATE;
|
||||
val userIsAParticipant = chat.users.filter(u => u.id == sender.id).length > 0;
|
||||
|
||||
val event = buildGroupChatMessageBroadcastEvtMsg(
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
msg.header.userId, msg.body.chatId, gcm
|
||||
)
|
||||
if ((chatIsPrivate && userIsAParticipant) || !chatIsPrivate) {
|
||||
val gcm = GroupChatApp.toGroupChatMessage(sender, msg.body.msg)
|
||||
val gcs = GroupChatApp.addGroupChatMessage(chat, state.groupChats, gcm)
|
||||
|
||||
bus.outGW.send(event)
|
||||
val event = buildGroupChatMessageBroadcastEvtMsg(
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
msg.header.userId, msg.body.chatId, gcm
|
||||
)
|
||||
|
||||
bus.outGW.send(event)
|
||||
|
||||
state.update(gcs)
|
||||
} else {
|
||||
val reason = "User isn't a participant of the chat"
|
||||
PermissionCheck.ejectUserForFailedPermission(msg.header.meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
state
|
||||
}
|
||||
|
||||
state.update(gcs)
|
||||
}
|
||||
|
||||
newState match {
|
||||
|
@ -41,7 +41,7 @@ trait SyncGetGroupChatsInfoMsgHdlr {
|
||||
val respMsg = buildSyncGetGroupChatMsgsRespMsg(msgs, pc.id)
|
||||
bus.outGW.send(respMsg)
|
||||
|
||||
GroupChatInfo(pc.id, pc.name, pc.access, pc.createdBy, pc.users)
|
||||
GroupChatInfo(pc.id, pc.access, pc.createdBy, pc.users)
|
||||
})
|
||||
|
||||
// publishing a message with the group chat info
|
||||
|
@ -51,7 +51,7 @@ trait EjectUserFromMeetingCmdMsgHdlr extends RightsManagementTrait {
|
||||
breakoutModel <- state.breakout
|
||||
} yield {
|
||||
breakoutModel.rooms.values.foreach { room =>
|
||||
room.users.filter(u => u.id == ru.externId + "-" + room.sequence).foreach(user => {
|
||||
room.users.filter(u => u.id == ru.id + "-" + room.sequence).foreach(user => {
|
||||
eventBus.publish(BigBlueButtonEvent(room.id, EjectUserFromBreakoutInternalMsg(meetingId, room.id, user.id, ejectedBy, reason, EjectReasonCode.EJECT_USER, ban)))
|
||||
})
|
||||
}
|
||||
|
@ -5,9 +5,9 @@ import org.bigbluebutton.core.util.RandomStringGenerator
|
||||
|
||||
object GroupChatFactory {
|
||||
def genId(): String = System.currentTimeMillis() + "-" + RandomStringGenerator.randomAlphanumericString(8)
|
||||
def create(id: String, name: String, access: String, createdBy: GroupChatUser,
|
||||
def create(id: String, access: String, createdBy: GroupChatUser,
|
||||
users: Vector[GroupChatUser], msgs: Vector[GroupChatMessage]): GroupChat = {
|
||||
new GroupChat(id, name, access, createdBy, users, msgs)
|
||||
new GroupChat(id, access, createdBy, users, msgs)
|
||||
}
|
||||
|
||||
}
|
||||
@ -23,7 +23,7 @@ case class GroupChats(chats: collection.immutable.Map[String, GroupChat]) {
|
||||
def getAllGroupChatsInMeeting(): Vector[GroupChat] = chats.values.toVector
|
||||
}
|
||||
|
||||
case class GroupChat(id: String, name: String, access: String, createdBy: GroupChatUser,
|
||||
case class GroupChat(id: String, access: String, createdBy: GroupChatUser,
|
||||
users: Vector[GroupChatUser],
|
||||
msgs: Vector[GroupChatMessage]) {
|
||||
def findMsgWithId(id: String): Option[GroupChatMessage] = msgs.find(m => m.id == id)
|
||||
|
@ -7,10 +7,9 @@ class GroupsChatTests extends UnitSpec {
|
||||
|
||||
"A GroupChat" should "be able to add and remove user" in {
|
||||
val gcId = "gc-id"
|
||||
val chatName = "Public"
|
||||
val userId = "uid-1"
|
||||
val createBy = GroupChatUser("groupId")
|
||||
val gc = GroupChatFactory.create(gcId, chatName, GroupChatAccess.PUBLIC, createBy, Vector.empty, Vector.empty)
|
||||
val gc = GroupChatFactory.create(gcId, GroupChatAccess.PUBLIC, createBy, Vector.empty, Vector.empty)
|
||||
val user = GroupChatUser(userId)
|
||||
val gc2 = gc.add(user)
|
||||
assert(gc2.users.size == 1)
|
||||
@ -26,8 +25,7 @@ class GroupsChatTests extends UnitSpec {
|
||||
"A GroupChat" should "be able to add, update, and remove msg" in {
|
||||
val createBy = GroupChatUser("groupId")
|
||||
val gcId = "gc-id"
|
||||
val chatName = "Public"
|
||||
val gc = GroupChatFactory.create(gcId, chatName, GroupChatAccess.PUBLIC, createBy, Vector.empty, Vector.empty)
|
||||
val gc = GroupChatFactory.create(gcId, GroupChatAccess.PUBLIC, createBy, Vector.empty, Vector.empty)
|
||||
val msgId1 = "msgid-1"
|
||||
val ts = System.currentTimeMillis()
|
||||
val hello = "Hello World!"
|
||||
|
@ -18,7 +18,7 @@ val compileSettings = Seq(
|
||||
"-Xlint",
|
||||
"-Ywarn-dead-code",
|
||||
"-language:_",
|
||||
"-target:jvm-1.8",
|
||||
"-target:jvm-1.11",
|
||||
"-encoding", "UTF-8"
|
||||
),
|
||||
javacOptions ++= List(
|
||||
|
@ -8,7 +8,7 @@ object GroupChatAccess {
|
||||
case class GroupChatUser(id: String, name: String = "", role: String = "VIEWER")
|
||||
case class GroupChatMsgFromUser(correlationId: String, sender: GroupChatUser, chatEmphasizedText: Boolean = false, message: String)
|
||||
case class GroupChatMsgToUser(id: String, timestamp: Long, correlationId: String, sender: GroupChatUser, chatEmphasizedText: Boolean = false, message: String)
|
||||
case class GroupChatInfo(id: String, name: String, access: String, createdBy: GroupChatUser, users: Vector[GroupChatUser])
|
||||
case class GroupChatInfo(id: String, access: String, createdBy: GroupChatUser, users: Vector[GroupChatUser])
|
||||
|
||||
object OpenGroupChatWindowReqMsg { val NAME = "OpenGroupChatWindowReqMsg" }
|
||||
case class OpenGroupChatWindowReqMsg(header: BbbClientMsgHeader, body: OpenGroupChatWindowReqMsgBody) extends StandardMsg
|
||||
@ -36,14 +36,14 @@ case class GetGroupChatMsgsRespMsgBody(chatId: String, msgs: Vector[GroupChatMsg
|
||||
|
||||
object CreateGroupChatReqMsg { val NAME = "CreateGroupChatReqMsg" }
|
||||
case class CreateGroupChatReqMsg(header: BbbClientMsgHeader, body: CreateGroupChatReqMsgBody) extends StandardMsg
|
||||
case class CreateGroupChatReqMsgBody(correlationId: String, name: String, access: String,
|
||||
case class CreateGroupChatReqMsgBody(correlationId: String, access: String,
|
||||
users: Vector[String], msg: Vector[GroupChatMsgFromUser])
|
||||
|
||||
object GroupChatCreatedEvtMsg { val NAME = "GroupChatCreatedEvtMsg" }
|
||||
case class GroupChatCreatedEvtMsg(header: BbbClientMsgHeader, body: GroupChatCreatedEvtMsgBody) extends BbbCoreMsg
|
||||
case class GroupChatCreatedEvtMsgBody(correlationId: String, chatId: String, createdBy: GroupChatUser,
|
||||
name: String, access: String,
|
||||
users: Vector[GroupChatUser], msg: Vector[GroupChatMsgToUser])
|
||||
access: String,
|
||||
users: Vector[GroupChatUser], msg: Vector[GroupChatMsgToUser])
|
||||
|
||||
object DestroyGroupChatReqMsg { val NAME = "DestroyGroupChatReqMsg" }
|
||||
case class DestroyGroupChatReqMsg(header: BbbClientMsgHeader, body: DestroyGroupChatReqMsgBody) extends StandardMsg
|
||||
|
40
bbb-learning-dashboard/package-lock.json
generated
40
bbb-learning-dashboard/package-lock.json
generated
@ -6251,11 +6251,11 @@
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"node_modules/ejs": {
|
||||
"version": "3.1.6",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz",
|
||||
"integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==",
|
||||
"version": "3.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz",
|
||||
"integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==",
|
||||
"dependencies": {
|
||||
"jake": "^10.6.1"
|
||||
"jake": "^10.8.5"
|
||||
},
|
||||
"bin": {
|
||||
"ejs": "bin/cli.js"
|
||||
@ -21236,9 +21236,9 @@
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"ejs": {
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.7.tgz",
|
||||
"integrity": "sha512-BIar7R6abbUxDA3bfXrO4DSgwo8I+fB5/1zgujl3HLLjwd6+9iOnrT+t3grn2qbk9vOgBubXOFwX2m9axoFaGw==",
|
||||
"version": "3.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz",
|
||||
"integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==",
|
||||
"requires": {
|
||||
"jake": "^10.8.5"
|
||||
}
|
||||
@ -22007,29 +22007,11 @@
|
||||
}
|
||||
},
|
||||
"filelist": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.3.tgz",
|
||||
"integrity": "sha512-LwjCsruLWQULGYKy7TX0OPtrL9kLpojOFKc5VCTxdFTV7w5zbsgqVKfnkKG7Qgjtq50gKfO56hJv88OfcGb70Q==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz",
|
||||
"integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==",
|
||||
"requires": {
|
||||
"minimatch": "^5.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
|
||||
"integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
|
||||
"requires": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
}
|
||||
}
|
||||
"minimatch": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"filesize": {
|
||||
|
@ -1 +1 @@
|
||||
git clone --branch v1.2.0 --depth 1 https://github.com/bigbluebutton/bbb-pads bbb-pads
|
||||
git clone --branch v1.2.1 --depth 1 https://github.com/bigbluebutton/bbb-pads bbb-pads
|
||||
|
@ -1 +1 @@
|
||||
git clone --branch v2.3.0 --depth 1 https://github.com/bigbluebutton/bbb-webhooks bbb-webhooks
|
||||
git clone --branch v2.6.0 --depth 1 https://github.com/bigbluebutton/bbb-webhooks bbb-webhooks
|
||||
|
@ -22,7 +22,6 @@ export default function createGroupChat(receiver) {
|
||||
msg: [],
|
||||
users: [receiver.userId],
|
||||
access: CHAT_ACCESS_PRIVATE,
|
||||
name: receiver.name,
|
||||
};
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
|
@ -9,7 +9,6 @@ export default function addGroupChat(meetingId, chat) {
|
||||
id: Match.Maybe(String),
|
||||
chatId: Match.Maybe(String),
|
||||
correlationId: Match.Maybe(String),
|
||||
name: String,
|
||||
access: String,
|
||||
createdBy: Object,
|
||||
users: Array,
|
||||
@ -19,9 +18,8 @@ export default function addGroupChat(meetingId, chat) {
|
||||
const chatDocument = {
|
||||
meetingId,
|
||||
chatId: chat.chatId || chat.id,
|
||||
name: chat.name,
|
||||
access: chat.access,
|
||||
users: chat.users.map(u => u.id),
|
||||
users: chat.users.map((u) => u.id),
|
||||
participants: chat.users,
|
||||
createdBy: chat.createdBy.id,
|
||||
};
|
||||
@ -39,9 +37,9 @@ export default function addGroupChat(meetingId, chat) {
|
||||
const { insertedId } = GroupChat.upsert(selector, modifier);
|
||||
|
||||
if (insertedId) {
|
||||
Logger.info(`Added group-chat name=${chat.name} meetingId=${meetingId}`);
|
||||
Logger.info(`Added group-chat chatId=${chatDocument.chatId} meetingId=${meetingId}`);
|
||||
} else {
|
||||
Logger.info(`Upserted group-chat name=${chat.name} meetingId=${meetingId}`);
|
||||
Logger.info(`Upserted group-chat chatId=${chatDocument.chatId} meetingId=${meetingId}`);
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(`Adding group-chat to collection: ${err}`);
|
||||
|
@ -4,6 +4,7 @@ import Users from '/imports/api/users';
|
||||
import userJoin from './userJoin';
|
||||
import pendingAuthenticationsStore from '../store/pendingAuthentications';
|
||||
import createDummyUser from '../modifiers/createDummyUser';
|
||||
import updateUserConnectionId from '../modifiers/updateUserConnectionId';
|
||||
import ClientConnections from '/imports/startup/server/ClientConnections';
|
||||
|
||||
import upsertValidationState from '/imports/api/auth-token-validation/server/modifiers/upsertValidationState';
|
||||
@ -81,6 +82,8 @@ export default function handleValidateAuthToken({ body }, meetingId) {
|
||||
|
||||
if (!User) {
|
||||
createDummyUser(meetingId, userId, authToken);
|
||||
}else{
|
||||
updateUserConnectionId(meetingId, userId, methodInvocationObject.connection.id);
|
||||
}
|
||||
|
||||
ClientConnections.add(sessionId, methodInvocationObject.connection);
|
||||
|
@ -0,0 +1,32 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
export default function updateUserConnectionId(meetingId, userId, connectionId) {
|
||||
check(meetingId, String);
|
||||
check(userId, String);
|
||||
check(connectionId, String);
|
||||
|
||||
const selector = { meetingId, userId };
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
currentConnectionId: connectionId,
|
||||
connectionIdUpdateTime: new Date().getTime(),
|
||||
},
|
||||
};
|
||||
|
||||
const User = Users.findOne(selector);
|
||||
|
||||
if (User) {
|
||||
try {
|
||||
const updated = Users.update(selector, modifier);
|
||||
|
||||
if (updated) {
|
||||
Logger.info(`Updated connection user=${userId} connectionid=${connectionId} meeting=${meetingId}`);
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(`Updating user connection: ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ export default function handleVoiceUpdate({ body }, meetingId) {
|
||||
const isDialInUser = (userId, meetingID) => !!Users.findOne({ meetingId: meetingID, userId, clientType: 'dial-in-user' });
|
||||
|
||||
// if the user is dial-in, leaving voice also means leaving userlist
|
||||
if (isDialInUser(voiceUserId, meetingId)) removeUser(meetingId, intId);
|
||||
if (isDialInUser(voiceUserId, meetingId)) removeUser(voiceUser, meetingId);
|
||||
|
||||
return removeVoiceUser(meetingId, voiceUser);
|
||||
}
|
||||
|
@ -353,6 +353,8 @@ export default withTracker(() => {
|
||||
userId: 1,
|
||||
inactivityCheck: 1,
|
||||
responseDelay: 1,
|
||||
currentConnectionId: 1,
|
||||
connectionIdUpdateTime: 1,
|
||||
};
|
||||
const User = Users.findOne({ intId: credentials.requesterUserId }, { fields });
|
||||
const meeting = Meetings.findOne({ meetingId }, {
|
||||
@ -371,6 +373,14 @@ export default withTracker(() => {
|
||||
const ejected = User?.ejected;
|
||||
const ejectedReason = User?.ejectedReason;
|
||||
const meetingEndedReason = meeting?.meetingEndedReason;
|
||||
const currentConnectionId = User?.currentConnectionId;
|
||||
const { connectionID, connectionAuthTime } = Auth;
|
||||
const connectionIdUpdateTime = User?.connectionIdUpdateTime;
|
||||
|
||||
if (currentConnectionId && currentConnectionId !== connectionID && connectionIdUpdateTime > connectionAuthTime) {
|
||||
Session.set('codeError', 403);
|
||||
Session.set('errorMessageDescription', 'joined_another_window_reason')
|
||||
}
|
||||
|
||||
let userSubscriptionHandler;
|
||||
|
||||
|
@ -284,6 +284,7 @@ class ActionsDropdown extends PureComponent {
|
||||
isMeteorConnected,
|
||||
isDropdownOpen,
|
||||
isMobile,
|
||||
isRTL,
|
||||
} = this.props;
|
||||
|
||||
const availableActions = this.getAvailableActions();
|
||||
@ -296,7 +297,7 @@ class ActionsDropdown extends PureComponent {
|
||||
|| !isMeteorConnected) {
|
||||
return null;
|
||||
}
|
||||
const customStyles = { top: '-3rem' };
|
||||
const customStyles = { top: '-1rem' };
|
||||
|
||||
return (
|
||||
<BBBMenu
|
||||
@ -324,8 +325,8 @@ class ActionsDropdown extends PureComponent {
|
||||
elevation: 3,
|
||||
getContentAnchorEl: null,
|
||||
fullwidth: "true",
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'left' },
|
||||
transformorigin: { vertical: 'top', horizontal: 'left' },
|
||||
anchorOrigin: { vertical: 'top', horizontal: isRTL ? 'right' : 'left' },
|
||||
transformOrigin: { vertical: 'bottom', horizontal: isRTL ? 'right' : 'left' },
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -4,7 +4,7 @@ import Presentations from '/imports/api/presentations';
|
||||
import PresentationUploaderService from '/imports/ui/components/presentation/presentation-uploader/service';
|
||||
import PresentationPodService from '/imports/ui/components/presentation-pod/service';
|
||||
import ActionsDropdown from './component';
|
||||
import { layoutSelectInput, layoutDispatch } from '../../layout/context';
|
||||
import { layoutSelectInput, layoutDispatch, layoutSelect } from '../../layout/context';
|
||||
import getFromUserSettings from '/imports/ui/services/users-settings';
|
||||
import { SMALL_VIEWPORT_BREAKPOINT } from '../../layout/enums';
|
||||
|
||||
@ -14,6 +14,7 @@ const ActionsDropdownContainer = (props) => {
|
||||
const { width: browserWidth } = layoutSelectInput((i) => i.browser);
|
||||
const isMobile = browserWidth <= SMALL_VIEWPORT_BREAKPOINT;
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
const isRTL = layoutSelect((i) => i.isRTL);
|
||||
|
||||
return (
|
||||
<ActionsDropdown {...{
|
||||
@ -21,6 +22,7 @@ const ActionsDropdownContainer = (props) => {
|
||||
sidebarContent,
|
||||
sidebarNavigation,
|
||||
isMobile,
|
||||
isRTL,
|
||||
...props,
|
||||
}}
|
||||
/>
|
||||
|
@ -23,6 +23,7 @@ import ManyWebcamsNotifier from '/imports/ui/components/video-provider/many-user
|
||||
import UploaderContainer from '/imports/ui/components/presentation/presentation-uploader/container';
|
||||
import CaptionsSpeechContainer from '/imports/ui/components/captions/speech/container';
|
||||
import RandomUserSelectContainer from '/imports/ui/components/common/modal/random-user/container';
|
||||
import ScreenReaderAlertContainer from '../screenreader-alert/container';
|
||||
import NewWebcamContainer from '../webcam/container';
|
||||
import PresentationAreaContainer from '../presentation/presentation-area/container';
|
||||
import ScreenshareContainer from '../screenshare/container';
|
||||
@ -609,6 +610,7 @@ class App extends Component {
|
||||
>
|
||||
{this.renderActivityCheck()}
|
||||
{this.renderUserInformation()}
|
||||
<ScreenReaderAlertContainer />
|
||||
<BannerBarContainer />
|
||||
<NotificationsBarContainer />
|
||||
<SidebarNavigationContainer />
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
setUserSelectedMicrophone,
|
||||
setUserSelectedListenOnly,
|
||||
} from '../audio-modal/service';
|
||||
import { layoutSelect } from '/imports/ui/components/layout/context';
|
||||
|
||||
import Service from '../service';
|
||||
import AppService from '/imports/ui/components/app/service';
|
||||
@ -25,7 +26,8 @@ const AudioControlsContainer = (props) => {
|
||||
const {
|
||||
users, lockSettings, userLocks, children, ...newProps
|
||||
} = props;
|
||||
return <AudioControls {...newProps} />;
|
||||
const isRTL = layoutSelect((i) => i.isRTL);
|
||||
return <AudioControls {...{ ...newProps, isRTL }} />;
|
||||
};
|
||||
|
||||
const handleLeaveAudio = () => {
|
||||
|
@ -346,6 +346,7 @@ class InputStreamLiveSelector extends Component {
|
||||
currentInputDeviceId,
|
||||
currentOutputDeviceId,
|
||||
isListenOnly,
|
||||
isRTL,
|
||||
} = this.props;
|
||||
|
||||
const inputDeviceList = !isListenOnly
|
||||
@ -389,10 +390,21 @@ class InputStreamLiveSelector extends Component {
|
||||
label={intl.formatMessage(intlMessages.changeAudioDevice)}
|
||||
hideLabel
|
||||
tabIndex={0}
|
||||
rotate
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
actions={dropdownListComplete}
|
||||
opts={{
|
||||
id: 'default-dropdown-menu',
|
||||
keepMounted: true,
|
||||
transitionDuration: 0,
|
||||
elevation: 3,
|
||||
getContentAnchorEl: null,
|
||||
fullwidth: 'true',
|
||||
anchorOrigin: { vertical: 'top', horizontal: isRTL ? 'left' : 'right' },
|
||||
transformOrigin: { vertical: 'bottom', horizontal: isRTL ? 'right' : 'left' },
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
} from '/imports/ui/stylesheets/styled-components/palette';
|
||||
import {
|
||||
mdPaddingY,
|
||||
btnSpacing,
|
||||
} from '/imports/ui/stylesheets/styled-components/general';
|
||||
import { lineHeightComputed } from '/imports/ui/stylesheets/styled-components/typography';
|
||||
|
||||
@ -48,6 +49,8 @@ const AudioModalButton = styled(Button)`
|
||||
color: black;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
margin-top: ${btnSpacing};
|
||||
line-height: 1.5;
|
||||
}
|
||||
`;
|
||||
|
||||
@ -115,6 +118,7 @@ const Title = styled.h2`
|
||||
font-size: 1.3rem;
|
||||
color: ${colorGrayDark};
|
||||
white-space: normal;
|
||||
margin: 0;
|
||||
|
||||
@media ${smallOnly} {
|
||||
font-size: 1rem;
|
||||
|
@ -2,8 +2,8 @@ import React, { PureComponent } from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { withModalMounter } from '/imports/ui/components/common/modal/service';
|
||||
import BBBMenu from "/imports/ui/components/common/menu/component";
|
||||
import Button from '/imports/ui/components/common/button/component';
|
||||
import CreateBreakoutRoomModal from '/imports/ui/components/actions-bar/create-breakout-room/container';
|
||||
import Trigger from "/imports/ui/components/common/control-header/right/component";
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
options: {
|
||||
@ -86,20 +86,16 @@ class BreakoutDropdown extends PureComponent {
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
isRTL,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<BBBMenu
|
||||
trigger={
|
||||
<Button
|
||||
<Trigger
|
||||
data-test="breakoutOptionsMenu"
|
||||
icon="more"
|
||||
size="sm"
|
||||
ghost
|
||||
circle
|
||||
hideLabel
|
||||
color="dark"
|
||||
label={intl.formatMessage(intlMessages.options)}
|
||||
aria-label={intl.formatMessage(intlMessages.options)}
|
||||
onClick={() => null}
|
||||
@ -112,8 +108,8 @@ class BreakoutDropdown extends PureComponent {
|
||||
elevation: 3,
|
||||
getContentAnchorEl: null,
|
||||
fullwidth: "true",
|
||||
anchorOrigin: { vertical: 'bottom', horizontal: 'left' },
|
||||
transformorigin: { vertical: 'bottom', horizontal: 'left' },
|
||||
anchorOrigin: { vertical: 'bottom', horizontal: isRTL ? 'right' : 'left' },
|
||||
transformOrigin: { vertical: 'top', horizontal: isRTL ? 'right' : 'left' },
|
||||
}}
|
||||
actions={this.getAvailableActions()}
|
||||
/>
|
||||
|
@ -15,6 +15,7 @@ import Settings from '/imports/ui/services/settings';
|
||||
import BreakoutDropdown from '/imports/ui/components/breakout-room/breakout-dropdown/component';
|
||||
import Users from '/imports/api/users';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import Header from '/imports/ui/components/common/control-header/component';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
breakoutTitle: {
|
||||
@ -549,19 +550,19 @@ class BreakoutRoom extends PureComponent {
|
||||
intl,
|
||||
endAllBreakouts,
|
||||
amIModerator,
|
||||
isRTL,
|
||||
} = this.props;
|
||||
return (
|
||||
<Styled.Panel ref={(n) => this.panel = n}>
|
||||
<Styled.Header>
|
||||
<Styled.HeaderButton
|
||||
icon="left_arrow"
|
||||
label={intl.formatMessage(intlMessages.breakoutTitle)}
|
||||
aria-label={intl.formatMessage(intlMessages.breakoutAriaTitle)}
|
||||
onClick={() => {
|
||||
<Header
|
||||
leftButtonProps={{
|
||||
'aria-label': intl.formatMessage(intlMessages.breakoutAriaTitle),
|
||||
label: intl.formatMessage(intlMessages.breakoutTitle),
|
||||
onClick: () => {
|
||||
this.closePanel();
|
||||
}}
|
||||
/>
|
||||
{ amIModerator && (
|
||||
},
|
||||
}}
|
||||
customRightButton={amIModerator && (
|
||||
<BreakoutDropdown
|
||||
openBreakoutTimeManager={this.showSetTimeForm}
|
||||
endAllBreakouts={() => {
|
||||
@ -570,9 +571,10 @@ class BreakoutRoom extends PureComponent {
|
||||
}}
|
||||
isMeteorConnected={isMeteorConnected}
|
||||
amIModerator={amIModerator}
|
||||
isRTL={isRTL}
|
||||
/>
|
||||
) }
|
||||
</Styled.Header>
|
||||
)}
|
||||
/>
|
||||
{this.renderDuration()}
|
||||
{amIModerator
|
||||
? (
|
||||
|
@ -4,7 +4,7 @@ import AudioService from '/imports/ui/components/audio/service';
|
||||
import AudioManager from '/imports/ui/services/audio-manager';
|
||||
import BreakoutComponent from './component';
|
||||
import Service from './service';
|
||||
import { layoutDispatch } from '../layout/context';
|
||||
import { layoutDispatch, layoutSelect } from '../layout/context';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import { UsersContext } from '/imports/ui/components/components-data/users-context/context';
|
||||
import {
|
||||
@ -18,10 +18,11 @@ const BreakoutContainer = (props) => {
|
||||
const usingUsersContext = useContext(UsersContext);
|
||||
const { users } = usingUsersContext;
|
||||
const amIPresenter = users[Auth.meetingID][Auth.userID].presenter;
|
||||
const isRTL = layoutSelect((i) => i.isRTL);
|
||||
|
||||
return <BreakoutComponent
|
||||
amIPresenter={amIPresenter}
|
||||
{...{ layoutContextDispatch, ...props }}
|
||||
{...{ layoutContextDispatch, isRTL, ...props }}
|
||||
/>;
|
||||
};
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { defineMessages, injectIntl } from 'react-intl';
|
||||
import deviceInfo from '/imports/utils/deviceInfo';
|
||||
import PropTypes from 'prop-types';
|
||||
import Styled from './styles';
|
||||
import { escapeHtml } from '/imports/utils/string-utils';
|
||||
import { isChatEnabled } from '/imports/ui/services/features';
|
||||
|
||||
const propTypes = {
|
||||
@ -140,7 +141,7 @@ class MessageForm extends PureComponent {
|
||||
handleSendMessage,
|
||||
} = this.props;
|
||||
const { message } = this.state;
|
||||
let msg = message.trim();
|
||||
const msg = message.trim();
|
||||
|
||||
if (msg.length < minMessageLength) return;
|
||||
|
||||
@ -150,13 +151,7 @@ class MessageForm extends PureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanitize. See: http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript/
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.appendChild(document.createTextNode(msg));
|
||||
msg = div.innerHTML;
|
||||
|
||||
handleSendMessage(msg);
|
||||
handleSendMessage(escapeHtml(msg));
|
||||
this.setState({ message: '', hasErrors: false });
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
colorPrimary,
|
||||
colorGray,
|
||||
colorDanger,
|
||||
colorGrayDark,
|
||||
userListBg,
|
||||
colorWhite,
|
||||
colorGrayLighter,
|
||||
@ -228,33 +227,6 @@ const Panel = styled(ScrollboxVertical)`
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const HeaderButton = styled(Button)`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
padding-left: 0;
|
||||
padding-right: inherit;
|
||||
background: none !important;
|
||||
|
||||
[dir="rtl"] & {
|
||||
margin: 0 0 2rem auto;
|
||||
padding-left: inherit;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
& > i {
|
||||
color: ${colorGrayDark};
|
||||
|
||||
[dir="rtl"] & {
|
||||
-webkit-transform: scale(-1, 1);
|
||||
-moz-transform: scale(-1, 1);
|
||||
-ms-transform: scale(-1, 1);
|
||||
-o-transform: scale(-1, 1);
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
}`;
|
||||
|
||||
const Separator = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
@ -264,13 +236,6 @@ const Separator = styled.div`
|
||||
margin: 30px 0px;
|
||||
`;
|
||||
|
||||
const Header = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: ${jumboPaddingY};
|
||||
`;
|
||||
|
||||
const FlexRow = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
@ -296,8 +261,6 @@ export default {
|
||||
EndButton,
|
||||
Duration,
|
||||
Panel,
|
||||
HeaderButton,
|
||||
Separator,
|
||||
Header,
|
||||
FlexRow,
|
||||
};
|
||||
|
@ -8,6 +8,7 @@ import Service from '/imports/ui/components/captions/service';
|
||||
import Styled from './styles';
|
||||
import { PANELS, ACTIONS } from '/imports/ui/components/layout/enums';
|
||||
import browserInfo from '/imports/utils/browserInfo';
|
||||
import Header from '/imports/ui/components/common/control-header/component';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
hide: {
|
||||
@ -71,54 +72,50 @@ const Captions = ({
|
||||
|
||||
return (
|
||||
<Styled.Captions isChrome={isChrome}>
|
||||
<Styled.Header>
|
||||
<Styled.Title>
|
||||
<Styled.HideBtn
|
||||
onClick={() => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||
value: false,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.NONE,
|
||||
});
|
||||
}}
|
||||
aria-label={intl.formatMessage(intlMessages.hide)}
|
||||
label={name}
|
||||
icon={isRTL ? 'right_arrow' : 'left_arrow'}
|
||||
/>
|
||||
</Styled.Title>
|
||||
{Service.amICaptionsOwner(ownerId)
|
||||
? (
|
||||
<span>
|
||||
<Button
|
||||
onClick={dictating
|
||||
? () => Service.stopDictation(locale)
|
||||
: () => Service.startDictation(locale)}
|
||||
label={dictating
|
||||
? intl.formatMessage(intlMessages.dictationStop)
|
||||
: intl.formatMessage(intlMessages.dictationStart)}
|
||||
aria-describedby="dictationBtnDesc"
|
||||
color={dictating ? 'danger' : 'primary'}
|
||||
disabled={!dictation}
|
||||
/>
|
||||
<div id="dictationBtnDesc" hidden>
|
||||
{dictating
|
||||
? intl.formatMessage(intlMessages.dictationOffDesc)
|
||||
: intl.formatMessage(intlMessages.dictationOnDesc)}
|
||||
</div>
|
||||
</span>
|
||||
) : (
|
||||
<Header
|
||||
leftButtonProps={{
|
||||
onClick: () => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||
value: false,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.NONE,
|
||||
});
|
||||
},
|
||||
'aria-label': intl.formatMessage(intlMessages.hide),
|
||||
label: name,
|
||||
}}
|
||||
customRightButton={Service.amICaptionsOwner(ownerId) ? (
|
||||
<span>
|
||||
<Button
|
||||
color="primary"
|
||||
tooltipLabel={intl.formatMessage(intlMessages.takeOwnershipTooltip, { 0: name })}
|
||||
onClick={() => Service.updateCaptionsOwner(locale, name)}
|
||||
aria-label={intl.formatMessage(intlMessages.takeOwnership)}
|
||||
label={intl.formatMessage(intlMessages.takeOwnership)}
|
||||
onClick={dictating
|
||||
? () => Service.stopDictation(locale)
|
||||
: () => Service.startDictation(locale)}
|
||||
label={dictating
|
||||
? intl.formatMessage(intlMessages.dictationStop)
|
||||
: intl.formatMessage(intlMessages.dictationStart)}
|
||||
aria-describedby="dictationBtnDesc"
|
||||
color={dictating ? 'danger' : 'primary'}
|
||||
disabled={!dictation}
|
||||
/>
|
||||
)}
|
||||
</Styled.Header>
|
||||
<div id="dictationBtnDesc" hidden>
|
||||
{dictating
|
||||
? intl.formatMessage(intlMessages.dictationOffDesc)
|
||||
: intl.formatMessage(intlMessages.dictationOnDesc)}
|
||||
</div>
|
||||
</span>
|
||||
) : (
|
||||
<Button
|
||||
color="primary"
|
||||
tooltipLabel={intl.formatMessage(intlMessages.takeOwnershipTooltip, { 0: name })}
|
||||
onClick={() => Service.updateCaptionsOwner(locale, name)}
|
||||
aria-label={intl.formatMessage(intlMessages.takeOwnership)}
|
||||
label={intl.formatMessage(intlMessages.takeOwnership)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<PadContainer
|
||||
externalId={locale}
|
||||
hasPermission={hasPermission}
|
||||
|
@ -1,23 +1,14 @@
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
colorWhite,
|
||||
colorGrayDark,
|
||||
} from '/imports/ui/stylesheets/styled-components/palette';
|
||||
import { colorWhite } from '/imports/ui/stylesheets/styled-components/palette';
|
||||
import {
|
||||
mdPaddingX,
|
||||
mdPaddingY,
|
||||
pollHeaderOffset,
|
||||
toastContentWidth,
|
||||
borderSize,
|
||||
borderSizeLarge,
|
||||
} from '/imports/ui/stylesheets/styled-components/general';
|
||||
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
|
||||
import { DivElipsis } from '/imports/ui/stylesheets/styled-components/placeholders';
|
||||
import Button from '/imports/ui/components/common/button/component';
|
||||
|
||||
const Captions = styled.div`
|
||||
background-color: ${colorWhite};
|
||||
padding: ${mdPaddingX} ${mdPaddingY} ${mdPaddingX} ${mdPaddingX};
|
||||
padding: ${mdPaddingY} ${mdPaddingY} ${mdPaddingX} ${mdPaddingX};
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
@ -33,58 +24,4 @@ const Captions = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const Header = styled.header`
|
||||
position: relative;
|
||||
top: ${pollHeaderOffset};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const Title = styled(DivElipsis)`
|
||||
flex: 1;
|
||||
|
||||
& > button, & > button:hover {
|
||||
max-width: ${toastContentWidth};
|
||||
}
|
||||
`;
|
||||
|
||||
const HideBtn = styled(Button)`
|
||||
position: relative;
|
||||
background-color: ${colorWhite};
|
||||
display: block;
|
||||
margin: ${borderSizeLarge};
|
||||
margin-bottom: ${borderSize};
|
||||
padding-left: 0;
|
||||
padding-right: inherit;
|
||||
|
||||
[dir="rtl"] & {
|
||||
padding-left: inherit;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
& > i {
|
||||
color: ${colorGrayDark};
|
||||
font-size: smaller;
|
||||
|
||||
[dir="rtl"] & {
|
||||
-webkit-transform: scale(-1, 1);
|
||||
-moz-transform: scale(-1, 1);
|
||||
-ms-transform: scale(-1, 1);
|
||||
-o-transform: scale(-1, 1);
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${colorWhite};
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
Captions,
|
||||
Header,
|
||||
Title,
|
||||
HideBtn,
|
||||
};
|
||||
export default { Captions };
|
||||
|
@ -6,6 +6,7 @@ import _ from 'lodash';
|
||||
import injectNotify from '/imports/ui/components/common/toast/inject-notify/component';
|
||||
import AudioService from '/imports/ui/components/audio/service';
|
||||
import ChatPushAlert from './push-alert/component';
|
||||
import { stripTags, unescapeHtml } from '/imports/utils/string-utils';
|
||||
import Service from '../service';
|
||||
import Styled from './styles';
|
||||
|
||||
@ -146,11 +147,8 @@ const ChatAlert = (props) => {
|
||||
if (content.text === PUBLIC_CHAT_CLEAR) {
|
||||
return intl.formatMessage(intlMessages.publicChatClear);
|
||||
}
|
||||
/* this code is to remove html tags that come in the server's messages */
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = content.text;
|
||||
const textWithoutTag = tempDiv.innerText;
|
||||
return textWithoutTag;
|
||||
|
||||
return unescapeHtml(stripTags(content.text));
|
||||
});
|
||||
|
||||
return contentMessage;
|
||||
|
@ -2,12 +2,12 @@ import React, { PureComponent } from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { withModalMounter } from '/imports/ui/components/common/modal/service';
|
||||
import _ from 'lodash';
|
||||
import BBBMenu from '/imports/ui/components/common/menu/component';
|
||||
import Button from '/imports/ui/components/common/button/component';
|
||||
|
||||
import { alertScreenReader } from '/imports/utils/dom-utils';
|
||||
import BBBMenu from "/imports/ui/components/common/menu/component";
|
||||
import { getDateString } from '/imports/utils/string-utils';
|
||||
import Trigger from "/imports/ui/components/common/control-header/right/component";
|
||||
|
||||
import ChatService from '../service';
|
||||
import { addNewAlert } from '../../screenreader-alert/service';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
clear: {
|
||||
@ -58,7 +58,6 @@ class ChatDropdown extends PureComponent {
|
||||
meetingIsBreakout,
|
||||
meetingName,
|
||||
timeWindowsValues,
|
||||
users,
|
||||
} = this.props;
|
||||
|
||||
const clearIcon = 'delete';
|
||||
@ -77,14 +76,11 @@ class ChatDropdown extends PureComponent {
|
||||
onClick: () => {
|
||||
const link = document.createElement('a');
|
||||
const mimeType = 'text/plain';
|
||||
const date = new Date();
|
||||
const time = `${date.getHours()}-${date.getMinutes()}`;
|
||||
const dateString = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}_${time}`;
|
||||
link.setAttribute('download', `bbb-${meetingName}[public-chat]_${dateString}.txt`);
|
||||
link.setAttribute('download', `bbb-${meetingName}[public-chat]_${getDateString()}.txt`);
|
||||
link.setAttribute(
|
||||
'href',
|
||||
`data: ${mimeType} ;charset=utf-8,`
|
||||
+ `${encodeURIComponent(ChatService.exportChat(timeWindowsValues, users, intl))}`,
|
||||
`data: ${mimeType};charset=utf-8,`
|
||||
+ `${encodeURIComponent(ChatService.exportChat(timeWindowsValues, intl))}`,
|
||||
);
|
||||
link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
|
||||
},
|
||||
@ -101,11 +97,11 @@ class ChatDropdown extends PureComponent {
|
||||
dataTest: 'chatCopy',
|
||||
label: intl.formatMessage(intlMessages.copy),
|
||||
onClick: () => {
|
||||
const chatHistory = ChatService.exportChat(timeWindowsValues, users, intl);
|
||||
const chatHistory = ChatService.exportChat(timeWindowsValues, intl);
|
||||
navigator.clipboard.writeText(chatHistory).then(() => {
|
||||
alertScreenReader(intl.formatMessage(intlMessages.copySuccess));
|
||||
addNewAlert(intl.formatMessage(intlMessages.copySuccess));
|
||||
}).catch(() => {
|
||||
alertScreenReader(intl.formatMessage(intlMessages.copyErr));
|
||||
addNewAlert(intl.formatMessage(intlMessages.copyErr));
|
||||
});
|
||||
},
|
||||
},
|
||||
@ -131,26 +127,22 @@ class ChatDropdown extends PureComponent {
|
||||
const {
|
||||
intl,
|
||||
amIModerator,
|
||||
isRTL,
|
||||
} = this.props;
|
||||
|
||||
if (!amIModerator && !ENABLE_SAVE_AND_COPY_PUBLIC_CHAT) return null;
|
||||
return (
|
||||
<>
|
||||
<BBBMenu
|
||||
trigger={(
|
||||
<Button
|
||||
trigger={
|
||||
<Trigger
|
||||
data-test="chatOptionsMenu"
|
||||
icon="more"
|
||||
size="sm"
|
||||
ghost
|
||||
circle
|
||||
hideLabel
|
||||
color="dark"
|
||||
label={intl.formatMessage(intlMessages.options)}
|
||||
aria-label={intl.formatMessage(intlMessages.options)}
|
||||
onClick={() => null}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
opts={{
|
||||
id: 'default-dropdown-menu',
|
||||
keepMounted: true,
|
||||
@ -158,8 +150,8 @@ class ChatDropdown extends PureComponent {
|
||||
elevation: 3,
|
||||
getContentAnchorEl: null,
|
||||
fullwidth: 'true',
|
||||
anchorOrigin: { vertical: 'bottom', horizontal: 'left' },
|
||||
transformorigin: { vertical: 'bottom', horizontal: 'left' },
|
||||
anchorOrigin: { vertical: 'bottom', horizontal: isRTL ? 'right' : 'left' },
|
||||
transformOrigin: { vertical: 'top', horizontal: isRTL ? 'right' : 'left' },
|
||||
}}
|
||||
actions={this.getAvailableActions()}
|
||||
/>
|
||||
|
@ -1,15 +1,14 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import { UsersContext } from '/imports/ui/components/components-data/users-context/context';
|
||||
import ChatDropdown from './component';
|
||||
import { layoutSelect } from '../../layout/context';
|
||||
|
||||
const ChatDropdownContainer = ({ ...props }) => {
|
||||
const usingUsersContext = useContext(UsersContext);
|
||||
const { users } = usingUsersContext;
|
||||
const isRTL = layoutSelect((i) => i.isRTL);
|
||||
|
||||
return <ChatDropdown {...props} users={users[Auth.meetingID]} />;
|
||||
return <ChatDropdown {...{ isRTL, ...props }} />;
|
||||
};
|
||||
|
||||
export default withTracker(() => {
|
||||
|
@ -2,7 +2,6 @@ import React, { memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
|
||||
import Button from '/imports/ui/components/common/button/component';
|
||||
import withShortcutHelper from '/imports/ui/components/shortcut-help/service';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger';
|
||||
@ -14,6 +13,7 @@ import { PANELS, ACTIONS } from '../layout/enums';
|
||||
import { UserSentMessageCollection } from './service';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import browserInfo from '/imports/utils/browserInfo';
|
||||
import Header from '/imports/ui/components/common/control-header/component';
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id;
|
||||
@ -67,68 +67,56 @@ const Chat = (props) => {
|
||||
isChrome={isChrome}
|
||||
data-test={isPublicChat ? 'publicChat' : 'privateChat'}
|
||||
>
|
||||
<Styled.Header>
|
||||
<Styled.Title data-test="chatTitle">
|
||||
<Styled.HideChatButton
|
||||
onClick={() => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||
value: false,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_ID_CHAT_OPEN,
|
||||
value: '',
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.NONE,
|
||||
});
|
||||
}}
|
||||
aria-label={intl.formatMessage(intlMessages.hideChatLabel, { 0: title })}
|
||||
accessKey={chatID !== 'public' ? HIDE_CHAT_AK : null}
|
||||
data-test={isPublicChat ? 'hidePublicChat' : 'hidePrivateChat'}
|
||||
label={title}
|
||||
icon="left_arrow"
|
||||
<Header
|
||||
leftButtonProps={{
|
||||
accessKey: chatID !== 'public' ? HIDE_CHAT_AK : null,
|
||||
'aria-label': intl.formatMessage(intlMessages.hideChatLabel, { 0: title }),
|
||||
'data-test': isPublicChat ? 'hidePublicChat' : 'hidePrivateChat',
|
||||
label: title,
|
||||
onClick: () => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||
value: false,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_ID_CHAT_OPEN,
|
||||
value: '',
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.NONE,
|
||||
});
|
||||
},
|
||||
}}
|
||||
rightButtonProps={{
|
||||
accessKey: CLOSE_CHAT_AK,
|
||||
'aria-label': intl.formatMessage(intlMessages.closeChatLabel, { 0: title }),
|
||||
'data-test': "closePrivateChat",
|
||||
icon: "close",
|
||||
label: intl.formatMessage(intlMessages.closeChatLabel, { 0: title }),
|
||||
onClick: () => {
|
||||
actions.handleClosePrivateChat(chatID);
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||
value: false,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_ID_CHAT_OPEN,
|
||||
value: '',
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.NONE,
|
||||
});
|
||||
},
|
||||
}}
|
||||
customRightButton={isPublicChat && (
|
||||
<ChatDropdownContainer {...{
|
||||
meetingIsBreakout, isMeteorConnected, amIModerator, timeWindowsValues,
|
||||
}}
|
||||
/>
|
||||
</Styled.Title>
|
||||
{
|
||||
!isPublicChat
|
||||
? (
|
||||
<Button
|
||||
icon="close"
|
||||
size="sm"
|
||||
ghost
|
||||
color="dark"
|
||||
hideLabel
|
||||
onClick={() => {
|
||||
actions.handleClosePrivateChat(chatID);
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||
value: false,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_ID_CHAT_OPEN,
|
||||
value: '',
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.NONE,
|
||||
});
|
||||
}}
|
||||
aria-label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })}
|
||||
label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })}
|
||||
accessKey={CLOSE_CHAT_AK}
|
||||
data-test="closePrivateChat"
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<ChatDropdownContainer {...{
|
||||
meetingIsBreakout, isMeteorConnected, amIModerator, timeWindowsValues,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Styled.Header>
|
||||
)}
|
||||
/>
|
||||
<TimeWindowList
|
||||
id={ELEMENT_ID}
|
||||
chatId={chatID}
|
||||
|
@ -13,6 +13,7 @@ import lockContextContainer from '/imports/ui/components/lock-viewers/context/co
|
||||
import Chat from '/imports/ui/components/chat/component';
|
||||
import ChatService from './service';
|
||||
import { layoutSelect, layoutDispatch } from '../layout/context';
|
||||
import { escapeHtml } from '/imports/utils/string-utils';
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const PUBLIC_CHAT_KEY = CHAT_CONFIG.public_id;
|
||||
@ -200,7 +201,7 @@ const ChatContainer = (props) => {
|
||||
id,
|
||||
content: [{
|
||||
id,
|
||||
text: intl.formatMessage(intlMessages.partnerDisconnected, { 0: chatName }),
|
||||
text: escapeHtml(intl.formatMessage(intlMessages.partnerDisconnected, { 0: chatName })),
|
||||
time,
|
||||
}],
|
||||
time,
|
||||
|
@ -7,6 +7,7 @@ import _ from 'lodash';
|
||||
import TypingIndicatorContainer from './typing-indicator/container';
|
||||
import ClickOutside from '/imports/ui/components/click-outside/component';
|
||||
import Styled from './styles';
|
||||
import { escapeHtml } from '/imports/utils/string-utils';
|
||||
import { isChatEnabled } from '/imports/ui/services/features';
|
||||
|
||||
const propTypes = {
|
||||
@ -257,15 +258,9 @@ class MessageForm extends PureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanitize. See: http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript/
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.appendChild(document.createTextNode(msg));
|
||||
msg = div.innerHTML;
|
||||
|
||||
const callback = this.typingIndicator ? stopUserTyping : null;
|
||||
|
||||
handleSendMessage(msg);
|
||||
handleSendMessage(escapeHtml(msg));
|
||||
this.setState({ message: '', hasErrors: false, showEmojiPicker: false }, callback);
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import UnreadMessages from '/imports/ui/services/unread-messages';
|
||||
import Storage from '/imports/ui/services/storage/session';
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
import _ from 'lodash';
|
||||
import { stripTags, unescapeHtml } from '/imports/utils/string-utils';
|
||||
import { meetingIsBreakout } from '/imports/ui/components/app/service';
|
||||
import { defineMessages } from 'react-intl';
|
||||
import PollService from '/imports/ui/components/poll/service';
|
||||
@ -246,17 +247,13 @@ const removeFromClosedChatsSession = (idChatOpen) => {
|
||||
}
|
||||
};
|
||||
|
||||
// We decode to prevent HTML5 escaped characters.
|
||||
const htmlDecode = (input) => {
|
||||
const e = document.createElement('div');
|
||||
e.innerHTML = input;
|
||||
const messages = Array.from(e.childNodes);
|
||||
const message = messages.map((chatMessage) => chatMessage.textContent);
|
||||
return message.join('');
|
||||
const replacedBRs = input.replaceAll('<br/>', '\n');
|
||||
return unescapeHtml(stripTags(replacedBRs));
|
||||
};
|
||||
|
||||
// Export the chat as [Hour:Min] user: message
|
||||
const exportChat = (timeWindowList, users, intl) => {
|
||||
const exportChat = (timeWindowList, intl) => {
|
||||
const messageList = timeWindowList.reduce((acc, timeWindow) => {
|
||||
const msgs = timeWindow.content.map((message) => {
|
||||
const date = new Date(message.time);
|
||||
@ -270,7 +267,7 @@ const exportChat = (timeWindowList, users, intl) => {
|
||||
|
||||
let userName = message.id.startsWith(SYSTEM_CHAT_TYPE)
|
||||
? ''
|
||||
: `${users[timeWindow.sender].name}: `;
|
||||
: `${timeWindow.senderName}: `;
|
||||
let messageText = '';
|
||||
if (message.text === PUBLIC_CHAT_CLEAR) {
|
||||
message.text = intl.formatMessage(intlMessages.publicChatClear);
|
||||
|
@ -1,23 +1,14 @@
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
colorWhite,
|
||||
colorGrayDark,
|
||||
colorPrimary
|
||||
} from '/imports/ui/stylesheets/styled-components/palette';
|
||||
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
|
||||
import Button from '/imports/ui/components/common/button/component';
|
||||
import {
|
||||
mdPaddingX,
|
||||
mdPaddingY,
|
||||
pollHeaderOffset,
|
||||
borderSizeLarge,
|
||||
borderSize,
|
||||
} from '/imports/ui/stylesheets/styled-components/general';
|
||||
import { DivElipsis } from '/imports/ui/stylesheets/styled-components/placeholders';
|
||||
import { mdPaddingX } from '/imports/ui/stylesheets/styled-components/general';
|
||||
|
||||
const Chat = styled.div`
|
||||
background-color: ${colorWhite};
|
||||
padding: ${mdPaddingX} ${mdPaddingY} ${mdPaddingX} ${mdPaddingX};
|
||||
padding: ${mdPaddingX};
|
||||
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
@ -64,59 +55,4 @@ const Chat = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const Header = styled.header`
|
||||
position: relative;
|
||||
top: ${pollHeaderOffset};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const Title = styled(DivElipsis)`
|
||||
flex: 1;
|
||||
|
||||
& > button, button:hover {
|
||||
max-width: 98%;
|
||||
}
|
||||
`;
|
||||
|
||||
const HideChatButton = styled(Button)`
|
||||
position: relative;
|
||||
background-color: ${colorWhite};
|
||||
display: block;
|
||||
margin: ${borderSizeLarge};
|
||||
margin-bottom: ${borderSize};
|
||||
padding-left: 0;
|
||||
padding-right: inherit;
|
||||
z-index: 3;
|
||||
|
||||
[dir="rtl"] & {
|
||||
padding-left: inherit;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
& > i {
|
||||
color: ${colorGrayDark};
|
||||
font-size: smaller;
|
||||
|
||||
[dir="rtl"] & {
|
||||
-webkit-transform: scale(-1, 1);
|
||||
-moz-transform: scale(-1, 1);
|
||||
-ms-transform: scale(-1, 1);
|
||||
-o-transform: scale(-1, 1);
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${colorWhite};
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
Chat,
|
||||
Header,
|
||||
Title,
|
||||
HideChatButton,
|
||||
};
|
||||
export default { Chat };
|
||||
|
@ -45,6 +45,11 @@ const EmojiButton = styled.button`
|
||||
z-index: 2;
|
||||
border: none;
|
||||
|
||||
[dir="rtl"] & {
|
||||
right: initial;
|
||||
left: -.2em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.5);
|
||||
transition-duration: 150ms;
|
||||
@ -60,6 +65,15 @@ const EmojiButton = styled.button`
|
||||
margin-top: 40%;
|
||||
color: ${btnDefaultColor};
|
||||
}
|
||||
|
||||
${({ rotate }) => rotate && `
|
||||
span {
|
||||
i {
|
||||
transform: rotate(180deg);
|
||||
margin-top: 20%;
|
||||
}
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const EmojiButtonSpace = styled.div`
|
||||
@ -70,6 +84,11 @@ const EmojiButtonSpace = styled.div`
|
||||
right: -.4em;
|
||||
bottom: -.2em;
|
||||
border-radius: 50%;
|
||||
|
||||
[dir="rtl"] & {
|
||||
right: initial;
|
||||
left: -.4em;
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
|
@ -95,7 +95,8 @@ const ButtonLabel = styled.span`
|
||||
margin: 0 ${btnSpacing} 0 0;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
&:hover,
|
||||
.buttonWrapper:hover & {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
@ -355,13 +356,13 @@ const ButtonSpan = styled.span`
|
||||
padding: ${jumboPaddingY} ${jumboPaddingX};
|
||||
`}
|
||||
|
||||
${({ color }) => color === 'default' && `
|
||||
${({ color, ghost }) => color === 'default' && !ghost && `
|
||||
color: ${btnDefaultColor};
|
||||
background-color: ${btnDefaultBg};
|
||||
border: ${borderSizeLarge} solid transparent;
|
||||
|
||||
&:focus,
|
||||
&:focus:not([aria-disabled="true"]) & {
|
||||
.buttonWrapper:focus:not([aria-disabled="true"]) & {
|
||||
color: ${btnDefaultColor};
|
||||
background-color: ${btnDefaultBg};
|
||||
background-clip: padding-box;
|
||||
@ -515,7 +516,7 @@ const ButtonSpan = styled.span`
|
||||
color: ${btnDefaultBg};
|
||||
background-color: ${btnDefaultColor};
|
||||
background-clip: padding-box;
|
||||
box-shadow: 0 0 0 ${borderSizeLarge} ${btnDefaultBg};
|
||||
box-shadow: 0 0 0 ${borderSizeLarge} ${btnDefaultBg} !important;
|
||||
}
|
||||
&:hover,
|
||||
.buttonWrapper:hover & {
|
||||
|
@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Styled from './styles';
|
||||
import Left from './left/component';
|
||||
import Right from './right/component';
|
||||
|
||||
const Header = ({
|
||||
leftButtonProps,
|
||||
rightButtonProps,
|
||||
customRightButton,
|
||||
}) => {
|
||||
const renderCloseButton = () => (
|
||||
<Right {...rightButtonProps} />
|
||||
);
|
||||
|
||||
const renderCustomRightButton = () => (
|
||||
<Styled.RightWrapper>
|
||||
{customRightButton}
|
||||
</Styled.RightWrapper>
|
||||
);
|
||||
|
||||
return (
|
||||
<Styled.Header>
|
||||
<Left {...leftButtonProps} />
|
||||
{customRightButton
|
||||
? renderCustomRightButton()
|
||||
: rightButtonProps
|
||||
? renderCloseButton()
|
||||
: null}
|
||||
</Styled.Header>
|
||||
);
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
leftButtonProps: PropTypes.object,
|
||||
rightButtonProps: PropTypes.object,
|
||||
customRightButton: PropTypes.element,
|
||||
};
|
||||
|
||||
export default Header;
|
@ -0,0 +1,30 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Styled from './styles';
|
||||
|
||||
class Left extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Styled.HideButton
|
||||
className="buttonWrapper"
|
||||
icon="left_arrow"
|
||||
tabindex={0}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Left.propTypes = {
|
||||
accessKey: PropTypes.any,
|
||||
'aria-label': PropTypes.string,
|
||||
'data-test': PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Left;
|
@ -0,0 +1,31 @@
|
||||
import styled from 'styled-components';
|
||||
import Button from '/imports/ui/components/common/button/component';
|
||||
import { fontSizeBase } from '/imports/ui/stylesheets/styled-components/typography';
|
||||
|
||||
const HideButton = styled(Button)`
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: normal;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
& > i,
|
||||
& > i::before {
|
||||
width: auto;
|
||||
font-size: ${fontSizeBase} !important;
|
||||
|
||||
[dir="rtl"] & {
|
||||
-webkit-transform: scale(-1, 1);
|
||||
-moz-transform: scale(-1, 1);
|
||||
-ms-transform: scale(-1, 1);
|
||||
-o-transform: scale(-1, 1);
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default { HideButton };
|
@ -0,0 +1,31 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Styled from './styles';
|
||||
|
||||
class Right extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Styled.CloseButton
|
||||
size="sm"
|
||||
hideLabel
|
||||
circle
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Right.propTypes = {
|
||||
accessKey: PropTypes.any,
|
||||
'aria-label': PropTypes.string,
|
||||
'data-test': PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Right;
|
@ -0,0 +1,26 @@
|
||||
import styled from 'styled-components';
|
||||
import Button from '/imports/ui/components/common/button/component';
|
||||
import { fontSizeBase } from '/imports/ui/stylesheets/styled-components/typography';
|
||||
import { colorWhite } from '/imports/ui/stylesheets/styled-components/palette';
|
||||
|
||||
const CloseButton = styled(Button)`
|
||||
span:first-of-type {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
& > i,
|
||||
& > i::before {
|
||||
width: auto;
|
||||
font-size: ${fontSizeBase};
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
.buttonWrapper:hover &,
|
||||
.buttonWrapper:focus & {
|
||||
background-color: ${colorWhite} !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default { CloseButton };
|
@ -0,0 +1,20 @@
|
||||
import styled from 'styled-components';
|
||||
import { jumboPaddingY } from '/imports/ui/stylesheets/styled-components/general';
|
||||
|
||||
const Header = styled.header`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: ${jumboPaddingY};
|
||||
`;
|
||||
|
||||
const RightWrapper = styled.div`
|
||||
& > div {
|
||||
display: flex;
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
Header,
|
||||
RightWrapper,
|
||||
};
|
@ -26,7 +26,7 @@ class BBBMenu extends React.Component {
|
||||
anchorEl: null,
|
||||
};
|
||||
|
||||
this.opts = props.opts;
|
||||
this.optsToMerge = {};
|
||||
this.autoFocus = false;
|
||||
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
@ -70,12 +70,12 @@ class BBBMenu extends React.Component {
|
||||
const emojiSelected = key?.toLowerCase()?.includes(selectedEmoji?.toLowerCase());
|
||||
|
||||
let customStyles = {
|
||||
paddingLeft: '4px',
|
||||
paddingRight: '4px',
|
||||
paddingTop: '8px',
|
||||
paddingBottom: '8px',
|
||||
marginLeft: '4px',
|
||||
marginRight: '4px'
|
||||
paddingLeft: '16px',
|
||||
paddingRight: '16px',
|
||||
paddingTop: '12px',
|
||||
paddingBottom: '12px',
|
||||
marginLeft: '0px',
|
||||
marginRight: '0px',
|
||||
};
|
||||
|
||||
if (a.customStyles) {
|
||||
@ -99,11 +99,11 @@ class BBBMenu extends React.Component {
|
||||
if (close) this.handleClose(event);
|
||||
event.stopPropagation();
|
||||
}}>
|
||||
<div style={{ display: 'flex', flexFlow: 'row', width: '100%' }}>
|
||||
<Styled.MenuItemWrapper>
|
||||
{a.icon ? <Icon iconName={a.icon} key="icon" /> : null}
|
||||
<Styled.Option>{label}</Styled.Option>
|
||||
{a.iconRight ? <Styled.IconRight iconName={a.iconRight} key="iconRight" /> : null}
|
||||
</div>
|
||||
</Styled.MenuItemWrapper>
|
||||
</Styled.BBBMenuItem>,
|
||||
a.divider && <Divider disabled />
|
||||
];
|
||||
@ -112,7 +112,7 @@ class BBBMenu extends React.Component {
|
||||
|
||||
render() {
|
||||
const { anchorEl } = this.state;
|
||||
const { trigger, intl, customStyles, dataTest } = this.props;
|
||||
const { trigger, intl, customStyles, dataTest, opts } = this.props;
|
||||
const actionsItems = this.makeMenuItems();
|
||||
|
||||
let menuStyles = { zIndex: 9999 };
|
||||
@ -129,7 +129,7 @@ class BBBMenu extends React.Component {
|
||||
const firefoxInputSource = !([1, 5].includes(e.nativeEvent.mozInputSource)); // 1 = mouse, 5 = touch (firefox only)
|
||||
const chromeInputSource = !(['mouse', 'touch'].includes(e.nativeEvent.pointerType));
|
||||
|
||||
this.opts.autoFocus = firefoxInputSource && chromeInputSource;
|
||||
this.optsToMerge.autoFocus = firefoxInputSource && chromeInputSource;
|
||||
this.handleClick(e);
|
||||
}}
|
||||
onKeyPress={(e) => {
|
||||
@ -144,7 +144,8 @@ class BBBMenu extends React.Component {
|
||||
</div>
|
||||
|
||||
<Menu
|
||||
{...this.opts}
|
||||
{...opts}
|
||||
{...this.optsToMerge}
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={this.handleClose}
|
||||
|
@ -6,6 +6,13 @@ import { colorWhite, colorPrimary } from '/imports/ui/stylesheets/styled-compone
|
||||
import { fontSizeLarge } from '/imports/ui/stylesheets/styled-components/typography';
|
||||
import { mediumUp } from '/imports/ui/stylesheets/styled-components/breakpoints';
|
||||
|
||||
const MenuItemWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const Option = styled.div`
|
||||
line-height: 1;
|
||||
margin-right: 1.65rem;
|
||||
@ -78,6 +85,7 @@ const BBBMenuItem = styled(MenuItem)`
|
||||
`;
|
||||
|
||||
export default {
|
||||
MenuItemWrapper,
|
||||
Option,
|
||||
CloseButton,
|
||||
IconRight,
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { useState, useContext, useEffect } from 'react';
|
||||
import {
|
||||
useState, useContext, useRef, useEffect,
|
||||
} from 'react';
|
||||
import { UsersContext } from '/imports/ui/components/components-data/users-context/context';
|
||||
import { throttle } from 'lodash';
|
||||
|
||||
const USER_JOIN_UPDATE_TIMEOUT = 1000;
|
||||
let updateTimeout = null;
|
||||
const USER_JOIN_UPDATE_THROTTLE_TIME = 1000;
|
||||
|
||||
export default function useContextUsers() {
|
||||
const usingUsersContext = useContext(UsersContext);
|
||||
@ -11,17 +13,15 @@ export default function useContextUsers() {
|
||||
const [users, setUsers] = useState(null);
|
||||
const [isReady, setIsReady] = useState(true);
|
||||
|
||||
const throttledSetUsers = useRef(throttle(() => {
|
||||
setUsers(contextUsers);
|
||||
setIsReady(true);
|
||||
},
|
||||
USER_JOIN_UPDATE_THROTTLE_TIME, { trailing: true }));
|
||||
|
||||
useEffect(() => {
|
||||
setIsReady(false);
|
||||
|
||||
if (updateTimeout) {
|
||||
clearTimeout(updateTimeout);
|
||||
}
|
||||
|
||||
updateTimeout = setTimeout(() => {
|
||||
setUsers(contextUsers);
|
||||
setIsReady(true);
|
||||
}, USER_JOIN_UPDATE_TIMEOUT);
|
||||
throttledSetUsers.current();
|
||||
}, [contextUsers]);
|
||||
|
||||
return {
|
||||
|
@ -162,6 +162,7 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
dataPage: '1',
|
||||
dataSaving: props.dataSaving,
|
||||
hasNetworkData: false,
|
||||
copyButtonText: intl.formatMessage(intlMessages.copy),
|
||||
networkData: {
|
||||
user: {
|
||||
|
||||
@ -180,6 +181,7 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
},
|
||||
};
|
||||
this.displaySettingsStatus = this.displaySettingsStatus.bind(this);
|
||||
this.setButtonMessage = this.setButtonMessage.bind(this);
|
||||
this.rateInterval = null;
|
||||
this.audioUploadLabel = intl.formatMessage(intlMessages.audioUploadRate);
|
||||
this.audioDownloadLabel = intl.formatMessage(intlMessages.audioDownloadRate);
|
||||
@ -287,6 +289,12 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
setButtonMessage(msg) {
|
||||
this.setState({
|
||||
copyButtonText: msg,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy network data to clipboard
|
||||
* @param {Object} e Event object from click event
|
||||
@ -305,14 +313,14 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
|
||||
const { target: copyButton } = e;
|
||||
|
||||
copyButton.innerHTML = intl.formatMessage(intlMessages.copied);
|
||||
this.setButtonMessage(intl.formatMessage(intlMessages.copied));
|
||||
|
||||
const data = JSON.stringify(networkData, null, 2);
|
||||
|
||||
await navigator.clipboard.writeText(data);
|
||||
|
||||
this.copyNetworkDataTimeout = setTimeout(() => {
|
||||
copyButton.innerHTML = intl.formatMessage(intlMessages.copy);
|
||||
this.setButtonMessage(intl.formatMessage(intlMessages.copy));
|
||||
}, MIN_TIMEOUT);
|
||||
}
|
||||
|
||||
@ -623,7 +631,7 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
onKeyPress={this.copyNetworkData.bind(this)}
|
||||
tabIndex={0}
|
||||
>
|
||||
{intl.formatMessage(intlMessages.copy)}
|
||||
{this.state.copyButtonText}
|
||||
</Styled.Copy>
|
||||
</Styled.CopyContainer>
|
||||
);
|
||||
|
@ -40,6 +40,9 @@ const intlMessages = defineMessages({
|
||||
banned_user_rejoining_reason: {
|
||||
id: 'app.error.userBanned',
|
||||
},
|
||||
joined_another_window_reason: {
|
||||
id: 'app.error.joinedAnotherWindow',
|
||||
},
|
||||
});
|
||||
|
||||
const propTypes = {
|
||||
|
@ -6,8 +6,8 @@ export const INITIAL_INPUT_STATE = {
|
||||
|
||||
},
|
||||
browser: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
width: window.document.documentElement.clientWidth,
|
||||
height: window.document.documentElement.clientHeight,
|
||||
},
|
||||
bannerBar: {
|
||||
hasBanner: false,
|
||||
|
@ -8,11 +8,11 @@ import RecordingIndicator from './recording-indicator/container';
|
||||
import TalkingIndicatorContainer from '/imports/ui/components/nav-bar/talking-indicator/container';
|
||||
import ConnectionStatusButton from '/imports/ui/components/connection-status/button/container';
|
||||
import ConnectionStatusService from '/imports/ui/components/connection-status/service';
|
||||
import { addNewAlert } from '/imports/ui/components/screenreader-alert/service';
|
||||
import SettingsDropdownContainer from './settings-dropdown/container';
|
||||
import browserInfo from '/imports/utils/browserInfo';
|
||||
import deviceInfo from '/imports/utils/deviceInfo';
|
||||
import _ from "lodash";
|
||||
import { politeSRAlert } from '/imports/utils/dom-utils';
|
||||
import { PANELS, ACTIONS } from '../layout/enums';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
@ -182,7 +182,7 @@ class NavBar extends Component {
|
||||
|
||||
activeChats.map((c, i) => {
|
||||
if (c?.unreadCounter > 0 && c?.unreadCounter !== acs[i]?.unreadCounter) {
|
||||
politeSRAlert(`${intl.formatMessage(intlMessages.newMsgAria, { 0: c.name })}`)
|
||||
addNewAlert(`${intl.formatMessage(intlMessages.newMsgAria, { 0: c.name })}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -279,9 +279,10 @@ class SettingsDropdown extends PureComponent {
|
||||
shortcuts: OPEN_OPTIONS_AK,
|
||||
isDropdownOpen,
|
||||
isMobile,
|
||||
isRTL,
|
||||
} = this.props;
|
||||
|
||||
const customStyles = { top: '3rem' };
|
||||
const customStyles = { top: '1rem' };
|
||||
|
||||
return (
|
||||
<BBBMenu
|
||||
@ -302,6 +303,16 @@ class SettingsDropdown extends PureComponent {
|
||||
/>
|
||||
)}
|
||||
actions={this.renderMenuItems()}
|
||||
opts={{
|
||||
id: "default-dropdown-menu",
|
||||
keepMounted: true,
|
||||
transitionDuration: 0,
|
||||
elevation: 3,
|
||||
getContentAnchorEl: null,
|
||||
fullwidth: "true",
|
||||
anchorOrigin: { vertical: 'bottom', horizontal: isRTL ? 'left' : 'right' },
|
||||
transformorigin: { vertical: 'top', horizontal: isRTL ? 'left' : 'right' },
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import browserInfo from '/imports/utils/browserInfo';
|
||||
import SettingsDropdown from './component';
|
||||
import FullscreenService from '/imports/ui/components/common/fullscreen-button/service';
|
||||
import { meetingIsBreakout } from '/imports/ui/components/app/service';
|
||||
import { layoutSelectInput } from '../../layout/context';
|
||||
import { layoutSelectInput, layoutSelect } from '../../layout/context';
|
||||
import { SMALL_VIEWPORT_BREAKPOINT } from '../../layout/enums';
|
||||
|
||||
const { isIphone } = deviceInfo;
|
||||
@ -16,9 +16,10 @@ const noIOSFullscreen = !!(((isSafari && !isValidSafariVersion) || isIphone));
|
||||
const SettingsDropdownContainer = (props) => {
|
||||
const { width: browserWidth } = layoutSelectInput((i) => i.browser);
|
||||
const isMobile = browserWidth <= SMALL_VIEWPORT_BREAKPOINT;
|
||||
const isRTL = layoutSelect((i) => i.isRTL);
|
||||
|
||||
return (
|
||||
<SettingsDropdown {...{ isMobile, ...props }} />
|
||||
<SettingsDropdown {...{ isMobile, isRTL, ...props }} />
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -8,6 +8,7 @@ import ConverterButtonContainer from './converter-button/container';
|
||||
import Styled from './styles';
|
||||
import { PANELS, ACTIONS } from '../layout/enums';
|
||||
import browserInfo from '/imports/utils/browserInfo';
|
||||
import Header from '/imports/ui/components/common/control-header/component';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
hide: {
|
||||
@ -42,27 +43,26 @@ const Notes = ({
|
||||
|
||||
return (
|
||||
<Styled.Notes data-test="notes" isChrome={isChrome}>
|
||||
<Styled.Header>
|
||||
<Styled.Title data-test="notesTitle">
|
||||
<Styled.HideButton
|
||||
onClick={() => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||
value: false,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.NONE,
|
||||
});
|
||||
}}
|
||||
data-test="hideNotesLabel"
|
||||
aria-label={intl.formatMessage(intlMessages.hide)}
|
||||
label={intl.formatMessage(intlMessages.title)}
|
||||
icon={isRTL ? 'right_arrow' : 'left_arrow'}
|
||||
/>
|
||||
</Styled.Title>
|
||||
<ConverterButtonContainer />
|
||||
</Styled.Header>
|
||||
<Header
|
||||
leftButtonProps={{
|
||||
onClick: () => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||
value: false,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.NONE,
|
||||
});
|
||||
},
|
||||
'data-test': 'hideNotesLabel',
|
||||
'aria-label': intl.formatMessage(intlMessages.hide),
|
||||
label: intl.formatMessage(intlMessages.title),
|
||||
}}
|
||||
customRightButton={
|
||||
<ConverterButtonContainer />
|
||||
}
|
||||
/>
|
||||
<PadContainer
|
||||
externalId={Service.ID}
|
||||
hasPermission={hasPermission}
|
||||
|
@ -13,14 +13,7 @@ const ConvertAndUpload = styled(Button)`
|
||||
position: relative;
|
||||
background-color: ${colorWhite};
|
||||
display: block;
|
||||
margin: ${borderSizeLarge};
|
||||
margin-bottom: ${borderSize};
|
||||
padding-left: 0;
|
||||
padding-right: inherit;
|
||||
[dir="rtl"] & {
|
||||
padding-left: inherit;
|
||||
padding-right: 0;
|
||||
}
|
||||
padding: 0;
|
||||
& > i {
|
||||
color: ${colorGrayDark};
|
||||
font-size: smaller;
|
||||
|
@ -1,23 +1,13 @@
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
mdPaddingX,
|
||||
mdPaddingY,
|
||||
pollHeaderOffset,
|
||||
toastContentWidth,
|
||||
borderSizeLarge,
|
||||
borderSize,
|
||||
} from '/imports/ui/stylesheets/styled-components/general';
|
||||
import {
|
||||
colorWhite,
|
||||
colorGrayDark,
|
||||
} from '/imports/ui/stylesheets/styled-components/palette';
|
||||
import { colorWhite } from '/imports/ui/stylesheets/styled-components/palette';
|
||||
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
|
||||
import { DivElipsis } from '/imports/ui/stylesheets/styled-components/placeholders';
|
||||
import Button from '/imports/ui/components/common/button/component';
|
||||
|
||||
const Notes = styled.div`
|
||||
background-color: ${colorWhite};
|
||||
padding: ${mdPaddingX} ${mdPaddingY} ${mdPaddingX} ${mdPaddingX};
|
||||
padding: ${mdPaddingX};
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
@ -33,58 +23,4 @@ const Notes = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const Header = styled.header`
|
||||
position: relative;
|
||||
top: ${pollHeaderOffset};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const Title = styled(DivElipsis)`
|
||||
flex: 1;
|
||||
|
||||
& > button, button:hover {
|
||||
max-width: ${toastContentWidth};
|
||||
}
|
||||
`;
|
||||
|
||||
const HideButton = styled(Button)`
|
||||
position: relative;
|
||||
background-color: ${colorWhite};
|
||||
display: block;
|
||||
margin: ${borderSizeLarge};
|
||||
margin-bottom: ${borderSize};
|
||||
padding-left: 0;
|
||||
padding-right: inherit;
|
||||
|
||||
[dir="rtl"] & {
|
||||
padding-left: inherit;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
& > i {
|
||||
color: ${colorGrayDark};
|
||||
font-size: smaller;
|
||||
|
||||
[dir="rtl"] & {
|
||||
-webkit-transform: scale(-1, 1);
|
||||
-moz-transform: scale(-1, 1);
|
||||
-ms-transform: scale(-1, 1);
|
||||
-o-transform: scale(-1, 1);
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${colorWhite};
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
Notes,
|
||||
Header,
|
||||
Title,
|
||||
HideButton,
|
||||
};
|
||||
export default { Notes };
|
||||
|
@ -1,6 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
colorGray,
|
||||
colorGrayLightest
|
||||
} from '/imports/ui/stylesheets/styled-components/palette';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
@ -16,8 +17,7 @@ font-size: 15px;
|
||||
color: ${colorGray};
|
||||
bottom: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: block;
|
||||
overflow-x: hidden;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-all;
|
||||
@ -42,6 +42,8 @@ top: 0;
|
||||
const Iframe = styled.iframe`
|
||||
border-width: 0;
|
||||
width: 100%;
|
||||
border-top: 1px solid ${colorGrayLightest};
|
||||
border-bottom: 1px solid ${colorGrayLightest};
|
||||
`;
|
||||
|
||||
export default {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import Pads, { PadsUpdates } from '/imports/api/pads';
|
||||
import Pads, { PadsSessions, PadsUpdates } from '/imports/api/pads';
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import Settings from '/imports/ui/services/settings';
|
||||
@ -47,9 +47,13 @@ const throttledCreateSession = _.throttle(createSession, THROTTLE_TIMEOUT, {
|
||||
|
||||
const buildPadURL = (padId) => {
|
||||
if (padId) {
|
||||
const params = getParams();
|
||||
const url = Auth.authenticateURL(`${PADS_CONFIG.url}/p/${padId}?${params}`);
|
||||
return url;
|
||||
const padsSessions = PadsSessions.findOne({});
|
||||
if (padsSessions && padsSessions.sessions) {
|
||||
const params = getParams();
|
||||
const sessionIds = padsSessions.sessions.map(session => Object.values(session)).join(',');
|
||||
const url = Auth.authenticateURL(`${PADS_CONFIG.url}/auth_session?padName=${padId}&sessionID=${sessionIds}&${params}`);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -8,9 +8,10 @@ import DraggableTextArea from '/imports/ui/components/poll/dragAndDrop/component
|
||||
import LiveResult from '/imports/ui/components/poll/live-result/component';
|
||||
import Styled from './styles';
|
||||
import Toggle from '/imports/ui/components/common/switch/component';
|
||||
import { alertScreenReader } from '/imports/utils/dom-utils';
|
||||
import { PANELS, ACTIONS } from '/imports/ui/components/layout/enums';
|
||||
import { withModalMounter } from '/imports/ui/components/common/modal/service';
|
||||
import { PANELS, ACTIONS } from '../layout/enums';
|
||||
import { addNewAlert } from '../screenreader-alert/service';
|
||||
import Header from '/imports/ui/components/common/control-header/component';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
pollPaneTitle: {
|
||||
@ -426,7 +427,7 @@ class Poll extends Component {
|
||||
: [],
|
||||
warning: clearWarning ? null : warning,
|
||||
}, () => {
|
||||
alertScreenReader(`${intl.formatMessage(intlMessages.removePollOpt,
|
||||
addNewAlert(`${intl.formatMessage(intlMessages.removePollOpt,
|
||||
{ 0: removed.val || intl.formatMessage(intlMessages.emptyPollOpt) })}`);
|
||||
});
|
||||
}
|
||||
@ -977,15 +978,12 @@ class Poll extends Component {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Styled.Header>
|
||||
<Styled.PollHideButton
|
||||
ref={(node) => { this.hideBtn = node; }}
|
||||
data-test="hidePollDesc"
|
||||
tabIndex={0}
|
||||
label={intl.formatMessage(intlMessages.pollPaneTitle)}
|
||||
icon="left_arrow"
|
||||
aria-label={intl.formatMessage(intlMessages.hidePollDesc)}
|
||||
onClick={() => {
|
||||
<Header
|
||||
leftButtonProps={{
|
||||
'aria-label': intl.formatMessage(intlMessages.hidePollDesc),
|
||||
'data-test': "hidePollDesc",
|
||||
label: intl.formatMessage(intlMessages.pollPaneTitle),
|
||||
onClick: () => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||
value: false,
|
||||
@ -994,12 +992,15 @@ class Poll extends Component {
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.NONE,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Styled.PollCloseButton
|
||||
label={intl.formatMessage(intlMessages.closeLabel)}
|
||||
aria-label={`${intl.formatMessage(intlMessages.closeLabel)} ${intl.formatMessage(intlMessages.pollPaneTitle)}`}
|
||||
onClick={() => {
|
||||
},
|
||||
ref: (node) => { this.hideBtn = node; },
|
||||
}}
|
||||
rightButtonProps={{
|
||||
'aria-label': `${intl.formatMessage(intlMessages.closeLabel)} ${intl.formatMessage(intlMessages.pollPaneTitle)}`,
|
||||
'data-test': "closePolling",
|
||||
icon: "close",
|
||||
label: intl.formatMessage(intlMessages.closeLabel),
|
||||
onClick: () => {
|
||||
if (currentPoll) stopPoll();
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||
@ -1011,13 +1012,9 @@ class Poll extends Component {
|
||||
});
|
||||
Session.set('forcePollOpen', false);
|
||||
Session.set('pollInitiated', false);
|
||||
}}
|
||||
icon="close"
|
||||
size="sm"
|
||||
hideLabel
|
||||
data-test="closePolling"
|
||||
/>
|
||||
</Styled.Header>
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{this.renderPollPanel()}
|
||||
<span className="sr-only" id="poll-config-button">{intl.formatMessage(intlMessages.showRespDesc)}</span>
|
||||
<span className="sr-only" id="add-item-button">{intl.formatMessage(intlMessages.addRespDesc)}</span>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import { CurrentPoll } from '/imports/api/polls';
|
||||
import { escapeHtml } from '/imports/utils/string-utils';
|
||||
import caseInsensitiveReducer from '/imports/utils/caseInsensitiveReducer';
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
@ -113,12 +114,7 @@ const isDefaultPoll = (pollType) => pollType !== pollTypes.Custom
|
||||
const getPollResultString = (pollResultData, intl) => {
|
||||
const formatBoldBlack = (s) => s.bold().fontcolor('black');
|
||||
|
||||
// Sanitize. See: https://gist.github.com/sagewall/47164de600df05fb0f6f44d48a09c0bd
|
||||
const sanitize = (value) => {
|
||||
const div = document.createElement('div');
|
||||
div.appendChild(document.createTextNode(value));
|
||||
return div.innerHTML;
|
||||
};
|
||||
const sanitize = (value) => escapeHtml(value);
|
||||
|
||||
const { answers, numRespondents, questionType } = pollResultData;
|
||||
const ísDefault = isDefaultPoll(questionType);
|
||||
|
@ -5,13 +5,10 @@ import {
|
||||
smPaddingY,
|
||||
lgPaddingX,
|
||||
borderRadius,
|
||||
mdPaddingY,
|
||||
borderSize,
|
||||
borderSizeLarge,
|
||||
pollInputHeight,
|
||||
pollSmMargin,
|
||||
pollMdMargin,
|
||||
pollHeaderOffset,
|
||||
} from '/imports/ui/stylesheets/styled-components/general';
|
||||
import {
|
||||
colorText,
|
||||
@ -338,56 +335,6 @@ const DragAndDropPollContainer = styled.div`
|
||||
height: 200px !important;
|
||||
`;
|
||||
|
||||
const Header = styled.header`
|
||||
position: relative;
|
||||
top: ${pollHeaderOffset};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: ${mdPaddingY};
|
||||
`;
|
||||
|
||||
const PollHideButton = styled(Button)`
|
||||
position: relative;
|
||||
background-color: ${colorWhite};
|
||||
display: block;
|
||||
margin: ${borderSizeLarge};
|
||||
margin-bottom: ${borderSize};
|
||||
padding-left: 0;
|
||||
padding-right: inherit;
|
||||
|
||||
[dir="rtl"] & {
|
||||
padding-left: inherit;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
> i {
|
||||
color: ${colorGrayDark};
|
||||
font-size: smaller;
|
||||
|
||||
[dir="rtl"] & {
|
||||
-webkit-transform: scale(-1, 1);
|
||||
-moz-transform: scale(-1, 1);
|
||||
-ms-transform: scale(-1, 1);
|
||||
-o-transform: scale(-1, 1);
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${colorWhite};
|
||||
}
|
||||
`;
|
||||
|
||||
const PollCloseButton = styled(Button)`
|
||||
font-size: ${fontSizeBase};
|
||||
position: relative;
|
||||
& > i {
|
||||
color: ${colorText};
|
||||
}
|
||||
`;
|
||||
|
||||
const Question = styled.div`
|
||||
margin-bottom: ${lgPaddingX};
|
||||
`;
|
||||
@ -424,9 +371,6 @@ export default {
|
||||
NoSlidePanelContainer,
|
||||
PollButton,
|
||||
DragAndDropPollContainer,
|
||||
Header,
|
||||
PollHideButton,
|
||||
PollCloseButton,
|
||||
Warning,
|
||||
CustomInputRow,
|
||||
Question,
|
||||
|
@ -6,7 +6,6 @@ import WhiteboardToolbarContainer from '/imports/ui/components/whiteboard/whiteb
|
||||
import { HUNDRED_PERCENT, MAX_PERCENT } from '/imports/utils/slideCalcUtils';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { toast } from 'react-toastify';
|
||||
import { politeSRAlert } from '/imports/utils/dom-utils';
|
||||
import { Session } from 'meteor/session';
|
||||
import PresentationToolbarContainer from './presentation-toolbar/container';
|
||||
import PresentationPlaceholder from './presentation-placeholder/component';
|
||||
@ -25,6 +24,7 @@ import { ACTIONS, LAYOUT_TYPE } from '../layout/enums';
|
||||
import DEFAULT_VALUES from '../layout/defaultValues';
|
||||
import { colorContentBackground } from '/imports/ui/stylesheets/styled-components/palette';
|
||||
import browserInfo from '/imports/utils/browserInfo';
|
||||
import { addNewAlert } from '../screenreader-alert/service';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
presentationLabel: {
|
||||
@ -177,7 +177,7 @@ class Presentation extends PureComponent {
|
||||
&& prevProps?.currentSlide?.num != null
|
||||
&& currentSlide?.num !== prevProps.currentSlide?.num
|
||||
) {
|
||||
politeSRAlert(intl.formatMessage(intlMessages.slideContentChanged, { 0: currentSlide.num }));
|
||||
addNewAlert(intl.formatMessage(intlMessages.slideContentChanged, { 0: currentSlide.num }));
|
||||
}
|
||||
|
||||
if (currentPresentation) {
|
||||
|
@ -0,0 +1,5 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
const ScreenReaderAlertCollection = new Mongo.Collection('Screenreader-alert', { connection: null });
|
||||
|
||||
export default ScreenReaderAlertCollection;
|
@ -0,0 +1,15 @@
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useEffect } from 'react';
|
||||
import { removeAlert } from './service';
|
||||
|
||||
const ARIA_ALERT_EXT_TIMEOUT = 15000;
|
||||
|
||||
const ScreenReaderAlert = ({ olderAlert }) => {
|
||||
useEffect(() => {
|
||||
if (olderAlert) setTimeout(() => removeAlert(olderAlert.id), ARIA_ALERT_EXT_TIMEOUT);
|
||||
}, [olderAlert?.id]);
|
||||
|
||||
return olderAlert ? createPortal(olderAlert.text, document.getElementById('aria-polite-alert')) : null;
|
||||
};
|
||||
|
||||
export default ScreenReaderAlert;
|
@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import ScreenReaderAlert from './component';
|
||||
import ScreenReaderAlertCollection from './collection';
|
||||
|
||||
const ScreenReaderAlertContainer = ({ ...props }) => {
|
||||
return (
|
||||
<ScreenReaderAlert
|
||||
{...{ ...props }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default withTracker(() => {
|
||||
const olderAlert = ScreenReaderAlertCollection
|
||||
.findOne({}, { sort: { insertTime: +1 } });
|
||||
|
||||
return { olderAlert };
|
||||
})(ScreenReaderAlertContainer);
|
@ -0,0 +1,19 @@
|
||||
import _ from 'lodash';
|
||||
import ScreenReaderAlertCollection from './collection';
|
||||
|
||||
export const addNewAlert = (text) => {
|
||||
const payload = {
|
||||
id: _.uniqueId('alert-'),
|
||||
insertedTime: Date.now(),
|
||||
text,
|
||||
};
|
||||
|
||||
return ScreenReaderAlertCollection.insert(payload);
|
||||
};
|
||||
|
||||
export const removeAlert = (id) => ScreenReaderAlertCollection.remove({ id });
|
||||
|
||||
export default {
|
||||
addNewAlert,
|
||||
removeAlert,
|
||||
};
|
@ -18,6 +18,7 @@ import { Session } from 'meteor/session';
|
||||
import Settings from '/imports/ui/services/settings';
|
||||
import { notify } from '/imports/ui/services/notification';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { getDateString } from '/imports/utils/string-utils';
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id;
|
||||
@ -666,13 +667,10 @@ export const getUserNamesLink = (docTitle, fnSortedLabel, lnSortedLabel) => {
|
||||
const link = document.createElement('a');
|
||||
const meeting = Meetings.findOne({ meetingId: Auth.meetingID },
|
||||
{ fields: { 'meetingProp.name': 1 } });
|
||||
const date = new Date();
|
||||
const time = `${date.getHours()}-${date.getMinutes()}`;
|
||||
const dateString = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}_${time}`;
|
||||
link.setAttribute('download', `bbb-${meeting.meetingProp.name}[users-list]_${dateString}.txt`);
|
||||
link.setAttribute('download', `bbb-${meeting.meetingProp.name}[users-list]_${getDateString()}.txt`);
|
||||
link.setAttribute(
|
||||
'href',
|
||||
`data: ${mimeType} ;charset=utf-16,${encodeURIComponent(namesListsString)}`,
|
||||
`data: ${mimeType};charset=utf-16,${encodeURIComponent(namesListsString)}`,
|
||||
);
|
||||
return link;
|
||||
};
|
||||
|
@ -22,9 +22,9 @@ const UserParticipantsContainer = (props) => {
|
||||
const { videoUsers, whiteboardUsers } = props;
|
||||
const { users: contextUsers, isReady } = useContextUsers();
|
||||
|
||||
const currentUser = contextUsers ? contextUsers[Auth.meetingID][Auth.userID] : null;
|
||||
const usersArray = contextUsers ? Object.values(contextUsers[Auth.meetingID]) : null;
|
||||
const users = contextUsers ? formatUsers(usersArray, videoUsers, whiteboardUsers) : [];
|
||||
const currentUser = contextUsers && isReady ? contextUsers[Auth.meetingID][Auth.userID] : null;
|
||||
const usersArray = contextUsers && isReady ? Object.values(contextUsers[Auth.meetingID]) : null;
|
||||
const users = contextUsers && isReady ? formatUsers(usersArray, videoUsers, whiteboardUsers) : [];
|
||||
|
||||
return (
|
||||
<UserParticipants {
|
||||
|
@ -323,7 +323,7 @@ class UserOptions extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl } = this.props;
|
||||
const { intl, isRTL } = this.props;
|
||||
|
||||
return (
|
||||
<BBBMenu
|
||||
@ -340,6 +340,16 @@ class UserOptions extends PureComponent {
|
||||
/>
|
||||
)}
|
||||
actions={this.renderMenuItems()}
|
||||
opts={{
|
||||
id: "default-dropdown-menu",
|
||||
keepMounted: true,
|
||||
transitionDuration: 0,
|
||||
elevation: 3,
|
||||
getContentAnchorEl: null,
|
||||
fullwidth: "true",
|
||||
anchorOrigin: { vertical: 'bottom', horizontal: isRTL ? 'right' : 'left' },
|
||||
transformOrigin: { vertical: 'top', horizontal: isRTL ? 'right' : 'left' },
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import logger from '/imports/startup/client/logger';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { notify } from '/imports/ui/services/notification';
|
||||
import UserOptions from './component';
|
||||
import { layoutSelect } from '/imports/ui/components/layout/context';
|
||||
|
||||
const propTypes = {
|
||||
users: PropTypes.arrayOf(Object).isRequired,
|
||||
@ -60,6 +61,8 @@ const UserOptionsContainer = withTracker((props) => {
|
||||
return name;
|
||||
};
|
||||
|
||||
const isRTL = layoutSelect((i) => i.isRTL);
|
||||
|
||||
return {
|
||||
toggleMuteAllUsers: () => {
|
||||
UserListService.muteAllUsers(Auth.userID);
|
||||
@ -91,6 +94,7 @@ const UserOptionsContainer = withTracker((props) => {
|
||||
meetingName: getMeetingName(),
|
||||
openLearningDashboardUrl: LearningDashboardService.openLearningDashboardUrl,
|
||||
dynamicGuestPolicy,
|
||||
isRTL,
|
||||
};
|
||||
})(UserOptions);
|
||||
|
||||
|
@ -151,6 +151,7 @@ const JoinVideoButton = ({
|
||||
emoji="device_list_selector"
|
||||
hideLabel
|
||||
label={intl.formatMessage(intlMessages.videoSettings)}
|
||||
rotate
|
||||
/>
|
||||
)}
|
||||
actions={actions}
|
||||
|
@ -23,7 +23,7 @@ const VideoListItem = (props) => {
|
||||
const {
|
||||
name, voiceUser, isFullscreenContext, layoutContextDispatch, user, onHandleVideoFocus,
|
||||
cameraId, numOfStreams, focused, onVideoItemMount, onVideoItemUnmount, onVirtualBgDrop,
|
||||
makeDragOperations,
|
||||
makeDragOperations, isRTL
|
||||
} = props;
|
||||
|
||||
const [videoIsReady, setVideoIsReady] = useState(false);
|
||||
@ -203,6 +203,50 @@ const VideoListItem = (props) => {
|
||||
animations={animations}
|
||||
{...makeDragOperations(onVirtualBgDrop, user.userId)}
|
||||
>
|
||||
{
|
||||
videoIsReady
|
||||
? (
|
||||
<>
|
||||
<Styled.TopBar>
|
||||
<PinArea
|
||||
user={user}
|
||||
/>
|
||||
<ViewActions
|
||||
videoContainer={videoContainer}
|
||||
name={name}
|
||||
cameraId={cameraId}
|
||||
isFullscreenContext={isFullscreenContext}
|
||||
layoutContextDispatch={layoutContextDispatch}
|
||||
/>
|
||||
</Styled.TopBar>
|
||||
<Styled.BottomBar>
|
||||
<UserActions
|
||||
name={name}
|
||||
user={user}
|
||||
cameraId={cameraId}
|
||||
numOfStreams={numOfStreams}
|
||||
onHandleVideoFocus={onHandleVideoFocus}
|
||||
focused={focused}
|
||||
onHandleMirror={() => setIsMirrored((value) => !value)}
|
||||
isRTL={isRTL}
|
||||
/>
|
||||
<UserStatus
|
||||
voiceUser={voiceUser}
|
||||
/>
|
||||
</Styled.BottomBar>
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<Styled.WebcamConnecting
|
||||
data-test="webcamConnecting"
|
||||
talking={talking}
|
||||
animations={animations}
|
||||
>
|
||||
<Styled.LoadingText>{name}</Styled.LoadingText>
|
||||
</Styled.WebcamConnecting>
|
||||
)
|
||||
}
|
||||
|
||||
<Styled.VideoContainer>
|
||||
<Styled.Video
|
||||
mirrored={isMirrored}
|
||||
|
@ -13,6 +13,7 @@ const VideoListItemContainer = (props) => {
|
||||
const { element } = fullscreen;
|
||||
const isFullscreenContext = (element === cameraId);
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
const isRTL = layoutSelect((i) => i.isRTL);
|
||||
|
||||
return (
|
||||
<VideoListItem
|
||||
@ -20,6 +21,7 @@ const VideoListItemContainer = (props) => {
|
||||
{...{
|
||||
isFullscreenContext,
|
||||
layoutContextDispatch,
|
||||
isRTL,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -51,7 +51,7 @@ const intlMessages = defineMessages({
|
||||
const UserActions = (props) => {
|
||||
const {
|
||||
name, cameraId, numOfStreams, onHandleVideoFocus, user, focused, onHandleMirror,
|
||||
isVideoSqueezed, videoContainer,
|
||||
isVideoSqueezed, videoContainer, isRTL
|
||||
} = props;
|
||||
|
||||
const intl = useIntl();
|
||||
@ -154,8 +154,8 @@ const UserActions = (props) => {
|
||||
elevation: 3,
|
||||
getContentAnchorEl: null,
|
||||
fullwidth: 'true',
|
||||
anchorOrigin: { vertical: 'bottom', horizontal: 'left' },
|
||||
transformorigin: { vertical: 'bottom', horizontal: 'left' },
|
||||
anchorOrigin: { vertical: 'bottom', horizontal: isRTL ? 'right' : 'left' },
|
||||
transformOrigin: { vertical: 'top', horizontal: isRTL ? 'right' : 'left' },
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
@ -8,6 +8,7 @@ import Styled from './styles';
|
||||
import { PANELS, ACTIONS } from '../layout/enums';
|
||||
import Settings from '/imports/ui/services/settings';
|
||||
import browserInfo from '/imports/utils/browserInfo';
|
||||
import Header from '/imports/ui/components/common/control-header/component';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
waitingUsersTitle: {
|
||||
@ -314,15 +315,12 @@ const WaitingUsers = (props) => {
|
||||
|
||||
return (
|
||||
<Styled.Panel data-test="note" isChrome={isChrome}>
|
||||
<Styled.Header>
|
||||
<Styled.Title data-test="noteTitle">
|
||||
<Styled.HideButton
|
||||
onClick={() => closePanel()}
|
||||
label={intl.formatMessage(intlMessages.title)}
|
||||
icon="left_arrow"
|
||||
/>
|
||||
</Styled.Title>
|
||||
</Styled.Header>
|
||||
<Header
|
||||
leftButtonProps={{
|
||||
onClick: () => closePanel(),
|
||||
label: intl.formatMessage(intlMessages.title),
|
||||
}}
|
||||
/>
|
||||
<Styled.ScrollableArea>
|
||||
{isGuestLobbyMessageEnabled ? (
|
||||
<Styled.LobbyMessage>
|
||||
|
@ -5,21 +5,16 @@ import {
|
||||
itemFocusBorder,
|
||||
colorGray,
|
||||
colorWhite,
|
||||
colorGrayDark,
|
||||
colorGrayLightest,
|
||||
colorOffWhite,
|
||||
} from '/imports/ui/stylesheets/styled-components/palette';
|
||||
import {
|
||||
borderSize,
|
||||
borderSizeLarge,
|
||||
mdPaddingX,
|
||||
mdPaddingY,
|
||||
pollHeaderOffset,
|
||||
toastContentWidth,
|
||||
} from '/imports/ui/stylesheets/styled-components/general';
|
||||
import { fontSizeBase } from '/imports/ui/stylesheets/styled-components/typography';
|
||||
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
|
||||
import { DivElipsis } from '/imports/ui/stylesheets/styled-components/placeholders';
|
||||
import Button from '/imports/ui/components/common/button/component';
|
||||
import { ScrollboxVertical } from '/imports/ui/stylesheets/styled-components/scrollable';
|
||||
|
||||
@ -150,55 +145,6 @@ const Panel = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const Header = styled.header`
|
||||
position: relative;
|
||||
top: ${pollHeaderOffset};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const Title = styled(DivElipsis)`
|
||||
flex: 1;
|
||||
|
||||
& > button, & > button:hover {
|
||||
max-width: ${toastContentWidth};
|
||||
}
|
||||
`;
|
||||
|
||||
const HideButton = styled(Button)`
|
||||
position: relative;
|
||||
background-color: ${colorWhite};
|
||||
display: block;
|
||||
margin: ${borderSizeLarge};
|
||||
margin-bottom: ${borderSize};
|
||||
padding-left: 0;
|
||||
padding-right: inherit;
|
||||
|
||||
[dir="rtl"] & {
|
||||
padding-left: inherit;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
& > i {
|
||||
color: ${colorGrayDark};
|
||||
font-size: smaller;
|
||||
|
||||
[dir="rtl"] & {
|
||||
-webkit-transform: scale(-1, 1);
|
||||
-moz-transform: scale(-1, 1);
|
||||
-ms-transform: scale(-1, 1);
|
||||
-o-transform: scale(-1, 1);
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${colorWhite};
|
||||
}
|
||||
`;
|
||||
|
||||
const LobbyMessage = styled.div`
|
||||
border-bottom: 1px solid ${colorGrayLightest};
|
||||
margin: 2px 2px 0 2px;
|
||||
@ -266,9 +212,6 @@ export default {
|
||||
Users,
|
||||
CustomButton,
|
||||
Panel,
|
||||
Header,
|
||||
Title,
|
||||
HideButton,
|
||||
LobbyMessage,
|
||||
RememberContainer,
|
||||
ScrollableArea,
|
||||
|
@ -24,6 +24,7 @@ const WebcamComponent = ({
|
||||
const [isFullscreen, setIsFullScreen] = useState(false);
|
||||
const [resizeStart, setResizeStart] = useState({ width: 0, height: 0 });
|
||||
const [cameraMaxWidth, setCameraMaxWidth] = useState(0);
|
||||
const [draggedAtLeastOneTime, setDraggedAtLeastOneTime] = useState(false);
|
||||
|
||||
const lastSize = Storage.getItem('webcamSize') || { width: 0, height: 0 };
|
||||
const { width: lastWidth, height: lastHeight } = lastSize;
|
||||
@ -119,9 +120,10 @@ const WebcamComponent = ({
|
||||
|
||||
const handleWebcamDragStop = (e) => {
|
||||
setIsDragging(false);
|
||||
setDraggedAtLeastOneTime(false);
|
||||
document.body.style.overflow = 'auto';
|
||||
|
||||
if (Object.values(CAMERADOCK_POSITION).includes(e.target.id)) {
|
||||
if (Object.values(CAMERADOCK_POSITION).includes(e.target.id) && draggedAtLeastOneTime) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_CAMERA_DOCK_POSITION,
|
||||
value: e.target.id,
|
||||
@ -160,6 +162,11 @@ const WebcamComponent = ({
|
||||
handle="video"
|
||||
bounds="html"
|
||||
onStart={handleWebcamDragStart}
|
||||
onDrag={() => {
|
||||
if (!draggedAtLeastOneTime) {
|
||||
setDraggedAtLeastOneTime(true);
|
||||
}
|
||||
}}
|
||||
onStop={handleWebcamDragStop}
|
||||
onMouseDown={
|
||||
cameraDock.isDraggable ? (e) => e.preventDefault() : undefined
|
||||
|
@ -81,14 +81,6 @@ const ToolbarButton = styled(Button)`
|
||||
& > i {
|
||||
color: ${toolbarListColor};
|
||||
}
|
||||
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: ${toolbarButtonBorderRadius};
|
||||
|
||||
[dir="rtl"] & {
|
||||
border-top-left-radius: ${toolbarButtonBorderRadius};
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
|
@ -32,6 +32,13 @@ const ButtonWrapper = styled.div`
|
||||
&:first-child > button {
|
||||
border-top-left-radius: ${toolbarButtonBorderRadius};
|
||||
border-bottom-left-radius: ${toolbarButtonBorderRadius};
|
||||
|
||||
[dir="rtl"] & {
|
||||
border-top-right-radius: ${toolbarButtonBorderRadius};
|
||||
border-bottom-right-radius: ${toolbarButtonBorderRadius};
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -58,10 +58,6 @@ class Auth {
|
||||
Storage.setItem('sessionToken', this._sessionToken);
|
||||
}
|
||||
|
||||
get sessionToken() {
|
||||
return this._sessionToken;
|
||||
}
|
||||
|
||||
get userID() {
|
||||
return this._userID;
|
||||
}
|
||||
@ -151,15 +147,6 @@ class Auth {
|
||||
};
|
||||
}
|
||||
|
||||
set _connectionID(connectionId) {
|
||||
this._connectionID = connectionId;
|
||||
Storage.setItem('sessionToken', this._connectionID);
|
||||
}
|
||||
|
||||
get sessionToken() {
|
||||
return this._sessionToken;
|
||||
}
|
||||
|
||||
set(
|
||||
meetingId,
|
||||
requesterUserId,
|
||||
@ -253,6 +240,7 @@ class Auth {
|
||||
initAnnotationsStreamListener();
|
||||
clearTimeout(validationTimeout);
|
||||
this.connectionID = authenticationTokenValidation.connectionId;
|
||||
this.connectionAuthTime = new Date().getTime();
|
||||
Session.set('userWillAuth', false);
|
||||
setTimeout(() => resolve(true), 100);
|
||||
break;
|
||||
|
@ -26,6 +26,10 @@ const GlobalStyle = createGlobalStyle`
|
||||
}
|
||||
}
|
||||
|
||||
.MuiList-padding {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.MuiPaper-root {
|
||||
background-color: ${dropdownBg};
|
||||
border-radius: ${borderRadius};
|
||||
|
@ -1,7 +1,5 @@
|
||||
|
||||
const TITLE_WITH_VIEW = 3;
|
||||
const ARIA_ALERT_TIMEOUT = 3000;
|
||||
const ARIA_ALERT_EXT_TIMEOUT = 15000;
|
||||
|
||||
const getTitleData = () => {
|
||||
const title = document.getElementsByTagName('title')[0];
|
||||
@ -23,27 +21,7 @@ export const unregisterTitleView = () => {
|
||||
title.text = data.join(' - ');
|
||||
};
|
||||
|
||||
export const alertScreenReader = (s = '') => {
|
||||
const app = document.getElementById('app');
|
||||
const ariaAlert = document.createElement("div");
|
||||
ariaAlert.setAttribute("id", "aria-alert");
|
||||
ariaAlert.setAttribute("role", "alert");
|
||||
ariaAlert.setAttribute("aria-hidden", false);
|
||||
ariaAlert.setAttribute("className", "sr-only");
|
||||
ariaAlert.textContent = s;
|
||||
app.appendChild(ariaAlert);
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById('aria-alert').remove();
|
||||
}, ARIA_ALERT_TIMEOUT);
|
||||
export default {
|
||||
registerTitleView,
|
||||
unregisterTitleView,
|
||||
};
|
||||
|
||||
export const politeSRAlert = (s = '') => {
|
||||
const liveArea = document.getElementById('aria-polite-alert')
|
||||
if (liveArea) liveArea.innerHTML = s;
|
||||
setTimeout(() => {
|
||||
if (liveArea) liveArea.innerHTML = '';
|
||||
}, ARIA_ALERT_EXT_TIMEOUT);
|
||||
};
|
||||
|
||||
export default { registerTitleView, unregisterTitleView, alertScreenReader, politeSRAlert };
|
||||
|
@ -1,3 +1,40 @@
|
||||
export const capitalizeFirstLetter = (s = '') => s.charAt(0).toUpperCase() + s.slice(1);
|
||||
|
||||
export default { capitalizeFirstLetter };
|
||||
import sanitize from 'sanitize-html';
|
||||
|
||||
export const capitalizeFirstLetter = (s = '') => s.charAt(0).toUpperCase() + s.slice(1);
|
||||
|
||||
/**
|
||||
* Returns a string in the format 'Year-Month-Day_Hour-Minutes'.
|
||||
* @param {Date} [date] - The Date object.
|
||||
*/
|
||||
export const getDateString = (date = new Date()) => {
|
||||
const hours = date.getHours().toString().padStart(2, 0);
|
||||
const minutes = date.getMinutes().toString().padStart(2, 0);
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, 0);
|
||||
const dayOfMonth = date.getDate().toString().padStart(2, 0);
|
||||
const time = `${hours}-${minutes}`;
|
||||
const dateString = `${date.getFullYear()}-${month}-${dayOfMonth}_${time}`;
|
||||
return dateString;
|
||||
};
|
||||
|
||||
export const stripTags = (text) => sanitize(text, { allowedTags: [] });
|
||||
|
||||
// Sanitize. See: https://gist.github.com/sagewall/47164de600df05fb0f6f44d48a09c0bd
|
||||
export const escapeHtml = (text) => {
|
||||
const div = document.createElement('div');
|
||||
div.appendChild(document.createTextNode(text));
|
||||
return div.innerHTML;
|
||||
};
|
||||
|
||||
export const unescapeHtml = (input) => {
|
||||
const e = document.createElement('textarea');
|
||||
e.innerHTML = input;
|
||||
return e.value;
|
||||
};
|
||||
|
||||
export default {
|
||||
capitalizeFirstLetter,
|
||||
getDateString,
|
||||
stripTags,
|
||||
escapeHtml,
|
||||
unescapeHtml,
|
||||
};
|
||||
|
@ -261,8 +261,6 @@
|
||||
updateMessage(_('app.guest.guestWait'));
|
||||
enableAnimation();
|
||||
try {
|
||||
const ATTEMPT_EVERY_MS = 10 * 1000; // 10 seconds
|
||||
|
||||
const sessionToken = getSearchParam('sessionToken');
|
||||
|
||||
if (!sessionToken) {
|
||||
@ -270,8 +268,32 @@
|
||||
updateMessage(_('app.guest.noSessionToken'));
|
||||
return;
|
||||
}
|
||||
//First, check that we already have a response
|
||||
const statusFromStorage = sessionStorage.getItem(`guestStatus_${sessionToken}`);
|
||||
|
||||
pollGuestStatus(sessionToken, ATTEMPT_EVERY_MS);
|
||||
if(statusFromStorage) {
|
||||
stopUpdatingWaitingPosition();
|
||||
|
||||
const statusParsed = JSON.parse(statusFromStorage);
|
||||
const { status, response } = statusParsed;
|
||||
|
||||
if(status === 'ALLOW'){
|
||||
updateLobbyMessage(_('app.guest.allow'));
|
||||
setTimeout(() => {
|
||||
disableAnimation();
|
||||
window.location = response.url;
|
||||
}, MESSAGE_TIMEOUT);
|
||||
} else {
|
||||
redirect(
|
||||
_('app.guest.' + response.messageKey),
|
||||
response.url,
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
pollGuestStatus(sessionToken, 0);
|
||||
} catch (e) {
|
||||
disableAnimation();
|
||||
console.error(e);
|
||||
@ -309,21 +331,28 @@
|
||||
.then((data) => {
|
||||
const code = data.response.returncode;
|
||||
|
||||
if (code === 'FAILED') {
|
||||
const response = data.response;
|
||||
|
||||
const saveStatusResponse = (status, response, token) => {
|
||||
stopUpdatingWaitingPosition();
|
||||
sessionStorage.setItem(`guestStatus_${token}`, JSON.stringify({ status, response }));
|
||||
};
|
||||
|
||||
if (code === 'FAILED') {
|
||||
saveStatusResponse(code, response, token);
|
||||
return redirect(_('app.guest.' + data.response.messageKey), data.response.url);
|
||||
}
|
||||
|
||||
const status = data.response.guestStatus;
|
||||
|
||||
if (status === 'DENY') {
|
||||
stopUpdatingWaitingPosition();
|
||||
saveStatusResponse(status, response, token);
|
||||
return redirect(_('app.guest.' + data.response.messageKey), data.response.url);
|
||||
}
|
||||
|
||||
if (status === 'ALLOW') {
|
||||
updateLobbyMessage(_('app.guest.allow'));
|
||||
stopUpdatingWaitingPosition();
|
||||
saveStatusResponse(status, response, token);
|
||||
// Timeout is required by accessibility to allow viewing of the message for a minimum of 3 seconds
|
||||
// before redirecting.
|
||||
setTimeout(() => {
|
||||
@ -335,7 +364,8 @@
|
||||
updatePositionInWaitingQueue(data.response.positionInWaitingQueue);
|
||||
updateLobbyMessage(data.response.lobbyMessage);
|
||||
|
||||
return pollGuestStatus(token, everyMs);
|
||||
const ATTEMPT_EVERY_MS = 10 * 1000; // 10 seconds
|
||||
return pollGuestStatus(token, ATTEMPT_EVERY_MS);
|
||||
});
|
||||
}, everyMs);
|
||||
};
|
||||
|
@ -111,7 +111,7 @@
|
||||
"app.userList.userOptions.unmuteAllLabel": "إيقاف كتم صوت الاجتماع",
|
||||
"app.userList.userOptions.unmuteAllDesc": "إلغاء كتم صوت الاجتماع",
|
||||
"app.userList.userOptions.lockViewersLabel": "قفل المشاهدين",
|
||||
"app.userList.userOptions.lockViewersDesc": "قفل وظائف معينة على اامشاركين في الاجتماع",
|
||||
"app.userList.userOptions.lockViewersDesc": "قفل وظائف معينة على المشاركين في الاجتماع",
|
||||
"app.userList.userOptions.guestPolicyLabel": "سياسة الضيف",
|
||||
"app.userList.userOptions.guestPolicyDesc": "تغيير إعداد سياسة الضيف في الاجتماع",
|
||||
"app.userList.userOptions.disableCam": "كاميرات المشاهدين معطلة",
|
||||
@ -444,7 +444,7 @@
|
||||
"app.actionsBar.actionsDropdown.presentationLabel": "إدارة العروض",
|
||||
"app.actionsBar.actionsDropdown.initPollLabel": "إنشاء تصويت جديد",
|
||||
"app.actionsBar.actionsDropdown.desktopShareLabel": "مشاركة الشاشة",
|
||||
"app.actionsBar.actionsDropdown.lockedDesktopShareLabel": "مشاركة الشاشة مقفل",
|
||||
"app.actionsBar.actionsDropdown.lockedDesktopShareLabel": "مشاركة الشاشة مقفلة",
|
||||
"app.actionsBar.actionsDropdown.stopDesktopShareLabel": "إيقاف مشاركة الشاشة",
|
||||
"app.actionsBar.actionsDropdown.presentationDesc": "إرفع عرضك",
|
||||
"app.actionsBar.actionsDropdown.initPollDesc": "إنشاء تصويت جديد",
|
||||
@ -501,17 +501,17 @@
|
||||
"app.audioNotification.audioFailedMessage": "فشل اتصال الصوت الخاص بك في الاتصال",
|
||||
"app.audioNotification.mediaFailedMessage": "فشل getUserMicMedia ، مسموح فقط للأصول الآمنة",
|
||||
"app.audioNotification.closeLabel": "غلق",
|
||||
"app.audioNotificaion.reconnectingAsListenOnly": "تم قفل صوت المشاهدين، حيث يتم اتصالك بالاستماع فقط",
|
||||
"app.audioNotificaion.reconnectingAsListenOnly": "تم قفل صوت المشاهدين، يتم اتصالك بالاستماع فقط",
|
||||
"app.breakoutJoinConfirmation.title": "انضم إلى الغرفة الجانبية",
|
||||
"app.breakoutJoinConfirmation.message": "هل تود الانضمام",
|
||||
"app.breakoutJoinConfirmation.confirmDesc": "انضم إليكم إلى الغرفة الجانبية",
|
||||
"app.breakoutJoinConfirmation.dismissLabel": "إلغاء",
|
||||
"app.breakoutJoinConfirmation.dismissDesc": "يغلق ويرفض الانضمام إلى الغرفة الجانبية",
|
||||
"app.breakoutJoinConfirmation.freeJoinMessage": "اختر الغرفة الجانبية للانضمام",
|
||||
"app.breakoutTimeRemainingMessage": "وقت انتهاء الغرفة المفرقة: {0}",
|
||||
"app.breakoutTimeRemainingMessage": "وقت انتهاء الغرفة الجانبية: {0}",
|
||||
"app.breakoutWillCloseMessage": "انتهى الوقت. سيتم إغلاق الغرفة الجانبية قريبًا",
|
||||
"app.breakout.dropdown.manageDuration": "إدارة المدة",
|
||||
"app.breakout.dropdown.destroyAll": "تدمير الجانبيات",
|
||||
"app.breakout.dropdown.manageDuration": "تغيير المدة",
|
||||
"app.breakout.dropdown.destroyAll": "إنهاء الغرف الجانبية",
|
||||
"app.breakout.dropdown.options": "خيارات الجانبيات",
|
||||
"app.calculatingBreakoutTimeRemaining": "حساب الوقت المتبقي ...",
|
||||
"app.audioModal.ariaTitle": "نافذة الانضمام إلى الصوت",
|
||||
@ -607,6 +607,7 @@
|
||||
"app.error.500": "عفوا، حدث خطأ ما",
|
||||
"app.error.userLoggedOut": "المستخدم لديه رمز جلسة غير صالح بسبب تسجيل الخروج",
|
||||
"app.error.ejectedUser": "المستخدم لديه رمز جلسة غير صالح بسبب الطرد",
|
||||
"app.error.joinedAnotherWindow": "يبدو أن هذه الجلسة مفتوحة في نافذة متصفح أخر.",
|
||||
"app.error.userBanned": "تم حظر المستخدم",
|
||||
"app.error.leaveLabel": "تسجيل الدخول مرة أخرى",
|
||||
"app.error.fallback.presentation.title": "حدث خطأ",
|
||||
@ -793,7 +794,7 @@
|
||||
"app.video.cancel": "إلغاء",
|
||||
"app.video.swapCam": "مبادلة",
|
||||
"app.video.swapCamDesc": "مبادلة اتجاه الكاميرات",
|
||||
"app.video.videoLocked": "مشاركة الكامرا مقفل",
|
||||
"app.video.videoLocked": "مشاركة الكامرا مقفلة",
|
||||
"app.video.videoButtonDesc": "مشاركة كاميرا",
|
||||
"app.video.videoMenu": "قائمة الفيديو",
|
||||
"app.video.videoMenuDisabled": "تم تعطيل قائمة كاميرا الفيديو في الإعدادات",
|
||||
@ -904,7 +905,7 @@
|
||||
"app.createBreakoutRoom.resetAssignmentsDesc": "إعادة تعيين جميع تعيينات غرفة المستخدم",
|
||||
"app.createBreakoutRoom.endAllBreakouts": "إنهاء جميع الغرف الجانبية",
|
||||
"app.createBreakoutRoom.chatTitleMsgAllRooms": "كل الغرف",
|
||||
"app.createBreakoutRoom.msgToBreakoutsSent": "تم إرسال الرسالة إلى {0} غرفة فرعية",
|
||||
"app.createBreakoutRoom.msgToBreakoutsSent": "تم إرسال الرسالة إلى {0} غرفة جانبية",
|
||||
"app.createBreakoutRoom.roomName": "{0} (غرفة- {1})",
|
||||
"app.createBreakoutRoom.doneLabel": "تم",
|
||||
"app.createBreakoutRoom.nextLabel": "التالي",
|
||||
|
@ -508,8 +508,6 @@
|
||||
"app.breakoutJoinConfirmation.freeJoinMessage": "Escull la sala separada per a unir-se",
|
||||
"app.breakoutTimeRemainingMessage": "Temps restant a la sala separada: {0}",
|
||||
"app.breakoutWillCloseMessage": "Temps finalitzat. La reunió separada es tancarà aviat",
|
||||
"app.breakout.dropdown.manageDuration": "Gestionar la durada",
|
||||
"app.breakout.dropdown.destroyAll": "Esborrar sales externes",
|
||||
"app.breakout.dropdown.options": "Opcions de sales externes",
|
||||
"app.calculatingBreakoutTimeRemaining": "Calculant temps restant ...",
|
||||
"app.audioModal.ariaTitle": "Entra a l'àudio modal",
|
||||
|
@ -35,7 +35,7 @@
|
||||
"app.captions.menu.ariaStartDesc": "Öffnet den Untertiteleditor und schließt diesen Dialog",
|
||||
"app.captions.menu.select": "Verfügbare Sprache auswählen",
|
||||
"app.captions.menu.ariaSelect": "Untertitelsprache",
|
||||
"app.captions.menu.subtitle": "Bitte eine Sprache und einen Schriftstil für die Untertitel der Konferenz wählen.",
|
||||
"app.captions.menu.subtitle": "Bitte wählen Sie eine Sprache und einen Schriftstil für die Untertitel Ihrer Konferenz.",
|
||||
"app.captions.menu.title": "Untertitel",
|
||||
"app.captions.menu.fontSize": "Größe",
|
||||
"app.captions.menu.fontColor": "Schriftfarbe",
|
||||
@ -62,7 +62,7 @@
|
||||
"app.note.converter-button.convertAndUpload": "Geteilte Notizen im Präsentations-Bereich laden",
|
||||
"app.pads.hint": "Esc drücken, um die Symbolleiste des Pads auszuwählen",
|
||||
"app.user.activityCheck": "Teilnehmeraktivitätsprüfung",
|
||||
"app.user.activityCheck.label": "Prüfen, ob der:die Teilnehmer:in noch in der Konferenz ist ({0})",
|
||||
"app.user.activityCheck.label": "Prüfen, ob der Teilnehmer noch in der Konferenz ist ({0})",
|
||||
"app.user.activityCheck.check": "Prüfen",
|
||||
"app.userList.usersTitle": "Teilnehmer",
|
||||
"app.userList.participantsTitle": "Teilnehmer",
|
||||
@ -87,11 +87,11 @@
|
||||
"app.userList.menu.clearStatus.label": "Status zurücksetzen",
|
||||
"app.userList.menu.removeUser.label": "Teilnehmer entfernen",
|
||||
"app.userList.menu.removeConfirmation.label": "Teilnehmer entfernen ({0})",
|
||||
"app.userlist.menu.removeConfirmation.desc": "Teilnehmer:in sperren, sodass eine erneute Teilnahme an dieser Konferenz nicht mehr möglich ist.",
|
||||
"app.userlist.menu.removeConfirmation.desc": "Teilnehmer sperren, sodass eine erneute Teilnahme an dieser Konferenz nicht mehr möglich ist.",
|
||||
"app.userList.menu.muteUserAudio.label": "Teilnehmer stummschalten",
|
||||
"app.userList.menu.unmuteUserAudio.label": "Stummschaltung aufheben",
|
||||
"app.userList.menu.webcamPin.label": "Webcam der Teilnehmer:in fixieren",
|
||||
"app.userList.menu.webcamUnpin.label": "Webcam der Teilnehmer:in lösen",
|
||||
"app.userList.menu.webcamPin.label": "Webcam der Teilnehmer anheften",
|
||||
"app.userList.menu.webcamUnpin.label": "Webcam der Teilnehmer lösen",
|
||||
"app.userList.menu.giveWhiteboardAccess.label" : "Zugriff auf Whiteboard erlauben",
|
||||
"app.userList.menu.removeWhiteboardAccess.label": "Zugriff auf Whiteboard aufheben",
|
||||
"app.userList.menu.ejectUserCameras.label": "Kameras schließen",
|
||||
@ -104,7 +104,7 @@
|
||||
"app.userList.menu.makePresenter.label": "Zum Präsentator machen",
|
||||
"app.userList.userOptions.manageUsersLabel": "Teilnehmer verwalten",
|
||||
"app.userList.userOptions.muteAllLabel": "Alle Teilnehmer stummschalten",
|
||||
"app.userList.userOptions.muteAllDesc": "Alle Teilnehmer:innen der Konferenz werden stumm geschaltet",
|
||||
"app.userList.userOptions.muteAllDesc": "Alle Teilnehmer der Konferenz werden stumm geschaltet",
|
||||
"app.userList.userOptions.clearAllLabel": "Alle Statusicons löschen",
|
||||
"app.userList.userOptions.clearAllDesc": "Alle Statusicons der Teilnehmer löschen",
|
||||
"app.userList.userOptions.muteAllExceptPresenterLabel": "Alle Teilnehmer außer den Präsentator stummschalten",
|
||||
@ -112,7 +112,7 @@
|
||||
"app.userList.userOptions.unmuteAllLabel": "Konferenz-Stummschaltung aufheben",
|
||||
"app.userList.userOptions.unmuteAllDesc": "Hebt die Konferenz-Stummschaltung auf",
|
||||
"app.userList.userOptions.lockViewersLabel": "Teilnehmerrechte einschränken",
|
||||
"app.userList.userOptions.lockViewersDesc": "Schränkt bestimmte Funktionen der Konferenzteilnehmer:innen ein",
|
||||
"app.userList.userOptions.lockViewersDesc": "Schränkt bestimmte Funktionen der Konferenzteilnehmer ein",
|
||||
"app.userList.userOptions.guestPolicyLabel": "Gastzugang regeln",
|
||||
"app.userList.userOptions.guestPolicyDesc": "Grundregel für den Gastzugang ändern",
|
||||
"app.userList.userOptions.disableCam": "Teilnehmerwebcams sind deaktiviert",
|
||||
@ -120,21 +120,21 @@
|
||||
"app.userList.userOptions.disablePrivChat": "Privater Chat ist deaktiviert",
|
||||
"app.userList.userOptions.disablePubChat": "Öffentlicher Chat ist deaktiviert",
|
||||
"app.userList.userOptions.disableNotes": "Geteilte Notizen sind jetzt gesperrt",
|
||||
"app.userList.userOptions.hideUserList": "Liste der Teilnehmer:innen ist jetzt für diese ausgeblendet",
|
||||
"app.userList.userOptions.hideUserList": "Teilnehmerliste ist jetzt für die Teilnehmer ausgeblendet",
|
||||
"app.userList.userOptions.webcamsOnlyForModerator": "Nur Moderatoren können die Teilnehmerwebcams sehen (wegen eingeschränkter Rechteeinstellungen)",
|
||||
"app.userList.content.participants.options.clearedStatus": "Status aller Teilnehmer:innen zurückgesetzt",
|
||||
"app.userList.content.participants.options.clearedStatus": "Status aller Teilnehmer zurückgesetzt",
|
||||
"app.userList.userOptions.enableCam": "Teilnehmer dürfen ihre Webcams verwenden",
|
||||
"app.userList.userOptions.enableMic": "Teilnehmer dürfen ihre Mikrofone verwenden",
|
||||
"app.userList.userOptions.enablePrivChat": "Privater Chat ist erlaubt",
|
||||
"app.userList.userOptions.enablePubChat": "Gemeinsamer Chat ist erlaubt",
|
||||
"app.userList.userOptions.enablePubChat": "Öffentlicher Chat ist erlaubt",
|
||||
"app.userList.userOptions.enableNotes": "Geteilte Notizen sind jetzt erlaubt",
|
||||
"app.userList.userOptions.showUserList": "Liste der Teilnehmer:innen ist für diese sichtbar",
|
||||
"app.userList.userOptions.enableOnlyModeratorWebcam": "Webcam nun freigeben um für jede:n sichtbar zu sein.",
|
||||
"app.userList.userOptions.showUserList": "Teilnehmerliste ist jetzt für die Teilnehmer sichtbar",
|
||||
"app.userList.userOptions.enableOnlyModeratorWebcam": "Sie können Ihre Webcam jetzt freigeben, jeder wird Sie sehen.",
|
||||
"app.userList.userOptions.savedNames.title": "Liste der Teilnehmer in der Konferenz {0} vom {1}",
|
||||
"app.userList.userOptions.sortedFirstName.heading": "Sortiert nach Vorname:",
|
||||
"app.userList.userOptions.sortedLastName.heading": "Sortiert nach Nachname:",
|
||||
"app.userList.userOptions.hideViewersCursor": "Cursor der Teilnehmer:innen sind gesperrt",
|
||||
"app.userList.userOptions.showViewersCursor": "Cursor der Teilnehmer:innen sind freigegeben",
|
||||
"app.userList.userOptions.hideViewersCursor": "Cursor der Teilnehmer sind gesperrt",
|
||||
"app.userList.userOptions.showViewersCursor": "Cursor der Teilnehmer sind freigegeben",
|
||||
"app.media.label": "Media",
|
||||
"app.media.autoplayAlertDesc": "Zugang erlauben",
|
||||
"app.media.screenshare.start": "Bildschirmfreigabe wurde gestartet",
|
||||
@ -142,7 +142,7 @@
|
||||
"app.media.screenshare.endDueToDataSaving": "Bildschirmübertragung im Datensparmodus gestoppt",
|
||||
"app.media.screenshare.unavailable": "Bildschirmfreigabe nicht verfügbar",
|
||||
"app.media.screenshare.notSupported": "Bildschirmfreigabe wird in diesem Browser nicht unterstützt.",
|
||||
"app.media.screenshare.autoplayBlockedDesc": "Wir benötigen deine/Ihre Zustimmung, um den Bildschirm des Präsentators zu zeigen.",
|
||||
"app.media.screenshare.autoplayBlockedDesc": "Wir benötigen Ihre Zustimmung, um den Bildschirm des Präsentators zu zeigen.",
|
||||
"app.media.screenshare.autoplayAllowLabel": "Geteilten Bildschirm ansehen",
|
||||
"app.screenshare.presenterLoadingLabel": "Ihr Bildschirm wird freigegeben",
|
||||
"app.screenshare.viewerLoadingLabel": "Der Bildschirm des Präsentators wird geladen",
|
||||
@ -150,14 +150,14 @@
|
||||
"app.screenshare.screenshareFinalError": "Fehler {0}. Bildschirm konnte nicht geteilt werden.",
|
||||
"app.screenshare.screenshareRetryError": "Fehler {0}. Bitte versuchen, den Bildschirm erneut zu teilen.",
|
||||
"app.screenshare.screenshareRetryOtherEnvError": "Fehler {0}. Der Bildschirm konnte nicht freigegeben werden. Bitte mit einem anderen Browser oder einem anderen Gerät probieren.",
|
||||
"app.screenshare.screenshareUnsupportedEnv": "Fehler {0}. Dieser Browser wird nicht unterstützt. Bitte einen anderen Browser oder ein anderes Endgerät probieren.",
|
||||
"app.screenshare.screenshareUnsupportedEnv": "Fehler {0}. Dieser Browser wird nicht unterstützt. Bitte einen anderen Browser oder ein anderes Gerät probieren.",
|
||||
"app.screenshare.screensharePermissionError": "Fehler {0}. Die Berechtigung zur Bildschirmfreigabe muss erteilt werden.",
|
||||
"app.meeting.ended": "Diese Konferenz wurde beendet",
|
||||
"app.meeting.meetingTimeRemaining": "Verbleibende Konferenzzeit: {0}",
|
||||
"app.meeting.meetingTimeHasEnded": "Die Zeit ist abgelaufen. Die Konferenz wird in Kürze beendet",
|
||||
"app.meeting.endedByUserMessage": "Diese Konferenz wurde durch {0} beendet",
|
||||
"app.meeting.endedByNoModeratorMessageSingular": "Die Konferenz ist beendet, da nach einer Minute kein:e Moderator:in anwesend ist",
|
||||
"app.meeting.endedByNoModeratorMessagePlural": "Die Konferenz ist beendet, da nach {0} Minuten kein:e Moderator:in anwesend ist",
|
||||
"app.meeting.endedByNoModeratorMessageSingular": "Die Konferenz ist beendet, da nach einer Minute kein Moderator anwesend ist",
|
||||
"app.meeting.endedByNoModeratorMessagePlural": "Die Konferenz ist beendet, da nach {0} Minuten kein Moderator anwesend ist",
|
||||
"app.meeting.endedMessage": "Sie werden zum Startbildschirm weitergeleitet",
|
||||
"app.meeting.alertMeetingEndsUnderMinutesSingular": "Konferenz endet in einer Minute.",
|
||||
"app.meeting.alertMeetingEndsUnderMinutesPlural": "Konferenz endet in {0} Minuten.",
|
||||
@ -204,8 +204,8 @@
|
||||
"app.presentation.presentationToolbar.goToSlide": "Folie {0}",
|
||||
"app.presentation.placeholder": "Es gibt derzeit keine aktive Präsentation",
|
||||
"app.presentationUploder.title": "Präsentation",
|
||||
"app.presentationUploder.message": "Präsentationen können als Office-Dokumente oder PDF-Dateien hochgeladen werden. PDF-Dateien haben dabei die bessere Qualität. Eine Präsentation muss dann durch das runde Markierungsfeld auf der rechten Seite ausgewählt sein.",
|
||||
"app.presentationUploder.extraHint": "Jede Datei darf {0} MB und {1} Seiten nicht überschreiten.",
|
||||
"app.presentationUploder.message": "Als Präsentator können Präsentationen als Office-Dokumente oder PDF-Dateien hochgeladen werden. PDF-Dateien haben dabei die bessere Qualität. Bitte achten Sie darauf, dass eine Präsentation über das runde Auswahlfeld auf der rechten Seite ausgewählt ist.",
|
||||
"app.presentationUploder.extraHint": "WICHTIG: Jede Datei darf {0} MB und {1} Seiten nicht überschreiten.",
|
||||
"app.presentationUploder.uploadLabel": "Hochladen",
|
||||
"app.presentationUploder.confirmLabel": "Bestätigen",
|
||||
"app.presentationUploder.confirmDesc": "Änderungen speichern und Präsentation starten",
|
||||
@ -257,12 +257,12 @@
|
||||
"app.poll.autoOptionInstructions.label": "Automatische Optionierung ist aktiviert – schreiben Sie die Umfragefrage und Option(en) im angegebenen Format.",
|
||||
"app.poll.maxOptionsWarning.label": "Nur die ersten 5 Optionen können verwendet werden!",
|
||||
"app.poll.pollPaneTitle": "Umfrage",
|
||||
"app.poll.enableMultipleResponseLabel": "Mehrere Antworten pro Befragte:n zulassen?",
|
||||
"app.poll.enableMultipleResponseLabel": "Mehrere Antworten pro Befragten zulassen?",
|
||||
"app.poll.quickPollTitle": "Schnellumfrage",
|
||||
"app.poll.hidePollDesc": "Versteckt das Umfragemenü",
|
||||
"app.poll.quickPollInstruction": "Bitte eine der unten stehenden Optionen wählen, um die Umfrage zu starten.",
|
||||
"app.poll.activePollInstruction": "Dieses Fenster offen lassen, um auf die Antworten der Teilnehmer:innen zu warten. Sobald auf 'Umfrageergebnisse veröffentlichen' geklickt wird, werden die Ergebnisse angezeigt und wird die Umfrage beendet.",
|
||||
"app.poll.dragDropPollInstruction": "Um die Umfrageoptionen automatisch auszufüllen, eine Textdatei mit den Umfrageoptionen per Drag&Drop in das hervorgehobene Feld ziehen",
|
||||
"app.poll.quickPollInstruction": "Wählen Sie eine der unten stehenden Optionen, um die Umfrage zu starten.",
|
||||
"app.poll.activePollInstruction": "Lassen Sie dieses Fenster offen, um auf die Antworten der Teilnehmer zu warten. Sobald Sie auf 'Umfrageergebnisse veröffentlichen' klicken, werden die Ergebnisse angezeigt und die Umfrage beendet.",
|
||||
"app.poll.dragDropPollInstruction": "Um die Umfrageoptionen automatisch auszufüllen, eine Textdatei mit den Umfrageoptionen per Drag&Drop in das hervorgehobene Feld ziehen",
|
||||
"app.poll.customPollTextArea": "Umfrageoptionen ausfüllen",
|
||||
"app.poll.publishLabel": "Umfrage veröffentlichen",
|
||||
"app.poll.cancelPollLabel": "Abbrechen",
|
||||
@ -273,13 +273,13 @@
|
||||
"app.poll.customPlaceholder": "Umfrageoption hinzufügen",
|
||||
"app.poll.noPresentationSelected": "Keine Präsentation ausgewählt! Bitte eine auswählen.",
|
||||
"app.poll.clickHereToSelect": "Zum Auswählen hier klicken",
|
||||
"app.poll.question.label" : "Eine Frage stellen ...",
|
||||
"app.poll.optionalQuestion.label" : "Eine Frage stellen (optional) ...",
|
||||
"app.poll.question.label" : "Eine Frage stellen...",
|
||||
"app.poll.optionalQuestion.label" : "Eine Frage stellen (optional)...",
|
||||
"app.poll.userResponse.label" : "Teilnehmerantwort",
|
||||
"app.poll.responseTypes.label" : "Antworttypen",
|
||||
"app.poll.optionDelete.label" : "Löschen",
|
||||
"app.poll.responseChoices.label" : "Antwortmöglichkeiten",
|
||||
"app.poll.typedResponse.desc" : "Den Teilnehmer:innen wird ein Textfeld angezeigt, um die Antwort einzutragen.",
|
||||
"app.poll.typedResponse.desc" : "Den Teilnehmern wird ein Textfeld angezeigt, um die Antwort einzutragen.",
|
||||
"app.poll.addItem.label" : "Element hinzufügen",
|
||||
"app.poll.start.label" : "Umfrage starten",
|
||||
"app.poll.secretPoll.label" : "Anonyme Umfrage",
|
||||
@ -330,7 +330,7 @@
|
||||
"app.connectingMessage": "Verbinde...",
|
||||
"app.waitingMessage": "Verbindung unterbrochen. Versuche in {0} Sekunden erneut zu verbinden...",
|
||||
"app.retryNow": "Jetzt erneut versuchen",
|
||||
"app.muteWarning.label": "Auf {0} klicken, um die Stummschaltung aufzuheben.",
|
||||
"app.muteWarning.label": "Auf {0} klicken, um Ihre Stummschaltung aufzuheben.",
|
||||
"app.muteWarning.disableMessage": "Hinweis auf Stummschaltung deaktiviert, bis die Stummschaltung aufgehoben wird",
|
||||
"app.muteWarning.tooltip": "Klicken, um den Hinweis bis zur nächsten Aufhebung der Stummschaltung zu schließen",
|
||||
"app.navBar.settingsDropdown.optionsLabel": "Optionen",
|
||||
@ -361,7 +361,7 @@
|
||||
"app.leaveConfirmation.confirmLabel": "Verlassen",
|
||||
"app.leaveConfirmation.confirmDesc": "Hiermit verlassen Sie die Konferenz",
|
||||
"app.endMeeting.title": "{0} beenden",
|
||||
"app.endMeeting.description": "Mit dieser Aktion wird die Sitzung für {0} aktive(n) Teilnehmer:innen beendet. Sicher, dass die Sitzung beendet werden soll?",
|
||||
"app.endMeeting.description": "Mit dieser Aktion wird die Konferenz für {0} aktive(n) Teilnehmer beendet. Sind Sie sicher, dass Sie diese Konferenz beenden möchten?",
|
||||
"app.endMeeting.noUserDescription": "Sind Sie sicher, dass Sie die Konferenz beenden wollen?",
|
||||
"app.endMeeting.contentWarning": "Chatnachrichten, geteilte Notizen, Whiteboard-Inhalte und geteilte Dokumente dieser Konferenz sind nicht mehr direkt zugänglich",
|
||||
"app.endMeeting.yesLabel": "Ja",
|
||||
@ -436,7 +436,7 @@
|
||||
"app.statusNotifier.and": "und",
|
||||
"app.switch.onLabel": "AN",
|
||||
"app.switch.offLabel": "AUS",
|
||||
"app.talkingIndicator.ariaMuteDesc" : "Auswählen, um Teilnehmer:in stummzuschalten",
|
||||
"app.talkingIndicator.ariaMuteDesc" : "Auswählen, um Teilnehmer stummzuschalten",
|
||||
"app.talkingIndicator.isTalking" : "{0} spricht",
|
||||
"app.talkingIndicator.moreThanMaxIndicatorsTalking" : "{0}+ sprechen",
|
||||
"app.talkingIndicator.moreThanMaxIndicatorsWereTalking" : "{0}+ sprachen",
|
||||
@ -460,8 +460,8 @@
|
||||
"app.actionsBar.actionsDropdown.captionsDesc": "Untertitelfenster umschalten",
|
||||
"app.actionsBar.actionsDropdown.takePresenter": "Zum Präsentator werden",
|
||||
"app.actionsBar.actionsDropdown.takePresenterDesc": "Sich selbst zum neuen Präsentator machen",
|
||||
"app.actionsBar.actionsDropdown.selectRandUserLabel": "Zufällig auswählen",
|
||||
"app.actionsBar.actionsDropdown.selectRandUserDesc": "Wählt eine:n Teilnehmer:in nach dem Zufallsprinzip aus.",
|
||||
"app.actionsBar.actionsDropdown.selectRandUserLabel": "Zufälligen Teilnehmer auswählen",
|
||||
"app.actionsBar.actionsDropdown.selectRandUserDesc": "Wählt einen Teilnehmer nach dem Zufallsprinzip aus",
|
||||
"app.actionsBar.emojiMenu.statusTriggerLabel": "Status setzen",
|
||||
"app.actionsBar.emojiMenu.awayLabel": "Abwesend",
|
||||
"app.actionsBar.emojiMenu.awayDesc": "Ihren Status auf abwesend setzen",
|
||||
@ -511,7 +511,7 @@
|
||||
"app.breakoutJoinConfirmation.freeJoinMessage": "Gruppenraum auswählen, dem beigetreten werden soll",
|
||||
"app.breakoutTimeRemainingMessage": "Verbleibende Gruppenraumzeit: {0}",
|
||||
"app.breakoutWillCloseMessage": "Zeit abgelaufen. Der Gruppenraum wird in Kürze geschlossen.",
|
||||
"app.breakout.dropdown.manageDuration": "Dauer verwalten",
|
||||
"app.breakout.dropdown.manageDuration": "Dauer ändern",
|
||||
"app.breakout.dropdown.destroyAll": "Gruppenräume beenden",
|
||||
"app.breakout.dropdown.options": "Gruppenraumoptionen",
|
||||
"app.calculatingBreakoutTimeRemaining": "Berechne verbleibende Zeit...",
|
||||
@ -576,13 +576,13 @@
|
||||
"app.audio.listenOnly.closeLabel": "Schließen",
|
||||
"app.audio.permissionsOverlay.title": "Zugriff auf das Mikrofon erlauben",
|
||||
"app.audio.permissionsOverlay.hint": "Zugriff auf die Mediengeräte muss im Browser erlaubt werden, um an einer Audiokonferenz teilnehmen zu können.",
|
||||
"app.error.removed": "Die Konferenz musste verlassen werden.",
|
||||
"app.error.meeting.ended": "Konferenz wurde verlassen",
|
||||
"app.meeting.logout.duplicateUserEjectReason": "Bereits anwesende:r Teilnehmer:in versucht erneut der Konferenz beizutreten",
|
||||
"app.error.removed": "Sie wurden aus der Konferenz entfernt",
|
||||
"app.error.meeting.ended": "Sie haben die Konferenz verlassen",
|
||||
"app.meeting.logout.duplicateUserEjectReason": "Bereits anwesender Teilnehmer versucht erneut der Konferenz beizutreten",
|
||||
"app.meeting.logout.permissionEjectReason": "Aufgrund einer Rechteverletzung aus der Konferenz entfernt worden",
|
||||
"app.meeting.logout.ejectedFromMeeting": "Sie wurden aus der Konferenz entfernt",
|
||||
"app.meeting.logout.validateTokenFailedEjectReason": "Verifikation des Autorisierungsmerkmals fehlgeschlagen",
|
||||
"app.meeting.logout.userInactivityEjectReason": "Teilnehmer:in war zu lange inaktiv",
|
||||
"app.meeting.logout.userInactivityEjectReason": "Teilnehmer war zu lange inaktiv",
|
||||
"app.meeting-ended.rating.legendLabel": "Feedbackbewertung",
|
||||
"app.meeting-ended.rating.starLabel": "Stern",
|
||||
"app.modal.close": "Schließen",
|
||||
@ -590,13 +590,13 @@
|
||||
"app.modal.confirm": "Fertig",
|
||||
"app.modal.newTab": "(Öffnet neuen Tab)",
|
||||
"app.modal.confirm.description": "Änderungen speichern und Dialog schließen",
|
||||
"app.modal.randomUser.noViewers.description": "Kein:e Teilnehmer:in zur zufälligen Auswahl verfügbar",
|
||||
"app.modal.randomUser.selected.description": "zufällig ausgewählt",
|
||||
"app.modal.randomUser.title": "Zufällig ausgewählte:r Teilnehmer:in",
|
||||
"app.modal.randomUser.noViewers.description": "Keine Teilnehmer zur zufälligen Auswahl verfügbar",
|
||||
"app.modal.randomUser.selected.description": "Sie wurden zufällig ausgewählt",
|
||||
"app.modal.randomUser.title": "Zufällig ausgewählter Teilnehmer",
|
||||
"app.modal.randomUser.who": "Wer wird ausgewählt...?",
|
||||
"app.modal.randomUser.alone": "Es gibt nur eine:n Teilnehmer:in",
|
||||
"app.modal.randomUser.alone": "Es gibt nur einen Teilnehmer",
|
||||
"app.modal.randomUser.reselect.label": "Erneut auswählen",
|
||||
"app.modal.randomUser.ariaLabel.title": "Dialog für zufällig ausgewählte:n Teilnehmer:in",
|
||||
"app.modal.randomUser.ariaLabel.title": "Dialog für zufällig ausgewählten Teilnehmer",
|
||||
"app.dropdown.close": "Schließen",
|
||||
"app.dropdown.list.item.activeLabel": "Aktiv",
|
||||
"app.error.400": "Ungültige Anfrage",
|
||||
@ -606,9 +606,10 @@
|
||||
"app.error.408": "Authentifizierung fehlgeschlagen",
|
||||
"app.error.410": "Die Konferenz ist zu Ende",
|
||||
"app.error.500": "Ups, irgendwas ist schiefgelaufen",
|
||||
"app.error.userLoggedOut": "Teilnehmer:in hat einen ungültigen Konferenz-Token, weil er:sie sich ausgeloggt hat",
|
||||
"app.error.ejectedUser": "Teilnehmer:in hat einen ungültigen Konferenz-Token, weil er:sie gesperrt wurde",
|
||||
"app.error.userBanned": "Teilnehmer:in wurde gesperrt",
|
||||
"app.error.userLoggedOut": "Teilnehmer hat einen ungültigen Konferenz-Token, weil er sich ausgeloggt hat",
|
||||
"app.error.ejectedUser": "Teilnehmer hat einen ungültigen Konferenz-Token, weil er gesperrt wurde",
|
||||
"app.error.joinedAnotherWindow": "Diese Konferenz scheint in einem anderen Browserfenster geöffnet zu sein.",
|
||||
"app.error.userBanned": "Teilnehmer wurde gesperrt",
|
||||
"app.error.leaveLabel": "Erneut einloggen",
|
||||
"app.error.fallback.presentation.title": "Es ist ein Fehler aufgetreten",
|
||||
"app.error.fallback.presentation.description": "Er wurde protokolliert. Bitte versuchen, die Seite neu zu laden.",
|
||||
@ -617,30 +618,30 @@
|
||||
"app.guest.errorSeeConsole": "Fehler: Weitere Details in der Konsole.",
|
||||
"app.guest.noModeratorResponse": "Keine Antwort vom Moderator.",
|
||||
"app.guest.noSessionToken": "Kein Konferenz-Token erhalten.",
|
||||
"app.guest.windowTitle": "Wartebereich für Gäste",
|
||||
"app.guest.windowTitle": "BigBlueButton - Wartebereich für Gäste",
|
||||
"app.guest.missingToken": "Gast fehlt Konferenz-Token.",
|
||||
"app.guest.missingSession": "Gast fehlt in der Konferenz.",
|
||||
"app.guest.missingMeeting": "Konferenz existiert nicht.",
|
||||
"app.guest.meetingEnded": "Konferenz beendet.",
|
||||
"app.guest.guestWait": "Bitte warten Sie, bis ein Moderator Ihre Teilnahme an der Konferenz freigibt.",
|
||||
"app.guest.guestDeny": "Moderator:in hat die Teilnahme an der Konferenz abgelehnt.",
|
||||
"app.guest.guestDeny": "Der Moderator hat die Teilnahme an der Konferenz abgelehnt.",
|
||||
"app.guest.seatWait": "Gast wartet auf die Teilnahme an der Konferenz.",
|
||||
"app.guest.allow": "Gast zugelassen und zur Konferenz weitergeleitet.",
|
||||
"app.guest.firstPositionInWaitingQueue": "Nun Erste:r in der Warteschlange!",
|
||||
"app.guest.firstPositionInWaitingQueue": "Sie sind der Erste in der Warteschlange!",
|
||||
"app.guest.positionInWaitingQueue": "Aktuelle Position in der Warteschlange: ",
|
||||
"app.guest.guestInvalid": "Gastteilnehmer ist ungültig",
|
||||
"app.guest.meetingForciblyEnded": "An einer Konferenz, die bereits beendet wurde, kann nicht teilgenommen werden",
|
||||
"app.userList.guest.waitingUsers": "Wartende Teilnehmer:innen",
|
||||
"app.userList.guest.waitingUsers": "Wartende Teilnehmer",
|
||||
"app.userList.guest.waitingUsersTitle": "Teilnehmerverwaltung",
|
||||
"app.userList.guest.optionTitle": "Unbearbeitete Teilnehmer:innen überprüfen",
|
||||
"app.userList.guest.optionTitle": "Unbearbeitete Teilnehmer überprüfen",
|
||||
"app.userList.guest.allowAllAuthenticated": "Alle Autorisierten erlauben",
|
||||
"app.userList.guest.allowAllGuests": "Alle Gäste erlauben",
|
||||
"app.userList.guest.allowEveryone": "Alle erlauben",
|
||||
"app.userList.guest.denyEveryone": "Alle verweigern",
|
||||
"app.userList.guest.pendingUsers": "{0} unbearbeitete Teilnehmer:innen",
|
||||
"app.userList.guest.noPendingUsers": "Derzeit keine ausstehenden Teilnehmer:innen ...",
|
||||
"app.userList.guest.pendingUsers": "{0} unbearbeitete Teilnehmer",
|
||||
"app.userList.guest.noPendingUsers": "Derzeit keine ausstehenden Teilnehmer...",
|
||||
"app.userList.guest.pendingGuestUsers": "{0} wartende Gäste",
|
||||
"app.userList.guest.pendingGuestAlert": "Ist der Konferenz beigetreten und wartet auf Teilnahmeerlaubnis",
|
||||
"app.userList.guest.pendingGuestAlert": "Ist der Konferenz beigetreten und wartet auf Ihre Teilnahmeerlaubnis",
|
||||
"app.userList.guest.rememberChoice": "Auswahl für die Zukunft speichern",
|
||||
"app.userList.guest.emptyMessage": "Momentan keine Nachricht vorhanden",
|
||||
"app.userList.guest.inputPlaceholder": "Nachricht für den Wartebereich",
|
||||
@ -655,7 +656,7 @@
|
||||
"app.toast.chat.system": "System",
|
||||
"app.toast.clearedEmoji.label": "Emojistatus zurückgesetzt",
|
||||
"app.toast.setEmoji.label": "Emojistatus auf {0} gesetzt",
|
||||
"app.toast.meetingMuteOn.label": "Alle Teilnehmer:innen wurden stummgeschaltet",
|
||||
"app.toast.meetingMuteOn.label": "Alle Teilnehmer wurden stummgeschaltet",
|
||||
"app.toast.meetingMuteOff.label": "Konferenz-Stummschaltung ausgeschaltet",
|
||||
"app.toast.setEmoji.raiseHand": "Sie haben Ihre Hand gehoben",
|
||||
"app.toast.setEmoji.lowerHand": "Habe meine Hand gesenkt",
|
||||
@ -692,7 +693,7 @@
|
||||
"app.shortcut-help.toggleFullscreenKey": "Enter",
|
||||
"app.shortcut-help.nextSlideKey": "Pfeil rechts",
|
||||
"app.shortcut-help.previousSlideKey": "Pfeil links",
|
||||
"app.lock-viewers.title": "Rechte der Teilnehmer:innen einschränken",
|
||||
"app.lock-viewers.title": "Teilnehmerrechte einschränken",
|
||||
"app.lock-viewers.description": "Diese Optionen ermöglichen es, bestimmte Funktionen für Teilnehmer einzuschränken.",
|
||||
"app.lock-viewers.featuresLable": "Funktion",
|
||||
"app.lock-viewers.lockStatusLabel": "Status",
|
||||
@ -786,7 +787,7 @@
|
||||
"app.video.notReadableError": "Konnte nicht auf die Webcam zugreifen. Bitte prüfen, dass kein anderes Programm auf die Webcam zugreift",
|
||||
"app.video.timeoutError": "Der Browser hat nicht rechtzeitig reagiert.",
|
||||
"app.video.genericError": "Ein unbekannter Fehler ist mit der Kamera aufgetreten (Fehler {0})",
|
||||
"app.video.mediaTimedOutError": "Die Webcam-Freigabe wurde unterbrochen. Bitte erneut starten.",
|
||||
"app.video.mediaTimedOutError": "Ihre Webcam-Freigabe wurde unterbrochen. Bitte erneut starten.",
|
||||
"app.video.mediaFlowTimeout1020": "Verbindung zum Server konnte nicht hergestellt werden (Fehler 1020)",
|
||||
"app.video.suggestWebcamLock": "Sperrung der Teilnehmerwebcams aktivieren?",
|
||||
"app.video.suggestWebcamLockReason": "(dies wird die Stabilität der Konferenz erhöhen)",
|
||||
@ -810,7 +811,7 @@
|
||||
"app.video.virtualBackground.background": "Hintergrund",
|
||||
"app.video.virtualBackground.genericError": "Virtueller Hintergrund konnte nicht angewendet werden. Bitte erneut versuchen.",
|
||||
"app.video.virtualBackground.camBgAriaDesc": "Setzt den virtuellen Hintergrund der Webcam auf {0}",
|
||||
"app.video.camCapReached": "Es kann keine weitere Kamera freigegeben werden",
|
||||
"app.video.camCapReached": "Sie können keine weiteren Kameras freigeben",
|
||||
"app.video.meetingCamCapReached": "Konferenz hat die maximale Anzahl gleichzeitiger Kameras erreicht",
|
||||
"app.video.dropZoneLabel": "Hier loslassen",
|
||||
"app.fullscreenButton.label": "{0} als Vollbild darstellen",
|
||||
@ -863,7 +864,7 @@
|
||||
"app.whiteboard.toolbar.fontSize": "Schriftgrößenliste",
|
||||
"app.whiteboard.toolbarAriaLabel": "Präsentationswerkzeuge",
|
||||
"app.feedback.title": "Sie haben sich aus der Konferenz ausgeloggt",
|
||||
"app.feedback.subtitle": "Wir würden gerne erfahren ob die Technik geklappt hat (optional)",
|
||||
"app.feedback.subtitle": "Wir würden gerne erfahren, wie Sie BigBlueButton finden (optional)",
|
||||
"app.feedback.textarea": "Wie können wir BigBlueButton verbessern?",
|
||||
"app.feedback.sendFeedback": "Feedback senden",
|
||||
"app.feedback.sendFeedbackDesc": "Feedback abschicken und Konferenz verlassen",
|
||||
@ -873,12 +874,12 @@
|
||||
"app.videoDock.webcamFocusDesc": "Ausgewählte Webcam vergrößern",
|
||||
"app.videoDock.webcamUnfocusLabel": "Normalgröße",
|
||||
"app.videoDock.webcamUnfocusDesc": "Ausgewählte Webcam auf Normalgröße verkleinern",
|
||||
"app.videoDock.webcamPinLabel": "Kamera anheften",
|
||||
"app.videoDock.webcamPinDesc": "Ausgewählte Webcam fixieren",
|
||||
"app.videoDock.webcamUnpinLabel": "Kamera lösen",
|
||||
"app.videoDock.webcamUnpinLabelDisabled": "Nur Moderator:innen können Teilnehmer:innen lösen",
|
||||
"app.videoDock.webcamPinLabel": "Anheften",
|
||||
"app.videoDock.webcamPinDesc": "Ausgewählte Webcam anheften",
|
||||
"app.videoDock.webcamUnpinLabel": "Lösen",
|
||||
"app.videoDock.webcamUnpinLabelDisabled": "Nur Moderatoren können Teilnehmer lösen",
|
||||
"app.videoDock.webcamUnpinDesc": "Ausgewählte Webcam lösen",
|
||||
"app.videoDock.autoplayBlockedDesc": "Wir benötigen Ihre/deine Zustimmung, um die Webcams anderer Teilnehmer:innen zu zeigen.",
|
||||
"app.videoDock.autoplayBlockedDesc": "Wir benötigen Ihre Zustimmung, um Ihnen die Webcams anderer Teilnehmer zu zeigen.",
|
||||
"app.videoDock.autoplayAllowLabel": "Webcams zeigen",
|
||||
"app.invitation.title": "Gruppenraumeinladung",
|
||||
"app.invitation.confirm": "Einladen",
|
||||
@ -887,7 +888,7 @@
|
||||
"app.createBreakoutRoom.breakoutRoomLabel": "Gruppenräume {0}",
|
||||
"app.createBreakoutRoom.askToJoin": "Raum beitreten",
|
||||
"app.createBreakoutRoom.generatingURL": "Erzeuge URL",
|
||||
"app.createBreakoutRoom.generatingURLMessage": "Wir generieren eine Teilnahme-URL für den ausgewählten Gruppenraum. Das kann ein paar Sekunden dauern ...",
|
||||
"app.createBreakoutRoom.generatingURLMessage": "Wir generieren eine Teilnahme-URL für den ausgewählten Gruppenraum. Das kann ein paar Sekunden dauern...",
|
||||
"app.createBreakoutRoom.duration": "Dauer {0}",
|
||||
"app.createBreakoutRoom.room": "Raum {0}",
|
||||
"app.createBreakoutRoom.notAssigned": "Nicht zugewiesen ({0})",
|
||||
@ -902,7 +903,7 @@
|
||||
"app.createBreakoutRoom.randomlyAssign": "Zufällig zuordnen",
|
||||
"app.createBreakoutRoom.randomlyAssignDesc": "Teilnehmer zufällig in Gruppenräume zuordnen",
|
||||
"app.createBreakoutRoom.resetAssignments": "Zuordnungen zurücksetzen",
|
||||
"app.createBreakoutRoom.resetAssignmentsDesc": "Alle Raumzuordnungen der Teilnehmer:innen zurücksetzen",
|
||||
"app.createBreakoutRoom.resetAssignmentsDesc": "Alle Raumzuordnungen der Teilnehmer zurücksetzen",
|
||||
"app.createBreakoutRoom.endAllBreakouts": "Alle Gruppenräume beenden",
|
||||
"app.createBreakoutRoom.chatTitleMsgAllRooms": "alle Räume",
|
||||
"app.createBreakoutRoom.msgToBreakoutsSent": "Nachricht wurde an {0} Gruppenräume gesendet",
|
||||
@ -912,10 +913,10 @@
|
||||
"app.createBreakoutRoom.minusRoomTime": "Gruppenraumzeit verringern auf",
|
||||
"app.createBreakoutRoom.addRoomTime": "Gruppenraumzeit erhöhen auf",
|
||||
"app.createBreakoutRoom.addParticipantLabel": "+ Teilnehmer hinzufügen",
|
||||
"app.createBreakoutRoom.freeJoin": "Erlauben, sich selbst einen Gruppenraum auszusuchen.",
|
||||
"app.createBreakoutRoom.leastOneWarnBreakout": "Jedem Gruppenraum muss wenigstens ein:e Teilnehmer:in zugeordnet sein.",
|
||||
"app.createBreakoutRoom.freeJoin": "Den Teilnehmern erlauben, sich selbst einen Gruppenraum auszusuchen.",
|
||||
"app.createBreakoutRoom.leastOneWarnBreakout": "Jedem Gruppenraum muss wenigstens ein Teilnehmer zugeordnet sein.",
|
||||
"app.createBreakoutRoom.minimumDurationWarnBreakout": "Die Mindestdauer für einen Gruppenraum beträgt {0} Minuten.",
|
||||
"app.createBreakoutRoom.modalDesc": "Tipp: Teilnehmer:in per Drag-and-Drop einem bestimmten Gruppenraum zuweisen.",
|
||||
"app.createBreakoutRoom.modalDesc": "Tipp: Sie können die Teilnehmer per Drag-and-Drop einem bestimmten Gruppenraum zuweisen.",
|
||||
"app.createBreakoutRoom.roomTime": "{0} Minuten",
|
||||
"app.createBreakoutRoom.numberOfRoomsError": "Die Raumanzahl ist ungültig.",
|
||||
"app.createBreakoutRoom.duplicatedRoomNameError": "Raumname kann nicht doppelt vergeben werden.",
|
||||
@ -1036,7 +1037,7 @@
|
||||
"app.learningDashboard.usersTable.userStatusOffline": "Offline",
|
||||
"app.learningDashboard.usersTable.noUsers": "Bisher keine Teilnehmer",
|
||||
"app.learningDashboard.usersTable.name": "Name",
|
||||
"app.learningDashboard.usersTable.moderator": "Moderator:in",
|
||||
"app.learningDashboard.usersTable.moderator": "Moderator",
|
||||
"app.learningDashboard.usersTable.pollVotes": "Abstimmungen",
|
||||
"app.learningDashboard.usersTable.join": "Teilnahme",
|
||||
"app.learningDashboard.usersTable.left": "Verlassen",
|
||||
@ -1052,11 +1053,11 @@
|
||||
"app.learningDashboard.errors.dataUnavailable": "Die Daten sind nicht mehr verfügbar",
|
||||
"mobileApp.portals.list.empty.addFirstPortal.label": "Fügen Sie Ihr erstes Portal über die Schaltfläche oben hinzu,",
|
||||
"mobileApp.portals.list.empty.orUseOurDemoServer.label": "oder nutzen Sie unseren Demo-Server.",
|
||||
"mobileApp.portals.list.add.button.label": "Neues Portal",
|
||||
"mobileApp.portals.list.add.button.label": "Portal hinzufügen",
|
||||
"mobileApp.portals.fields.name.label": "Portalname",
|
||||
"mobileApp.portals.fields.name.placeholder": "BigBlueButton Demo",
|
||||
"mobileApp.portals.fields.url.label": "Server-URL",
|
||||
"mobileApp.portals.addPortalPopup.confirm.button.label": "Portal hinzufügen",
|
||||
"mobileApp.portals.addPortalPopup.confirm.button.label": "Speichern",
|
||||
"mobileApp.portals.drawerNavigation.button.label": "Portale",
|
||||
"mobileApp.portals.addPortalPopup.validation.emptyFields": "Benötigte Felder",
|
||||
"mobileApp.portals.addPortalPopup.validation.portalNameAlreadyExists": "Name existiert bereits",
|
||||
|
@ -485,7 +485,6 @@
|
||||
"app.breakoutJoinConfirmation.freeJoinMessage": "Επιλέξτε ένα υπο-δωμάτιο (breakout) για συμμετοχή",
|
||||
"app.breakoutTimeRemainingMessage": "Χρόνος υπο-δωματίου (breakout) που απομένει: {0}",
|
||||
"app.breakoutWillCloseMessage": "Ο χρόνος τελείωσε. Το υπο-δωμάτιο (breakout) θα κλείσει σύντομα.",
|
||||
"app.breakout.dropdown.manageDuration": "Διαχείριση διάρκειας",
|
||||
"app.calculatingBreakoutTimeRemaining": "Υπολογισμός υπόλοιπου χρόνου ...",
|
||||
"app.audioModal.ariaTitle": "Συμμετοχή με τυπικό ήχο",
|
||||
"app.audioModal.microphoneLabel": "Μικρόφωνο",
|
||||
|
@ -541,8 +541,8 @@
|
||||
"app.breakoutJoinConfirmation.freeJoinMessage": "Choose a breakout room to join",
|
||||
"app.breakoutTimeRemainingMessage": "Breakout room time remaining: {0}",
|
||||
"app.breakoutWillCloseMessage": "Time ended. Breakout room will close soon",
|
||||
"app.breakout.dropdown.manageDuration": "Manage duration",
|
||||
"app.breakout.dropdown.destroyAll": "Destroy breakouts",
|
||||
"app.breakout.dropdown.manageDuration": "Change duration",
|
||||
"app.breakout.dropdown.destroyAll": "End breakout rooms",
|
||||
"app.breakout.dropdown.options": "Breakout Options",
|
||||
"app.calculatingBreakoutTimeRemaining": "Calculating remaining time ...",
|
||||
"app.audioModal.ariaTitle": "Join audio modal",
|
||||
@ -643,6 +643,7 @@
|
||||
"app.error.500": "Ops, something went wrong",
|
||||
"app.error.userLoggedOut": "User has an invalid sessionToken due to log out",
|
||||
"app.error.ejectedUser": "User has an invalid sessionToken due to ejection",
|
||||
"app.error.joinedAnotherWindow": "This session seems to be opened in another browser window.",
|
||||
"app.error.userBanned": "User has been banned",
|
||||
"app.error.leaveLabel": "Log in again",
|
||||
"app.error.fallback.presentation.title": "An error occurred",
|
||||
|
@ -510,8 +510,6 @@
|
||||
"app.breakoutJoinConfirmation.freeJoinMessage": "Vali eraldatud ruum, millega liituda",
|
||||
"app.breakoutTimeRemainingMessage": "Eraldatud ruumi järelejäänud aeg: {0}",
|
||||
"app.breakoutWillCloseMessage": "Aeg sai läbi. Eraldatud ruum suletakse kohe",
|
||||
"app.breakout.dropdown.manageDuration": "Halda kestust",
|
||||
"app.breakout.dropdown.destroyAll": "Lõpeta eraldatud ruumid",
|
||||
"app.breakout.dropdown.options": "Eraldatud ruumide valikud",
|
||||
"app.calculatingBreakoutTimeRemaining": "Arvutan järelejäänud aega...",
|
||||
"app.audioModal.ariaTitle": "Audioga liitumise aken",
|
||||
|
@ -510,8 +510,6 @@
|
||||
"app.breakoutJoinConfirmation.freeJoinMessage": "Aukeratu azpitalde baten gela bertara sartzeko",
|
||||
"app.breakoutTimeRemainingMessage": "Azpitaldearen gelari geratzen zaion denbora: {0}",
|
||||
"app.breakoutWillCloseMessage": "Denbora agortu da. Azpitaldearen gela laster itxiko da",
|
||||
"app.breakout.dropdown.manageDuration": "Kudeatu iraupena",
|
||||
"app.breakout.dropdown.destroyAll": "Bukatu azpitaldeak",
|
||||
"app.breakout.dropdown.options": "Azpitaldeen aukerak",
|
||||
"app.calculatingBreakoutTimeRemaining": "Geratzen den denbora kalkulatzen...",
|
||||
"app.audioModal.ariaTitle": "Erabili audio modala",
|
||||
|
@ -6,6 +6,7 @@
|
||||
"app.chat.disconnected": "ارتباط شما قطع شده است، امکان ارسال پیامها وجود ندارد",
|
||||
"app.chat.locked": "گفنگو قفل شده است، امکان ارسال هیچ پیامی وجود ندارد",
|
||||
"app.chat.inputLabel": "ورودی پیام برای گفتگو {0}",
|
||||
"app.chat.inputPlaceholder": "پیامها {0}",
|
||||
"app.chat.titlePublic": "گفتگوی عمومی",
|
||||
"app.chat.titlePrivate": "گفتگوی خصوصی با {0}",
|
||||
"app.chat.partnerDisconnected": "{0} جلسه را ترک کرد",
|
||||
@ -19,6 +20,7 @@
|
||||
"app.chat.label": "گفتگو",
|
||||
"app.chat.offline": "آفلاین",
|
||||
"app.chat.pollResult": "نتایج نظرسنجی",
|
||||
"app.chat.breakoutDurationUpdated": "زمان جلسه زیرمجموعه اکنون {0} دقیقه است. ",
|
||||
"app.chat.emptyLogLabel": "پاک کردن سابقه گفتگو",
|
||||
"app.chat.clearPublicChatMessage": "سابقه گفتگوها توسط مدیر حذف گردید",
|
||||
"app.chat.multi.typing": "چند کاربر در حال نوشتن هستند",
|
||||
@ -41,8 +43,23 @@
|
||||
"app.captions.menu.backgroundColor": "رنگ پسزمینه",
|
||||
"app.captions.menu.previewLabel": "پیشنمایش",
|
||||
"app.captions.menu.cancelLabel": "لغو",
|
||||
"app.captions.hide": "پنهانسازی زیرنویسها",
|
||||
"app.captions.ownership": "گرفتن کنترل",
|
||||
"app.captions.ownershipTooltip": "شما به عنوان صاحب زیرنویسهای {0} منسوب خواهید شد",
|
||||
"app.captions.dictationStart": "آغاز نوشتن کلمات",
|
||||
"app.captions.dictationStop": "توقف نوشتن کلمات",
|
||||
"app.captions.dictationOnDesc": "روشن کردن امکان تشخیص گفتار",
|
||||
"app.captions.dictationOffDesc": "غیرفعال کردن امکان تشخیص گفتار",
|
||||
"app.captions.speech.start": "امکان تشخیص گفتار آغاز شد",
|
||||
"app.captions.speech.stop": "امکان تشخیص گفتار متوقف شد",
|
||||
"app.captions.speech.error": "به دلیل ناسازگاری مرورگر یا ایجاد وقفه، تشخیص گفتار متوقف شده است",
|
||||
"app.textInput.sendLabel": "ارسال",
|
||||
"app.title.defaultViewLabel": "نمای پیشفرض ارائه",
|
||||
"app.notes.title": "یادداشتهای اشتراکی",
|
||||
"app.notes.label": "یادداشتها",
|
||||
"app.notes.hide": "پنهان کردن یادداشتها",
|
||||
"app.notes.locked": "قفل شده",
|
||||
"app.pads.hint": "برای تمرکز روی نوار ابزار برگه یادداشت، Esc را فشار دهید",
|
||||
"app.user.activityCheck": "بررسی فعالیت کاربر",
|
||||
"app.user.activityCheck.label": "بررسی کنید آیا کاربر هنوز در جلسه ({0}) حضور دارد",
|
||||
"app.user.activityCheck.check": "بررسی",
|
||||
@ -101,6 +118,7 @@
|
||||
"app.userList.userOptions.disableMic": "استفاده از امکان صدا برای کاربران غیرفعال شده است",
|
||||
"app.userList.userOptions.disablePrivChat": "گفتگوی خصوصی غیرفعال شده است",
|
||||
"app.userList.userOptions.disablePubChat": "گفتگوی عمومی غیرفعال شده است",
|
||||
"app.userList.userOptions.disableNotes": "یادداشت اشتراکی در حال حاضر قفل هستند",
|
||||
"app.userList.userOptions.hideUserList": "لیست کاربران در حال حاضر برای شرکت کنندگان قابل مشاهده نیست",
|
||||
"app.userList.userOptions.webcamsOnlyForModerator": "تنها مدیران امکان مشاهده دوربینهای کاربران را دارند (به دلیل تنظیمات قفل)",
|
||||
"app.userList.content.participants.options.clearedStatus": "وضعیت همه کاربرها پاک شد",
|
||||
@ -108,11 +126,14 @@
|
||||
"app.userList.userOptions.enableMic": "میکروفون کاربران فعال شد",
|
||||
"app.userList.userOptions.enablePrivChat": "گفتگوی خصوصی فعال شد",
|
||||
"app.userList.userOptions.enablePubChat": "گفتگوی عمومی فعال شد",
|
||||
"app.userList.userOptions.enableNotes": "یادداشتهای اشتراکی در حال حاضر فعال هستند",
|
||||
"app.userList.userOptions.showUserList": "لیست کاربران در حال حاضر برای شرکت کنندگان قابل مشاهده است",
|
||||
"app.userList.userOptions.enableOnlyModeratorWebcam": "شما در حال حاضر میتوانید دوربین خود را به اشتراک بگذارید، همه تصویر شما را خواهند دید",
|
||||
"app.userList.userOptions.savedNames.title": "لیست کاربران در جلسه {0} در {1}",
|
||||
"app.userList.userOptions.sortedFirstName.heading": "مرتب شده بر اساس نام:",
|
||||
"app.userList.userOptions.sortedLastName.heading": "مرتب شده بر اساس نام خانوادگی:",
|
||||
"app.userList.userOptions.hideViewersCursor": "نشانگر بیننده قفل شده است",
|
||||
"app.userList.userOptions.showViewersCursor": "قفل نشانگر بیننده باز شده است",
|
||||
"app.media.label": "صدا و تصویر",
|
||||
"app.media.autoplayAlertDesc": "دادن اجازه دسترسی",
|
||||
"app.media.screenshare.start": "اشتراک صفحه نمایش شروع شد",
|
||||
@ -125,9 +146,9 @@
|
||||
"app.screenshare.presenterLoadingLabel": "اشتراکگذاری صفحه نمایش شما در حال بارگذاری است",
|
||||
"app.screenshare.viewerLoadingLabel": "اشتراکگذاری صفحه نمایش ارائهدهنده در حال بارگذاری است",
|
||||
"app.screenshare.presenterSharingLabel": "اکنون شما در حال اشتراکگذاری صفحه نمایشتان هستید",
|
||||
"app.screenshare.screenshareFinalError": "کد {0}: نتوانست صفحهی نمایش را به اشتراک بگذارد.",
|
||||
"app.screenshare.screenshareFinalError": "کد {0}: اشتراکگذاری صفحهی نمایش امکانپذیر نیست..",
|
||||
"app.screenshare.screenshareRetryError": "کد {0}: دوباره سعی کنید صفحه نمایش را به اشتراک بگذارید. ",
|
||||
"app.screenshare.screenshareRetryOtherEnvError": "کد {0}: نتواست صفحه را به اشتراک بگذارد. با مرورگر یا وسیله دیگری امتحان کنید.",
|
||||
"app.screenshare.screenshareRetryOtherEnvError": "کد {0}: اشتراکگذاری صفحهی نمایش امکانپذیر نیست. با مرورگر یا وسیله دیگری امتحان کنید.",
|
||||
"app.screenshare.screenshareUnsupportedEnv": "کد {0}: مرورگر پشتیبانی نمی شود. با مرورگر یا وسیله دیگری امتحان کنید.",
|
||||
"app.screenshare.screensharePermissionError": "کد {0}: برای گرفتن صفحه باید دسترسی آن را فعال کنید.",
|
||||
"app.meeting.ended": "جلسه پایان یافت",
|
||||
@ -149,6 +170,13 @@
|
||||
"app.presentation.endSlideContent": "انتهای محتوای اسلاید",
|
||||
"app.presentation.changedSlideContent": "ارائه به اسلاید {0} تغییر کرد",
|
||||
"app.presentation.emptySlideContent": "محتوایی برای اسلاید کنونی وجود ندارد",
|
||||
"app.presentation.options.fullscreen": "تمام صفحه",
|
||||
"app.presentation.options.exitFullscreen": "بستن حالت تمام صفحه",
|
||||
"app.presentation.options.minimize": "کمینهکردن",
|
||||
"app.presentation.options.snapshot": "تصویر لحظهای نمای فعلی ارائه",
|
||||
"app.presentation.options.downloading": "در حال بارگیری...",
|
||||
"app.presentation.options.downloaded": "ارائه فعلی بارگیری شد",
|
||||
"app.presentation.options.downloadFailed": "بارگیری ارائه فعلی امکانپذیر نیست",
|
||||
"app.presentation.presentationToolbar.noNextSlideDesc": "انتهای ارائه",
|
||||
"app.presentation.presentationToolbar.noPrevSlideDesc": "ابتدای ارائه",
|
||||
"app.presentation.presentationToolbar.selectLabel": "انتخاب اسلاید",
|
||||
@ -173,6 +201,7 @@
|
||||
"app.presentation.presentationToolbar.fitToWidth": "اندازه تصویر را متناسب با عرض ارائه کن",
|
||||
"app.presentation.presentationToolbar.fitToPage": "اندازه تصویر را متناسب با عرض صفحه کن",
|
||||
"app.presentation.presentationToolbar.goToSlide": "اسلاید {0}",
|
||||
"app.presentation.placeholder": "در حال حاضر هیچ ارائه فعالی وجود ندارد",
|
||||
"app.presentationUploder.title": "ارائه",
|
||||
"app.presentationUploder.message": "به عنوان یک ارائه دهنده شما قادرید انواع فایل های مجموعه آفیس و یا فایل PDF را بارگذاری نمایید؛ پیشنهاد ما برای رسیدن به بهترین نتایج، استفاده از فایل PDF میباشد. لطفا از انتخاب بودن یک ارائه توسط گزینه سمت راست اطمینان حاصل کنید.",
|
||||
"app.presentationUploder.extraHint": "*مهم*: هر فایل نباید از {0} مگابایت و {1} صفحه تجاوز کند. ",
|
||||
@ -188,6 +217,7 @@
|
||||
"app.presentationUploder.fileToUpload": "آماده بارگذاری ...",
|
||||
"app.presentationUploder.currentBadge": "کنونی",
|
||||
"app.presentationUploder.rejectedError": "پرونده(های) انتخاب شده رد شدند. لطفا نوع پرونده(ها) را بررسی کنید.",
|
||||
"app.presentationUploder.connectionClosedError": "به دلیل اتصال ضعیف قطع شد. لطفا دوباره تلاش کنید. ",
|
||||
"app.presentationUploder.upload.progress": "در حال بارگذاری ({0}%)",
|
||||
"app.presentationUploder.upload.413": "حجم پرونده زیاد است، از حداکثر {0} مگابایت بیشتر است",
|
||||
"app.presentationUploder.genericError": "آخ، خطای پیش آمده است...",
|
||||
@ -226,6 +256,7 @@
|
||||
"app.poll.autoOptionInstructions.label": "انتخاب خودکار فعال است - سوال نظرسنجی و گزینه(های) را در قالب داده شده بنویسید.",
|
||||
"app.poll.maxOptionsWarning.label": "فقط 5 گزینه اول قابل استفاده است!",
|
||||
"app.poll.pollPaneTitle": "نظرسنجی",
|
||||
"app.poll.enableMultipleResponseLabel": "اجازه برای پاسخهای متعدد به ازای هر پاسخدهنده؟ ",
|
||||
"app.poll.quickPollTitle": "نظرسنجی سریع",
|
||||
"app.poll.hidePollDesc": "پنهانسازی منوی نظرسنجی",
|
||||
"app.poll.quickPollInstruction": "برای شروع نظرسنجی خود، گزینه زیر را انتخاب کنید.",
|
||||
@ -250,11 +281,11 @@
|
||||
"app.poll.typedResponse.desc" : "برای پرکردن پاسخ خود، یک جعبه متن به کاربران نمایش داده می شود.",
|
||||
"app.poll.addItem.label" : "اضافه کردن آیتم",
|
||||
"app.poll.start.label" : "آغاز نظرسنجی",
|
||||
"app.poll.secretPoll.label" : "رأیگیری ناشناس",
|
||||
"app.poll.secretPoll.label" : "نظرسنجی ناشناس",
|
||||
"app.poll.secretPoll.isSecretLabel": "این نظرسنجی ناشناس است - شما قادر نخواهید بود پاسخهای فردی را ببینید.",
|
||||
"app.poll.questionErr": "ارائه یک سوال الزامی است.",
|
||||
"app.poll.optionErr": "یک گزینه نظرسنجی وارد کنید",
|
||||
"app.poll.startPollDesc": "آغاز رایگیری",
|
||||
"app.poll.startPollDesc": "آغاز نظرسنجی",
|
||||
"app.poll.showRespDesc": "نمایش پاسخ پیکربندی",
|
||||
"app.poll.addRespDesc": "اضافه کردن ورودی پاسخ نظر سنجی",
|
||||
"app.poll.deleteRespDesc": "حذف گزینه {0}",
|
||||
@ -282,7 +313,7 @@
|
||||
"app.poll.liveResult.usersTitle": "کاربران",
|
||||
"app.poll.liveResult.responsesTitle": "پاسخ",
|
||||
"app.poll.liveResult.secretLabel": "این یک نظرسنجی ناشناس است. پاسخ هر فرد نمایش داده نمیشود.",
|
||||
"app.poll.removePollOpt": "گزینه نظرسنجی حذف شد {0}",
|
||||
"app.poll.removePollOpt": "گزینه {0} نظرسنجی حذف شد",
|
||||
"app.poll.emptyPollOpt": "خالی",
|
||||
"app.polling.pollingTitle": "امکانات نظرسنجی",
|
||||
"app.polling.pollQuestionTitle": "سوال نظرسنجی",
|
||||
@ -336,6 +367,7 @@
|
||||
"app.endMeeting.noLabel": "خیر",
|
||||
"app.about.title": "درباره",
|
||||
"app.about.version": "نسخه کاربر:",
|
||||
"app.about.version_label": "نسخه بیگبلوباتن:",
|
||||
"app.about.copyright": "حق نشر:",
|
||||
"app.about.confirmLabel": "تایید",
|
||||
"app.about.confirmDesc": "تایید",
|
||||
@ -396,6 +428,7 @@
|
||||
"app.settings.dataSavingTab.description": "برای صرفه جویی در مصرف پهنای باند اینترنت آیتم هایی که باید نمایش داده شوند را انتخاب کنید.",
|
||||
"app.settings.save-notification.label": "تنظیمات ذخیره شدند",
|
||||
"app.statusNotifier.lowerHands": "دستهای پایین",
|
||||
"app.statusNotifier.lowerHandDescOneUser": "پایین آوردن دست {0}",
|
||||
"app.statusNotifier.raisedHandsTitle": "دستهای بالا برده شده",
|
||||
"app.statusNotifier.raisedHandDesc": "{0} دستشان را بالا بردند",
|
||||
"app.statusNotifier.raisedHandDescOneUser": "{0} دستش را بالا برد",
|
||||
@ -477,6 +510,9 @@
|
||||
"app.breakoutJoinConfirmation.freeJoinMessage": "یک اتاق مجموعه را برای ملحق شدن انتخاب کنید",
|
||||
"app.breakoutTimeRemainingMessage": "زمان باقی مانده از اتاق زیرمجموعه: {0}",
|
||||
"app.breakoutWillCloseMessage": "زمان به اتمام رسید. اتاق زیرمجموعه به زودی بسته خواهد شد",
|
||||
"app.breakout.dropdown.manageDuration": "تغیر مدت زمان",
|
||||
"app.breakout.dropdown.destroyAll": "اتمام اتاقهای زیرمجموعه",
|
||||
"app.breakout.dropdown.options": "گزینههای زیرمجموعه",
|
||||
"app.calculatingBreakoutTimeRemaining": "در حال محاسبه زمان باقی مانده ...",
|
||||
"app.audioModal.ariaTitle": "ملحق شدن به مدال صدا",
|
||||
"app.audioModal.microphoneLabel": "میکروفون",
|
||||
@ -532,6 +568,7 @@
|
||||
"app.audio.audioSettings.descriptionLabel": "لطفا توجه کنید، یک پیام در مرورگر شما ظاهر میشود، که از شما میخواهد اجازه اشتراک میکروفن خود را بدهید.",
|
||||
"app.audio.audioSettings.microphoneSourceLabel": "منبع ورودی میکروفن",
|
||||
"app.audio.audioSettings.speakerSourceLabel": "منبع خروجی صدا",
|
||||
"app.audio.audioSettings.testSpeakerLabel": "بلندگوی خود را امتحان کنید",
|
||||
"app.audio.audioSettings.microphoneStreamLabel": "بلندی صدای میکروفن شما",
|
||||
"app.audio.audioSettings.retryLabel": "تلاش مجدد",
|
||||
"app.audio.listenOnly.backLabel": "بازگشت",
|
||||
@ -570,6 +607,7 @@
|
||||
"app.error.500": "آخ، خطای پیش آمده است",
|
||||
"app.error.userLoggedOut": "کاربر به خاطر خروج sessionToken غیر معتبر دارد",
|
||||
"app.error.ejectedUser": "کاربر به خاطر اخراج sessionToken غیر معتبر دارد",
|
||||
"app.error.joinedAnotherWindow": "به نظر میرسد که این جلسه در پنجره مرورگر دیگری باز شده است. ",
|
||||
"app.error.userBanned": "کاربر مسدود شده است",
|
||||
"app.error.leaveLabel": "دوباره وارد شوید",
|
||||
"app.error.fallback.presentation.title": "یک خطا رخ داده است",
|
||||
@ -588,6 +626,8 @@
|
||||
"app.guest.guestDeny": "درخواست ورود به جلسه مورد پذیرش قرار نگرفت",
|
||||
"app.guest.seatWait": "میهمان در انتظار اجازه ورود به اتاق میباشد.",
|
||||
"app.guest.allow": "میربان تائید شد و به جلسه هدایت گردید.",
|
||||
"app.guest.firstPositionInWaitingQueue": "شما اولین نفر در صف هستید!",
|
||||
"app.guest.positionInWaitingQueue": "موقعیت فعلی شما در صف انتظار:",
|
||||
"app.guest.guestInvalid": " کاربر مهمان نامعتبر است",
|
||||
"app.guest.meetingForciblyEnded": "شما نمیتوانید به جلسهای بپیوندید که از قبل به اجبار پایان یافته است",
|
||||
"app.userList.guest.waitingUsers": "کاربران در حال انتظار",
|
||||
@ -598,11 +638,14 @@
|
||||
"app.userList.guest.allowEveryone": "اجازه ورود به همه",
|
||||
"app.userList.guest.denyEveryone": "رد همه",
|
||||
"app.userList.guest.pendingUsers": "{0} کاربر در انتظار تایید",
|
||||
"app.userList.guest.noPendingUsers": "در حال حاظر هیچ کاربر معلقی وجود ندارد...",
|
||||
"app.userList.guest.pendingGuestUsers": "{0} کاربر مهمان در انتظار تایید",
|
||||
"app.userList.guest.pendingGuestAlert": "به جلسه ملحق شد و منتظر تایید شماست",
|
||||
"app.userList.guest.rememberChoice": "به یاد داشتن انتخاب",
|
||||
"app.userList.guest.emptyMessage": "فعلا پیامی نیست",
|
||||
"app.userList.guest.inputPlaceholder": "ارسال پیام به اتاق انتظار مهمانان",
|
||||
"app.userList.guest.privateInputPlaceholder": "پیام به {0}",
|
||||
"app.userList.guest.privateMessageLabel": "پیامدادن",
|
||||
"app.userList.guest.acceptLabel": "پذیرفتن",
|
||||
"app.userList.guest.denyLabel": "رد کردن",
|
||||
"app.user-info.title": "جستجوی دایرکتوری",
|
||||
@ -615,6 +658,9 @@
|
||||
"app.toast.meetingMuteOn.label": "صدای همه کاربران به حالت بیصدا تغییر کرد",
|
||||
"app.toast.meetingMuteOff.label": "امکان بیصدا کردن جلسه غیرفعال شد",
|
||||
"app.toast.setEmoji.raiseHand": "شما دستتان را بالا برده اید",
|
||||
"app.toast.setEmoji.lowerHand": "دست شما پایین آورده شد",
|
||||
"app.toast.promotedLabel": "نقش شما به مدیر ارتقا یافت",
|
||||
"app.toast.demotedLabel": "نقش شما به کاربر عادی تنزل یافت",
|
||||
"app.notification.recordingStart": "جلسه در حال ضبط شدن است",
|
||||
"app.notification.recordingStop": "این جلسه ضبط نمیشود",
|
||||
"app.notification.recordingPaused": "جلسه دیگر ضبط نمیشود",
|
||||
@ -642,6 +688,10 @@
|
||||
"app.shortcut-help.toggleFullscreen": "تغییر حالت تمام صفحه (ارائه دهنده)",
|
||||
"app.shortcut-help.nextSlideDesc": "اسلاید بعدی (ارائه دهنده)",
|
||||
"app.shortcut-help.previousSlideDesc": "اسلاید قبلی (ارائه دهنده)",
|
||||
"app.shortcut-help.togglePanKey": "کلید فاصله",
|
||||
"app.shortcut-help.toggleFullscreenKey": "کلید Enter",
|
||||
"app.shortcut-help.nextSlideKey": "کلید سمت راست",
|
||||
"app.shortcut-help.previousSlideKey": "کلید سمت چپ",
|
||||
"app.lock-viewers.title": "قفل کردن کاربران",
|
||||
"app.lock-viewers.description": "این قابلیت شما را قادر میسازد تا دسترسی به امکانات ویژه را از کاربران بگیرید.",
|
||||
"app.lock-viewers.featuresLable": "امکان",
|
||||
@ -657,6 +707,7 @@
|
||||
"app.lock-viewers.button.apply": "اعمال",
|
||||
"app.lock-viewers.button.cancel": "لغو",
|
||||
"app.lock-viewers.locked": "قفل شده",
|
||||
"app.lock-viewers.hideViewersCursor": "مشاهده نشانگر دیگر کاربران",
|
||||
"app.guest-policy.ariaTitle": "فرم تنظیمات سیاست پذیرش درخواست ورود کاربران",
|
||||
"app.guest-policy.title": "سیاست پذیرش درخواست ورود کاربران",
|
||||
"app.guest-policy.description": "سیاست پذیرش درخواست ورود کاربران در جلسه را مشخصی کنید",
|
||||
@ -669,15 +720,26 @@
|
||||
"app.connection-status.description": "وضعیت اتصال کاربران را مشاهده کنید",
|
||||
"app.connection-status.empty": "در حال حاضر هیچ مشکلی در رابطه با اتصال گزارش نشده است",
|
||||
"app.connection-status.more": "بیشتر",
|
||||
"app.connection-status.copy": "کپی آمار",
|
||||
"app.connection-status.copied": "کپی شد!",
|
||||
"app.connection-status.jitter": "Jitter",
|
||||
"app.connection-status.label": "وضعیت اتصال",
|
||||
"app.connection-status.settings": "تغییردادن تنظیمات شما",
|
||||
"app.connection-status.no": "No",
|
||||
"app.connection-status.notification": "قطعی در اتصال شما پیدا شد",
|
||||
"app.connection-status.offline": "آفلاین",
|
||||
"app.connection-status.audioUploadRate": "نرخ بارگذاری صدا",
|
||||
"app.connection-status.audioDownloadRate": "نرخ بارگیری صدا",
|
||||
"app.connection-status.videoUploadRate": "نرخ بارگذاری تصویر",
|
||||
"app.connection-status.videoDownloadRate": "نرخ بارگیری تصویر",
|
||||
"app.connection-status.lostPackets": "Lost packets",
|
||||
"app.connection-status.usingTurn": "Using TURN",
|
||||
"app.connection-status.yes": "Yes",
|
||||
"app.connection-status.connectionStats": "آمار اتصال",
|
||||
"app.connection-status.myLogs": "گزارشهای من",
|
||||
"app.connection-status.sessionLogs": "گزارشهای جلسه",
|
||||
"app.connection-status.next": "صفحه بعدی",
|
||||
"app.connection-status.prev": "صفحه قبلی",
|
||||
"app.learning-dashboard.label": "پیشخوان تحلیل یادگیری",
|
||||
"app.learning-dashboard.description": "باز کردن پیشخوان به همراه فعالیتهای کاربران",
|
||||
"app.learning-dashboard.clickHereToOpen": "باز کردن پیشخوان تحلیل یادگیری",
|
||||
@ -748,6 +810,8 @@
|
||||
"app.video.virtualBackground.background": "پسزمینه",
|
||||
"app.video.virtualBackground.genericError": "افکت دوربین اعمال نشد. مجددا تلاش کنید.",
|
||||
"app.video.virtualBackground.camBgAriaDesc": "تنظیم پسزمینه مجازی دوربین به {0}",
|
||||
"app.video.camCapReached": "نمیتوانید دوربینهای بیشتری را به اشتراک بگذارید",
|
||||
"app.video.meetingCamCapReached": "جلسه به حد مجاز دوربینهای همزمان خود رسیده است",
|
||||
"app.video.dropZoneLabel": "اینجا بیندازید",
|
||||
"app.fullscreenButton.label": "تغییر {0} به تمام صفحه",
|
||||
"app.fullscreenUndoButton.label": "{0} تمام صفحه را واگرد کنید",
|
||||
@ -837,7 +901,11 @@
|
||||
"app.createBreakoutRoom.durationInMinutes": "مدت زمان (دقیقه)",
|
||||
"app.createBreakoutRoom.randomlyAssign": "به صورت تصادفی واگذار شده",
|
||||
"app.createBreakoutRoom.randomlyAssignDesc": "توزیع تصادفی کاربران به اتاقهای زیر مجموعه",
|
||||
"app.createBreakoutRoom.resetAssignments": "بازنشانی وظایف",
|
||||
"app.createBreakoutRoom.resetAssignmentsDesc": "بازنشانی همه وظایف اتاق کاربر",
|
||||
"app.createBreakoutRoom.endAllBreakouts": "پایان تمام اتاقهای زیرمجموعه",
|
||||
"app.createBreakoutRoom.chatTitleMsgAllRooms": "همه اتاقها",
|
||||
"app.createBreakoutRoom.msgToBreakoutsSent": "پیام به {0} اتاق زیرمجموعه ارسال شد",
|
||||
"app.createBreakoutRoom.roomName": "{0} (اتاق - {1})",
|
||||
"app.createBreakoutRoom.doneLabel": "انجام شد",
|
||||
"app.createBreakoutRoom.nextLabel": "بعدی",
|
||||
@ -852,6 +920,10 @@
|
||||
"app.createBreakoutRoom.numberOfRoomsError": "تعداد اتاق ها نادرست است.",
|
||||
"app.createBreakoutRoom.duplicatedRoomNameError": "نام اتاق نمیتواند تکراری باشد.",
|
||||
"app.createBreakoutRoom.emptyRoomNameError": "نام اتاق نمیتواند خالی باشد.",
|
||||
"app.createBreakoutRoom.setTimeInMinutes": "تنظیم مدت زمان به (دقیقه)",
|
||||
"app.createBreakoutRoom.setTimeLabel": "اعمال",
|
||||
"app.createBreakoutRoom.setTimeCancel": "لغو",
|
||||
"app.createBreakoutRoom.setTimeHigherThanMeetingTimeError": "مدت زمان اتاقهای زیرمجموعه نمیتواند از زمان جلسه بیشتر باشد.",
|
||||
"app.createBreakoutRoom.roomNameInputDesc": "به روز رسانی نام اتاقهای زیر مجموعه",
|
||||
"app.externalVideo.start": "به اشتراک گذاری ویدئو جدید",
|
||||
"app.externalVideo.title": "اشتراک یک ویدیوی خارجی",
|
||||
@ -863,6 +935,8 @@
|
||||
"app.externalVideo.refreshLabel": "تازه سازی پخش کننده ویدئو/صوت",
|
||||
"app.externalVideo.fullscreenLabel": "پخش کننده ویدئو/صوت",
|
||||
"app.externalVideo.noteLabel": "نکته: ویدیوهای خارجی به اشتراک گذاشته شده در ضبط ظاهر نمیشوند.نشانیهای وب یوتیوب، ویمیو، Instructure Media، توییچ، دیلیموشن و فایلهای رسانهای (به عنوان مثال https://example.com/xy.mp4) پشتیبانی میشوند.",
|
||||
"app.externalVideo.subtitlesOn": "خاموشکردن",
|
||||
"app.externalVideo.subtitlesOff": "روشنکردن (در صورت وجود)",
|
||||
"app.actionsBar.actionsDropdown.shareExternalVideo": "اشتراک یک ویدیوی خارجی",
|
||||
"app.actionsBar.actionsDropdown.stopShareExternalVideo": "متوقف کردن نمایش ویدیوی خارجی",
|
||||
"app.iOSWarning.label": "لطفا به iOS نسخه 12.2 یا بالاتر ارتقا دهید",
|
||||
@ -892,6 +966,7 @@
|
||||
"playback.button.search.aria": "جستجو",
|
||||
"playback.button.section.aria": "بخش کناری",
|
||||
"playback.button.swap.aria": "تعویض محتوا",
|
||||
"playback.button.theme.aria": "تغییر زمینه",
|
||||
"playback.error.wrapper.aria": "محدوده خطا",
|
||||
"playback.loader.wrapper.aria": "محدوده بارگذارنده",
|
||||
"playback.player.wrapper.aria": "محدوده پخشکننده",
|
||||
@ -924,14 +999,30 @@
|
||||
"playback.player.thumbnails.wrapper.aria": "محدوده تصاویر بندانگشتی",
|
||||
"playback.player.webcams.wrapper.aria": "ناحیه وبکم",
|
||||
"app.learningDashboard.dashboardTitle": "پیشخوان تحلیل یادگیری",
|
||||
"app.learningDashboard.downloadSessionDataLabel": "بارگیری دادههای جلسه",
|
||||
"app.learningDashboard.lastUpdatedLabel": "آخرین بهروزرسانی در",
|
||||
"app.learningDashboard.sessionDataDownloadedLabel": "بارگیری شد!",
|
||||
"app.learningDashboard.shareButton": "اشتراکگذاری با دیگران",
|
||||
"app.learningDashboard.shareLinkCopied": "پیوند با موفقیت کپی شد!",
|
||||
"app.learningDashboard.user": "کاربر",
|
||||
"app.learningDashboard.indicators.meetingStatusEnded": "پایان یافته",
|
||||
"app.learningDashboard.indicators.meetingStatusActive": "فعال",
|
||||
"app.learningDashboard.indicators.usersOnline": "کاربران فعال",
|
||||
"app.learningDashboard.indicators.usersTotal": "تعداد کل کاربران",
|
||||
"app.learningDashboard.indicators.polls": "نظرسنجیها",
|
||||
"app.learningDashboard.indicators.timeline": "خط زمانی",
|
||||
"app.learningDashboard.indicators.activityScore": "امتیاز فعالیت",
|
||||
"app.learningDashboard.indicators.duration": "مدت زمان",
|
||||
"app.learningDashboard.userDetails.startTime": "زمان شروع",
|
||||
"app.learningDashboard.userDetails.endTime": "زمان پایان",
|
||||
"app.learningDashboard.userDetails.joined": "پیوست",
|
||||
"app.learningDashboard.userDetails.category": "دستهبندی",
|
||||
"app.learningDashboard.userDetails.average": "میانگین",
|
||||
"app.learningDashboard.userDetails.activityPoints": "نقاط فعالیت",
|
||||
"app.learningDashboard.userDetails.poll": "نظرسنجی",
|
||||
"app.learningDashboard.userDetails.response": "پاسخ",
|
||||
"app.learningDashboard.userDetails.mostCommonAnswer": "رایجترین پاسخ",
|
||||
"app.learningDashboard.userDetails.anonymousAnswer": "نظرسنجی ناشناس",
|
||||
"app.learningDashboard.usersTable.title": "مرور کلی",
|
||||
"app.learningDashboard.usersTable.colOnline": "زمان آنلاین بودن",
|
||||
"app.learningDashboard.usersTable.colTalk": "زمان صحبت کردن",
|
||||
@ -944,10 +1035,32 @@
|
||||
"app.learningDashboard.usersTable.userStatusOnline": "آنلاین",
|
||||
"app.learningDashboard.usersTable.userStatusOffline": "آفلاین",
|
||||
"app.learningDashboard.usersTable.noUsers": "هنوز هیچ کاربری وجود ندارد",
|
||||
"app.learningDashboard.usersTable.name": "نام",
|
||||
"app.learningDashboard.usersTable.moderator": "مدیر",
|
||||
"app.learningDashboard.usersTable.pollVotes": "رأیهای نظرسنجی",
|
||||
"app.learningDashboard.usersTable.join": "پیوستن",
|
||||
"app.learningDashboard.usersTable.left": "ترککردن",
|
||||
"app.learningDashboard.usersTable.notAvailable": "در دسترس نیست",
|
||||
"app.learningDashboard.pollsTable.title": "نظرسنجیها",
|
||||
"app.learningDashboard.pollsTable.anonymousAnswer": "نظرسنجی ناشناس (پاسخها در ردیف آخر)",
|
||||
"app.learningDashboard.pollsTable.anonymousRowName": "ناشناس",
|
||||
"app.learningDashboard.pollsTable.noPollsCreatedHeading": "هیچ نظرسنجی ایجاد نشده است",
|
||||
"app.learningDashboard.pollsTable.noPollsCreatedMessage": "زمانی که یک نظرسنجی برای کاربران ارسال شد، نتایج آنها در این فهرست ظاهر میشود.",
|
||||
"app.learningDashboard.statusTimelineTable.title": "خط زمانی",
|
||||
"app.learningDashboard.statusTimelineTable.thumbnail": "تصویر کوچک ارائه",
|
||||
"app.learningDashboard.errors.invalidToken": "توکن نشست نامعتبر است",
|
||||
"app.learningDashboard.errors.dataUnavailable": "داده دیگر موجود نیست"
|
||||
"app.learningDashboard.errors.dataUnavailable": "داده دیگر موجود نیست",
|
||||
"mobileApp.portals.list.empty.addFirstPortal.label": "با استفاده از دکمه بالا، اولین درگاه خود را اضافه کنید،",
|
||||
"mobileApp.portals.list.empty.orUseOurDemoServer.label": "یا از سرورهای آزمایشی ما استفاده کنید.",
|
||||
"mobileApp.portals.list.add.button.label": "افزودن درگاه",
|
||||
"mobileApp.portals.fields.name.label": "نام درگاه",
|
||||
"mobileApp.portals.fields.name.placeholder": "نسخه آزمایشی بیگبلوباتن",
|
||||
"mobileApp.portals.fields.url.label": "آدرس سرور",
|
||||
"mobileApp.portals.addPortalPopup.confirm.button.label": "ذخیره",
|
||||
"mobileApp.portals.drawerNavigation.button.label": "درگاهها",
|
||||
"mobileApp.portals.addPortalPopup.validation.emptyFields": "زمینههای مورد نیاز",
|
||||
"mobileApp.portals.addPortalPopup.validation.portalNameAlreadyExists": "نام در حال حاضر استفاده شده است",
|
||||
"mobileApp.portals.addPortalPopup.validation.urlInvalid": "خطا در تلاش برای بارگیری صفحه - آدرس و اتصال شبکه را بررسی کنید"
|
||||
|
||||
}
|
||||
|
||||
|
@ -132,6 +132,8 @@
|
||||
"app.userList.userOptions.savedNames.title": "Liste des utilisateurs de la conférence {0} à {1}",
|
||||
"app.userList.userOptions.sortedFirstName.heading": "Trié par prénom :",
|
||||
"app.userList.userOptions.sortedLastName.heading": "Trié par nom :",
|
||||
"app.userList.userOptions.hideViewersCursor": "Les curseurs du visualiseur sont bloqués",
|
||||
"app.userList.userOptions.showViewersCursor": "Les curseurs du visualiseur sont débloqués",
|
||||
"app.media.label": "Média",
|
||||
"app.media.autoplayAlertDesc": "Autoriser l'accès",
|
||||
"app.media.screenshare.start": "Partage d'écran commencé",
|
||||
@ -508,9 +510,9 @@
|
||||
"app.breakoutJoinConfirmation.freeJoinMessage": "Choisissez une réunion privée à rejoindre",
|
||||
"app.breakoutTimeRemainingMessage": "Temps restant dans la réunion privée : {0}",
|
||||
"app.breakoutWillCloseMessage": "Le temps s'est écoulé. La réunion privée fermera bientôt",
|
||||
"app.breakout.dropdown.manageDuration": "Gérer la durée",
|
||||
"app.breakout.dropdown.destroyAll": "Supprimer les salles attenantes",
|
||||
"app.breakout.dropdown.options": "Options des salles attenantes",
|
||||
"app.breakout.dropdown.manageDuration": "Changer la durée",
|
||||
"app.breakout.dropdown.destroyAll": "Terminer les réunions privées",
|
||||
"app.breakout.dropdown.options": "Options des réunions privées",
|
||||
"app.calculatingBreakoutTimeRemaining": "Calcul du temps restant...",
|
||||
"app.audioModal.ariaTitle": "Fenêtre modale pour joindre la réunion en audio",
|
||||
"app.audioModal.microphoneLabel": "Microphone",
|
||||
@ -566,6 +568,7 @@
|
||||
"app.audio.audioSettings.descriptionLabel": "Veuillez noter qu'une boîte de dialogue apparaîtra dans votre navigateur, vous demandant d'accepter le partage de votre micro.",
|
||||
"app.audio.audioSettings.microphoneSourceLabel": "Choix du micro",
|
||||
"app.audio.audioSettings.speakerSourceLabel": "Choix du haut-parleur",
|
||||
"app.audio.audioSettings.testSpeakerLabel": "Testez votre haut-parleur",
|
||||
"app.audio.audioSettings.microphoneStreamLabel": "Volume de votre flux audio",
|
||||
"app.audio.audioSettings.retryLabel": "Réessayer",
|
||||
"app.audio.listenOnly.backLabel": "Retour",
|
||||
@ -604,6 +607,7 @@
|
||||
"app.error.500": "Oups, quelque chose s'est mal passé",
|
||||
"app.error.userLoggedOut": "Le jeton de session est invalide car l'utilisateur est déconnecté",
|
||||
"app.error.ejectedUser": "Le jeton de session est invalide car l'utilisateur a été expulsé",
|
||||
"app.error.joinedAnotherWindow": "Il semble que cette conférence est ouverte dans une autre fenêtre de navigateur",
|
||||
"app.error.userBanned": "L'utilisateur a été banni",
|
||||
"app.error.leaveLabel": "Connectez-vous à nouveau",
|
||||
"app.error.fallback.presentation.title": "Une erreur s'est produite",
|
||||
@ -684,6 +688,10 @@
|
||||
"app.shortcut-help.toggleFullscreen": "Basculer le mode plein-écran (Présentateur)",
|
||||
"app.shortcut-help.nextSlideDesc": "Diapositive suivante (présentateur)",
|
||||
"app.shortcut-help.previousSlideDesc": "Diapositive précédente (présentateur)",
|
||||
"app.shortcut-help.togglePanKey": "Barre d'espace",
|
||||
"app.shortcut-help.toggleFullscreenKey": "Entrée",
|
||||
"app.shortcut-help.nextSlideKey": "Flèche droite",
|
||||
"app.shortcut-help.previousSlideKey": "Flèche gauche",
|
||||
"app.lock-viewers.title": "Limiter la communication des participants",
|
||||
"app.lock-viewers.description": "Ces options vous permettent de restreindre l'utilisation de certaines fonctionnalités par les participants.",
|
||||
"app.lock-viewers.featuresLable": "Fonctionnalité",
|
||||
@ -699,6 +707,7 @@
|
||||
"app.lock-viewers.button.apply": "Appliquer",
|
||||
"app.lock-viewers.button.cancel": "Annuler",
|
||||
"app.lock-viewers.locked": "Verrouillé",
|
||||
"app.lock-viewers.hideViewersCursor": "Voir les curseurs des autres spectateurs",
|
||||
"app.guest-policy.ariaTitle": "Fenêtre des paramètres de gestion des accès",
|
||||
"app.guest-policy.title": "Gestion des accès",
|
||||
"app.guest-policy.description": "Modifier le paramétrage de la gestion des accès à la réunion ",
|
||||
@ -926,6 +935,8 @@
|
||||
"app.externalVideo.refreshLabel": "Réactualiser le lecteur vidéo",
|
||||
"app.externalVideo.fullscreenLabel": "Lecteur vidéo",
|
||||
"app.externalVideo.noteLabel": "Remarque : les vidéos externes partagées n'apparaîtront pas dans l'enregistrement. Les URL YouTube, Vimeo, Instructure Media, Twitch, Dailymotion et les URL de fichiers multimédias (par exemple https://example.com/xy.mp4) sont pris en charge.",
|
||||
"app.externalVideo.subtitlesOn": "Éteindre",
|
||||
"app.externalVideo.subtitlesOff": "Activer (si disponible)",
|
||||
"app.actionsBar.actionsDropdown.shareExternalVideo": "Partager une vidéo externe",
|
||||
"app.actionsBar.actionsDropdown.stopShareExternalVideo": "Arrêter le partage de vidéo externe",
|
||||
"app.iOSWarning.label": "Veuillez mettre à jour vers iOS 12.2 ou supérieur",
|
||||
@ -955,6 +966,7 @@
|
||||
"playback.button.search.aria": "Rechercher",
|
||||
"playback.button.section.aria": "Section de côté",
|
||||
"playback.button.swap.aria": "Permuter le contenu",
|
||||
"playback.button.theme.aria": "Basculer le thème",
|
||||
"playback.error.wrapper.aria": "Zone d'erreur",
|
||||
"playback.loader.wrapper.aria": "Zone de chargement",
|
||||
"playback.player.wrapper.aria": "Zone de lecture",
|
||||
@ -1038,10 +1050,17 @@
|
||||
"app.learningDashboard.statusTimelineTable.thumbnail": "Vignette de présentation",
|
||||
"app.learningDashboard.errors.invalidToken": "Jeton de session invalide",
|
||||
"app.learningDashboard.errors.dataUnavailable": "Les données ne sont plus disponibles",
|
||||
"mobileApp.portals.list.empty.addFirstPortal.label": "Ajoutez votre premier portail en utilisant le bouton ci-dessus,",
|
||||
"mobileApp.portals.list.empty.orUseOurDemoServer.label": "ou utilisez notre serveur de démo.",
|
||||
"mobileApp.portals.list.add.button.label": "Ajouter un portail",
|
||||
"mobileApp.portals.fields.name.label": "Nom du portail",
|
||||
"mobileApp.portals.fields.name.placeholder": "Démo BigBlueButton",
|
||||
"mobileApp.portals.fields.url.label": "URL du serveur",
|
||||
"mobileApp.portals.drawerNavigation.button.label": "Portails"
|
||||
"mobileApp.portals.addPortalPopup.confirm.button.label": "Enregistrer",
|
||||
"mobileApp.portals.drawerNavigation.button.label": "Portails",
|
||||
"mobileApp.portals.addPortalPopup.validation.emptyFields": "Champs requis",
|
||||
"mobileApp.portals.addPortalPopup.validation.portalNameAlreadyExists": "Ce nom est déjà utilisé",
|
||||
"mobileApp.portals.addPortalPopup.validation.urlInvalid": "Erreur de chargement de la page - vérifiez l'URL et la connexion réseau"
|
||||
|
||||
}
|
||||
|
||||
|
@ -510,8 +510,8 @@
|
||||
"app.breakoutJoinConfirmation.freeJoinMessage": "Seleccionar a sala parcial na que incorporarse",
|
||||
"app.breakoutTimeRemainingMessage": "Tempo restante da sala parcial: {0}",
|
||||
"app.breakoutWillCloseMessage": "Rematou o tempo. A sala parcial pecharase en breve.",
|
||||
"app.breakout.dropdown.manageDuration": "Xestionar a duración",
|
||||
"app.breakout.dropdown.destroyAll": "Destruír as salas parciais",
|
||||
"app.breakout.dropdown.manageDuration": "Cambiar a duración",
|
||||
"app.breakout.dropdown.destroyAll": "Remate das salas parciais",
|
||||
"app.breakout.dropdown.options": "Opcións das salas parciais",
|
||||
"app.calculatingBreakoutTimeRemaining": "Calculando tempo restante…",
|
||||
"app.audioModal.ariaTitle": " Xanela modal para unirse ao son",
|
||||
@ -607,6 +607,7 @@
|
||||
"app.error.500": "Ouh! algo foi mal",
|
||||
"app.error.userLoggedOut": "O usuario ten un testemuño de sesión non válido por mor do peche da sesión",
|
||||
"app.error.ejectedUser": "O usuario ten un testemuño de sesión non válido por mor da expulsión",
|
||||
"app.error.joinedAnotherWindow": "Semella que esta sesión está aberta noutra xanela do navegador.",
|
||||
"app.error.userBanned": "O usuario foi expulsado",
|
||||
"app.error.leaveLabel": "Acceder de novo",
|
||||
"app.error.fallback.presentation.title": "Produciuse un erro",
|
||||
|
@ -510,8 +510,6 @@
|
||||
"app.breakoutJoinConfirmation.freeJoinMessage": "Csoportterem választása",
|
||||
"app.breakoutTimeRemainingMessage": "Csoport hátralévő ideje: {0}",
|
||||
"app.breakoutWillCloseMessage": "Az idő lejárt. A csoportterem hamarosan bezárul",
|
||||
"app.breakout.dropdown.manageDuration": "Időtartam kezelése",
|
||||
"app.breakout.dropdown.destroyAll": "Csoporttermek megszüntetése",
|
||||
"app.breakout.dropdown.options": "Csoporttermek beállításai",
|
||||
"app.calculatingBreakoutTimeRemaining": "Hátralévő idő számítása ...",
|
||||
"app.audioModal.ariaTitle": "Csatlakozás hangablak",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user