Fixed all connections count and renamed MY_STORIES_ID and EditMyStoriesPrivacyModal
This commit is contained in:
parent
be6e988a95
commit
45069673ce
16 changed files with 86 additions and 82 deletions
|
@ -10,7 +10,7 @@ import { within, userEvent } from '@storybook/testing-library';
|
||||||
|
|
||||||
import type { PropsType } from './MyStories';
|
import type { PropsType } from './MyStories';
|
||||||
import enMessages from '../../_locales/en/messages.json';
|
import enMessages from '../../_locales/en/messages.json';
|
||||||
import { MY_STORIES_ID } from '../types/Stories';
|
import { MY_STORY_ID } from '../types/Stories';
|
||||||
import { MyStories } from './MyStories';
|
import { MyStories } from './MyStories';
|
||||||
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
|
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
|
||||||
import { getFakeMyStory } from '../test-both/helpers/getFakeStory';
|
import { getFakeMyStory } from '../test-both/helpers/getFakeStory';
|
||||||
|
@ -89,7 +89,7 @@ const interactionTest: PlayFunction<ReactFramework, PropsType> = async ({
|
||||||
|
|
||||||
export const SingleListStories = Template.bind({});
|
export const SingleListStories = Template.bind({});
|
||||||
SingleListStories.args = {
|
SingleListStories.args = {
|
||||||
myStories: [getFakeMyStory(MY_STORIES_ID)],
|
myStories: [getFakeMyStory(MY_STORY_ID)],
|
||||||
};
|
};
|
||||||
SingleListStories.play = interactionTest;
|
SingleListStories.play = interactionTest;
|
||||||
SingleListStories.story = {
|
SingleListStories.story = {
|
||||||
|
@ -99,7 +99,7 @@ SingleListStories.story = {
|
||||||
export const MultiListStories = Template.bind({});
|
export const MultiListStories = Template.bind({});
|
||||||
MultiListStories.args = {
|
MultiListStories.args = {
|
||||||
myStories: [
|
myStories: [
|
||||||
getFakeMyStory(MY_STORIES_ID),
|
getFakeMyStory(MY_STORY_ID),
|
||||||
getFakeMyStory(uuid(), 'Cool Peeps'),
|
getFakeMyStory(uuid(), 'Cool Peeps'),
|
||||||
getFakeMyStory(uuid(), 'Family'),
|
getFakeMyStory(uuid(), 'Family'),
|
||||||
],
|
],
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
getListViewers,
|
getListViewers,
|
||||||
DistributionListSettingsModal,
|
DistributionListSettingsModal,
|
||||||
EditDistributionListModal,
|
EditDistributionListModal,
|
||||||
EditMyStoriesPrivacy,
|
EditMyStoryPrivacy,
|
||||||
Page as StoriesSettingsPage,
|
Page as StoriesSettingsPage,
|
||||||
} from './StoriesSettingsModal';
|
} from './StoriesSettingsModal';
|
||||||
import type { StoryDistributionListWithMembersDataType } from '../types/Stories';
|
import type { StoryDistributionListWithMembersDataType } from '../types/Stories';
|
||||||
|
@ -28,7 +28,7 @@ import { Checkbox } from './Checkbox';
|
||||||
import { ConfirmationDialog } from './ConfirmationDialog';
|
import { ConfirmationDialog } from './ConfirmationDialog';
|
||||||
import { ContextMenu } from './ContextMenu';
|
import { ContextMenu } from './ContextMenu';
|
||||||
|
|
||||||
import { MY_STORIES_ID, getStoryDistributionListName } from '../types/Stories';
|
import { MY_STORY_ID, getStoryDistributionListName } from '../types/Stories';
|
||||||
import type { RenderModalPage, ModalPropsType } from './Modal';
|
import type { RenderModalPage, ModalPropsType } from './Modal';
|
||||||
import { PagedModal, ModalPage } from './Modal';
|
import { PagedModal, ModalPage } from './Modal';
|
||||||
import { StoryDistributionListName } from './StoryDistributionListName';
|
import { StoryDistributionListName } from './StoryDistributionListName';
|
||||||
|
@ -97,7 +97,7 @@ function getListMemberUuids(
|
||||||
): Array<string> {
|
): Array<string> {
|
||||||
const memberUuids = list.members.map(({ uuid }) => uuid).filter(isNotNil);
|
const memberUuids = list.members.map(({ uuid }) => uuid).filter(isNotNil);
|
||||||
|
|
||||||
if (list.id === MY_STORIES_ID && list.isBlockList) {
|
if (list.id === MY_STORY_ID && list.isBlockList) {
|
||||||
const excludeUuids = new Set<string>(memberUuids);
|
const excludeUuids = new Set<string>(memberUuids);
|
||||||
return signalConnections
|
return signalConnections
|
||||||
.map(conversation => conversation.uuid)
|
.map(conversation => conversation.uuid)
|
||||||
|
@ -230,14 +230,14 @@ export const SendStoryModal = ({
|
||||||
// during the first time posting to My Stories experience where we have
|
// during the first time posting to My Stories experience where we have
|
||||||
// to select the privacy settings.
|
// to select the privacy settings.
|
||||||
const ogMyStories = useMemo(
|
const ogMyStories = useMemo(
|
||||||
() => distributionLists.find(list => list.id === MY_STORIES_ID),
|
() => distributionLists.find(list => list.id === MY_STORY_ID),
|
||||||
[distributionLists]
|
[distributionLists]
|
||||||
);
|
);
|
||||||
|
|
||||||
const initialMyStories: StoryDistributionListWithMembersDataType = useMemo(
|
const initialMyStories: StoryDistributionListWithMembersDataType = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
allowsReplies: true,
|
allowsReplies: true,
|
||||||
id: MY_STORIES_ID,
|
id: MY_STORY_ID,
|
||||||
name: i18n('Stories__mine'),
|
name: i18n('Stories__mine'),
|
||||||
isBlockList: ogMyStories?.isBlockList ?? true,
|
isBlockList: ogMyStories?.isBlockList ?? true,
|
||||||
members: ogMyStories?.members || [],
|
members: ogMyStories?.members || [],
|
||||||
|
@ -310,7 +310,7 @@ export const SendStoryModal = ({
|
||||||
setMyStoriesToAllSignalConnections();
|
setMyStoriesToAllSignalConnections();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onViewersUpdated(MY_STORIES_ID, stagedMyStoriesMemberUuids);
|
onViewersUpdated(MY_STORY_ID, stagedMyStoriesMemberUuids);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedContacts([]);
|
setSelectedContacts([]);
|
||||||
|
@ -332,11 +332,12 @@ export const SendStoryModal = ({
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
{...modalCommonProps}
|
{...modalCommonProps}
|
||||||
>
|
>
|
||||||
<EditMyStoriesPrivacy
|
<EditMyStoryPrivacy
|
||||||
hasDisclaimerAbove
|
hasDisclaimerAbove
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
learnMore="SendStoryModal__privacy-disclaimer"
|
learnMore="SendStoryModal__privacy-disclaimer"
|
||||||
myStories={stagedMyStories}
|
myStories={stagedMyStories}
|
||||||
|
signalConnectionsCount={signalConnections.length}
|
||||||
onClickExclude={() => {
|
onClickExclude={() => {
|
||||||
let nextSelectedContacts = stagedMyStories.members;
|
let nextSelectedContacts = stagedMyStories.members;
|
||||||
|
|
||||||
|
@ -385,6 +386,7 @@ export const SendStoryModal = ({
|
||||||
getPreferredBadge={getPreferredBadge}
|
getPreferredBadge={getPreferredBadge}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
listToEdit={listToEdit}
|
listToEdit={listToEdit}
|
||||||
|
signalConnectionsCount={signalConnections.length}
|
||||||
onRemoveMember={onRemoveMember}
|
onRemoveMember={onRemoveMember}
|
||||||
onRepliesNReactionsChanged={onRepliesNReactionsChanged}
|
onRepliesNReactionsChanged={onRepliesNReactionsChanged}
|
||||||
setConfirmDeleteList={setConfirmDeleteList}
|
setConfirmDeleteList={setConfirmDeleteList}
|
||||||
|
@ -595,7 +597,7 @@ export const SendStoryModal = ({
|
||||||
const fullList = sortBy(
|
const fullList = sortBy(
|
||||||
[...groupStories, ...distributionLists],
|
[...groupStories, ...distributionLists],
|
||||||
listOrGroup => {
|
listOrGroup => {
|
||||||
if (listOrGroup.id === MY_STORIES_ID) {
|
if (listOrGroup.id === MY_STORY_ID) {
|
||||||
return Number.NEGATIVE_INFINITY;
|
return Number.NEGATIVE_INFINITY;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -618,7 +620,7 @@ export const SendStoryModal = ({
|
||||||
name="SendStoryModal__distribution-list"
|
name="SendStoryModal__distribution-list"
|
||||||
onChange={(value: boolean) => {
|
onChange={(value: boolean) => {
|
||||||
if (
|
if (
|
||||||
list.id === MY_STORIES_ID &&
|
list.id === MY_STORY_ID &&
|
||||||
hasFirstStoryPostExperience &&
|
hasFirstStoryPostExperience &&
|
||||||
value
|
value
|
||||||
) {
|
) {
|
||||||
|
@ -643,7 +645,7 @@ export const SendStoryModal = ({
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
menuOptions={
|
menuOptions={
|
||||||
list.id === MY_STORIES_ID
|
list.id === MY_STORY_ID
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: i18n('StoriesSettings__context-menu'),
|
label: i18n('StoriesSettings__context-menu'),
|
||||||
|
@ -676,7 +678,7 @@ export const SendStoryModal = ({
|
||||||
className="SendStoryModal__distribution-list__label"
|
className="SendStoryModal__distribution-list__label"
|
||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
>
|
>
|
||||||
{list.id === MY_STORIES_ID ? (
|
{list.id === MY_STORY_ID ? (
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={me.acceptedMessageRequest}
|
acceptedMessageRequest={me.acceptedMessageRequest}
|
||||||
avatarPath={me.avatarPath}
|
avatarPath={me.avatarPath}
|
||||||
|
@ -703,13 +705,12 @@ export const SendStoryModal = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="SendStoryModal__distribution-list__description">
|
<div className="SendStoryModal__distribution-list__description">
|
||||||
{hasFirstStoryPostExperience &&
|
{hasFirstStoryPostExperience && list.id === MY_STORY_ID ? (
|
||||||
list.id === MY_STORIES_ID ? (
|
|
||||||
i18n('SendStoryModal__choose-who-can-view')
|
i18n('SendStoryModal__choose-who-can-view')
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<span className="SendStoryModal__rtl-span">
|
<span className="SendStoryModal__rtl-span">
|
||||||
{list.id === MY_STORIES_ID
|
{list.id === MY_STORY_ID
|
||||||
? getI18nForMyStory(list, i18n)
|
? getI18nForMyStory(list, i18n)
|
||||||
: i18n('SendStoryModal__custom-story')}
|
: i18n('SendStoryModal__custom-story')}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { ContactPill } from './ContactPill';
|
||||||
import { ConversationList, RowType } from './ConversationList';
|
import { ConversationList, RowType } from './ConversationList';
|
||||||
import { Input } from './Input';
|
import { Input } from './Input';
|
||||||
import { Intl } from './Intl';
|
import { Intl } from './Intl';
|
||||||
import { MY_STORIES_ID, getStoryDistributionListName } from '../types/Stories';
|
import { MY_STORY_ID, getStoryDistributionListName } from '../types/Stories';
|
||||||
import { PagedModal, ModalPage } from './Modal';
|
import { PagedModal, ModalPage } from './Modal';
|
||||||
import { SearchInput } from './SearchInput';
|
import { SearchInput } from './SearchInput';
|
||||||
import { StoryDistributionListName } from './StoryDistributionListName';
|
import { StoryDistributionListName } from './StoryDistributionListName';
|
||||||
|
@ -104,7 +104,7 @@ export function getListViewers(
|
||||||
): string {
|
): string {
|
||||||
let memberCount = list.members.length;
|
let memberCount = list.members.length;
|
||||||
|
|
||||||
if (list.id === MY_STORIES_ID && list.isBlockList) {
|
if (list.id === MY_STORY_ID && list.isBlockList) {
|
||||||
memberCount = list.isBlockList
|
memberCount = list.isBlockList
|
||||||
? signalConnections.length - list.members.length
|
? signalConnections.length - list.members.length
|
||||||
: signalConnections.length;
|
: signalConnections.length;
|
||||||
|
@ -143,7 +143,7 @@ function DistributionListItem({
|
||||||
signalConnections,
|
signalConnections,
|
||||||
onSelectItemToEdit,
|
onSelectItemToEdit,
|
||||||
}: DistributionListItemProps) {
|
}: DistributionListItemProps) {
|
||||||
const isMyStories = distributionList.id === MY_STORIES_ID;
|
const isMyStory = distributionList.id === MY_STORY_ID;
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="StoriesSettingsModal__list"
|
className="StoriesSettingsModal__list"
|
||||||
|
@ -153,7 +153,7 @@ function DistributionListItem({
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span className="StoriesSettingsModal__list__left">
|
<span className="StoriesSettingsModal__list__left">
|
||||||
{isMyStories ? (
|
{isMyStory ? (
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={me.acceptedMessageRequest}
|
acceptedMessageRequest={me.acceptedMessageRequest}
|
||||||
avatarPath={me.avatarPath}
|
avatarPath={me.avatarPath}
|
||||||
|
@ -176,7 +176,7 @@ function DistributionListItem({
|
||||||
name={distributionList.name}
|
name={distributionList.name}
|
||||||
/>
|
/>
|
||||||
<span className="StoriesSettingsModal__list__viewers">
|
<span className="StoriesSettingsModal__list__viewers">
|
||||||
{isMyStories
|
{isMyStory
|
||||||
? getI18nForMyStory(distributionList, i18n)
|
? getI18nForMyStory(distributionList, i18n)
|
||||||
: i18n('icu:StoriesSettings__custom-story-subtitle')}
|
: i18n('icu:StoriesSettings__custom-story-subtitle')}
|
||||||
·
|
·
|
||||||
|
@ -354,6 +354,7 @@ export const StoriesSettingsModal = ({
|
||||||
getPreferredBadge={getPreferredBadge}
|
getPreferredBadge={getPreferredBadge}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
listToEdit={listToEdit}
|
listToEdit={listToEdit}
|
||||||
|
signalConnectionsCount={signalConnections.length}
|
||||||
onRemoveMember={onRemoveMember}
|
onRemoveMember={onRemoveMember}
|
||||||
onRepliesNReactionsChanged={onRepliesNReactionsChanged}
|
onRepliesNReactionsChanged={onRepliesNReactionsChanged}
|
||||||
setConfirmDeleteList={setConfirmDeleteList}
|
setConfirmDeleteList={setConfirmDeleteList}
|
||||||
|
@ -542,6 +543,7 @@ export const StoriesSettingsModal = ({
|
||||||
type DistributionListSettingsModalPropsType = {
|
type DistributionListSettingsModalPropsType = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
listToEdit: StoryDistributionListWithMembersDataType;
|
listToEdit: StoryDistributionListWithMembersDataType;
|
||||||
|
signalConnectionsCount: number;
|
||||||
setConfirmDeleteList: (_: { id: string; name: string }) => unknown;
|
setConfirmDeleteList: (_: { id: string; name: string }) => unknown;
|
||||||
setPage: (page: Page) => unknown;
|
setPage: (page: Page) => unknown;
|
||||||
setSelectedContacts: (contacts: Array<ConversationType>) => unknown;
|
setSelectedContacts: (contacts: Array<ConversationType>) => unknown;
|
||||||
|
@ -569,6 +571,7 @@ export const DistributionListSettingsModal = ({
|
||||||
setPage,
|
setPage,
|
||||||
setSelectedContacts,
|
setSelectedContacts,
|
||||||
toggleSignalConnectionsModal,
|
toggleSignalConnectionsModal,
|
||||||
|
signalConnectionsCount,
|
||||||
}: DistributionListSettingsModalPropsType): JSX.Element => {
|
}: DistributionListSettingsModalPropsType): JSX.Element => {
|
||||||
const [confirmRemoveMember, setConfirmRemoveMember] = useState<
|
const [confirmRemoveMember, setConfirmRemoveMember] = useState<
|
||||||
| undefined
|
| undefined
|
||||||
|
@ -579,7 +582,7 @@ export const DistributionListSettingsModal = ({
|
||||||
}
|
}
|
||||||
>();
|
>();
|
||||||
|
|
||||||
const isMyStories = listToEdit.id === MY_STORIES_ID;
|
const isMyStory = listToEdit.id === MY_STORY_ID;
|
||||||
|
|
||||||
const modalTitle = getStoryDistributionListName(
|
const modalTitle = getStoryDistributionListName(
|
||||||
i18n,
|
i18n,
|
||||||
|
@ -596,7 +599,7 @@ export const DistributionListSettingsModal = ({
|
||||||
title={modalTitle}
|
title={modalTitle}
|
||||||
{...modalCommonProps}
|
{...modalCommonProps}
|
||||||
>
|
>
|
||||||
{!isMyStories && (
|
{!isMyStory && (
|
||||||
<>
|
<>
|
||||||
<div className="StoriesSettingsModal__list StoriesSettingsModal__list--no-pointer">
|
<div className="StoriesSettingsModal__list StoriesSettingsModal__list--no-pointer">
|
||||||
<span className="StoriesSettingsModal__list__left">
|
<span className="StoriesSettingsModal__list__left">
|
||||||
|
@ -619,8 +622,8 @@ export const DistributionListSettingsModal = ({
|
||||||
{i18n('StoriesSettings__who-can-see')}
|
{i18n('StoriesSettings__who-can-see')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isMyStories && (
|
{isMyStory && (
|
||||||
<EditMyStoriesPrivacy
|
<EditMyStoryPrivacy
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
learnMore="StoriesSettings__mine__disclaimer"
|
learnMore="StoriesSettings__mine__disclaimer"
|
||||||
myStories={listToEdit}
|
myStories={listToEdit}
|
||||||
|
@ -635,10 +638,11 @@ export const DistributionListSettingsModal = ({
|
||||||
setMyStoriesToAllSignalConnections
|
setMyStoriesToAllSignalConnections
|
||||||
}
|
}
|
||||||
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
||||||
|
signalConnectionsCount={signalConnectionsCount}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isMyStories && (
|
{!isMyStory && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="StoriesSettingsModal__list"
|
className="StoriesSettingsModal__list"
|
||||||
|
@ -714,7 +718,7 @@ export const DistributionListSettingsModal = ({
|
||||||
onChange={value => onRepliesNReactionsChanged(listToEdit.id, value)}
|
onChange={value => onRepliesNReactionsChanged(listToEdit.id, value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!isMyStories && (
|
{!isMyStory && (
|
||||||
<>
|
<>
|
||||||
<hr className="StoriesSettingsModal__divider" />
|
<hr className="StoriesSettingsModal__divider" />
|
||||||
|
|
||||||
|
@ -782,7 +786,7 @@ function CheckboxRender({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type EditMyStoriesPrivacyPropsType = {
|
type EditMyStoryPrivacyPropsType = {
|
||||||
hasDisclaimerAbove?: boolean;
|
hasDisclaimerAbove?: boolean;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
learnMore: string;
|
learnMore: string;
|
||||||
|
@ -790,12 +794,13 @@ type EditMyStoriesPrivacyPropsType = {
|
||||||
onClickExclude: () => unknown;
|
onClickExclude: () => unknown;
|
||||||
onClickOnlyShareWith: () => unknown;
|
onClickOnlyShareWith: () => unknown;
|
||||||
setSelectedContacts: (contacts: Array<ConversationType>) => unknown;
|
setSelectedContacts: (contacts: Array<ConversationType>) => unknown;
|
||||||
|
signalConnectionsCount: number;
|
||||||
} & Pick<
|
} & Pick<
|
||||||
PropsType,
|
PropsType,
|
||||||
'setMyStoriesToAllSignalConnections' | 'toggleSignalConnectionsModal'
|
'setMyStoriesToAllSignalConnections' | 'toggleSignalConnectionsModal'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const EditMyStoriesPrivacy = ({
|
export const EditMyStoryPrivacy = ({
|
||||||
hasDisclaimerAbove,
|
hasDisclaimerAbove,
|
||||||
i18n,
|
i18n,
|
||||||
learnMore,
|
learnMore,
|
||||||
|
@ -805,7 +810,8 @@ export const EditMyStoriesPrivacy = ({
|
||||||
setSelectedContacts,
|
setSelectedContacts,
|
||||||
setMyStoriesToAllSignalConnections,
|
setMyStoriesToAllSignalConnections,
|
||||||
toggleSignalConnectionsModal,
|
toggleSignalConnectionsModal,
|
||||||
}: EditMyStoriesPrivacyPropsType): JSX.Element => {
|
signalConnectionsCount,
|
||||||
|
}: EditMyStoryPrivacyPropsType): JSX.Element => {
|
||||||
const disclaimerElement = (
|
const disclaimerElement = (
|
||||||
<div className="StoriesSettingsModal__disclaimer">
|
<div className="StoriesSettingsModal__disclaimer">
|
||||||
<Intl
|
<Intl
|
||||||
|
@ -849,7 +855,7 @@ export const EditMyStoriesPrivacy = ({
|
||||||
checked && (
|
checked && (
|
||||||
<>
|
<>
|
||||||
{i18n('icu:StoriesSettings__viewers', {
|
{i18n('icu:StoriesSettings__viewers', {
|
||||||
count: myStories.members.length,
|
count: signalConnectionsCount,
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -61,7 +61,7 @@ import type {
|
||||||
StickerPackType,
|
StickerPackType,
|
||||||
UninstalledStickerPackType,
|
UninstalledStickerPackType,
|
||||||
} from '../sql/Interface';
|
} from '../sql/Interface';
|
||||||
import { MY_STORIES_ID } from '../types/Stories';
|
import { MY_STORY_ID } from '../types/Stories';
|
||||||
import { isNotNil } from '../util/isNotNil';
|
import { isNotNil } from '../util/isNotNil';
|
||||||
import { isSignalConversation } from '../util/isSignalConversation';
|
import { isSignalConversation } from '../util/isSignalConversation';
|
||||||
|
|
||||||
|
@ -1292,17 +1292,17 @@ async function processManifest(
|
||||||
|
|
||||||
// Check to make sure we have a "My Stories" distribution list set up
|
// Check to make sure we have a "My Stories" distribution list set up
|
||||||
const myStories = storyDistributionLists.find(
|
const myStories = storyDistributionLists.find(
|
||||||
({ id }) => id === MY_STORIES_ID
|
({ id }) => id === MY_STORY_ID
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!myStories) {
|
if (!myStories) {
|
||||||
log.info(`storageService.process(${version}): creating my stories`);
|
log.info(`storageService.process(${version}): creating my stories`);
|
||||||
const storyDistribution: StoryDistributionWithMembersType = {
|
const storyDistribution: StoryDistributionWithMembersType = {
|
||||||
allowsReplies: true,
|
allowsReplies: true,
|
||||||
id: MY_STORIES_ID,
|
id: MY_STORY_ID,
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
members: [],
|
members: [],
|
||||||
name: MY_STORIES_ID,
|
name: MY_STORY_ID,
|
||||||
senderKeyInfo: undefined,
|
senderKeyInfo: undefined,
|
||||||
storageNeedsSync: true,
|
storageNeedsSync: true,
|
||||||
};
|
};
|
||||||
|
|
|
@ -50,11 +50,11 @@ import type {
|
||||||
StickerPackInfoType,
|
StickerPackInfoType,
|
||||||
} from '../sql/Interface';
|
} from '../sql/Interface';
|
||||||
import dataInterface from '../sql/Client';
|
import dataInterface from '../sql/Client';
|
||||||
import { MY_STORIES_ID, StorySendMode } from '../types/Stories';
|
import { MY_STORY_ID, StorySendMode } from '../types/Stories';
|
||||||
import * as RemoteConfig from '../RemoteConfig';
|
import * as RemoteConfig from '../RemoteConfig';
|
||||||
import { findAndDeleteOnboardingStoryIfExists } from '../util/findAndDeleteOnboardingStoryIfExists';
|
import { findAndDeleteOnboardingStoryIfExists } from '../util/findAndDeleteOnboardingStoryIfExists';
|
||||||
|
|
||||||
const MY_STORIES_BYTES = uuidToBytes(MY_STORIES_ID);
|
const MY_STORY_BYTES = uuidToBytes(MY_STORY_ID);
|
||||||
|
|
||||||
type RecordClass =
|
type RecordClass =
|
||||||
| Proto.IAccountRecord
|
| Proto.IAccountRecord
|
||||||
|
@ -1418,13 +1418,13 @@ export async function mergeStoryDistributionListRecord(
|
||||||
|
|
||||||
const details: Array<string> = [];
|
const details: Array<string> = [];
|
||||||
|
|
||||||
const isMyStories = Bytes.areEqual(
|
const isMyStory = Bytes.areEqual(
|
||||||
MY_STORIES_BYTES,
|
MY_STORY_BYTES,
|
||||||
storyDistributionListRecord.identifier
|
storyDistributionListRecord.identifier
|
||||||
);
|
);
|
||||||
|
|
||||||
const listId = isMyStories
|
const listId = isMyStory
|
||||||
? MY_STORIES_ID
|
? MY_STORY_ID
|
||||||
: bytesToUuid(storyDistributionListRecord.identifier);
|
: bytesToUuid(storyDistributionListRecord.identifier);
|
||||||
|
|
||||||
if (!listId) {
|
if (!listId) {
|
||||||
|
@ -1449,7 +1449,7 @@ export async function mergeStoryDistributionListRecord(
|
||||||
const storyDistribution: StoryDistributionWithMembersType = {
|
const storyDistribution: StoryDistributionWithMembersType = {
|
||||||
id: listId,
|
id: listId,
|
||||||
name: String(storyDistributionListRecord.name),
|
name: String(storyDistributionListRecord.name),
|
||||||
deletedAtTimestamp: isMyStories ? undefined : deletedAtTimestamp,
|
deletedAtTimestamp: isMyStory ? undefined : deletedAtTimestamp,
|
||||||
allowsReplies: Boolean(storyDistributionListRecord.allowsReplies),
|
allowsReplies: Boolean(storyDistributionListRecord.allowsReplies),
|
||||||
isBlockList: Boolean(storyDistributionListRecord.isBlockList),
|
isBlockList: Boolean(storyDistributionListRecord.isBlockList),
|
||||||
members: remoteListMembers,
|
members: remoteListMembers,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import type { StoryDistributionWithMembersType } from '../../sql/Interface';
|
||||||
import type { UUIDStringType } from '../../types/UUID';
|
import type { UUIDStringType } from '../../types/UUID';
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
import dataInterface from '../../sql/Client';
|
import dataInterface from '../../sql/Client';
|
||||||
import { MY_STORIES_ID } from '../../types/Stories';
|
import { MY_STORY_ID } from '../../types/Stories';
|
||||||
import { UUID } from '../../types/UUID';
|
import { UUID } from '../../types/UUID';
|
||||||
import { deleteStoryForEveryone } from '../../util/deleteStoryForEveryone';
|
import { deleteStoryForEveryone } from '../../util/deleteStoryForEveryone';
|
||||||
import { replaceIndex } from '../../util/replaceIndex';
|
import { replaceIndex } from '../../util/replaceIndex';
|
||||||
|
@ -265,7 +265,7 @@ function hideMyStoriesFrom(
|
||||||
): ThunkAction<void, RootStateType, null, HideMyStoriesFromActionType> {
|
): ThunkAction<void, RootStateType, null, HideMyStoriesFromActionType> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const myStories = await dataInterface.getStoryDistributionWithMembers(
|
const myStories = await dataInterface.getStoryDistributionWithMembers(
|
||||||
MY_STORIES_ID
|
MY_STORY_ID
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!myStories) {
|
if (!myStories) {
|
||||||
|
@ -363,7 +363,7 @@ function setMyStoriesToAllSignalConnections(): ThunkAction<
|
||||||
> {
|
> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const myStories = await dataInterface.getStoryDistributionWithMembers(
|
const myStories = await dataInterface.getStoryDistributionWithMembers(
|
||||||
MY_STORIES_ID
|
MY_STORY_ID
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!myStories) {
|
if (!myStories) {
|
||||||
|
@ -445,7 +445,7 @@ function updateStoryViewers(
|
||||||
|
|
||||||
storageServiceUploadJob();
|
storageServiceUploadJob();
|
||||||
|
|
||||||
if (listId === MY_STORIES_ID) {
|
if (listId === MY_STORY_ID) {
|
||||||
window.storage.put('hasSetMyStoriesPrivacy', true);
|
window.storage.put('hasSetMyStoriesPrivacy', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -562,7 +562,7 @@ export function reducer(
|
||||||
if (action.type === HIDE_MY_STORIES_FROM) {
|
if (action.type === HIDE_MY_STORIES_FROM) {
|
||||||
const distributionLists = replaceDistributionListData(
|
const distributionLists = replaceDistributionListData(
|
||||||
state.distributionLists,
|
state.distributionLists,
|
||||||
MY_STORIES_ID,
|
MY_STORY_ID,
|
||||||
() => ({
|
() => ({
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
memberUuids: action.payload,
|
memberUuids: action.payload,
|
||||||
|
@ -614,7 +614,7 @@ export function reducer(
|
||||||
if (action.type === RESET_MY_STORIES) {
|
if (action.type === RESET_MY_STORIES) {
|
||||||
const distributionLists = replaceDistributionListData(
|
const distributionLists = replaceDistributionListData(
|
||||||
state.distributionLists,
|
state.distributionLists,
|
||||||
MY_STORIES_ID,
|
MY_STORY_ID,
|
||||||
() => ({
|
() => ({
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
memberUuids: [],
|
memberUuids: [],
|
||||||
|
|
|
@ -22,7 +22,7 @@ import type {
|
||||||
StoriesStateType,
|
StoriesStateType,
|
||||||
AddStoryData,
|
AddStoryData,
|
||||||
} from '../ducks/stories';
|
} from '../ducks/stories';
|
||||||
import { HasStories, MY_STORIES_ID } from '../../types/Stories';
|
import { HasStories, MY_STORY_ID } from '../../types/Stories';
|
||||||
import { ReadStatus } from '../../messages/MessageReadStatus';
|
import { ReadStatus } from '../../messages/MessageReadStatus';
|
||||||
import { SendStatus } from '../../messages/MessageSendState';
|
import { SendStatus } from '../../messages/MessageSendState';
|
||||||
import { canReply } from './message';
|
import { canReply } from './message';
|
||||||
|
@ -102,11 +102,11 @@ function sortByRecencyAndUnread(
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortMyStories(storyA: MyStoryType, storyB: MyStoryType): number {
|
function sortMyStories(storyA: MyStoryType, storyB: MyStoryType): number {
|
||||||
if (storyA.id === MY_STORIES_ID) {
|
if (storyA.id === MY_STORY_ID) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storyB.id === MY_STORIES_ID) {
|
if (storyB.id === MY_STORY_ID) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,8 +382,8 @@ export const getStories = createSelector(
|
||||||
|
|
||||||
if (story.storyDistributionListId) {
|
if (story.storyDistributionListId) {
|
||||||
const list =
|
const list =
|
||||||
story.storyDistributionListId === MY_STORIES_ID
|
story.storyDistributionListId === MY_STORY_ID
|
||||||
? { id: MY_STORIES_ID, name: MY_STORIES_ID }
|
? { id: MY_STORY_ID, name: MY_STORY_ID }
|
||||||
: distributionListSelector(
|
: distributionListSelector(
|
||||||
story.storyDistributionListId.toLowerCase()
|
story.storyDistributionListId.toLowerCase()
|
||||||
);
|
);
|
||||||
|
@ -528,8 +528,8 @@ export const getStoryByIdSelector = createSelector(
|
||||||
| undefined;
|
| undefined;
|
||||||
if (story.storyDistributionListId) {
|
if (story.storyDistributionListId) {
|
||||||
distributionList =
|
distributionList =
|
||||||
story.storyDistributionListId === MY_STORIES_ID
|
story.storyDistributionListId === MY_STORY_ID
|
||||||
? { id: MY_STORIES_ID, name: MY_STORIES_ID }
|
? { id: MY_STORY_ID, name: MY_STORY_ID }
|
||||||
: distributionListSelector(
|
: distributionListSelector(
|
||||||
story.storyDistributionListId.toLowerCase()
|
story.storyDistributionListId.toLowerCase()
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,14 +7,14 @@ import type { StateType } from '../reducer';
|
||||||
import type { StoryDistributionListDataType } from '../ducks/storyDistributionLists';
|
import type { StoryDistributionListDataType } from '../ducks/storyDistributionLists';
|
||||||
import type { StoryDistributionListWithMembersDataType } from '../../types/Stories';
|
import type { StoryDistributionListWithMembersDataType } from '../../types/Stories';
|
||||||
import { getConversationSelector } from './conversations';
|
import { getConversationSelector } from './conversations';
|
||||||
import { MY_STORIES_ID } from '../../types/Stories';
|
import { MY_STORY_ID } from '../../types/Stories';
|
||||||
|
|
||||||
export const getDistributionLists = (
|
export const getDistributionLists = (
|
||||||
state: StateType
|
state: StateType
|
||||||
): Array<StoryDistributionListDataType> =>
|
): Array<StoryDistributionListDataType> =>
|
||||||
state.storyDistributionLists.distributionLists
|
state.storyDistributionLists.distributionLists
|
||||||
.filter(list => !list.deletedAtTimestamp)
|
.filter(list => !list.deletedAtTimestamp)
|
||||||
.sort(list => (list.id === MY_STORIES_ID ? -1 : 1));
|
.sort(list => (list.id === MY_STORY_ID ? -1 : 1));
|
||||||
|
|
||||||
export const getDistributionListSelector = createSelector(
|
export const getDistributionListSelector = createSelector(
|
||||||
getDistributionLists,
|
getDistributionLists,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import casual from 'casual';
|
||||||
|
|
||||||
import type { StoryDistributionListDataType } from '../../state/ducks/storyDistributionLists';
|
import type { StoryDistributionListDataType } from '../../state/ducks/storyDistributionLists';
|
||||||
import type { StoryDistributionListWithMembersDataType } from '../../types/Stories';
|
import type { StoryDistributionListWithMembersDataType } from '../../types/Stories';
|
||||||
import { MY_STORIES_ID } from '../../types/Stories';
|
import { MY_STORY_ID } from '../../types/Stories';
|
||||||
import { UUID } from '../../types/UUID';
|
import { UUID } from '../../types/UUID';
|
||||||
import { getDefaultConversation } from './getDefaultConversation';
|
import { getDefaultConversation } from './getDefaultConversation';
|
||||||
|
|
||||||
|
@ -46,9 +46,9 @@ export function getFakeDistributionList(): StoryDistributionListDataType {
|
||||||
export function getMyStories(): StoryDistributionListDataType {
|
export function getMyStories(): StoryDistributionListDataType {
|
||||||
return {
|
return {
|
||||||
allowsReplies: true,
|
allowsReplies: true,
|
||||||
id: MY_STORIES_ID,
|
id: MY_STORY_ID,
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
memberUuids: [],
|
memberUuids: [],
|
||||||
name: MY_STORIES_ID,
|
name: MY_STORY_ID,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import * as durations from '../../util/durations';
|
||||||
import { UUID } from '../../types/UUID';
|
import { UUID } from '../../types/UUID';
|
||||||
import { getDefaultConversation } from './getDefaultConversation';
|
import { getDefaultConversation } from './getDefaultConversation';
|
||||||
import { fakeAttachment, fakeThumbnail } from './fakeAttachment';
|
import { fakeAttachment, fakeThumbnail } from './fakeAttachment';
|
||||||
import { MY_STORIES_ID } from '../../types/Stories';
|
import { MY_STORY_ID } from '../../types/Stories';
|
||||||
|
|
||||||
function getAttachmentWithThumbnail(url: string): AttachmentType {
|
function getAttachmentWithThumbnail(url: string): AttachmentType {
|
||||||
return fakeAttachment({
|
return fakeAttachment({
|
||||||
|
@ -28,7 +28,7 @@ export function getFakeMyStory(id?: string, name?: string): MyStoryType {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: id || UUID.generate().toString(),
|
id: id || UUID.generate().toString(),
|
||||||
name: name || id === MY_STORIES_ID ? 'My Stories' : casual.catch_phrase,
|
name: name || id === MY_STORY_ID ? 'My Stories' : casual.catch_phrase,
|
||||||
stories: Array.from(Array(storyCount), () => ({
|
stories: Array.from(Array(storyCount), () => ({
|
||||||
...getFakeStoryView(),
|
...getFakeStoryView(),
|
||||||
sendState: [],
|
sendState: [],
|
||||||
|
|
|
@ -13,7 +13,7 @@ import createDebug from 'debug';
|
||||||
|
|
||||||
import * as durations from '../../util/durations';
|
import * as durations from '../../util/durations';
|
||||||
import { uuidToBytes } from '../../util/uuidToBytes';
|
import { uuidToBytes } from '../../util/uuidToBytes';
|
||||||
import { MY_STORIES_ID } from '../../types/Stories';
|
import { MY_STORY_ID } from '../../types/Stories';
|
||||||
import { Bootstrap } from '../bootstrap';
|
import { Bootstrap } from '../bootstrap';
|
||||||
import type { App } from '../bootstrap';
|
import type { App } from '../bootstrap';
|
||||||
|
|
||||||
|
@ -77,9 +77,9 @@ describe('pnp/PNI Signature', function needsName() {
|
||||||
record: {
|
record: {
|
||||||
storyDistributionList: {
|
storyDistributionList: {
|
||||||
allowsReplies: true,
|
allowsReplies: true,
|
||||||
identifier: uuidToBytes(MY_STORIES_ID),
|
identifier: uuidToBytes(MY_STORY_ID),
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
name: MY_STORIES_ID,
|
name: MY_STORY_ID,
|
||||||
recipientUuids: [],
|
recipientUuids: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,7 +9,7 @@ import createDebug from 'debug';
|
||||||
import * as durations from '../../util/durations';
|
import * as durations from '../../util/durations';
|
||||||
import { Bootstrap } from '../bootstrap';
|
import { Bootstrap } from '../bootstrap';
|
||||||
import type { App } from '../bootstrap';
|
import type { App } from '../bootstrap';
|
||||||
import { MY_STORIES_ID } from '../../types/Stories';
|
import { MY_STORY_ID } from '../../types/Stories';
|
||||||
import { uuidToBytes } from '../../util/uuidToBytes';
|
import { uuidToBytes } from '../../util/uuidToBytes';
|
||||||
|
|
||||||
const IdentifierType = Proto.ManifestRecord.Identifier.Type;
|
const IdentifierType = Proto.ManifestRecord.Identifier.Type;
|
||||||
|
@ -69,9 +69,9 @@ describe('pnp/send gv2 invite', function needsName() {
|
||||||
record: {
|
record: {
|
||||||
storyDistributionList: {
|
storyDistributionList: {
|
||||||
allowsReplies: true,
|
allowsReplies: true,
|
||||||
identifier: uuidToBytes(MY_STORIES_ID),
|
identifier: uuidToBytes(MY_STORY_ID),
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
name: MY_STORIES_ID,
|
name: MY_STORY_ID,
|
||||||
recipientUuids: [],
|
recipientUuids: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { Proto, StorageState } from '@signalapp/mock-server';
|
||||||
|
|
||||||
import * as durations from '../../util/durations';
|
import * as durations from '../../util/durations';
|
||||||
import { uuidToBytes } from '../../util/uuidToBytes';
|
import { uuidToBytes } from '../../util/uuidToBytes';
|
||||||
import { MY_STORIES_ID } from '../../types/Stories';
|
import { MY_STORY_ID } from '../../types/Stories';
|
||||||
import { Bootstrap } from '../bootstrap';
|
import { Bootstrap } from '../bootstrap';
|
||||||
import type { App } from '../bootstrap';
|
import type { App } from '../bootstrap';
|
||||||
|
|
||||||
|
@ -44,9 +44,9 @@ describe('story/no-sender-key', function needsName() {
|
||||||
record: {
|
record: {
|
||||||
storyDistributionList: {
|
storyDistributionList: {
|
||||||
allowsReplies: true,
|
allowsReplies: true,
|
||||||
identifier: uuidToBytes(MY_STORIES_ID),
|
identifier: uuidToBytes(MY_STORY_ID),
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
name: MY_STORIES_ID,
|
name: MY_STORY_ID,
|
||||||
recipientUuids: [],
|
recipientUuids: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { StorageState, Proto } from '@signalapp/mock-server';
|
||||||
import { App } from '../playwright';
|
import { App } from '../playwright';
|
||||||
import { Bootstrap } from '../bootstrap';
|
import { Bootstrap } from '../bootstrap';
|
||||||
import type { BootstrapOptions } from '../bootstrap';
|
import type { BootstrapOptions } from '../bootstrap';
|
||||||
import { MY_STORIES_ID } from '../../types/Stories';
|
import { MY_STORY_ID } from '../../types/Stories';
|
||||||
import { uuidToBytes } from '../../util/uuidToBytes';
|
import { uuidToBytes } from '../../util/uuidToBytes';
|
||||||
|
|
||||||
export const debug = createDebug('mock:test:storage');
|
export const debug = createDebug('mock:test:storage');
|
||||||
|
@ -88,9 +88,9 @@ export async function initStorage(
|
||||||
record: {
|
record: {
|
||||||
storyDistributionList: {
|
storyDistributionList: {
|
||||||
allowsReplies: true,
|
allowsReplies: true,
|
||||||
identifier: uuidToBytes(MY_STORIES_ID),
|
identifier: uuidToBytes(MY_STORY_ID),
|
||||||
isBlockList: true,
|
isBlockList: true,
|
||||||
name: MY_STORIES_ID,
|
name: MY_STORY_ID,
|
||||||
recipientUuids: [],
|
recipientUuids: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -105,7 +105,7 @@ export type MyStoryType = {
|
||||||
stories: Array<StoryViewType>;
|
stories: Array<StoryViewType>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MY_STORIES_ID: UUIDStringType =
|
export const MY_STORY_ID: UUIDStringType =
|
||||||
'00000000-0000-0000-0000-000000000000';
|
'00000000-0000-0000-0000-000000000000';
|
||||||
|
|
||||||
export enum StoryViewDirectionType {
|
export enum StoryViewDirectionType {
|
||||||
|
@ -145,7 +145,7 @@ export function getStoryDistributionListName(
|
||||||
id: string,
|
id: string,
|
||||||
name: string
|
name: string
|
||||||
): string {
|
): string {
|
||||||
return id === MY_STORIES_ID ? i18n('Stories__mine') : name;
|
return id === MY_STORY_ID ? i18n('Stories__mine') : name;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum HasStories {
|
export enum HasStories {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import type { UUIDStringType } from '../types/UUID';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import dataInterface from '../sql/Client';
|
import dataInterface from '../sql/Client';
|
||||||
import { DAY, SECOND } from './durations';
|
import { DAY, SECOND } from './durations';
|
||||||
import { MY_STORIES_ID, StorySendMode } from '../types/Stories';
|
import { MY_STORY_ID, StorySendMode } from '../types/Stories';
|
||||||
import { getStoriesBlocked } from './stories';
|
import { getStoriesBlocked } from './stories';
|
||||||
import { ReadStatus } from '../messages/MessageReadStatus';
|
import { ReadStatus } from '../messages/MessageReadStatus';
|
||||||
import { SeenStatus } from '../MessageSeenStatus';
|
import { SeenStatus } from '../MessageSeenStatus';
|
||||||
|
@ -82,10 +82,7 @@ export async function sendStoryMessage(
|
||||||
|
|
||||||
let distributionListMembers: Array<UUIDStringType> = [];
|
let distributionListMembers: Array<UUIDStringType> = [];
|
||||||
|
|
||||||
if (
|
if (distributionList.id === MY_STORY_ID && distributionList.isBlockList) {
|
||||||
distributionList.id === MY_STORIES_ID &&
|
|
||||||
distributionList.isBlockList
|
|
||||||
) {
|
|
||||||
const inBlockList = new Set<UUIDStringType>(distributionList.members);
|
const inBlockList = new Set<UUIDStringType>(distributionList.members);
|
||||||
distributionListMembers = getSignalConnections().reduce(
|
distributionListMembers = getSignalConnections().reduce(
|
||||||
(acc, convo) => {
|
(acc, convo) => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue