[mirotalksfu] - add collaborative powerful rich text editor
هذا الالتزام موجود في:
@@ -61,6 +61,7 @@
|
||||
- Speech recognition, execute the app features simply with your voice.
|
||||
- Push-to-talk functionality, similar to a walkie-talkie.
|
||||
- Advanced collaborative whiteboard for teachers.
|
||||
- Advanced collaborative powerful rich text editor.
|
||||
- Real-time sharing of YouTube embed videos, video files (MP4, WebM, OGG), and audio files (MP3).
|
||||
- Real-time polls, allows users to create and participate in live polls, providing instant feedback and results.
|
||||
- Integrated RTMP server, fully compatible with **[OBS](https://obsproject.com)**.
|
||||
|
||||
@@ -43,7 +43,7 @@ dependencies: {
|
||||
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.5.47
|
||||
* @version 1.5.50
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -2512,6 +2512,42 @@ function startServer() {
|
||||
}
|
||||
});
|
||||
|
||||
// Room collaborative editor
|
||||
|
||||
socket.on('editorChange', (dataObject) => {
|
||||
if (!roomList.has(socket.room_id)) return;
|
||||
|
||||
//const data = checkXSS(dataObject);
|
||||
const data = dataObject;
|
||||
|
||||
const room = roomList.get(socket.room_id);
|
||||
|
||||
room.broadCast(socket.id, 'editorChange', data);
|
||||
});
|
||||
|
||||
socket.on('editorActions', (dataObject) => {
|
||||
if (!roomList.has(socket.room_id)) return;
|
||||
|
||||
const data = checkXSS(dataObject);
|
||||
|
||||
const room = roomList.get(socket.room_id);
|
||||
|
||||
log.debug('editorActions', data);
|
||||
|
||||
room.broadCast(socket.id, 'editorActions', data);
|
||||
});
|
||||
|
||||
socket.on('editorUpdate', (dataObject) => {
|
||||
if (!roomList.has(socket.room_id)) return;
|
||||
|
||||
//const data = checkXSS(dataObject);
|
||||
const data = dataObject;
|
||||
|
||||
const room = roomList.get(socket.room_id);
|
||||
|
||||
room.broadCast(socket.id, 'editorUpdate', data);
|
||||
});
|
||||
|
||||
socket.on('disconnect', async () => {
|
||||
if (!roomList.has(socket.room_id)) return;
|
||||
|
||||
|
||||
@@ -356,6 +356,7 @@ module.exports = {
|
||||
swapCameraButton: true,
|
||||
chatButton: true,
|
||||
pollButton: true,
|
||||
editorButton: true,
|
||||
raiseHandButton: true,
|
||||
transcriptionButton: true,
|
||||
whiteboardButton: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mirotalksfu",
|
||||
"version": "1.5.47",
|
||||
"version": "1.5.50",
|
||||
"description": "WebRTC SFU browser-based video calls",
|
||||
"main": "Server.js",
|
||||
"scripts": {
|
||||
|
||||
79
public/css/Editor.css
Normal file
79
public/css/Editor.css
Normal file
@@ -0,0 +1,79 @@
|
||||
.editor-container {
|
||||
z-index: 5;
|
||||
position: absolute;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
background: var(--body-bg);
|
||||
border: var(--border);
|
||||
box-shadow: var(--box-shadow);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.editor-header {
|
||||
display: inline-flex;
|
||||
text-align: left;
|
||||
margin-bottom: 30px;
|
||||
width: 100%;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.editor-header-btns {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
position: absolute;
|
||||
float: right;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.editor {
|
||||
height: 85vh !important;
|
||||
color: white !important;
|
||||
border-radius: 0px 0px 10px 10px; /* Top-left, top-right, bottom-right, bottom-left */
|
||||
border: var(--border) !important;
|
||||
box-shadow: var(--box-shadow) !important;
|
||||
}
|
||||
|
||||
.ql-toolbar {
|
||||
background: rgba(0, 0, 0, 0.4) !important;
|
||||
color: white !important;
|
||||
border-radius: 10px 10px 0px 0px; /* Top-left, top-right, bottom-right, bottom-left */
|
||||
border: var(--border) !important;
|
||||
}
|
||||
|
||||
/* Optional: Customize code block appearance */
|
||||
.ql-snow .ql-code-block-container {
|
||||
padding: 4px !important;
|
||||
font-family: monospace;
|
||||
margin-bottom: 20px !important;
|
||||
border-radius: 4px !important;
|
||||
/* border: var(--border) !important; */
|
||||
background: var(--select-bg) !important;
|
||||
box-shadow: var(--box-shadow) !important;
|
||||
}
|
||||
|
||||
.ql-snow .ql-code-block {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.ql-toolbar button:hover {
|
||||
background: var(--body-bg) !important;
|
||||
}
|
||||
|
||||
.ql-picker-options {
|
||||
color: white;
|
||||
border: none !important;
|
||||
border-radius: 10px;
|
||||
background: var(--body-bg);
|
||||
box-shadow: var(--box-shadow) !important;
|
||||
}
|
||||
|
||||
.ql-ui {
|
||||
padding: 5px;
|
||||
height: 30px;
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
background: var(--body-bg);
|
||||
}
|
||||
@@ -11,7 +11,7 @@ if (location.href.substr(0, 5) !== 'https') location.href = 'https' + location.h
|
||||
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.5.47
|
||||
* @version 1.5.50
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -252,6 +252,8 @@ let transcription;
|
||||
|
||||
let showFreeAvatars = true;
|
||||
|
||||
let quill = null;
|
||||
|
||||
// ####################################################
|
||||
// INIT ROOM
|
||||
// ####################################################
|
||||
@@ -338,6 +340,14 @@ function initClient() {
|
||||
setTippy('pollMinButton', 'Minimize', 'bottom');
|
||||
setTippy('pollSaveButton', 'Save results', 'bottom');
|
||||
setTippy('pollCloseBtn', 'Close', 'bottom');
|
||||
setTippy('editorLockBtn', 'Toggle Lock editor', 'bottom');
|
||||
setTippy('editorUnlockBtn', 'Toggle Lock editor', 'bottom');
|
||||
setTippy('editorUndoBtn', 'Undo', 'bottom');
|
||||
setTippy('editorRedoBtn', 'Redo', 'bottom');
|
||||
setTippy('editorCopyBtn', 'Copy', 'bottom');
|
||||
setTippy('editorSaveBtn', 'Save', 'bottom');
|
||||
setTippy('editorCloseBtn', 'Close', 'bottom');
|
||||
setTippy('editorCleanBtn', 'Clean', 'bottom');
|
||||
setTippy('pollAddOptionBtn', 'Add option', 'top');
|
||||
setTippy('pollDelOptionBtn', 'Delete option', 'top');
|
||||
setTippy('participantsSaveBtn', 'Save participants info', 'bottom');
|
||||
@@ -375,6 +385,7 @@ function refreshMainButtonsToolTipPlacement() {
|
||||
setTippy('emojiRoomButton', 'Toggle emoji reaction', placement);
|
||||
setTippy('chatButton', 'Toggle the chat', placement);
|
||||
setTippy('pollButton', 'Toggle the poll', placement);
|
||||
setTippy('editorButton', 'Toggle the editor', placement);
|
||||
setTippy('transcriptionButton', 'Toggle transcription', placement);
|
||||
setTippy('whiteboardButton', 'Toggle the whiteboard', placement);
|
||||
setTippy('snapshotRoomButton', 'Snapshot screen, window, or tab', placement);
|
||||
@@ -1185,7 +1196,7 @@ function copyRoomURL() {
|
||||
userLog('info', 'Meeting URL copied to clipboard 👍', 'top-end');
|
||||
}
|
||||
|
||||
function copyToClipboard(txt) {
|
||||
function copyToClipboard(txt, showTxt = true) {
|
||||
let tmpInput = document.createElement('input');
|
||||
document.body.appendChild(tmpInput);
|
||||
tmpInput.value = txt;
|
||||
@@ -1193,7 +1204,9 @@ function copyToClipboard(txt) {
|
||||
tmpInput.setSelectionRange(0, 99999); // For mobile devices
|
||||
navigator.clipboard.writeText(tmpInput.value);
|
||||
document.body.removeChild(tmpInput);
|
||||
userLog('info', `${txt} copied to clipboard 👍`, 'top-end');
|
||||
showTxt
|
||||
? userLog('info', `${txt} copied to clipboard 👍`, 'top-end')
|
||||
: userLog('info', `Copied to clipboard 👍`, 'top-end');
|
||||
}
|
||||
|
||||
function shareRoomByEmail() {
|
||||
@@ -1284,6 +1297,7 @@ function roomIsReady() {
|
||||
}
|
||||
BUTTONS.main.chatButton && show(chatButton);
|
||||
BUTTONS.main.pollButton && show(pollButton);
|
||||
BUTTONS.main.editorButton && show(editorButton);
|
||||
BUTTONS.main.raiseHandButton && show(raiseHandButton);
|
||||
BUTTONS.main.emojiRoomButton && show(emojiRoomButton);
|
||||
!BUTTONS.chat.chatSaveButton && hide(chatSaveButton);
|
||||
@@ -1375,6 +1389,7 @@ function roomIsReady() {
|
||||
handleInputs();
|
||||
handleChatEmojiPicker();
|
||||
handleRoomEmojiPicker();
|
||||
handleEditor();
|
||||
loadSettingsFromLocalStorage();
|
||||
startSessionTimer();
|
||||
document.body.addEventListener('mousemove', (e) => {
|
||||
@@ -1619,6 +1634,39 @@ function handleButtons() {
|
||||
pollCreateForm.onsubmit = (e) => {
|
||||
rc.pollCreateNewForm(e);
|
||||
};
|
||||
editorButton.onclick = () => {
|
||||
rc.toggleEditor();
|
||||
if (isPresenter && !rc.editorIsLocked()) {
|
||||
rc.editorSendAction('toggle');
|
||||
}
|
||||
};
|
||||
editorCloseBtn.onclick = () => {
|
||||
rc.toggleEditor();
|
||||
if (isPresenter && !rc.editorIsLocked()) {
|
||||
rc.editorSendAction('toggle');
|
||||
}
|
||||
};
|
||||
editorLockBtn.onclick = () => {
|
||||
rc.toggleLockUnlockEditor();
|
||||
};
|
||||
editorUnlockBtn.onclick = () => {
|
||||
rc.toggleLockUnlockEditor();
|
||||
};
|
||||
editorCleanBtn.onclick = () => {
|
||||
rc.editorClean();
|
||||
};
|
||||
editorCopyBtn.onclick = () => {
|
||||
rc.editorCopy();
|
||||
};
|
||||
editorSaveBtn.onclick = () => {
|
||||
rc.editorSave();
|
||||
};
|
||||
editorUndoBtn.onclick = () => {
|
||||
rc.editorUndo();
|
||||
};
|
||||
editorRedoBtn.onclick = () => {
|
||||
rc.editorRedo();
|
||||
};
|
||||
transcriptionButton.onclick = () => {
|
||||
transcription.toggle();
|
||||
};
|
||||
@@ -2667,6 +2715,50 @@ function handleRoomEmojiPicker() {
|
||||
}
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// ROOM EDITOR
|
||||
// ####################################################
|
||||
|
||||
function handleEditor() {
|
||||
const toolbarOptions = [
|
||||
[{ header: [1, 2, 3, false] }],
|
||||
['bold', 'italic', 'underline', 'link', 'code-block'],
|
||||
[{ list: 'ordered' }, { list: 'bullet' }, { list: 'check' }],
|
||||
[{ indent: '+1' }, { indent: '-1' }], // { align: [] }
|
||||
//...
|
||||
];
|
||||
|
||||
quill = new Quill('#editor', {
|
||||
modules: {
|
||||
toolbar: {
|
||||
container: toolbarOptions,
|
||||
},
|
||||
syntax: true,
|
||||
},
|
||||
theme: 'snow',
|
||||
});
|
||||
|
||||
applySyntaxHighlighting();
|
||||
|
||||
quill.on('text-change', (delta, oldDelta, source) => {
|
||||
if (!isPresenter && rc.editorIsLocked()) {
|
||||
return;
|
||||
}
|
||||
console.log('text-change', { delta, oldDelta, source });
|
||||
applySyntaxHighlighting();
|
||||
if (rc.thereAreParticipants() && source === 'user') {
|
||||
socket.emit('editorChange', delta);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function applySyntaxHighlighting() {
|
||||
const codeBlocks = document.querySelectorAll('.ql-syntax');
|
||||
codeBlocks.forEach((block) => {
|
||||
hljs.highlightElement(block);
|
||||
});
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// LOAD SETTINGS FROM LOCAL STORAGE
|
||||
// ####################################################
|
||||
@@ -4344,7 +4436,7 @@ function showAbout() {
|
||||
imageUrl: image.about,
|
||||
customClass: { image: 'img-about' },
|
||||
position: 'center',
|
||||
title: 'WebRTC SFU v1.5.47',
|
||||
title: 'WebRTC SFU v1.5.50',
|
||||
html: `
|
||||
<br />
|
||||
<div id="about">
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
|
||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
|
||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||
* @version 1.5.47
|
||||
* @version 1.5.50
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -68,6 +68,7 @@ const icons = {
|
||||
theme: '<i class="fas fa-fill-drip"></i>',
|
||||
recSync: '<i class="fa-solid fa-cloud-arrow-up"></i>',
|
||||
refresh: '<i class="fas fa-rotate"></i>',
|
||||
editor: '<i class="fas fa-pen-to-square"></i>',
|
||||
};
|
||||
|
||||
const image = {
|
||||
@@ -261,6 +262,8 @@ class RoomClient {
|
||||
this.isChatEmojiOpen = false;
|
||||
this.isPollOpen = false;
|
||||
this.isPollPinned = false;
|
||||
this.isEditorOpen = false;
|
||||
this.isEditorLocked = false;
|
||||
this.isSpeechSynthesisSupported = isSpeechSynthesisSupported;
|
||||
this.speechInMessages = false;
|
||||
this.showChatOnMessage = true;
|
||||
@@ -905,6 +908,9 @@ class RoomClient {
|
||||
this.socket.on('endRTMPfromURL', this.handleEndRTMPfromURL);
|
||||
this.socket.on('errorRTMPfromURL', this.handleErrorRTMPfromURL);
|
||||
this.socket.on('updatePolls', this.handleUpdatePolls);
|
||||
this.socket.on('editorChange', this.handleEditorChange);
|
||||
this.socket.on('editorActions', this.handleEditorActions);
|
||||
this.socket.on('editorUpdate', this.handleEditorUpdate);
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
@@ -944,6 +950,7 @@ class RoomClient {
|
||||
if (isBroadcastingEnabled) {
|
||||
if (isParticipantsListOpen) getRoomParticipants();
|
||||
wbUpdate();
|
||||
this.editorUpdate();
|
||||
} else {
|
||||
adaptAspectRatio(participantsCount);
|
||||
}
|
||||
@@ -1079,6 +1086,18 @@ class RoomClient {
|
||||
this.pollsUpdate(data);
|
||||
};
|
||||
|
||||
handleEditorChange = (data) => {
|
||||
this.handleEditorData(data);
|
||||
};
|
||||
|
||||
handleEditorActions = (data) => {
|
||||
this.handleEditorActionsData(data);
|
||||
};
|
||||
|
||||
handleEditorUpdate = (data) => {
|
||||
this.handleEditorUpdateData(data);
|
||||
};
|
||||
|
||||
// ####################################################
|
||||
// SERVER AWAY/MAINTENANCE
|
||||
// ####################################################
|
||||
@@ -2166,6 +2185,8 @@ class RoomClient {
|
||||
try {
|
||||
wbUpdate();
|
||||
|
||||
this.editorUpdate();
|
||||
|
||||
const { consumer, stream, kind } = await this.getConsumeStream(producer_id, peer_info.peer_id, type);
|
||||
|
||||
console.log('CONSUMER MEDIA TYPE ----> ' + type);
|
||||
@@ -2608,6 +2629,8 @@ class RoomClient {
|
||||
//
|
||||
wbUpdate();
|
||||
|
||||
this.editorUpdate();
|
||||
|
||||
this.handleHideMe();
|
||||
}
|
||||
|
||||
@@ -4656,6 +4679,160 @@ class RoomClient {
|
||||
return `Poll_${roomName}_${dateTime}.txt`;
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// EDITOR
|
||||
// ####################################################
|
||||
|
||||
toggleEditor() {
|
||||
editorRoom.classList.toggle('show');
|
||||
if (!this.isEditorOpen) {
|
||||
this.sound('open');
|
||||
}
|
||||
this.isEditorOpen = !this.isEditorOpen;
|
||||
}
|
||||
|
||||
toggleLockUnlockEditor() {
|
||||
this.isEditorLocked = !this.isEditorLocked;
|
||||
|
||||
const btnToShow = this.isEditorLocked ? editorLockBtn : editorUnlockBtn;
|
||||
const btnToHide = this.isEditorLocked ? editorUnlockBtn : editorLockBtn;
|
||||
const btnColor = this.isEditorLocked ? 'red' : 'white';
|
||||
const action = this.isEditorLocked ? 'lock' : 'unlock';
|
||||
|
||||
show(btnToShow);
|
||||
hide(btnToHide);
|
||||
setColor(editorLockBtn, btnColor);
|
||||
|
||||
this.editorSendAction(action);
|
||||
|
||||
if (this.isEditorLocked) {
|
||||
userLog('info', 'The Editor is locked. \n The participants cannot interact with it.', 'top-right');
|
||||
sound('locked');
|
||||
}
|
||||
}
|
||||
|
||||
editorCenter() {
|
||||
editorRoom.style.position = 'fixed';
|
||||
editorRoom.style.transform = 'translate(-50%, -50%)';
|
||||
editorRoom.style.top = '50%';
|
||||
editorRoom.style.left = '50%';
|
||||
}
|
||||
|
||||
editorUpdate() {
|
||||
if (this.isEditorOpen && (!isRulesActive || isPresenter)) {
|
||||
console.log('IsPresenter: update editor content to the participants in the room');
|
||||
const content = quill.getContents(); // Get content in Delta format
|
||||
this.socket.emit('editorUpdate', content);
|
||||
const action = this.isEditorLocked ? 'lock' : 'unlock';
|
||||
this.editorSendAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
handleEditorUpdateData(data) {
|
||||
this.editorOpen();
|
||||
quill.setContents(data);
|
||||
}
|
||||
|
||||
handleEditorData(data) {
|
||||
this.editorOpen();
|
||||
quill.updateContents(data);
|
||||
}
|
||||
|
||||
editorOpen() {
|
||||
if (!this.isEditorOpen) {
|
||||
this.sound('open');
|
||||
this.toggleEditor();
|
||||
}
|
||||
}
|
||||
|
||||
handleEditorActionsData(data) {
|
||||
const { peer_name, action } = data;
|
||||
switch (action) {
|
||||
case 'toggle':
|
||||
this.toggleEditor();
|
||||
this.userLog('info', `${icons.editor} ${peer_name} toggle editor`, 'top-end', 6000);
|
||||
break;
|
||||
case 'clean':
|
||||
quill.setText('');
|
||||
this.userLog('info', `${icons.editor} ${peer_name} cleared editor`, 'top-end', 6000);
|
||||
break;
|
||||
case 'lock':
|
||||
this.isEditorLocked = true;
|
||||
quill.enable(false);
|
||||
this.userLog('info', `${icons.editor} ${peer_name} locked the editor`, 'top-end', 6000);
|
||||
break;
|
||||
case 'unlock':
|
||||
this.isEditorLocked = false;
|
||||
quill.enable(true);
|
||||
this.userLog('info', `${icons.editor} ${peer_name} unlocked the editor`, 'top-end', 6000);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
editorIsLocked() {
|
||||
return this.isEditorLocked;
|
||||
}
|
||||
|
||||
editorUndo() {
|
||||
quill.history.undo();
|
||||
}
|
||||
|
||||
editorRedo() {
|
||||
quill.history.redo();
|
||||
}
|
||||
|
||||
editorCopy() {
|
||||
const content = quill.getText();
|
||||
if (content.trim().length === 0) {
|
||||
return this.userLog('info', 'Nothing to copy', 'top-end');
|
||||
}
|
||||
copyToClipboard(content, false);
|
||||
}
|
||||
|
||||
editorClean() {
|
||||
if (!isPresenter && this.editorIsLocked()) {
|
||||
userLog('info', 'The Editor is locked. \n You cannot interact with it.', 'top-right');
|
||||
return;
|
||||
}
|
||||
const content = quill.getText();
|
||||
if (content.trim().length === 0) {
|
||||
return this.userLog('info', 'Nothing to clear', 'top-end');
|
||||
}
|
||||
Swal.fire({
|
||||
background: swalBackground,
|
||||
position: 'center',
|
||||
title: 'Clear the editor content?',
|
||||
imageUrl: image.delete,
|
||||
showDenyButton: true,
|
||||
confirmButtonText: `Yes`,
|
||||
denyButtonText: `No`,
|
||||
showClass: { popup: 'animate__animated animate__fadeInDown' },
|
||||
hideClass: { popup: 'animate__animated animate__fadeOutUp' },
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
quill.setText('');
|
||||
this.editorSendAction('clean');
|
||||
this.sound('delete');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
editorSave() {
|
||||
const content = quill.getText();
|
||||
if (content.trim().length === 0) {
|
||||
return this.userLog('info', 'No data to save!', 'top-end');
|
||||
}
|
||||
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
|
||||
const file = 'Room_' + this.room_id + getDataTimeString() + '_editor.txt';
|
||||
this.saveBlobToFile(blob, file);
|
||||
}
|
||||
|
||||
editorSendAction(action) {
|
||||
this.socket.emit('editorActions', { peer_name: this.peer_name, action: action });
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// RECORDING
|
||||
// ####################################################
|
||||
|
||||
@@ -134,6 +134,7 @@ function handleRules(isPresenter) {
|
||||
//BUTTONS.consumerVideo.muteAudioButton = false;
|
||||
//BUTTONS.consumerVideo.muteVideoButton = false;
|
||||
BUTTONS.whiteboard.whiteboardLockButton = false;
|
||||
|
||||
//...
|
||||
} else {
|
||||
// ##################################
|
||||
@@ -144,6 +145,8 @@ function handleRules(isPresenter) {
|
||||
BUTTONS.settings.lockRoomButton = BUTTONS.settings.lockRoomButton && !isRoomLocked;
|
||||
BUTTONS.settings.unlockRoomButton = BUTTONS.settings.lockRoomButton && isRoomLocked;
|
||||
BUTTONS.settings.sendEmailInvitation = true;
|
||||
|
||||
show(editorUnlockBtn);
|
||||
//...
|
||||
|
||||
// ##################################
|
||||
@@ -267,6 +270,7 @@ function handleRulesBroadcasting() {
|
||||
//elemDisplay('snapshotRoomButton', false);
|
||||
//elemDisplay('emojiRoomButton', false);
|
||||
//elemDisplay('pollButton', false);
|
||||
//elemDisplay('editorButton', false);
|
||||
elemDisplay('transcriptionButton', false);
|
||||
elemDisplay('lockRoomButton', false);
|
||||
elemDisplay('unlockRoomButton', false);
|
||||
|
||||
@@ -30,6 +30,8 @@ const commands = {
|
||||
chatOff: 'close the chat',
|
||||
pollOn: 'open the poll',
|
||||
pollOff: 'close the poll',
|
||||
editorOn: 'open the editor',
|
||||
editorOff: 'close the editor',
|
||||
toggleTr: 'toggle transcription',
|
||||
whiteboardOn: 'open the whiteboard',
|
||||
whiteboardOff: 'close the whiteboard',
|
||||
@@ -197,6 +199,14 @@ function execVoiceCommands(transcript) {
|
||||
printCommand(commands.pollOff);
|
||||
pollCloseBtn.click();
|
||||
break;
|
||||
case commands.editorOn:
|
||||
printCommand(commands.editorOn);
|
||||
editorButton.click();
|
||||
break;
|
||||
case commands.editorOff:
|
||||
printCommand(commands.editorOff);
|
||||
editorCloseBtn.click();
|
||||
break;
|
||||
case commands.chatSend:
|
||||
printCommand(commands.chatSend);
|
||||
chatSendButton.click();
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
<link rel="stylesheet" href="../css/VideoGrid.css" />
|
||||
<link rel="stylesheet" href="../css/GroupChat.css" />
|
||||
<link rel="stylesheet" href="../css/Polls.css" />
|
||||
<link rel="stylesheet" href="../css/Editor.css" />
|
||||
|
||||
<!-- https://cdnjs.com/libraries/font-awesome -->
|
||||
|
||||
@@ -67,6 +68,17 @@
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/monolith.min.css" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/nano.min.css" />
|
||||
|
||||
<!-- Highlight https://github.com/highlightjs/highlight.js -->
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/atom-one-dark.min.css"
|
||||
/>
|
||||
|
||||
<!-- Quill https://github.com/slab/quill -->
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.snow.css" />
|
||||
|
||||
<script defer src="../js/Brand.js"></script>
|
||||
|
||||
<!-- Modern or es5 bundle -->
|
||||
@@ -124,6 +136,8 @@
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/pdfjs-dist@3.11.174/build/pdf.min.js"></script>
|
||||
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script>
|
||||
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/highlight.min.js"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
@@ -169,8 +183,9 @@ access to use this app.
|
||||
<button id="stopRecButton" class="hidden"><i class="fas fa-record-vinyl cr"></i></button>
|
||||
<button id="emojiRoomButton" class="hidden"><i class="fas fa-face-smile"></i></button>
|
||||
<button id="chatButton" class="hidden"><i class="fas fa-comments"></i></button>
|
||||
<button id="pollButton" class="hidden"><i class="fas fa-square-poll-horizontal"></i></button>
|
||||
<button id="transcriptionButton" class="hidden"><i class="fas fa-closed-captioning"></i></button>
|
||||
<button id="pollButton" class="hidden"><i class="fas fa-square-poll-horizontal"></i></button>
|
||||
<button id="editorButton" class="hidden"><i class="fas fa-pen-to-square"></i></button>
|
||||
<button id="whiteboardButton" class="hidden"><i class="fas fa-chalkboard-teacher"></i></button>
|
||||
<button id="snapshotRoomButton" class="hidden"><i class="fas fas fa-camera-retro"></i></button>
|
||||
<button id="settingsButton" class="hidden"><i class="fas fa-cogs"></i></button>
|
||||
@@ -1526,6 +1541,22 @@ access to use this app.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="editorRoom" class="editor-container fadein hidden">
|
||||
<div id="editorHeader" class="editor-header">
|
||||
<div class="editor-header-btns">
|
||||
<button id="editorUnlockBtn" class="fa-solid fa-lock-open hidden"></button>
|
||||
<button id="editorLockBtn" class="fa-solid fa-lock hidden"></button>
|
||||
<button id="editorUndoBtn" class="fas fa-undo"></button>
|
||||
<button id="editorRedoBtn" class="fas fa-redo"></button>
|
||||
<button id="editorCopyBtn" class="fas fa-paste"></button>
|
||||
<button id="editorSaveBtn" class="fas fa-floppy-disk"></button>
|
||||
<button id="editorCleanBtn" class="fas fa-trash"></button>
|
||||
<button id="editorCloseBtn" class="fas fa-times"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor" id="editor"></div>
|
||||
</section>
|
||||
|
||||
<section id="transcriptionRoom" class="transcription-room fadein">
|
||||
<section id="transcriptionSection" class="transcription">
|
||||
<header id="transcriptionHeader" class="transcription-header">
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم