diff --git a/package.json b/package.json index b7458f953..4c3fe34d1 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,15 @@ "description": "", "main": "index.js", "scripts": { - "dev": "cross-env APP_ENV=development webpack serve --mode development", + "dev": "cross-env APP_ENV=development APP_VERSION=$(npm run print_version --silent) webpack serve --mode development", "dev:mocked": "cross-env APP_ENV=test APP_MOCKED_CLIENT=1 webpack serve --mode development --port 1235", "build:mocked": "cross-env APP_ENV=test APP_MOCKED_CLIENT=1 webpack --mode development", "build:staging": "APP_ENV=staging APP_VERSION=$(npm run print_version --silent) webpack --mode development && ./deploy/copy_to_dist.sh", "build:production": "npm i && APP_VERSION=$(npm run inc_version --silent) webpack && ./deploy/copy_to_dist.sh", "deploy:production": "npm run build:production && git add -A && git commit -a -m '[Build]' --no-verify && git push", - "print_version": "node -p -e \"require('./package.json').version\"", - "inc_version": "echo $((`cat .patch-version` + 1)) > .patch-version && echo \"$(node -p -e \"require('./package.json').version.match(/^\\d+\\.\\d+/)[0]\").$(cat .patch-version)\"", + "update_version": "npm run print_version --silent > ./public/version.txt", + "print_version": "echo \"$(node -p -e \"require('./package.json').version.match(/^\\d+\\.\\d+/)[0]\").$(cat .patch-version)\"", + "inc_version": "echo $((`cat .patch-version` + 1)) > .patch-version && npm run update_version --silent && npm run print_version", "telegraph:update_changelog": "node ./dev/telegraphChangelog.js", "check": "tsc && stylelint \"**/*.{css,scss}\" && eslint . --ext .ts,.tsx,.js --ignore-pattern src/lib/gramjs", "check:fix": "npm run check -- --fix", diff --git a/public/version.txt b/public/version.txt new file mode 100644 index 000000000..c73f500ec --- /dev/null +++ b/public/version.txt @@ -0,0 +1 @@ +1.51.1 diff --git a/src/components/left/LeftColumn.tsx b/src/components/left/LeftColumn.tsx index 1d865d239..ab6a30b78 100644 --- a/src/components/left/LeftColumn.tsx +++ b/src/components/left/LeftColumn.tsx @@ -32,6 +32,7 @@ type StateProps = { hasPasscode?: boolean; nextSettingsScreen?: SettingsScreens; isChatOpen: boolean; + isUpdateAvailable?: boolean; }; enum ContentType { @@ -58,6 +59,7 @@ const LeftColumn: FC = ({ hasPasscode, nextSettingsScreen, isChatOpen, + isUpdateAvailable, }) => { const { setGlobalSearchQuery, @@ -440,6 +442,7 @@ const LeftColumn: FC = ({ onScreenSelect={handleSettingsScreenSelect} onReset={handleReset} shouldSkipTransition={shouldSkipHistoryAnimations} + isUpdateAvailable={isUpdateAvailable} /> ); } @@ -474,6 +477,7 @@ export default memo(withGlobal( settings: { nextScreen: nextSettingsScreen, }, + isUpdateAvailable, } = global; const isChatOpen = Boolean(selectCurrentChat(global)?.id); @@ -488,6 +492,7 @@ export default memo(withGlobal( hasPasscode, nextSettingsScreen, isChatOpen, + isUpdateAvailable, }; }, )(LeftColumn)); diff --git a/src/components/left/main/LeftMain.scss b/src/components/left/main/LeftMain.scss index 246cad141..b10c78b15 100644 --- a/src/components/left/main/LeftMain.scss +++ b/src/components/left/main/LeftMain.scss @@ -43,6 +43,7 @@ > span { padding-left: 0.5rem; padding-right: 0.5rem; + white-space: pre; } } diff --git a/src/components/left/main/LeftMain.tsx b/src/components/left/main/LeftMain.tsx index eca2ace99..b257535d6 100644 --- a/src/components/left/main/LeftMain.tsx +++ b/src/components/left/main/LeftMain.tsx @@ -9,7 +9,6 @@ import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReduc import { IS_TOUCH_ENV } from '../../../util/environment'; import buildClassName from '../../../util/buildClassName'; -import useFlag from '../../../hooks/useFlag'; import useShowTransition from '../../../hooks/useShowTransition'; import useLang from '../../../hooks/useLang'; @@ -30,6 +29,7 @@ type OwnProps = { contactsFilter: string; shouldSkipTransition?: boolean; foldersDispatch: FolderEditDispatch; + isUpdateAvailable?: boolean; onSearchQuery: (query: string) => void; onContentChange: (content: LeftColumnContent) => void; onScreenSelect: (screen: SettingsScreens) => void; @@ -38,7 +38,6 @@ type OwnProps = { const TRANSITION_RENDER_COUNT = Object.keys(LeftColumnContent).length / 2; const BUTTON_CLOSE_DELAY_MS = 250; -const APP_OUTDATED_TIMEOUT = 3 * 24 * 60 * 60 * 1000; // 3 days let closeTimeout: number | undefined; @@ -49,6 +48,7 @@ const LeftMain: FC = ({ contactsFilter, shouldSkipTransition, foldersDispatch, + isUpdateAvailable, onSearchQuery, onContentChange, onScreenSelect, @@ -56,28 +56,13 @@ const LeftMain: FC = ({ }) => { const [isNewChatButtonShown, setIsNewChatButtonShown] = useState(IS_TOUCH_ENV); + const { + shouldRender: shouldRenderUpdateButton, + transitionClassNames: updateButtonClassNames, + } = useShowTransition(isUpdateAvailable); + const isMouseInside = useRef(false); - const handleSelectSettings = useCallback(() => { - onContentChange(LeftColumnContent.Settings); - }, [onContentChange]); - - const handleSelectContacts = useCallback(() => { - onContentChange(LeftColumnContent.Contacts); - }, [onContentChange]); - - const handleSelectNewChannel = useCallback(() => { - onContentChange(LeftColumnContent.NewChannelStep1); - }, [onContentChange]); - - const handleSelectNewGroup = useCallback(() => { - onContentChange(LeftColumnContent.NewGroupStep1); - }, [onContentChange]); - - const handleSelectArchived = useCallback(() => { - onContentChange(LeftColumnContent.Archived); - }, [onContentChange]); - const handleMouseEnter = useCallback(() => { if (content !== LeftColumnContent.ChatList) { return; @@ -101,6 +86,30 @@ const LeftMain: FC = ({ }, BUTTON_CLOSE_DELAY_MS); }, []); + const handleSelectSettings = useCallback(() => { + onContentChange(LeftColumnContent.Settings); + }, [onContentChange]); + + const handleSelectContacts = useCallback(() => { + onContentChange(LeftColumnContent.Contacts); + }, [onContentChange]); + + const handleSelectArchived = useCallback(() => { + onContentChange(LeftColumnContent.Archived); + }, [onContentChange]); + + const handleUpdateClick = useCallback(() => { + window.location.reload(); + }, []); + + const handleSelectNewChannel = useCallback(() => { + onContentChange(LeftColumnContent.NewChannelStep1); + }, [onContentChange]); + + const handleSelectNewGroup = useCallback(() => { + onContentChange(LeftColumnContent.NewGroupStep1); + }, [onContentChange]); + useEffect(() => { let autoCloseTimeout: number | undefined; if (content !== LeftColumnContent.ChatList) { @@ -119,8 +128,6 @@ const LeftMain: FC = ({ }; }, [content]); - const [shouldRenderUpdateButton, updateButtonClassNames, handleUpdateClick] = useAppOutdatedCheck(); - const lang = useLang(); return ( @@ -186,24 +193,4 @@ const LeftMain: FC = ({ ); }; -function useAppOutdatedCheck() { - const [isAppOutdated, markIsAppOutdated] = useFlag(false); - - useEffect(() => { - const timeout = window.setTimeout(markIsAppOutdated, APP_OUTDATED_TIMEOUT); - - return () => { - clearTimeout(timeout); - }; - }, [markIsAppOutdated]); - - const { shouldRender, transitionClassNames } = useShowTransition(isAppOutdated); - - const handleUpdateClick = () => { - window.location.reload(); - }; - - return [shouldRender, transitionClassNames, handleUpdateClick] as const; -} - export default memo(LeftMain); diff --git a/src/components/left/search/ChatResults.tsx b/src/components/left/search/ChatResults.tsx index 9cc5cc936..9032ec598 100644 --- a/src/components/left/search/ChatResults.tsx +++ b/src/components/left/search/ChatResults.tsx @@ -230,7 +230,7 @@ const ChatResults: FC = ({

{localResults.length > LESS_LIST_ITEMS_AMOUNT && ( - + {lang(shouldShowMoreLocal ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')} )} @@ -254,7 +254,7 @@ const ChatResults: FC = ({

{globalResults.length > LESS_LIST_ITEMS_AMOUNT && ( - + {lang(shouldShowMoreGlobal ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')} )} diff --git a/src/components/left/settings/folders/SettingsFoldersMain.tsx b/src/components/left/settings/folders/SettingsFoldersMain.tsx index 15f01a466..723c694c9 100644 --- a/src/components/left/settings/folders/SettingsFoldersMain.tsx +++ b/src/components/left/settings/folders/SettingsFoldersMain.tsx @@ -16,6 +16,7 @@ import useHistoryBack from '../../../../hooks/useHistoryBack'; import { useFolderManagerForChatsCount } from '../../../../hooks/useFolderManager'; import { selectCurrentLimit } from '../../../../global/selectors/limits'; import { selectIsCurrentUserPremium } from '../../../../global/selectors'; +import renderText from '../../../common/helpers/renderText'; import ListItem from '../../../ui/ListItem'; import Button from '../../../ui/Button'; @@ -277,7 +278,7 @@ const SettingsFoldersMain: FC = ({ }} > - {folder.title} + {renderText(folder.title, ['emoji'])} {isBlocked && } {folder.subtitle} @@ -307,7 +308,7 @@ const SettingsFoldersMain: FC = ({ >
- {folder.title} + {renderText(folder.title, ['emoji'])} {folder.description}
diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index a700418ec..5c20fddc1 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -39,6 +39,7 @@ import useForceUpdate from '../../hooks/useForceUpdate'; import { LOCATION_HASH } from '../../hooks/useHistoryBack'; import useShowTransition from '../../hooks/useShowTransition'; import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'; +import useInterval from '../../hooks/useInterval'; import StickerSetModal from '../common/StickerSetModal.async'; import UnreadCount from '../common/UnreadCounter'; @@ -118,6 +119,7 @@ type StateProps = { }; const NOTIFICATION_INTERVAL = 1000; +const APP_OUTDATED_TIMEOUT_MS = 5 * 60 * 1000; // 5 min let notificationInterval: number | undefined; @@ -189,6 +191,7 @@ const Main: FC = ({ loadCustomEmojis, closePaymentModal, clearReceipt, + checkAppVersion, } = getActions(); if (DEBUG && !DEBUG_isLogged) { @@ -203,6 +206,8 @@ const Main: FC = ({ } }, [connectionState, authState, sync]); + useInterval(checkAppVersion, APP_OUTDATED_TIMEOUT_MS, true); + // Initial API calls useEffect(() => { if (lastSyncTime) { @@ -217,11 +222,12 @@ const Main: FC = ({ loadAttachMenuBots(); loadContactList(); loadPremiumGifts(); + checkAppVersion(); } }, [ lastSyncTime, loadAnimatedEmojis, loadEmojiKeywords, loadNotificationExceptions, loadNotificationSettings, loadTopInlineBots, updateIsOnline, loadAvailableReactions, loadAppConfig, loadAttachMenuBots, loadContactList, - loadPremiumGifts, + loadPremiumGifts, checkAppVersion, ]); // Language-based API calls diff --git a/src/config.ts b/src/config.ts index 9997a530f..f1375c7ea 100644 --- a/src/config.ts +++ b/src/config.ts @@ -14,6 +14,7 @@ export const IS_PERF = process.env.APP_ENV === 'perf'; export const IS_BETA = process.env.APP_ENV === 'staging'; export const BETA_CHANGELOG_URL = 'https://telegra.ph/WebZ-Beta-04-01'; +export const APP_VERSION_URL_PATH = '/version.txt'; export const DEBUG_ALERT_MSG = 'Shoot!\nSomething went wrong, please see the error details in Dev Tools Console.'; export const DEBUG_GRAMJS = false; diff --git a/src/global/actions/ui/misc.ts b/src/global/actions/ui/misc.ts index 8c50653c5..51cc3e570 100644 --- a/src/global/actions/ui/misc.ts +++ b/src/global/actions/ui/misc.ts @@ -1,13 +1,11 @@ -import { addActionHandler, setGlobal } from '../../index'; +import { addActionHandler, getGlobal, setGlobal } from '../../index'; import type { ApiError } from '../../../api/types'; -import { GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT } from '../../../config'; +import { APP_VERSION, APP_VERSION_URL_PATH, GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT } from '../../../config'; import { IS_SINGLE_COLUMN_LAYOUT, IS_TABLET_COLUMN_LAYOUT } from '../../../util/environment'; import getReadableErrorText from '../../../util/getReadableErrorText'; -import { - selectChatMessage, selectCurrentMessageList, selectIsTrustedBot, -} from '../../selectors'; +import { selectChatMessage, selectCurrentMessageList, selectIsTrustedBot } from '../../selectors'; import generateIdFor from '../../../util/generateIdFor'; import { unique } from '../../../util/iteratees'; @@ -405,3 +403,21 @@ addActionHandler('updateLastRenderedCustomEmojis', (global, actions, payload) => }, }; }); + +addActionHandler('checkAppVersion', () => { + const APP_VERSION_REGEX = /^\d+\.\d+(\.\d+)?$/; + + fetch(APP_VERSION_URL_PATH) + .then((response) => { + return response.text(); + }).then((version) => { + version = version.trim(); + + if (APP_VERSION_REGEX.test(version) && version !== APP_VERSION) { + setGlobal({ + ...getGlobal(), + isUpdateAvailable: true, + }); + } + }); +}); diff --git a/src/global/initialState.ts b/src/global/initialState.ts index c50c92ef1..409b55cfb 100644 --- a/src/global/initialState.ts +++ b/src/global/initialState.ts @@ -15,6 +15,7 @@ export const INITIAL_STATE: GlobalState = { newChatMembersProgress: NewChatMembersProgress.Closed, uiReadyState: 0, serverTimeOffset: 0, + isUpdateAvailable: false, authRememberMe: true, countryList: { diff --git a/src/global/types.ts b/src/global/types.ts index cf5f7538b..a29396f49 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -147,6 +147,7 @@ export type GlobalState = { connectionState?: ApiUpdateConnectionStateType; currentUserId?: string; isSyncing?: boolean; + isUpdateAvailable?: boolean; lastSyncTime?: number; serverTimeOffset: number; leftColumnWidth?: number; @@ -690,6 +691,7 @@ export interface ActionPayloads { setInstallPrompt: { canInstall: boolean }; openLimitReachedModal: { limit: ApiLimitTypeWithModal }; closeLimitReachedModal: never; + checkAppVersion: never; // Accounts reportPeer: {