Read Pinned Chats
Co-authored-by: Sidney Keese <sidney@carbonfive.com>
This commit is contained in:
parent
3ca547f3dd
commit
63b2644cb4
15 changed files with 444 additions and 46 deletions
|
@ -193,6 +193,14 @@
|
|||
"message": "Archived Conversations",
|
||||
"description": "Shown in place of the search box when showing archived conversation list"
|
||||
},
|
||||
"LeftPane--pinned": {
|
||||
"message": "Pinned",
|
||||
"description": "Shown as a header for pinned conversations in the left pane"
|
||||
},
|
||||
"LeftPane--chats": {
|
||||
"message": "Chats",
|
||||
"description": "Shown as a header for non-pinned conversations in the left pane"
|
||||
},
|
||||
"archiveHelperText": {
|
||||
"message": "These conversations are archived and will only appear in the Inbox if new messages are received.",
|
||||
"description": "Shown at the top of the archived conversations list in the left pane"
|
||||
|
|
|
@ -90,15 +90,29 @@ message GroupV2Record {
|
|||
}
|
||||
|
||||
message AccountRecord {
|
||||
optional bytes profileKey = 1;
|
||||
optional string givenName = 2;
|
||||
optional string familyName = 3;
|
||||
optional string avatarUrl = 4;
|
||||
optional bool noteToSelfArchived = 5;
|
||||
optional bool readReceipts = 6;
|
||||
optional bool sealedSenderIndicators = 7;
|
||||
optional bool typingIndicators = 8;
|
||||
optional bool proxiedLinkPreviews = 9;
|
||||
optional bool noteToSelfUnread = 10;
|
||||
optional bool linkPreviews = 11;
|
||||
message PinnedConversation {
|
||||
message Contact {
|
||||
optional string uuid = 1;
|
||||
optional string e164 = 2;
|
||||
}
|
||||
|
||||
oneof identifier {
|
||||
Contact contact = 1;
|
||||
bytes legacyGroupId = 3;
|
||||
bytes groupMasterKey = 4;
|
||||
}
|
||||
}
|
||||
|
||||
optional bytes profileKey = 1;
|
||||
optional string givenName = 2;
|
||||
optional string familyName = 3;
|
||||
optional string avatarUrl = 4;
|
||||
optional bool noteToSelfArchived = 5;
|
||||
optional bool readReceipts = 6;
|
||||
optional bool sealedSenderIndicators = 7;
|
||||
optional bool typingIndicators = 8;
|
||||
optional bool proxiedLinkPreviews = 9;
|
||||
optional bool noteToSelfUnread = 10;
|
||||
optional bool linkPreviews = 11;
|
||||
repeated PinnedConversation pinnedConversations = 14;
|
||||
}
|
||||
|
|
|
@ -6176,6 +6176,18 @@ button.module-image__border-overlay:focus {
|
|||
}
|
||||
}
|
||||
|
||||
.module-left-pane__header-row {
|
||||
@include font-body-1-bold;
|
||||
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding-left: 16px;
|
||||
|
||||
@include dark-theme {
|
||||
color: $color-gray-05;
|
||||
}
|
||||
}
|
||||
|
||||
.module-left-pane__to-inbox-button {
|
||||
@include button-reset;
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ export function stringFromBytes(buffer: ArrayBuffer): string {
|
|||
export function hexFromBytes(buffer: ArrayBuffer): string {
|
||||
return window.dcodeIO.ByteBuffer.wrap(buffer).toString('hex');
|
||||
}
|
||||
|
||||
export function bytesFromHexString(string: string): ArrayBuffer {
|
||||
return window.dcodeIO.ByteBuffer.wrap(string, 'hex').toArrayBuffer();
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ export type PropsData = {
|
|||
text: string;
|
||||
deletedForEveryone?: boolean;
|
||||
};
|
||||
isPinned?: boolean;
|
||||
};
|
||||
|
||||
type PropsHousekeeping = {
|
||||
|
|
|
@ -40,12 +40,32 @@ const defaultArchivedConversations: Array<PropsData> = [
|
|||
},
|
||||
];
|
||||
|
||||
const pinnedConversations: Array<PropsData> = [
|
||||
{
|
||||
id: 'philly-convo',
|
||||
isPinned: true,
|
||||
isSelected: false,
|
||||
lastUpdated: Date.now(),
|
||||
title: 'Philip Glass',
|
||||
type: 'direct',
|
||||
},
|
||||
{
|
||||
id: 'robbo-convo',
|
||||
isPinned: true,
|
||||
isSelected: false,
|
||||
lastUpdated: Date.now(),
|
||||
title: 'Robert Moog',
|
||||
type: 'direct',
|
||||
},
|
||||
];
|
||||
|
||||
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||
archivedConversations:
|
||||
overrideProps.archivedConversations || defaultArchivedConversations,
|
||||
conversations: overrideProps.conversations || defaultConversations,
|
||||
i18n,
|
||||
openConversationInternal: action('openConversationInternal'),
|
||||
pinnedConversations: overrideProps.pinnedConversations || [],
|
||||
renderExpiredBuildDialog: () => <div />,
|
||||
renderMainHeader: () => <div />,
|
||||
renderMessageSearchResult: () => <div />,
|
||||
|
@ -69,6 +89,14 @@ story.add('Conversation States (Active, Selected, Archived)', () => {
|
|||
return <LeftPane {...props} />;
|
||||
});
|
||||
|
||||
story.add('Pinned Conversations', () => {
|
||||
const props = createProps({
|
||||
pinnedConversations,
|
||||
});
|
||||
|
||||
return <LeftPane {...props} />;
|
||||
});
|
||||
|
||||
story.add('Archived Conversations Shown', () => {
|
||||
const props = createProps({
|
||||
showArchived: true,
|
||||
|
|
|
@ -17,6 +17,7 @@ import { cleanId } from './_util';
|
|||
export interface PropsType {
|
||||
conversations?: Array<ConversationListItemPropsType>;
|
||||
archivedConversations?: Array<ConversationListItemPropsType>;
|
||||
pinnedConversations?: Array<ConversationListItemPropsType>;
|
||||
selectedConversationId?: string;
|
||||
searchResults?: SearchResultsProps;
|
||||
showArchived?: boolean;
|
||||
|
@ -51,6 +52,43 @@ type RowRendererParamsType = {
|
|||
style: CSSProperties;
|
||||
};
|
||||
|
||||
enum RowType {
|
||||
ArchiveButton,
|
||||
ArchivedConversation,
|
||||
Conversation,
|
||||
Header,
|
||||
PinnedConversation,
|
||||
Undefined,
|
||||
}
|
||||
|
||||
enum HeaderType {
|
||||
Pinned,
|
||||
Chats,
|
||||
}
|
||||
|
||||
interface ArchiveButtonRow {
|
||||
type: RowType.ArchiveButton;
|
||||
}
|
||||
|
||||
interface ConversationRow {
|
||||
index: number;
|
||||
type:
|
||||
| RowType.ArchivedConversation
|
||||
| RowType.Conversation
|
||||
| RowType.PinnedConversation;
|
||||
}
|
||||
|
||||
interface HeaderRow {
|
||||
headerType: HeaderType;
|
||||
type: RowType.Header;
|
||||
}
|
||||
|
||||
interface UndefinedRow {
|
||||
type: RowType.Undefined;
|
||||
}
|
||||
|
||||
type Row = ArchiveButtonRow | ConversationRow | HeaderRow | UndefinedRow;
|
||||
|
||||
export class LeftPane extends React.Component<PropsType> {
|
||||
public listRef = React.createRef<List>();
|
||||
|
||||
|
@ -60,31 +98,77 @@ export class LeftPane extends React.Component<PropsType> {
|
|||
|
||||
public setFocusToLastNeeded = false;
|
||||
|
||||
public renderRow = ({
|
||||
index,
|
||||
key,
|
||||
style,
|
||||
}: RowRendererParamsType): JSX.Element => {
|
||||
public calculateRowHeight = ({ index }: { index: number }): number => {
|
||||
const { type } = this.getRowFromIndex(index);
|
||||
return type === RowType.Header ? 40 : 68;
|
||||
};
|
||||
|
||||
public getRowFromIndex = (index: number): Row => {
|
||||
const {
|
||||
archivedConversations,
|
||||
conversations,
|
||||
i18n,
|
||||
openConversationInternal,
|
||||
pinnedConversations,
|
||||
showArchived,
|
||||
} = this.props;
|
||||
if (!conversations || !archivedConversations) {
|
||||
throw new Error(
|
||||
'renderRow: Tried to render without conversations or archivedConversations'
|
||||
);
|
||||
|
||||
if (!conversations || !pinnedConversations || !archivedConversations) {
|
||||
return {
|
||||
type: RowType.Undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (!showArchived && index === conversations.length) {
|
||||
return this.renderArchivedButton({ key, style });
|
||||
if (showArchived) {
|
||||
return {
|
||||
index,
|
||||
type: RowType.ArchivedConversation,
|
||||
};
|
||||
}
|
||||
|
||||
const conversation = showArchived
|
||||
? archivedConversations[index]
|
||||
: conversations[index];
|
||||
let conversationIndex = index;
|
||||
|
||||
if (pinnedConversations.length) {
|
||||
if (index === 0) {
|
||||
return {
|
||||
headerType: HeaderType.Pinned,
|
||||
type: RowType.Header,
|
||||
};
|
||||
}
|
||||
|
||||
if (index <= pinnedConversations.length) {
|
||||
return {
|
||||
index: index - 1,
|
||||
type: RowType.PinnedConversation,
|
||||
};
|
||||
}
|
||||
|
||||
if (index === pinnedConversations.length + 1) {
|
||||
return {
|
||||
headerType: HeaderType.Chats,
|
||||
type: RowType.Header,
|
||||
};
|
||||
}
|
||||
|
||||
conversationIndex -= pinnedConversations.length + 2;
|
||||
}
|
||||
|
||||
if (conversationIndex === conversations.length) {
|
||||
return {
|
||||
type: RowType.ArchiveButton,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
index: conversationIndex,
|
||||
type: RowType.Conversation,
|
||||
};
|
||||
};
|
||||
|
||||
public renderConversationRow(
|
||||
conversation: ConversationListItemPropsType,
|
||||
key: string,
|
||||
style: CSSProperties
|
||||
): JSX.Element {
|
||||
const { i18n, openConversationInternal } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -99,15 +183,90 @@ export class LeftPane extends React.Component<PropsType> {
|
|||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public renderHeaderRow = (
|
||||
index: number,
|
||||
key: string,
|
||||
style: CSSProperties
|
||||
): JSX.Element => {
|
||||
const { i18n } = this.props;
|
||||
|
||||
switch (index) {
|
||||
case HeaderType.Pinned: {
|
||||
return (
|
||||
<div className="module-left-pane__header-row" key={key} style={style}>
|
||||
{i18n('LeftPane--pinned')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case HeaderType.Chats: {
|
||||
return (
|
||||
<div className="module-left-pane__header-row" key={key} style={style}>
|
||||
{i18n('LeftPane--chats')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
default: {
|
||||
window.log.warn('LeftPane: invalid HeaderRowIndex received');
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public renderArchivedButton = ({
|
||||
public renderRow = ({
|
||||
index,
|
||||
key,
|
||||
style,
|
||||
}: {
|
||||
key: string;
|
||||
style: CSSProperties;
|
||||
}): JSX.Element => {
|
||||
}: RowRendererParamsType): JSX.Element => {
|
||||
const {
|
||||
archivedConversations,
|
||||
conversations,
|
||||
pinnedConversations,
|
||||
} = this.props;
|
||||
|
||||
if (!conversations || !pinnedConversations || !archivedConversations) {
|
||||
throw new Error(
|
||||
'renderRow: Tried to render without conversations or pinnedConversations or archivedConversations'
|
||||
);
|
||||
}
|
||||
|
||||
const row = this.getRowFromIndex(index);
|
||||
|
||||
switch (row.type) {
|
||||
case RowType.ArchiveButton: {
|
||||
return this.renderArchivedButton(key, style);
|
||||
}
|
||||
case RowType.ArchivedConversation: {
|
||||
return this.renderConversationRow(
|
||||
archivedConversations[row.index],
|
||||
key,
|
||||
style
|
||||
);
|
||||
}
|
||||
case RowType.Conversation: {
|
||||
return this.renderConversationRow(conversations[row.index], key, style);
|
||||
}
|
||||
case RowType.Header: {
|
||||
return this.renderHeaderRow(row.headerType, key, style);
|
||||
}
|
||||
case RowType.PinnedConversation: {
|
||||
return this.renderConversationRow(
|
||||
pinnedConversations[row.index],
|
||||
key,
|
||||
style
|
||||
);
|
||||
}
|
||||
default:
|
||||
window.log.warn('LeftPane: unknown RowType received');
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
|
||||
public renderArchivedButton = (
|
||||
key: string,
|
||||
style: CSSProperties
|
||||
): JSX.Element => {
|
||||
const {
|
||||
archivedConversations,
|
||||
i18n,
|
||||
|
@ -199,6 +358,14 @@ export class LeftPane extends React.Component<PropsType> {
|
|||
this.listRef.current.scrollToRow(row);
|
||||
};
|
||||
|
||||
public recomputeRowHeights = (): void => {
|
||||
if (!this.listRef || !this.listRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.listRef.current.recomputeRowHeights();
|
||||
};
|
||||
|
||||
public getScrollContainer = (): HTMLDivElement | null => {
|
||||
if (!this.listRef || !this.listRef.current) {
|
||||
return null;
|
||||
|
@ -269,16 +436,34 @@ export class LeftPane extends React.Component<PropsType> {
|
|||
);
|
||||
|
||||
public getLength = (): number => {
|
||||
const { archivedConversations, conversations, showArchived } = this.props;
|
||||
const {
|
||||
archivedConversations,
|
||||
conversations,
|
||||
pinnedConversations,
|
||||
showArchived,
|
||||
} = this.props;
|
||||
|
||||
if (!conversations || !archivedConversations) {
|
||||
if (!conversations || !archivedConversations || !pinnedConversations) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// That extra 1 element added to the list is the 'archived conversations' button
|
||||
return showArchived
|
||||
? archivedConversations.length
|
||||
: conversations.length + (archivedConversations.length ? 1 : 0);
|
||||
if (showArchived) {
|
||||
return archivedConversations.length;
|
||||
}
|
||||
|
||||
let { length } = conversations;
|
||||
|
||||
// includes two additional rows for pinned/chats headers
|
||||
if (pinnedConversations.length) {
|
||||
length += pinnedConversations.length + 2;
|
||||
}
|
||||
|
||||
// includes one additional row for 'archived conversations' button
|
||||
if (archivedConversations.length) {
|
||||
length += 1;
|
||||
}
|
||||
|
||||
return length;
|
||||
};
|
||||
|
||||
public renderList = ({
|
||||
|
@ -290,6 +475,7 @@ export class LeftPane extends React.Component<PropsType> {
|
|||
i18n,
|
||||
conversations,
|
||||
openConversationInternal,
|
||||
pinnedConversations,
|
||||
renderMessageSearchResult,
|
||||
startNewConversation,
|
||||
searchResults,
|
||||
|
@ -310,7 +496,7 @@ export class LeftPane extends React.Component<PropsType> {
|
|||
);
|
||||
}
|
||||
|
||||
if (!conversations || !archivedConversations) {
|
||||
if (!conversations || !archivedConversations || !pinnedConversations) {
|
||||
throw new Error(
|
||||
'render: must provided conversations and archivedConverstions if no search results are provided'
|
||||
);
|
||||
|
@ -345,7 +531,7 @@ export class LeftPane extends React.Component<PropsType> {
|
|||
onScroll={this.onScroll}
|
||||
ref={this.listRef}
|
||||
rowCount={length}
|
||||
rowHeight={68}
|
||||
rowHeight={this.calculateRowHeight}
|
||||
rowRenderer={this.renderRow}
|
||||
tabIndex={-1}
|
||||
width={width || 0}
|
||||
|
@ -412,4 +598,16 @@ export class LeftPane extends React.Component<PropsType> {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate(oldProps: PropsType): void {
|
||||
const { pinnedConversations: oldPinned } = oldProps;
|
||||
const { pinnedConversations: pinned } = this.props;
|
||||
|
||||
const oldLength = (oldPinned && oldPinned.length) || 0;
|
||||
const newLength = (pinned && pinned.length) || 0;
|
||||
|
||||
if (oldLength !== newLength) {
|
||||
this.recomputeRowHeights();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
2
ts/model-types.d.ts
vendored
2
ts/model-types.d.ts
vendored
|
@ -145,12 +145,14 @@ export type ConversationAttributesType = {
|
|||
draftAttachments: Array<unknown>;
|
||||
draftTimestamp: number | null;
|
||||
inbox_position: number;
|
||||
isPinned: boolean;
|
||||
lastMessageDeletedForEveryone: unknown;
|
||||
lastMessageStatus: LastMessageStatus | null;
|
||||
messageCount: number;
|
||||
messageCountBeforeMessageRequests: number;
|
||||
messageRequestResponseType: number;
|
||||
muteExpiresAt: number;
|
||||
pinIndex?: number;
|
||||
profileAvatar: WhatIsThis;
|
||||
profileKeyCredential: unknown | null;
|
||||
profileKeyVersion: string;
|
||||
|
|
|
@ -748,6 +748,7 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
isArchived: this.get('isArchived')!,
|
||||
isBlocked: this.isBlocked(),
|
||||
isMe: this.isMe(),
|
||||
isPinned: this.get('isPinned'),
|
||||
isVerified: this.isVerified(),
|
||||
lastMessage: {
|
||||
status: this.get('lastMessageStatus')!,
|
||||
|
@ -762,6 +763,7 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
muteExpiresAt: this.get('muteExpiresAt')!,
|
||||
name: this.get('name')!,
|
||||
phoneNumber: this.getNumber()!,
|
||||
pinIndex: this.get('pinIndex'),
|
||||
profileName: this.getProfileName()!,
|
||||
sharedGroupNames: this.get('sharedGroupNames')!,
|
||||
shouldShowDraft,
|
||||
|
|
|
@ -648,7 +648,9 @@ async function processManifest(
|
|||
|
||||
const decryptedStorageItems = await pMap(
|
||||
storageItems.items,
|
||||
async (storageRecordWrapper: StorageItemClass) => {
|
||||
async (
|
||||
storageRecordWrapper: StorageItemClass
|
||||
): Promise<MergeableItemType> => {
|
||||
const { key, value: storageItemCiphertext } = storageRecordWrapper;
|
||||
|
||||
if (!key || !storageItemCiphertext) {
|
||||
|
@ -695,11 +697,19 @@ async function processManifest(
|
|||
{ concurrency: 50 }
|
||||
);
|
||||
|
||||
// Merge Account records last
|
||||
const sortedStorageItems = ([] as Array<MergeableItemType>).concat(
|
||||
..._.partition(
|
||||
decryptedStorageItems,
|
||||
storageRecord => storageRecord.storageRecord.account === undefined
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
window.log.info(
|
||||
`storageService.processManifest: Attempting to merge ${decryptedStorageItems.length} records`
|
||||
`storageService.processManifest: Attempting to merge ${sortedStorageItems.length} records`
|
||||
);
|
||||
const mergedRecords = await pMap(decryptedStorageItems, mergeRecord, {
|
||||
const mergedRecords = await pMap(sortedStorageItems, mergeRecord, {
|
||||
concurrency: 5,
|
||||
});
|
||||
window.log.info(
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
} from '../textsecure.d';
|
||||
import { deriveGroupFields, waitThenMaybeUpdateGroup } from '../groups';
|
||||
import { ConversationModel } from '../models/conversations';
|
||||
import { ConversationAttributesTypeType } from '../model-types.d';
|
||||
|
||||
const { updateConversation } = dataInterface;
|
||||
|
||||
|
@ -496,6 +497,7 @@ export async function mergeAccountRecord(
|
|||
avatarUrl,
|
||||
linkPreviews,
|
||||
noteToSelfArchived,
|
||||
pinnedConversations: remotelyPinnedConversationClasses,
|
||||
profileKey,
|
||||
readReceipts,
|
||||
sealedSenderIndicators,
|
||||
|
@ -520,6 +522,104 @@ export async function mergeAccountRecord(
|
|||
window.storage.put('profileKey', profileKey.toArrayBuffer());
|
||||
}
|
||||
|
||||
if (remotelyPinnedConversationClasses) {
|
||||
const locallyPinnedConversations = window.ConversationController._conversations.filter(
|
||||
conversation => Boolean(conversation.get('isPinned'))
|
||||
);
|
||||
|
||||
const remotelyPinnedConversationPromises = remotelyPinnedConversationClasses.map(
|
||||
async pinnedConversation => {
|
||||
let conversationId;
|
||||
let conversationType: ConversationAttributesTypeType = 'private';
|
||||
|
||||
switch (pinnedConversation.identifier) {
|
||||
case 'contact': {
|
||||
if (!pinnedConversation.contact) {
|
||||
throw new Error('mergeAccountRecord: no contact found');
|
||||
}
|
||||
conversationId = window.ConversationController.ensureContactIds(
|
||||
pinnedConversation.contact
|
||||
);
|
||||
conversationType = 'private';
|
||||
break;
|
||||
}
|
||||
case 'legacyGroupId': {
|
||||
if (!pinnedConversation.legacyGroupId) {
|
||||
throw new Error('mergeAccountRecord: no legacyGroupId found');
|
||||
}
|
||||
conversationId = pinnedConversation.legacyGroupId.toBinary();
|
||||
conversationType = 'group';
|
||||
break;
|
||||
}
|
||||
case 'groupMasterKey': {
|
||||
if (!pinnedConversation.groupMasterKey) {
|
||||
throw new Error('mergeAccountRecord: no groupMasterKey found');
|
||||
}
|
||||
const masterKeyBuffer = pinnedConversation.groupMasterKey.toArrayBuffer();
|
||||
const groupFields = deriveGroupFields(masterKeyBuffer);
|
||||
const groupId = arrayBufferToBase64(groupFields.id);
|
||||
|
||||
conversationId = groupId;
|
||||
conversationType = 'group';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
window.log.error('mergeAccountRecord: Invalid identifier received');
|
||||
}
|
||||
}
|
||||
|
||||
if (!conversationId) {
|
||||
window.log.error(
|
||||
`mergeAccountRecord: missing conversation id. looking based on ${pinnedConversation.identifier}`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (conversationType === 'private') {
|
||||
return window.ConversationController.getOrCreateAndWait(
|
||||
conversationId,
|
||||
conversationType
|
||||
);
|
||||
}
|
||||
|
||||
return window.ConversationController.get(conversationId);
|
||||
}
|
||||
);
|
||||
|
||||
const remotelyPinnedConversations = (
|
||||
await Promise.all(remotelyPinnedConversationPromises)
|
||||
).filter(
|
||||
(conversation): conversation is ConversationModel =>
|
||||
conversation !== undefined
|
||||
);
|
||||
|
||||
const remotelyPinnedConversationIds = remotelyPinnedConversations.map(
|
||||
({ id }) => id
|
||||
);
|
||||
|
||||
const conversationsToUnpin = locallyPinnedConversations.filter(
|
||||
({ id }) => !remotelyPinnedConversationIds.includes(id)
|
||||
);
|
||||
|
||||
window.log.info(
|
||||
`mergeAccountRecord: unpinning ${conversationsToUnpin.length} conversations`
|
||||
);
|
||||
|
||||
window.log.info(
|
||||
`mergeAccountRecord: pinning ${conversationsToUnpin.length} conversations`
|
||||
);
|
||||
|
||||
conversationsToUnpin.forEach(conversation => {
|
||||
conversation.set({ isPinned: false, pinIndex: undefined });
|
||||
updateConversation(conversation.attributes);
|
||||
});
|
||||
|
||||
remotelyPinnedConversations.forEach((conversation, index) => {
|
||||
conversation.set({ isPinned: true, pinIndex: index });
|
||||
updateConversation(conversation.attributes);
|
||||
});
|
||||
}
|
||||
|
||||
const ourID = window.ConversationController.getOurConversationId();
|
||||
|
||||
if (!ourID) {
|
||||
|
|
|
@ -45,6 +45,7 @@ export type ConversationType = {
|
|||
color?: ColorType;
|
||||
isArchived?: boolean;
|
||||
isBlocked?: boolean;
|
||||
isPinned?: boolean;
|
||||
isVerified?: boolean;
|
||||
activeAt?: number;
|
||||
timestamp?: number;
|
||||
|
@ -54,6 +55,7 @@ export type ConversationType = {
|
|||
text: string;
|
||||
};
|
||||
phoneNumber?: string;
|
||||
pinIndex?: number;
|
||||
membersCount?: number;
|
||||
muteExpiresAt?: number;
|
||||
type: ConversationTypeType;
|
||||
|
|
|
@ -128,9 +128,11 @@ export const _getLeftPaneLists = (
|
|||
): {
|
||||
conversations: Array<ConversationType>;
|
||||
archivedConversations: Array<ConversationType>;
|
||||
pinnedConversations: Array<ConversationType>;
|
||||
} => {
|
||||
const conversations: Array<ConversationType> = [];
|
||||
const archivedConversations: Array<ConversationType> = [];
|
||||
const pinnedConversations: Array<ConversationType> = [];
|
||||
|
||||
const values = Object.values(lookup);
|
||||
const max = values.length;
|
||||
|
@ -146,6 +148,8 @@ export const _getLeftPaneLists = (
|
|||
|
||||
if (conversation.isArchived) {
|
||||
archivedConversations.push(conversation);
|
||||
} else if (conversation.isPinned) {
|
||||
pinnedConversations.push(conversation);
|
||||
} else {
|
||||
conversations.push(conversation);
|
||||
}
|
||||
|
@ -154,8 +158,9 @@ export const _getLeftPaneLists = (
|
|||
|
||||
conversations.sort(comparator);
|
||||
archivedConversations.sort(comparator);
|
||||
pinnedConversations.sort((a, b) => (a.pinIndex || 0) - (b.pinIndex || 0));
|
||||
|
||||
return { conversations, archivedConversations };
|
||||
return { conversations, archivedConversations, pinnedConversations };
|
||||
};
|
||||
|
||||
export const getLeftPaneLists = createSelector(
|
||||
|
|
15
ts/textsecure.d.ts
vendored
15
ts/textsecure.d.ts
vendored
|
@ -961,6 +961,20 @@ export declare class GroupV2RecordClass {
|
|||
__unknownFields?: ArrayBuffer;
|
||||
}
|
||||
|
||||
export declare class PinnedConversationClass {
|
||||
toArrayBuffer: () => ArrayBuffer;
|
||||
|
||||
// identifier is produced by the oneof field in the PinnedConversation protobuf
|
||||
// and determined which one of the following optional fields are in use
|
||||
identifier: 'contact' | 'legacyGroupId' | 'groupMasterKey';
|
||||
contact?: {
|
||||
uuid?: string;
|
||||
e164?: string;
|
||||
};
|
||||
legacyGroupId?: ProtoBinaryType;
|
||||
groupMasterKey?: ProtoBinaryType;
|
||||
}
|
||||
|
||||
export declare class AccountRecordClass {
|
||||
static decode: (
|
||||
data: ArrayBuffer | ByteBufferClass,
|
||||
|
@ -977,6 +991,7 @@ export declare class AccountRecordClass {
|
|||
sealedSenderIndicators?: boolean | null;
|
||||
typingIndicators?: boolean | null;
|
||||
linkPreviews?: boolean | null;
|
||||
pinnedConversations?: PinnedConversationClass[];
|
||||
|
||||
__unknownFields?: ArrayBuffer;
|
||||
}
|
||||
|
|
|
@ -12932,7 +12932,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/LeftPane.js",
|
||||
"line": " this.listRef = react_1.default.createRef();",
|
||||
"lineNumber": 16,
|
||||
"lineNumber": 30,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-09-11T17:24:56.124Z",
|
||||
"reasonDetail": "Used for scroll calculations"
|
||||
|
@ -12941,7 +12941,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/LeftPane.js",
|
||||
"line": " this.containerRef = react_1.default.createRef();",
|
||||
"lineNumber": 17,
|
||||
"lineNumber": 31,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-09-11T17:24:56.124Z",
|
||||
"reasonDetail": "Used for scroll calculations"
|
||||
|
|
Loading…
Add table
Reference in a new issue