diff --git a/app/main.ts b/app/main.ts
index cb7037d14..ec4ffd2c8 100644
--- a/app/main.ts
+++ b/app/main.ts
@@ -865,6 +865,11 @@ function openJoinTheBeta() {
}
function openReleaseNotes() {
+ if (mainWindow && mainWindow.isVisible()) {
+ mainWindow.webContents.send('show-release-notes');
+ return;
+ }
+
shell.openExternal(
`https://github.com/signalapp/Signal-Desktop/releases/tag/v${app.getVersion()}`
);
diff --git a/js/modules/signal.js b/js/modules/signal.js
index 4c34b5be9..b1d42bd83 100644
--- a/js/modules/signal.js
+++ b/js/modules/signal.js
@@ -56,7 +56,7 @@ const {
const {
SystemTraySettingsCheckboxes,
} = require('../../ts/components/conversation/SystemTraySettingsCheckboxes');
-const { WhatsNew } = require('../../ts/components/WhatsNew');
+const { WhatsNewLink } = require('../../ts/components/WhatsNewLink');
// State
const {
@@ -338,7 +338,7 @@ exports.setup = (options = {}) => {
StagedLinkPreview,
DisappearingTimeDialog,
SystemTraySettingsCheckboxes,
- WhatsNew,
+ WhatsNewLink,
};
const Roots = {
diff --git a/preload.js b/preload.js
index bf985456c..7313e18cb 100644
--- a/preload.js
+++ b/preload.js
@@ -340,6 +340,13 @@ try {
}
});
+ ipc.on('show-release-notes', () => {
+ const { showReleaseNotes } = window.Events;
+ if (showReleaseNotes) {
+ showReleaseNotes();
+ }
+ });
+
window.addSetupMenuItems = () => ipc.send('add-setup-menu-items');
window.removeSetupMenuItems = () => ipc.send('remove-setup-menu-items');
diff --git a/ts/components/GlobalModalContainer.tsx b/ts/components/GlobalModalContainer.tsx
index 6047c160d..d35cb8042 100644
--- a/ts/components/GlobalModalContainer.tsx
+++ b/ts/components/GlobalModalContainer.tsx
@@ -1,9 +1,14 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
+import React from 'react';
import { ContactModalStateType } from '../state/ducks/globalModals';
+import { LocalizerType } from '../types/Util';
+
+import { WhatsNewModal } from './WhatsNewModal';
type PropsType = {
+ i18n: LocalizerType;
// ContactModal
contactModalState?: ContactModalStateType;
renderContactModal: () => JSX.Element;
@@ -13,9 +18,13 @@ type PropsType = {
// SafetyNumberModal
safetyNumberModalContactId?: string;
renderSafetyNumber: () => JSX.Element;
+ // WhatsNewModal
+ isWhatsNewVisible: boolean;
+ hideWhatsNewModal: () => unknown;
};
export const GlobalModalContainer = ({
+ i18n,
// ContactModal
contactModalState,
renderContactModal,
@@ -25,6 +34,9 @@ export const GlobalModalContainer = ({
// SafetyNumberModal
safetyNumberModalContactId,
renderSafetyNumber,
+ // WhatsNewModal
+ hideWhatsNewModal,
+ isWhatsNewVisible,
}: PropsType): JSX.Element | null => {
if (safetyNumberModalContactId) {
return renderSafetyNumber();
@@ -38,5 +50,9 @@ export const GlobalModalContainer = ({
return renderProfileEditor();
}
+ if (isWhatsNewVisible) {
+ return ;
+ }
+
return null;
};
diff --git a/ts/components/WhatsNew.tsx b/ts/components/WhatsNew.tsx
deleted file mode 100644
index c06c0f670..000000000
--- a/ts/components/WhatsNew.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2021 Signal Messenger, LLC
-// SPDX-License-Identifier: AGPL-3.0-only
-
-import React, { ReactChild, ReactNode, useState } from 'react';
-import moment from 'moment';
-
-import { Modal } from './Modal';
-import { Intl, IntlComponentsType } from './Intl';
-import { Emojify } from './conversation/Emojify';
-import type { LocalizerType, RenderTextCallbackType } from '../types/Util';
-
-export type PropsType = {
- i18n: LocalizerType;
-};
-
-type ReleaseNotesType = {
- date: Date;
- version: string;
- features: Array<{ key: string; components: IntlComponentsType }>;
-};
-
-const renderText: RenderTextCallbackType = ({ key, text }) => (
-
-);
-
-export const WhatsNew = ({ i18n }: PropsType): JSX.Element => {
- const [releaseNotes, setReleaseNotes] = useState<
- ReleaseNotesType | undefined
- >();
-
- const viewReleaseNotes = () => {
- setReleaseNotes({
- date: new Date(window.getBuildCreation?.() || Date.now()),
- version: window.getVersion(),
- features: [
- {
- key: 'WhatsNew__v5.22',
- components: undefined,
- },
- ],
- });
- };
-
- let modalNode: ReactNode;
- if (releaseNotes) {
- let contentNode: ReactChild;
- if (releaseNotes.features.length === 1) {
- const { key, components } = releaseNotes.features[0];
- contentNode = (
-
-
-
- );
- } else {
- contentNode = (
-
- {releaseNotes.features.map(({ key, components }) => (
- -
-
-
- ))}
-
- );
- }
-
- modalNode = (
- setReleaseNotes(undefined)}
- title={i18n('WhatsNew__modal-title')}
- >
- <>
-
- {moment(releaseNotes.date).format('LL')} ·{' '}
- {releaseNotes.version}
-
- {contentNode}
- >
-
- );
- }
-
- return (
- <>
- {modalNode}
-
- {i18n('viewReleaseNotes')}
- ,
- ]}
- />
- >
- );
-};
diff --git a/ts/components/WhatsNewLink.tsx b/ts/components/WhatsNewLink.tsx
new file mode 100644
index 000000000..15eb0119e
--- /dev/null
+++ b/ts/components/WhatsNewLink.tsx
@@ -0,0 +1,29 @@
+// Copyright 2021 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import React from 'react';
+
+import { Intl } from './Intl';
+
+import { LocalizerType } from '../types/Util';
+
+export type PropsType = {
+ i18n: LocalizerType;
+ showWhatsNewModal: () => unknown;
+};
+
+export const WhatsNewLink = (props: PropsType): JSX.Element => {
+ const { i18n, showWhatsNewModal } = props;
+
+ return (
+
+ {i18n('viewReleaseNotes')}
+ ,
+ ]}
+ />
+ );
+};
diff --git a/ts/components/WhatsNewModal.tsx b/ts/components/WhatsNewModal.tsx
new file mode 100644
index 000000000..2cfab8c5f
--- /dev/null
+++ b/ts/components/WhatsNewModal.tsx
@@ -0,0 +1,89 @@
+// Copyright 2021 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import React, { ReactChild } from 'react';
+import moment from 'moment';
+
+import { Modal } from './Modal';
+import { Intl, IntlComponentsType } from './Intl';
+import { Emojify } from './conversation/Emojify';
+import type { LocalizerType, RenderTextCallbackType } from '../types/Util';
+
+export type PropsType = {
+ hideWhatsNewModal: () => unknown;
+ i18n: LocalizerType;
+};
+
+type ReleaseNotesType = {
+ date: Date;
+ version: string;
+ features: Array<{ key: string; components: IntlComponentsType }>;
+};
+
+const renderText: RenderTextCallbackType = ({ key, text }) => (
+
+);
+
+const releaseNotes: ReleaseNotesType = {
+ date: new Date(window.getBuildCreation?.() || Date.now()),
+ version: window.getVersion(),
+ features: [
+ {
+ key: 'WhatsNew__v5.22',
+ components: undefined,
+ },
+ ],
+};
+
+export const WhatsNewModal = ({
+ i18n,
+ hideWhatsNewModal,
+}: PropsType): JSX.Element => {
+ let contentNode: ReactChild;
+
+ if (releaseNotes.features.length === 1) {
+ const { key, components } = releaseNotes.features[0];
+ contentNode = (
+
+
+
+ );
+ } else {
+ contentNode = (
+
+ {releaseNotes.features.map(({ key, components }) => (
+ -
+
+
+ ))}
+
+ );
+ }
+
+ return (
+
+ <>
+
+ {moment(releaseNotes.date).format('LL')} ·{' '}
+ {releaseNotes.version}
+
+ {contentNode}
+ >
+
+ );
+};
diff --git a/ts/state/ducks/globalModals.ts b/ts/state/ducks/globalModals.ts
index 367bfc773..5e943ab7d 100644
--- a/ts/state/ducks/globalModals.ts
+++ b/ts/state/ducks/globalModals.ts
@@ -8,12 +8,15 @@ export type GlobalModalsStateType = {
readonly isProfileEditorVisible: boolean;
readonly profileEditorHasError: boolean;
readonly safetyNumberModalContactId?: string;
+ readonly isWhatsNewVisible: boolean;
};
// Actions
const HIDE_CONTACT_MODAL = 'globalModals/HIDE_CONTACT_MODAL';
const SHOW_CONTACT_MODAL = 'globalModals/SHOW_CONTACT_MODAL';
+const SHOW_WHATS_NEW_MODAL = 'globalModals/SHOW_WHATS_NEW_MODAL_MODAL';
+const HIDE_WHATS_NEW_MODAL = 'globalModals/HIDE_WHATS_NEW_MODAL_MODAL';
const TOGGLE_PROFILE_EDITOR = 'globalModals/TOGGLE_PROFILE_EDITOR';
export const TOGGLE_PROFILE_EDITOR_ERROR =
'globalModals/TOGGLE_PROFILE_EDITOR_ERROR';
@@ -33,6 +36,14 @@ type ShowContactModalActionType = {
payload: ContactModalStateType;
};
+type HideWhatsNewModalActionType = {
+ type: typeof HIDE_WHATS_NEW_MODAL;
+};
+
+type ShowWhatsNewModalActionType = {
+ type: typeof SHOW_WHATS_NEW_MODAL;
+};
+
type ToggleProfileEditorActionType = {
type: typeof TOGGLE_PROFILE_EDITOR;
};
@@ -49,6 +60,8 @@ type ToggleSafetyNumberModalActionType = {
export type GlobalModalsActionType =
| HideContactModalActionType
| ShowContactModalActionType
+ | HideWhatsNewModalActionType
+ | ShowWhatsNewModalActionType
| ToggleProfileEditorActionType
| ToggleProfileEditorErrorActionType
| ToggleSafetyNumberModalActionType;
@@ -58,6 +71,8 @@ export type GlobalModalsActionType =
export const actions = {
hideContactModal,
showContactModal,
+ hideWhatsNewModal,
+ showWhatsNewModal,
toggleProfileEditor,
toggleProfileEditorHasError,
toggleSafetyNumberModal,
@@ -82,6 +97,18 @@ function showContactModal(
};
}
+function hideWhatsNewModal(): HideWhatsNewModalActionType {
+ return {
+ type: HIDE_WHATS_NEW_MODAL,
+ };
+}
+
+function showWhatsNewModal(): ShowWhatsNewModalActionType {
+ return {
+ type: SHOW_WHATS_NEW_MODAL,
+ };
+}
+
function toggleProfileEditor(): ToggleProfileEditorActionType {
return { type: TOGGLE_PROFILE_EDITOR };
}
@@ -105,6 +132,7 @@ export function getEmptyState(): GlobalModalsStateType {
return {
isProfileEditorVisible: false,
profileEditorHasError: false,
+ isWhatsNewVisible: false,
};
}
@@ -126,6 +154,20 @@ export function reducer(
};
}
+ if (action.type === SHOW_WHATS_NEW_MODAL) {
+ return {
+ ...state,
+ isWhatsNewVisible: true,
+ };
+ }
+
+ if (action.type === HIDE_WHATS_NEW_MODAL) {
+ return {
+ ...state,
+ isWhatsNewVisible: false,
+ };
+ }
+
if (action.type === SHOW_CONTACT_MODAL) {
return {
...state,
diff --git a/ts/state/smart/GlobalModalContainer.tsx b/ts/state/smart/GlobalModalContainer.tsx
index 03d5277ed..018cebdce 100644
--- a/ts/state/smart/GlobalModalContainer.tsx
+++ b/ts/state/smart/GlobalModalContainer.tsx
@@ -10,6 +10,8 @@ import { SmartProfileEditorModal } from './ProfileEditorModal';
import { SmartContactModal } from './ContactModal';
import { SmartSafetyNumberModal } from './SafetyNumberModal';
+import { getIntl } from '../selectors/user';
+
const FilteredSmartProfileEditorModal = SmartProfileEditorModal;
function renderProfileEditor(): JSX.Element {
@@ -21,8 +23,11 @@ function renderContactModal(): JSX.Element {
}
const mapStateToProps = (state: StateType) => {
+ const i18n = getIntl(state);
+
return {
...state.globalModals,
+ i18n,
renderContactModal,
renderProfileEditor,
renderSafetyNumber: () => (
diff --git a/ts/test-both/state/ducks/globalModals_test.ts b/ts/test-both/state/ducks/globalModals_test.ts
index ad5f132ba..4da6eb68f 100644
--- a/ts/test-both/state/ducks/globalModals_test.ts
+++ b/ts/test-both/state/ducks/globalModals_test.ts
@@ -24,4 +24,19 @@ describe('both/state/ducks/globalModals', () => {
assert.isFalse(nextNextState.isProfileEditorVisible);
});
});
+
+ describe('showWhatsNewModal/hideWhatsNewModal', () => {
+ const { showWhatsNewModal, hideWhatsNewModal } = actions;
+
+ it('toggles isWhatsNewVisible to true', () => {
+ const state = getEmptyState();
+ const nextState = reducer(state, showWhatsNewModal());
+
+ assert.isTrue(nextState.isWhatsNewVisible);
+
+ const nextNextState = reducer(nextState, hideWhatsNewModal());
+
+ assert.isFalse(nextNextState.isWhatsNewVisible);
+ });
+ });
});
diff --git a/ts/util/createIPCEvents.ts b/ts/util/createIPCEvents.ts
index 643a9018a..43780ef24 100644
--- a/ts/util/createIPCEvents.ts
+++ b/ts/util/createIPCEvents.ts
@@ -99,6 +99,7 @@ export type IPCEventsCallbacksType = {
showConversationViaSignalDotMe: (hash: string) => void;
showKeyboardShortcuts: () => void;
showGroupViaLink: (x: string) => Promise;
+ showReleaseNotes: () => void;
showStickerPack: (packId: string, key: string) => void;
shutdown: () => Promise;
unknownSignalLink: () => void;
@@ -505,6 +506,10 @@ export function createIPCEvents(
},
shutdown: () => Promise.resolve(),
+ showReleaseNotes: () => {
+ const { showWhatsNewModal } = window.reduxActions.globalModals;
+ showWhatsNewModal();
+ },
getMediaPermissions: window.getMediaPermissions,
getMediaCameraPermissions: window.getMediaCameraPermissions,
diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json
index b29113200..8debd7457 100644
--- a/ts/util/lint/exceptions.json
+++ b/ts/util/lint/exceptions.json
@@ -12784,14 +12784,6 @@
"updated": "2020-08-28T16:12:19.904Z",
"reasonDetail": "Used to reference popup menu"
},
- {
- "rule": "React-createRef",
- "path": "ts/components/conversation/ConversationHeader.tsx",
- "line": " this.menuTriggerRef = React.createRef();",
- "reasonCategory": "usageTrusted",
- "updated": "2020-05-20T20:10:43.540Z",
- "reasonDetail": "Used to reference popup menu"
- },
{
"rule": "React-createRef",
"path": "ts/components/conversation/ConversationHeader.js",
@@ -12800,6 +12792,14 @@
"updated": "2021-01-18T22:24:05.937Z",
"reasonDetail": "Used to reference popup menu boundaries element"
},
+ {
+ "rule": "React-createRef",
+ "path": "ts/components/conversation/ConversationHeader.tsx",
+ "line": " this.menuTriggerRef = React.createRef();",
+ "reasonCategory": "usageTrusted",
+ "updated": "2020-05-20T20:10:43.540Z",
+ "reasonDetail": "Used to reference popup menu"
+ },
{
"rule": "React-createRef",
"path": "ts/components/conversation/ConversationHeader.tsx",
@@ -13329,13 +13329,6 @@
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
- {
- "rule": "jQuery-$(",
- "path": "ts/views/inbox_view.js",
- "line": " this.$('.whats-new-placeholder').append(this.whatsNewView.el);",
- "reasonCategory": "usageTrusted",
- "updated": "2021-09-15T21:07:50.995Z"
- },
{
"rule": "jQuery-$(",
"path": "ts/views/inbox_view.js",
@@ -13350,12 +13343,19 @@
"reasonCategory": "usageTrusted",
"updated": "2021-10-08T17:40:22.770Z"
},
+ {
+ "rule": "jQuery-$(",
+ "path": "ts/views/inbox_view.js",
+ "line": " this.$('.whats-new-placeholder').append(this.whatsNewLink.el);",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-10-22T20:58:48.103Z"
+ },
{
"rule": "jQuery-append(",
"path": "ts/views/inbox_view.js",
- "line": " this.$('.whats-new-placeholder').append(this.whatsNewView.el);",
+ "line": " this.$('.whats-new-placeholder').append(this.whatsNewLink.el);",
"reasonCategory": "usageTrusted",
- "updated": "2021-09-15T21:07:50.995Z"
+ "updated": "2021-10-22T20:58:48.103Z"
},
{
"rule": "jQuery-appendTo(",
@@ -13413,13 +13413,6 @@
"reasonCategory": "usageTrusted",
"updated": "2021-09-15T21:07:50.995Z"
},
- {
- "rule": "jQuery-$(",
- "path": "ts/views/inbox_view.ts",
- "line": " this.$('.whats-new-placeholder').append(this.whatsNewView.el);",
- "reasonCategory": "usageTrusted",
- "updated": "2021-09-15T21:07:50.995Z"
- },
{
"rule": "jQuery-$(",
"path": "ts/views/inbox_view.ts",
@@ -13434,12 +13427,19 @@
"reasonCategory": "usageTrusted",
"updated": "2021-10-08T17:40:22.770Z"
},
+ {
+ "rule": "jQuery-$(",
+ "path": "ts/views/inbox_view.ts",
+ "line": " this.$('.whats-new-placeholder').append(this.whatsNewLink.el);",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-10-22T20:58:48.103Z"
+ },
{
"rule": "jQuery-append(",
"path": "ts/views/inbox_view.ts",
- "line": " this.$('.whats-new-placeholder').append(this.whatsNewView.el);",
+ "line": " this.$('.whats-new-placeholder').append(this.whatsNewLink.el);",
"reasonCategory": "usageTrusted",
- "updated": "2021-09-15T21:07:50.995Z"
+ "updated": "2021-10-22T20:58:48.103Z"
},
{
"rule": "jQuery-appendTo(",
diff --git a/ts/views/inbox_view.ts b/ts/views/inbox_view.ts
index d0e3b7f87..a44f2d2b2 100644
--- a/ts/views/inbox_view.ts
+++ b/ts/views/inbox_view.ts
@@ -160,16 +160,18 @@ Whisper.InboxView = Whisper.View.extend({
click: 'onClick',
},
renderWhatsNew() {
- if (this.whatsNewView) {
+ if (this.whatsNewLink) {
return;
}
- this.whatsNewView = new Whisper.ReactWrapperView({
- Component: window.Signal.Components.WhatsNew,
+ const { showWhatsNewModal } = window.reduxActions.globalModals;
+ this.whatsNewLink = new Whisper.ReactWrapperView({
+ Component: window.Signal.Components.WhatsNewLink,
props: {
i18n: window.i18n,
+ showWhatsNewModal,
},
});
- this.$('.whats-new-placeholder').append(this.whatsNewView.el);
+ this.$('.whats-new-placeholder').append(this.whatsNewLink.el);
},
setupLeftPane() {
if (this.leftPaneView) {
diff --git a/ts/window.d.ts b/ts/window.d.ts
index dabc427d2..59143440e 100644
--- a/ts/window.d.ts
+++ b/ts/window.d.ts
@@ -92,7 +92,7 @@ import { ProgressModal } from './components/ProgressModal';
import { Quote } from './components/conversation/Quote';
import { StagedLinkPreview } from './components/conversation/StagedLinkPreview';
import { DisappearingTimeDialog } from './components/DisappearingTimeDialog';
-import { WhatsNew } from './components/WhatsNew';
+import { WhatsNewLink } from './components/WhatsNewLink';
import { MIMEType } from './types/MIME';
import { DownloadedAttachmentType } from './types/Attachment';
import { ElectronLocaleType } from './util/mapToSupportLocale';
@@ -403,7 +403,7 @@ declare global {
ProgressModal: typeof ProgressModal;
Quote: typeof Quote;
StagedLinkPreview: typeof StagedLinkPreview;
- WhatsNew: typeof WhatsNew;
+ WhatsNewLink: typeof WhatsNewLink;
};
OS: typeof OS;
Workflow: {