Merge branch 'clearnet' into PR_file_server
* clearnet: fix method separate out new/open, more lint/cleanup separate out note to self create window.mixpanel here use window.mixpanel, remove eventEmitter allow mixpanel to be already set up elsewhere Address review comments Add missing file in tests Add UI elements for searching and selecting members in a group chat Desktop Analytics Replaced value with a descriptive constant. Add comment to fix up confusion. Keep a cache of the last 5 fetched messages for public chat so we can use it to detect duplicate messages. # Conflicts: # js/background.js # js/modules/loki_public_chat_api.jspull/518/head
commit
7756d4f0f3
@ -0,0 +1,12 @@
|
|||||||
|
const Mixpanel = require('mixpanel');
|
||||||
|
|
||||||
|
class LokiMixpanelAPI {
|
||||||
|
constructor() {
|
||||||
|
this.mixpanel = Mixpanel.init('736cd9a854a157591153efacd1164e9a');
|
||||||
|
}
|
||||||
|
track(label) {
|
||||||
|
this.mixpanel.track(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = LokiMixpanelAPI;
|
@ -0,0 +1,64 @@
|
|||||||
|
/* global _, Whisper, */
|
||||||
|
|
||||||
|
// eslint-disable-next-line func-names
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
window.Whisper = window.Whisper || {};
|
||||||
|
|
||||||
|
Whisper.MemberListView = Whisper.View.extend({
|
||||||
|
initialize(options) {
|
||||||
|
this.member_list = [];
|
||||||
|
this.selected_idx = 0;
|
||||||
|
this.onClicked = options.onClicked;
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
if (this.memberView) {
|
||||||
|
this.memberView.remove();
|
||||||
|
this.memberView = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.memberView = new Whisper.ReactWrapperView({
|
||||||
|
className: 'member-list',
|
||||||
|
Component: window.Signal.Components.MemberList,
|
||||||
|
props: {
|
||||||
|
members: this.member_list,
|
||||||
|
selected: this.selectedMember(),
|
||||||
|
onMemberClicked: this.handleMemberClicked.bind(this),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$el.append(this.memberView.el);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
handleMemberClicked(member) {
|
||||||
|
this.onClicked(member);
|
||||||
|
},
|
||||||
|
updateMembers(members) {
|
||||||
|
if (!_.isEqual(this.member_list, members)) {
|
||||||
|
// Whenever the list is updated, we reset the selection
|
||||||
|
this.selected_idx = 0;
|
||||||
|
this.member_list = members;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
membersShown() {
|
||||||
|
return this.member_list.length !== 0;
|
||||||
|
},
|
||||||
|
selectUp() {
|
||||||
|
this.selected_idx = Math.max(this.selected_idx - 1, 0);
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
selectDown() {
|
||||||
|
this.selected_idx = Math.min(
|
||||||
|
this.selected_idx + 1,
|
||||||
|
this.member_list.length - 1
|
||||||
|
);
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
selectedMember() {
|
||||||
|
return this.member_list[this.selected_idx];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})();
|
@ -0,0 +1,49 @@
|
|||||||
|
.member-list-container {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
max-height: 240px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
|
||||||
|
.member-item {
|
||||||
|
padding: 4px;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:hover:not(.member-selected) {
|
||||||
|
background-color: $color-light-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
background-color: $color-light-10;
|
||||||
|
|
||||||
|
.name-part {
|
||||||
|
font-weight: 300;
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pubkey-part {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-selected {
|
||||||
|
background-color: $color-light-35;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-theme {
|
||||||
|
.member-list-container {
|
||||||
|
.member-item {
|
||||||
|
&:hover:not(.member-selected) {
|
||||||
|
background-color: $color-dark-55;
|
||||||
|
}
|
||||||
|
|
||||||
|
background-color: $color-dark-70;
|
||||||
|
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-selected {
|
||||||
|
background-color: $color-dark-60;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Avatar } from '../Avatar';
|
||||||
|
|
||||||
|
interface MemberItemProps {
|
||||||
|
member: any;
|
||||||
|
selected: Boolean;
|
||||||
|
onClicked: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MemberItem extends React.Component<MemberItemProps> {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props);
|
||||||
|
this.handleClick = this.handleClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const name = this.props.member.authorProfileName;
|
||||||
|
const pubkey = this.props.member.authorPhoneNumber;
|
||||||
|
const selected = this.props.selected;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
className={classNames(
|
||||||
|
'member-item',
|
||||||
|
selected ? 'member-selected' : null
|
||||||
|
)}
|
||||||
|
onClick={this.handleClick}
|
||||||
|
>
|
||||||
|
{this.renderAvatar()}
|
||||||
|
<span className="name-part">{name}</span>
|
||||||
|
<span className="pubkey-part">{pubkey}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleClick() {
|
||||||
|
this.props.onClicked(this.props.member);
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderAvatar() {
|
||||||
|
return (
|
||||||
|
<Avatar
|
||||||
|
avatarPath={this.props.member.authorAvatarPath}
|
||||||
|
color={this.props.member.authorColor}
|
||||||
|
conversationType="direct"
|
||||||
|
i18n={this.props.member.i18n}
|
||||||
|
name={this.props.member.authorName}
|
||||||
|
phoneNumber={this.props.member.authorPhoneNumber}
|
||||||
|
profileName={this.props.member.authorProfileName}
|
||||||
|
size={28}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MemberListProps {
|
||||||
|
members: [any];
|
||||||
|
selected: any;
|
||||||
|
onMemberClicked: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MemberList extends React.Component<MemberListProps> {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleMemberClicked = this.handleMemberClicked.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const { members } = this.props;
|
||||||
|
|
||||||
|
const itemList = members.map(item => {
|
||||||
|
const selected = item === this.props.selected;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MemberItem
|
||||||
|
key={item.id}
|
||||||
|
member={item}
|
||||||
|
selected={selected}
|
||||||
|
onClicked={this.handleMemberClicked}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div>{itemList}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMemberClicked(member: any) {
|
||||||
|
this.props.onMemberClicked(member);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue