Merge branch 'note-unread-indicator' of github.com:pedrobmarin/bigbluebutton into locales-8

This commit is contained in:
Anton Georgiev 2019-06-11 20:10:31 +00:00
commit c79eddf46d
13 changed files with 146 additions and 12 deletions

View File

@ -3,7 +3,7 @@ import Logger from '/imports/startup/server/logger';
import editCaptions from '/imports/api/captions/server/methods/editCaptions';
import { check } from 'meteor/check';
export default function padUpdate(padId, data, revs) {
export default function updatePad(padId, data, revs) {
check(padId, String);
check(data, String);
check(revs, Number);

View File

@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor';
const Note = new Mongo.Collection('note');
if (Meteor.isServer) {
Note._ensureIndex({ meetingId: 1 });
Note._ensureIndex({ meetingId: 1, noteId: 1 });
}
export default Note;

View File

@ -0,0 +1,5 @@
import RedisPubSub from '/imports/startup/server/redis';
import { processForNotePadOnly } from '/imports/api/note/server/helpers';
import handlePadUpdate from './handlers/padUpdate';
RedisPubSub.on('PadUpdateSysMsg', processForNotePadOnly(handlePadUpdate));

View File

@ -0,0 +1,12 @@
import { check } from 'meteor/check';
import updateNote from '/imports/api/note/server/modifiers/updateNote';
export default function handlePadUpdate({ body }) {
const { pad, revs } = body;
const { id } = pad;
check(id, String);
check(revs, Number);
updateNote(id, revs);
}

View File

@ -4,6 +4,7 @@ import { hashFNV32a } from '/imports/api/common/server/helpers';
const ETHERPAD = Meteor.settings.private.etherpad;
const NOTE_CONFIG = Meteor.settings.public.note;
const BASE_URL = `http://${ETHERPAD.host}:${ETHERPAD.port}/api/${ETHERPAD.version}`;
const TOKEN = '_';
const createPadURL = padId => `${BASE_URL}/createPad?apikey=${ETHERPAD.apikey}&padID=${padId}`;
@ -26,10 +27,26 @@ const getDataFromResponse = (data, key) => {
return null;
};
const isNotePad = (padId) => {
return padId.search(TOKEN);
};
const processForNotePadOnly = fn => (message, ...args) => {
const { body } = message;
const { pad } = body;
const { id } = pad;
check(id, String);
if (isNotePad(id)) return fn(message, ...args);
return () => {};
};
export {
generateNoteId,
createPadURL,
getReadOnlyIdURL,
isEnabled,
getDataFromResponse,
processForNotePadOnly,
};

View File

@ -1 +1,2 @@
import './publishers';
import './eventHandlers';

View File

@ -9,12 +9,14 @@ export default function addNote(meetingId, noteId, readOnlyNoteId) {
const selector = {
meetingId,
noteId,
};
const modifier = {
meetingId,
noteId,
readOnlyNoteId,
revs: 0,
};
const cb = (err, numChanged) => {

View File

@ -0,0 +1,28 @@
import Note from '/imports/api/note';
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
export default function updateNote(noteId, revs) {
check(noteId, String);
check(revs, Number);
const selector = {
noteId,
};
const modifier = {
$set: {
revs,
},
};
const cb = (err) => {
if (err) {
return Logger.error(`Updating note pad: ${err}`);
}
return Logger.verbose(`Update note pad=${noteId} revs=${revs}`);
};
return Note.update(selector, modifier, { multi: true }, cb);
}

View File

@ -4,6 +4,7 @@ import Note from '/imports/api/note';
import Auth from '/imports/ui/services/auth';
import Settings from '/imports/ui/services/settings';
import mapUser from '/imports/ui/services/user/mapUser';
import { Session } from 'meteor/session';
const NOTE_CONFIG = Meteor.settings.public.note;
@ -66,14 +67,25 @@ const getNoteURL = () => {
return url;
};
const getRevs = () => {
const note = Note.findOne({ meetingId: Auth.meetingID });
return note ? note.revs : 0;
};
const isEnabled = () => {
const note = Note.findOne({ meetingId: Auth.meetingID });
return NOTE_CONFIG.enabled && note;
};
const isPanelOpened = () => {
return Session.get('openPanel') === 'note';
};
export default {
getNoteURL,
getReadOnlyURL,
isLocked,
isEnabled,
isPanelOpened,
getRevs,
};

View File

@ -3,7 +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 UserNotesContainer from './user-notes/container';
import UserCaptionsContainer from './user-captions/container';
import WaitingUsers from './waiting-users/component';
import UserPolls from './user-polls/component';
@ -101,7 +101,7 @@ class UserContent extends PureComponent {
/>
) : null
}
<UserNotes
<UserNotesContainer
{...{
intl,
}}

View File

@ -1,8 +1,8 @@
import React, { PureComponent } from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { defineMessages } from 'react-intl';
import Icon from '/imports/ui/components/icon/component';
import { Session } from 'meteor/session';
import NoteService from '/imports/ui/components/note/service';
import { styles } from './styles';
@ -10,6 +10,8 @@ const propTypes = {
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,
revs: PropTypes.number.isRequired,
isPanelOpened: PropTypes.bool.isRequired,
};
const intlMessages = defineMessages({
@ -23,23 +25,52 @@ const intlMessages = defineMessages({
},
});
class UserNotes extends PureComponent {
class UserNotes extends Component {
constructor(props) {
super(props);
this.state = {
unread: false,
};
}
componentDidMount() {
const { revs } = this.props;
if (revs !== 0) this.setState({ unread: true });
}
componentDidUpdate(prevProps) {
const { isPanelOpened, revs } = this.props;
const { unread } = this.state;
if (!isPanelOpened && !unread) {
if (prevProps.revs !== revs) this.setState({ unread: true });
}
if (isPanelOpened && unread) {
this.setState({ unread: false });
}
}
render() {
const {
intl,
} = this.props;
const { intl, isPanelOpened } = this.props;
const { unread } = this.state;
if (!NoteService.isEnabled()) return null;
const toggleNotePanel = () => {
Session.set(
'openPanel',
Session.get('openPanel') === 'note'
isPanelOpened
? 'userlist'
: 'note',
);
};
const iconClasses = {};
iconClasses[styles.notification] = unread;
return (
<div className={styles.messages}>
{
@ -54,7 +85,7 @@ class UserNotes extends PureComponent {
className={styles.noteLink}
onClick={toggleNotePanel}
>
<Icon iconName="copy" />
<Icon iconName="copy" className={cx(iconClasses)}/>
<span>{intl.formatMessage(intlMessages.title)}</span>
</div>
</div>

View File

@ -0,0 +1,11 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import NoteService from '/imports/ui/components/note/service';
import UserNotes from './component';
const UserNotesContainer = props => <UserNotes {...props} />;
export default withTracker(() => ({
isPanelOpened: NoteService.isPanelOpened(),
revs: NoteService.getRevs(),
}))(UserNotesContainer);

View File

@ -73,3 +73,18 @@
box-shadow: inset 0 0 0 var(--border-size) var(--item-focus-border), inset 1px 0 0 1px var(--item-focus-border);
}
}
.notification {
position: relative;
&:after {
content: '';
position: absolute;
border-radius: 50%;
width: 8px;
height: 8px;
bottom: 3px;
right: 3px;
background-color: var(--color-danger);
}
}