Merge branch 'master' of https://github.com/bigbluebutton/bigbluebutton into tRecord
This commit is contained in:
commit
6317647b23
@ -80,6 +80,7 @@ public class ParamsProcessorUtil {
|
||||
private String html5ClientUrl;
|
||||
private Boolean moderatorsJoinViaHTML5Client;
|
||||
private Boolean attendeesJoinViaHTML5Client;
|
||||
private Boolean allowRequestsWithoutSession;
|
||||
private String defaultAvatarURL;
|
||||
private String defaultConfigURL;
|
||||
private String defaultGuestPolicy;
|
||||
@ -421,6 +422,10 @@ public class ParamsProcessorUtil {
|
||||
return moderatorsJoinViaHTML5Client;
|
||||
}
|
||||
|
||||
public Boolean getAllowRequestsWithoutSession() {
|
||||
return allowRequestsWithoutSession;
|
||||
}
|
||||
|
||||
public String getDefaultConfigXML() {
|
||||
defaultConfigXML = getConfig(defaultConfigURL);
|
||||
|
||||
@ -775,6 +780,10 @@ public class ParamsProcessorUtil {
|
||||
this.moderatorsJoinViaHTML5Client = moderatorsJoinViaHTML5Client;
|
||||
}
|
||||
|
||||
public void setAllowRequestsWithoutSession(Boolean allowRequestsWithoutSession) {
|
||||
this.allowRequestsWithoutSession = allowRequestsWithoutSession;
|
||||
}
|
||||
|
||||
public void setAttendeesJoinViaHTML5Client(Boolean attendeesJoinViaHTML5Client) {
|
||||
this.attendeesJoinViaHTML5Client = attendeesJoinViaHTML5Client;
|
||||
}
|
||||
|
@ -123,6 +123,7 @@
|
||||
|
||||
.poll,
|
||||
.breakoutRoom,
|
||||
.note,
|
||||
.chat {
|
||||
@extend %full-page;
|
||||
|
||||
|
@ -153,6 +153,7 @@ class MessageList extends Component {
|
||||
<Button
|
||||
aria-hidden="true"
|
||||
className={styles.unreadButton}
|
||||
color="primary"
|
||||
size="sm"
|
||||
label={intl.formatMessage(intlMessages.moreMessages)}
|
||||
onClick={() => this.scrollTo()}
|
||||
|
@ -37,8 +37,6 @@
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
text-transform: uppercase;
|
||||
// margin-top: .25rem;
|
||||
margin-bottom: .25rem;
|
||||
background-color: #808080;
|
||||
@extend %text-elipsis;
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ const propTypes = {
|
||||
|
||||
const defaultProps = {
|
||||
children: null,
|
||||
isOpen: false,
|
||||
keepOpen: null,
|
||||
onShow: noop,
|
||||
onHide: noop,
|
||||
autoFocus: false,
|
||||
@ -78,10 +78,12 @@ class Dropdown extends Component {
|
||||
onShow,
|
||||
onHide,
|
||||
} = this.props;
|
||||
|
||||
const { isOpen } = this.state;
|
||||
|
||||
if (this.state.isOpen && !prevState.isOpen) { onShow(); }
|
||||
if (isOpen && !prevState.isOpen) { onShow(); }
|
||||
|
||||
if (!this.state.isOpen && prevState.isOpen) { onHide(); }
|
||||
if (!isOpen && prevState.isOpen) { onHide(); }
|
||||
}
|
||||
|
||||
handleShow() {
|
||||
@ -99,16 +101,29 @@ class Dropdown extends Component {
|
||||
}
|
||||
|
||||
handleWindowClick(event) {
|
||||
const { keepOpen, onHide } = this.props;
|
||||
const { isOpen } = this.state;
|
||||
const triggerElement = findDOMNode(this.trigger);
|
||||
const contentElement = findDOMNode(this.content);
|
||||
const closeDropdown = this.props.isOpen && this.state.isOpen && triggerElement.contains(event.target);
|
||||
const preventHide = this.props.isOpen && contentElement.contains(event.target) || !triggerElement;
|
||||
|
||||
if (closeDropdown) {
|
||||
return this.props.onHide();
|
||||
|
||||
if (keepOpen === null) {
|
||||
if (triggerElement.contains(event.target)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (contentElement && preventHide) {
|
||||
if (triggerElement && triggerElement.contains(event.target)) {
|
||||
if (keepOpen) return onHide();
|
||||
if (isOpen) return this.handleHide();
|
||||
}
|
||||
|
||||
if (keepOpen && isOpen && !contentElement.contains(event.target)) {
|
||||
onHide();
|
||||
this.handleHide();
|
||||
return;
|
||||
}
|
||||
|
||||
if (keepOpen !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -116,7 +131,8 @@ class Dropdown extends Component {
|
||||
}
|
||||
|
||||
handleToggle() {
|
||||
return this.state.isOpen ? this.handleHide() : this.handleShow();
|
||||
const { isOpen } = this.state;
|
||||
return isOpen ? this.handleHide() : this.handleShow();
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -125,15 +141,18 @@ class Dropdown extends Component {
|
||||
className,
|
||||
style,
|
||||
intl,
|
||||
keepOpen,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const { isOpen } = this.state;
|
||||
|
||||
let trigger = children.find(x => x.type === DropdownTrigger);
|
||||
let content = children.find(x => x.type === DropdownContent);
|
||||
|
||||
trigger = React.cloneElement(trigger, {
|
||||
ref: (ref) => { this.trigger = ref; },
|
||||
dropdownIsOpen: this.state.isOpen,
|
||||
dropdownIsOpen: isOpen,
|
||||
dropdownToggle: this.handleToggle,
|
||||
dropdownShow: this.handleShow,
|
||||
dropdownHide: this.handleHide,
|
||||
@ -141,12 +160,14 @@ class Dropdown extends Component {
|
||||
|
||||
content = React.cloneElement(content, {
|
||||
ref: (ref) => { this.content = ref; },
|
||||
'aria-expanded': this.state.isOpen,
|
||||
dropdownIsOpen: this.state.isOpen,
|
||||
'aria-expanded': isOpen,
|
||||
dropdownIsOpen: isOpen,
|
||||
dropdownToggle: this.handleToggle,
|
||||
dropdownShow: this.handleShow,
|
||||
dropdownHide: this.handleHide,
|
||||
});
|
||||
|
||||
const showCloseBtn = (isOpen && keepOpen) || (isOpen && keepOpen === null);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -162,7 +183,7 @@ class Dropdown extends Component {
|
||||
>
|
||||
{trigger}
|
||||
{content}
|
||||
{this.state.isOpen ?
|
||||
{showCloseBtn ?
|
||||
<Button
|
||||
className={styles.close}
|
||||
label={intl.formatMessage(intlMessages.close)}
|
||||
|
@ -81,10 +81,10 @@
|
||||
color: var(--color-white);
|
||||
|
||||
.verticalList & {
|
||||
margin-left: calc((var(--line-height-computed) / 2) * -1);
|
||||
margin-right: calc((var(--line-height-computed) / 2) * -1);
|
||||
padding-left: calc(var(--line-height-computed) / 2);
|
||||
padding-right: calc(var(--line-height-computed) / 2);
|
||||
margin-left: -.25rem;
|
||||
margin-right: -.25rem;
|
||||
padding-left: .25rem;
|
||||
padding-right: .25rem;
|
||||
}
|
||||
|
||||
.horizontalList & {
|
||||
|
@ -23,7 +23,7 @@
|
||||
background: var(--dropdown-bg);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
|
||||
border: 1px solid rgba(0, 0, 0, .15);
|
||||
border: 0;
|
||||
padding: calc(var(--line-height-computed) / 2);
|
||||
z-index: 1000;
|
||||
|
||||
|
68
bigbluebutton-html5/imports/ui/components/note/component.jsx
Normal file
68
bigbluebutton-html5/imports/ui/components/note/component.jsx
Normal file
@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Session } from 'meteor/session';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import { styles } from './styles';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
hideNoteLabel: {
|
||||
id: 'app.note.hideNoteLabel',
|
||||
description: 'Label for hiding note button',
|
||||
},
|
||||
title: {
|
||||
id: 'app.note.title',
|
||||
description: 'Title for the shared notes',
|
||||
},
|
||||
});
|
||||
|
||||
const propTypes = {
|
||||
url: PropTypes.string.isRequired,
|
||||
intl: PropTypes.shape({
|
||||
formatMessage: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
};
|
||||
|
||||
const Note = (props) => {
|
||||
const {
|
||||
url,
|
||||
intl,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-test="note"
|
||||
className={styles.note}
|
||||
>
|
||||
<header className={styles.header}>
|
||||
<div
|
||||
data-test="noteTitle"
|
||||
className={styles.title}
|
||||
>
|
||||
<Button
|
||||
onClick={() => {
|
||||
Session.set('openPanel', 'userlist');
|
||||
}}
|
||||
aria-label={intl.formatMessage(intlMessages.hideNoteLabel)}
|
||||
label={intl.formatMessage(intlMessages.title)}
|
||||
icon="left_arrow"
|
||||
className={styles.hideBtn}
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
<iframe
|
||||
title="etherpad"
|
||||
src={url}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Note.propTypes = propTypes;
|
||||
Note.defaultProps = defaultProps;
|
||||
|
||||
export default injectWbResizeEvent(injectIntl(Note));
|
22
bigbluebutton-html5/imports/ui/components/note/container.jsx
Normal file
22
bigbluebutton-html5/imports/ui/components/note/container.jsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import Note from './component';
|
||||
import NoteService from './service';
|
||||
|
||||
class NoteContainer extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<Note {...this.props}>
|
||||
{this.props.children}
|
||||
</Note>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTracker(() => {
|
||||
const url = NoteService.getNoteURL();
|
||||
|
||||
return {
|
||||
url,
|
||||
};
|
||||
})(NoteContainer);
|
74
bigbluebutton-html5/imports/ui/components/note/service.js
Normal file
74
bigbluebutton-html5/imports/ui/components/note/service.js
Normal file
@ -0,0 +1,74 @@
|
||||
import Users from '/imports/api/users';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import Settings from '/imports/ui/services/settings';
|
||||
|
||||
const NOTE_CONFIG = Meteor.settings.public.note;
|
||||
|
||||
/**
|
||||
* Calculate a 32 bit FNV-1a hash
|
||||
* Found here: https://gist.github.com/vaiorabbit/5657561
|
||||
* Ref.: http://isthe.com/chongo/tech/comp/fnv/
|
||||
*
|
||||
* @param {string} str the input value
|
||||
* @param {boolean} [asString=false] set to true to return the hash value as
|
||||
* 8-digit hex string instead of an integer
|
||||
* @param {integer} [seed] optionally pass the hash of the previous chunk
|
||||
* @returns {integer | string}
|
||||
*/
|
||||
const hashFNV32a = (str, asString, seed) => {
|
||||
let hval = (seed === undefined) ? 0x811c9dc5 : seed;
|
||||
|
||||
for (let i = 0, l = str.length; i < l; i++) {
|
||||
hval ^= str.charCodeAt(i);
|
||||
hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
|
||||
}
|
||||
if (asString) {
|
||||
return ("0000000" + (hval >>> 0).toString(16)).substr(-8);
|
||||
}
|
||||
return hval >>> 0;
|
||||
}
|
||||
|
||||
const generateNoteId = () => {
|
||||
const meetingId = Auth.meetingID;
|
||||
const noteId = hashFNV32a(meetingId, true);
|
||||
return noteId;
|
||||
};
|
||||
|
||||
const getLang = () => {
|
||||
const locale = Settings.application.locale;
|
||||
const lang = locale.toLowerCase();
|
||||
return lang;
|
||||
};
|
||||
|
||||
const getCurrentUser = () => {
|
||||
const userId = Auth.userID;
|
||||
const User = Users.findOne({ userId });
|
||||
return User;
|
||||
};
|
||||
|
||||
const getNoteParams = () => {
|
||||
let config = NOTE_CONFIG.config;
|
||||
const User = getCurrentUser();
|
||||
config.userName = User.name;
|
||||
config.userColor = User.color;
|
||||
config.lang = getLang();
|
||||
|
||||
let params = [];
|
||||
for (var key in config) {
|
||||
if (config.hasOwnProperty(key)) {
|
||||
params.push(key + '=' + encodeURIComponent(config[key]));
|
||||
}
|
||||
}
|
||||
return params.join('&');
|
||||
}
|
||||
|
||||
const getNoteURL = () => {
|
||||
const noteId = generateNoteId();
|
||||
const params = getNoteParams();
|
||||
let url = NOTE_CONFIG.url + '/p/' + noteId + '?' + params;
|
||||
return url;
|
||||
};
|
||||
|
||||
export default {
|
||||
getNoteURL,
|
||||
};
|
73
bigbluebutton-html5/imports/ui/components/note/styles.scss
Normal file
73
bigbluebutton-html5/imports/ui/components/note/styles.scss
Normal file
@ -0,0 +1,73 @@
|
||||
@import "/imports/ui/stylesheets/mixins/focus";
|
||||
@import "/imports/ui/stylesheets/variables/_all";
|
||||
|
||||
.note {
|
||||
background-color: var(--color-white);
|
||||
padding: var(--md-padding-x);
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: left;
|
||||
flex-shrink: 0;
|
||||
|
||||
a {
|
||||
@include elementFocus(var(--color-primary));
|
||||
padding-bottom: var(--sm-padding-y);
|
||||
padding-left: var(--sm-padding-y);
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
[class^="icon-bbb-"],
|
||||
[class*=" icon-bbb-"] {
|
||||
font-size: 85%;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
@extend %text-elipsis;
|
||||
flex: 1;
|
||||
|
||||
& > button, button:hover {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.hideBtn {
|
||||
position: relative;
|
||||
background-color: var(--color-white);
|
||||
display: block;
|
||||
margin: 4px;
|
||||
margin-bottom: 2px;
|
||||
padding-left: 0px;
|
||||
|
||||
> i {
|
||||
color: black;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-white);
|
||||
}
|
||||
}
|
||||
|
||||
iframe {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
border-style: none;
|
||||
}
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import BreakoutRoomContainer from '/imports/ui/components/breakout-room/container';
|
||||
import UserListContainer from '/imports/ui/components/user-list/container';
|
||||
import ChatContainer from '/imports/ui/components/chat/container';
|
||||
import NoteContainer from '/imports/ui/components/note/container';
|
||||
import PollContainer from '/imports/ui/components/poll/container';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import Resizable from 're-resizable';
|
||||
@ -14,6 +15,10 @@ const intlMessages = defineMessages({
|
||||
id: 'app.chat.label',
|
||||
description: 'Aria-label for Chat Section',
|
||||
},
|
||||
noteLabel: {
|
||||
id: 'app.note.label',
|
||||
description: 'Aria-label for Note Section',
|
||||
},
|
||||
userListLabel: {
|
||||
id: 'app.userList.label',
|
||||
description: 'Aria-label for Userlist Nav',
|
||||
@ -36,6 +41,10 @@ const USERLIST_MAX_WIDTH_PX = 240;
|
||||
const CHAT_MIN_WIDTH = 150;
|
||||
const CHAT_MAX_WIDTH = 350;
|
||||
|
||||
// I like big notes and I can not lie
|
||||
const NOTE_MIN_WIDTH = 400;
|
||||
const NOTE_MAX_WIDTH = 800;
|
||||
|
||||
class PanelManager extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
@ -45,10 +54,12 @@ class PanelManager extends Component {
|
||||
this.breakoutroomKey = _.uniqueId('breakoutroom-');
|
||||
this.chatKey = _.uniqueId('chat-');
|
||||
this.pollKey = _.uniqueId('poll-');
|
||||
this.noteKey = _.uniqueId('note-');
|
||||
|
||||
this.state = {
|
||||
chatWidth: 340,
|
||||
userlistWidth: 180,
|
||||
noteWidth: 400,
|
||||
};
|
||||
}
|
||||
|
||||
@ -149,6 +160,53 @@ class PanelManager extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderNote() {
|
||||
const { intl, enableResize } = this.props;
|
||||
|
||||
return (
|
||||
<section
|
||||
className={styles.note}
|
||||
aria-label={intl.formatMessage(intlMessages.noteLabel)}
|
||||
key={enableResize ? null : this.noteKey}
|
||||
>
|
||||
<NoteContainer />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
renderNoteResizable() {
|
||||
const { noteWidth } = this.state;
|
||||
|
||||
const resizableEnableOptions = {
|
||||
top: false,
|
||||
right: true,
|
||||
bottom: false,
|
||||
left: false,
|
||||
topRight: false,
|
||||
bottomRight: false,
|
||||
bottomLeft: false,
|
||||
topLeft: false,
|
||||
};
|
||||
|
||||
return (
|
||||
<Resizable
|
||||
minWidth={NOTE_MIN_WIDTH}
|
||||
maxWidth={NOTE_MAX_WIDTH}
|
||||
ref={(node) => { this.resizableNote = node; }}
|
||||
enable={resizableEnableOptions}
|
||||
key={this.noteKey}
|
||||
size={{ width: noteWidth }}
|
||||
onResizeStop={(e, direction, ref, d) => {
|
||||
this.setState({
|
||||
noteWidth: noteWidth + d.width,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{this.renderNote()}
|
||||
</Resizable>
|
||||
);
|
||||
}
|
||||
|
||||
renderPoll() {
|
||||
return (
|
||||
<div className={styles.poll} key={this.pollKey}>
|
||||
@ -183,6 +241,14 @@ class PanelManager extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
if (openPanel === 'note') {
|
||||
if (enableResize) {
|
||||
resizablePanels.push(this.renderNoteResizable());
|
||||
} else {
|
||||
panels.push(this.renderNote());
|
||||
}
|
||||
}
|
||||
|
||||
if (openPanel === 'poll') {
|
||||
if (enableResize) {
|
||||
resizablePanels.push(this.renderPoll());
|
||||
|
@ -15,6 +15,22 @@
|
||||
.presentationControls,
|
||||
.zoomWrapper {
|
||||
box-shadow: 0 0 10px -2px rgba(0, 0, 0, .25);
|
||||
|
||||
span:first-of-type button,
|
||||
button:first-of-type {
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
span:last-of-type button,
|
||||
button:last-of-type {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.presentationToolbarWrapper {
|
||||
@ -47,18 +63,6 @@
|
||||
i {
|
||||
color: var(--toolbar-button-color);
|
||||
}
|
||||
|
||||
|
||||
|
||||
button:first-of-type {
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
|
||||
button:last-of-type {
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.zoomWrapper {
|
||||
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { styles } from './styles';
|
||||
import UserParticipantsContainer from './user-participants/container';
|
||||
import UserMessages from './user-messages/component';
|
||||
import UserNotes from './user-notes/component';
|
||||
import UserPolls from './user-polls/component';
|
||||
import BreakoutRoomItem from './breakout-room/component';
|
||||
|
||||
@ -83,6 +84,11 @@ class UserContent extends PureComponent {
|
||||
roving,
|
||||
}}
|
||||
/>
|
||||
<UserNotes
|
||||
{...{
|
||||
intl,
|
||||
}}
|
||||
/>
|
||||
<UserPolls
|
||||
isPresenter={currentUser.isPresenter}
|
||||
{...{
|
||||
|
@ -0,0 +1,67 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages } from 'react-intl';
|
||||
import Icon from '/imports/ui/components/icon/component';
|
||||
import { Session } from 'meteor/session';
|
||||
import { styles } from './styles';
|
||||
|
||||
const propTypes = {
|
||||
intl: PropTypes.shape({
|
||||
formatMessage: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
notesTitle: {
|
||||
id: 'app.userList.notesTitle',
|
||||
description: 'Title for the notes list',
|
||||
},
|
||||
title: {
|
||||
id: 'app.note.title',
|
||||
description: 'Title for the shared notes',
|
||||
},
|
||||
});
|
||||
|
||||
class UserNotes extends PureComponent {
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
} = this.props;
|
||||
|
||||
if (!Meteor.settings.public.note.enabled) return null;
|
||||
|
||||
const toggleNotePanel = () => {
|
||||
Session.set(
|
||||
'openPanel',
|
||||
Session.get('openPanel') === 'note'
|
||||
? 'userlist'
|
||||
: 'note',
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.messages}>
|
||||
{
|
||||
<h2 className={styles.smallTitle}>
|
||||
{intl.formatMessage(intlMessages.notesTitle)}
|
||||
</h2>
|
||||
}
|
||||
<div className={styles.scrollableList}>
|
||||
<div
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
className={styles.noteLink}
|
||||
onClick={toggleNotePanel}
|
||||
>
|
||||
<Icon iconName='copy' className={styles.icon} />
|
||||
<span className={styles.label} >{intl.formatMessage(intlMessages.title)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UserNotes.propTypes = propTypes;
|
||||
|
||||
export default UserNotes;
|
@ -0,0 +1,60 @@
|
||||
@import "../../styles.scss";
|
||||
@import "/imports/ui/stylesheets/variables/_all";
|
||||
|
||||
.smallTitle {
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
padding: 0 var(--sm-padding-x);
|
||||
color: var(--color-gray-light);
|
||||
margin-bottom: var(--sm-padding-x);
|
||||
margin-top: var(--sm-padding-x);
|
||||
}
|
||||
|
||||
.scrollableList {
|
||||
margin-left: 0.45rem;
|
||||
margin-bottom: 1px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.noteLink {
|
||||
@extend %list-item;
|
||||
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
padding-top: var(--lg-padding-y);
|
||||
padding-bottom: var(--lg-padding-y);
|
||||
padding-left: var(--lg-padding-y);
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
color: var(--color-gray-dark);
|
||||
background-color: var(--color-off-white);
|
||||
|
||||
> i {
|
||||
display: flex;
|
||||
font-size: 175%;
|
||||
color: var(--color-gray-light);
|
||||
flex: 0 0 2.2rem;
|
||||
}
|
||||
|
||||
> span {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 400;
|
||||
color: black;
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
line-height: 2;
|
||||
text-align: left;
|
||||
padding-left: var(--lg-padding-y);
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--list-item-bg-hover);
|
||||
box-shadow: inset 0 0 0 var(--border-size) var(--item-focus-border), inset 1px 0 0 1px var(--item-focus-border);
|
||||
outline: none;
|
||||
}
|
||||
}
|
@ -448,6 +448,7 @@ class UserDropdown extends PureComponent {
|
||||
dropdownVisible,
|
||||
dropdownDirection,
|
||||
dropdownOffset,
|
||||
showNestedOptions,
|
||||
} = this.state;
|
||||
|
||||
const actions = this.getUsersActions();
|
||||
@ -509,7 +510,7 @@ class UserDropdown extends PureComponent {
|
||||
return (
|
||||
<Dropdown
|
||||
ref={(ref) => { this.dropdown = ref; }}
|
||||
isOpen={isActionsOpen}
|
||||
keepOpen={isActionsOpen || showNestedOptions}
|
||||
onShow={this.onActionsShow}
|
||||
onHide={this.onActionsHide}
|
||||
className={userItemContentsStyle}
|
||||
|
@ -120,6 +120,13 @@ public:
|
||||
storage_key: UNREAD_CHATS
|
||||
system_messages_keys:
|
||||
chat_clear: PUBLIC_CHAT_CLEAR
|
||||
note:
|
||||
enabled: false
|
||||
url: ETHERPAD_HOST
|
||||
config:
|
||||
noColors: true
|
||||
showControls: true
|
||||
rtl: false
|
||||
layout:
|
||||
autoSwapLayout: false
|
||||
hidePresentation: false
|
||||
|
@ -18,9 +18,13 @@
|
||||
"app.chat.label": "Chat",
|
||||
"app.chat.emptyLogLabel": "Chat log empty",
|
||||
"app.chat.clearPublicChatMessage": "The public chat history was cleared by a moderator",
|
||||
"app.note.title": "Shared Notes",
|
||||
"app.note.label": "Note",
|
||||
"app.note.hideNoteLabel": "Hide note",
|
||||
"app.userList.usersTitle": "Users",
|
||||
"app.userList.participantsTitle": "Participants",
|
||||
"app.userList.messagesTitle": "Messages",
|
||||
"app.userList.notesTitle": "Notes",
|
||||
"app.userList.presenter": "Presenter",
|
||||
"app.userList.you": "You",
|
||||
"app.userList.locked": "Locked",
|
||||
|
@ -200,6 +200,9 @@ bigbluebutton.web.logoutURL=default
|
||||
# successfully joining the meeting.
|
||||
defaultClientUrl=${bigbluebutton.web.serverURL}/client/BigBlueButton.html
|
||||
|
||||
# Allow requests without JSESSIONID to be handled (default = false)
|
||||
allowRequestsWithoutSession=false
|
||||
|
||||
# Force all attendees to join the meeting using the HTML5 client
|
||||
attendeesJoinViaHTML5Client=false
|
||||
|
||||
|
@ -109,6 +109,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<property name="defaultClientUrl" value="${defaultClientUrl}"/>
|
||||
<property name="defaultGuestWaitURL" value="${defaultGuestWaitURL}"/>
|
||||
<property name="html5ClientUrl" value="${html5ClientUrl}"/>
|
||||
<property name="allowRequestsWithoutSession" value="${allowRequestsWithoutSession}"/>
|
||||
<property name="moderatorsJoinViaHTML5Client" value="${moderatorsJoinViaHTML5Client}"/>
|
||||
<property name="attendeesJoinViaHTML5Client" value="${attendeesJoinViaHTML5Client}"/>
|
||||
<property name="defaultMeetingDuration" value="${defaultMeetingDuration}"/>
|
||||
|
@ -1419,10 +1419,15 @@ class ApiController {
|
||||
Meeting meeting = null;
|
||||
UserSession userSession = null;
|
||||
|
||||
Boolean allowEnterWithoutSession = false;
|
||||
// Depending on configuration, allow ENTER requests to proceed without session
|
||||
if (paramsProcessorUtil.getAllowRequestsWithoutSession()) {
|
||||
allowEnterWithoutSession = paramsProcessorUtil.getAllowRequestsWithoutSession();
|
||||
}
|
||||
|
||||
String respMessage = "Session " + sessionToken + " not found."
|
||||
if (!session[sessionToken]) {
|
||||
reject = true;
|
||||
} else if (meetingService.getUserSessionWithAuthToken(sessionToken) == null) {
|
||||
|
||||
if (meetingService.getUserSessionWithAuthToken(sessionToken) == null || (!allowEnterWithoutSession && !session[sessionToken])) {
|
||||
reject = true;
|
||||
respMessage = "Session " + sessionToken + " not found."
|
||||
} else {
|
||||
@ -1562,11 +1567,15 @@ class ApiController {
|
||||
println("Session token = [" + sessionToken + "]")
|
||||
}
|
||||
|
||||
if (!session[sessionToken]) {
|
||||
Boolean allowStunsWithoutSession = false;
|
||||
// Depending on configuration, allow STUNS requests to proceed without session
|
||||
if (paramsProcessorUtil.getAllowRequestsWithoutSession()) {
|
||||
allowStunsWithoutSession = paramsProcessorUtil.getAllowRequestsWithoutSession();
|
||||
}
|
||||
|
||||
if (meetingService.getUserSessionWithAuthToken(sessionToken) == null || (!allowStunsWithoutSession && !session[sessionToken])) {
|
||||
reject = true;
|
||||
} else if (meetingService.getUserSessionWithAuthToken(sessionToken) == null)
|
||||
reject = true;
|
||||
else {
|
||||
} else {
|
||||
us = meetingService.getUserSessionWithAuthToken(sessionToken);
|
||||
meeting = meetingService.getMeeting(us.meetingID);
|
||||
if (meeting == null || meeting.isForciblyEnded()) {
|
||||
|
Loading…
Reference in New Issue
Block a user