diff --git a/.eslintrc.js b/.eslintrc.js index fe589052..3d1cd41c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -15,7 +15,6 @@ module.exports = { }, globals: { BASE_PATH: 'readonly', - __SHELL_ENV__: 'readonly', __CARBONIO_DEV__: 'readonly', PACKAGE_NAME: 'readonly', PACKAGE_VERSION: 'readonly', diff --git a/.nvmrc b/.nvmrc index 5edcff03..b3dc644b 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v16 \ No newline at end of file +v16.13 \ No newline at end of file diff --git a/babel.config.js b/babel.config.js index 3516a294..9a60e717 100644 --- a/babel.config.js +++ b/babel.config.js @@ -25,7 +25,8 @@ module.exports = { { outputPath: 'translations/{{ns}}.json', defaultNS: 'en', - jsonSpace: 4 + jsonSpace: 4, + compatibilityJSON: 'v3' } ] ] diff --git a/carbonio.webpack.js b/carbonio.webpack.js index 6097df8d..313d4b96 100644 --- a/carbonio.webpack.js +++ b/carbonio.webpack.js @@ -41,8 +41,7 @@ module.exports = (conf, pkg, options, mode) => { inject: true, template: path.resolve(process.cwd(), 'src', 'index.template.html'), chunks: ['index'], - BASE_PATH: baseStaticPath, - SHELL_ENV: root + BASE_PATH: baseStaticPath }), new HtmlWebpackPlugin({ inject: false, diff --git a/package-lock.json b/package-lock.json index 298bb98e..cc7e366f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,10 @@ "hasInstallScript": true, "license": "AGPL-3.0-only", "dependencies": { + "@fontsource/roboto": "^4.5.7", "@sentry/browser": "^6.17.7", "@tinymce/tinymce-react": "^3.13.0", - "@zextras/carbonio-design-system": "^0.3.0", + "@zextras/carbonio-design-system": "^0.3.5", "@zextras/carbonio-ui-preview": "^0.1.5", "darkreader": "4.9.46", "history": "^5.2.0", @@ -62,7 +63,7 @@ "@types/ua-parser-js": "^0.7.36", "@types/webpack-env": "1.16.3", "@zextras/carbonio-ui-configs": "^0.1.11", - "@zextras/carbonio-ui-sdk": "github:Zextras/carbonio-ui-sdk#v1.2.4", + "@zextras/carbonio-ui-sdk": "1.3.1", "autoprefixer": "10.4.2", "babel-jest": "27.3.1", "babel-loader": "8.2.3", @@ -88,7 +89,6 @@ "rollup": "2.66.1", "semver": "7.3.5", "styled-components": "5.3.3", - "typeface-roboto": "1.1.13", "webpack": "5.67.0", "webpack-cli": "4.9.2", "webpack-dev-server": "4.7.3", @@ -2308,6 +2308,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@fontsource/roboto": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.7.tgz", + "integrity": "sha512-m57UMER23Mk6Drg9OjtHW1Y+0KPGyZfE5XJoPTOsLARLar6013kJj4X2HICt+iFLJqIgTahA/QAvSn9lwF1EEw==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.5.0", "dev": true, @@ -3905,8 +3910,9 @@ "license": "Apache-2.0" }, "node_modules/@zextras/carbonio-design-system": { - "version": "0.3.4", - "license": "AGPL-3.0-only", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@zextras/carbonio-design-system/-/carbonio-design-system-0.3.5.tgz", + "integrity": "sha512-XLeUla+iV1yTqkfrEZwibl+9cjNtL55vCGdcHaHsk6QujZjizlwqd3oX9Iy0PHkxx2ltcXkBRx5bQOyXbJK/vA==", "dependencies": { "@popperjs/core": "2.11.0", "darkreader": "4.9.44", @@ -3989,10 +3995,10 @@ } }, "node_modules/@zextras/carbonio-ui-sdk": { - "version": "1.2.4", - "resolved": "git+ssh://git@github.com/Zextras/carbonio-ui-sdk.git#7b96be5805a7e60b8975056f86dbf6689821a50a", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@zextras/carbonio-ui-sdk/-/carbonio-ui-sdk-1.3.1.tgz", + "integrity": "sha512-OcR3Al3qyvPQbiTA7xDh/AJd2/qmvEPFJYR1XgLmxSxD69z2FPAzq27nEOwsCcxXekYQ8wJaOIvSLm1HxXn6Rg==", "dev": true, - "license": "AGPL-3.0-only", "dependencies": { "@babel/runtime": "^7.16.7", "arg": "^5.0.1", @@ -14066,11 +14072,6 @@ "is-typedarray": "^1.0.0" } }, - "node_modules/typeface-roboto": { - "version": "1.1.13", - "dev": true, - "license": "MIT" - }, "node_modules/typescript": { "version": "4.5.5", "license": "Apache-2.0", @@ -16588,6 +16589,11 @@ } } }, + "@fontsource/roboto": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.7.tgz", + "integrity": "sha512-m57UMER23Mk6Drg9OjtHW1Y+0KPGyZfE5XJoPTOsLARLar6013kJj4X2HICt+iFLJqIgTahA/QAvSn9lwF1EEw==" + }, "@humanwhocodes/config-array": { "version": "0.5.0", "dev": true, @@ -17658,7 +17664,9 @@ "version": "4.2.2" }, "@zextras/carbonio-design-system": { - "version": "0.3.4", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@zextras/carbonio-design-system/-/carbonio-design-system-0.3.5.tgz", + "integrity": "sha512-XLeUla+iV1yTqkfrEZwibl+9cjNtL55vCGdcHaHsk6QujZjizlwqd3oX9Iy0PHkxx2ltcXkBRx5bQOyXbJK/vA==", "requires": { "@popperjs/core": "2.11.0", "darkreader": "4.9.44", @@ -17717,9 +17725,10 @@ } }, "@zextras/carbonio-ui-sdk": { - "version": "git+ssh://git@github.com/Zextras/carbonio-ui-sdk.git#7b96be5805a7e60b8975056f86dbf6689821a50a", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@zextras/carbonio-ui-sdk/-/carbonio-ui-sdk-1.3.1.tgz", + "integrity": "sha512-OcR3Al3qyvPQbiTA7xDh/AJd2/qmvEPFJYR1XgLmxSxD69z2FPAzq27nEOwsCcxXekYQ8wJaOIvSLm1HxXn6Rg==", "dev": true, - "from": "@zextras/carbonio-ui-sdk@github:Zextras/carbonio-ui-sdk#v1.2.4", "requires": { "@babel/runtime": "^7.16.7", "arg": "^5.0.1", @@ -23935,10 +23944,6 @@ "is-typedarray": "^1.0.0" } }, - "typeface-roboto": { - "version": "1.1.13", - "dev": true - }, "typescript": { "version": "4.5.5" }, diff --git a/package.json b/package.json index 9c437b8b..6e343a4d 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@types/ua-parser-js": "^0.7.36", "@types/webpack-env": "1.16.3", "@zextras/carbonio-ui-configs": "^0.1.11", - "@zextras/carbonio-ui-sdk": "github:Zextras/carbonio-ui-sdk#v1.2.4", + "@zextras/carbonio-ui-sdk": "1.3.1", "autoprefixer": "10.4.2", "babel-jest": "27.3.1", "babel-loader": "8.2.3", @@ -90,16 +90,16 @@ "rollup": "2.66.1", "semver": "7.3.5", "styled-components": "5.3.3", - "typeface-roboto": "1.1.13", "webpack": "5.67.0", "webpack-cli": "4.9.2", "webpack-dev-server": "4.7.3", "webpack-merge": "5.8.0" }, "dependencies": { + "@fontsource/roboto": "^4.5.7", "@sentry/browser": "^6.17.7", "@tinymce/tinymce-react": "^3.13.0", - "@zextras/carbonio-design-system": "^0.3.0", + "@zextras/carbonio-design-system": "^0.3.5", "@zextras/carbonio-ui-preview": "^0.1.5", "darkreader": "4.9.46", "history": "^5.2.0", diff --git a/src/boot/app/load-app.ts b/src/boot/app/load-app.ts index f301044d..7acbb7bf 100644 --- a/src/boot/app/load-app.ts +++ b/src/boot/app/load-app.ts @@ -44,7 +44,7 @@ function loadAppModule(appPkg: CarbonioModule, store: Store): Promise void = (e) => { + const reject: (e: unknown) => void = (e) => { if (!resolved) { resolved = true; _reject(e); @@ -98,10 +98,8 @@ function loadAppModule(appPkg: CarbonioModule, store: Store): Promise injectSharedLibraries(); const appsToLoad = filter(apps, (app) => { if (app.name === SHELL_APP_ID) return false; - if (app.attrKey && getUserSetting('attrs', app.attrKey) !== 'TRUE') return false; + if (app.attrKey && getUserSetting('attrs', app.attrKey) === 'FALSE') return false; return true; }); console.log( diff --git a/src/boot/bootstrapper-router.tsx b/src/boot/bootstrapper-router.tsx index 49700b27..07dade60 100644 --- a/src/boot/bootstrapper-router.tsx +++ b/src/boot/bootstrapper-router.tsx @@ -4,15 +4,16 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import React, { FC, useContext } from 'react'; -import { BrowserRouter, useHistory } from 'react-router-dom'; +import React, { FC, useContext, useEffect } from 'react'; +import { BrowserRouter, Route, Switch, useHistory, useParams } from 'react-router-dom'; import { SnackbarManagerContext, ModalManagerContext } from '@zextras/carbonio-design-system'; import AppLoaderMounter from './app/app-loader-mounter'; import { useBridge } from '../store/context-bridge'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import ShellView from '../shell/shell-view'; -import { BASENAME } from '../constants'; +import { BASENAME, IS_STANDALONE } from '../constants'; +import { useAppStore } from '../store/app'; const ContextBridge: FC = () => { const history = useHistory(); @@ -30,12 +31,26 @@ const ContextBridge: FC = () => { return null; }; +const StandaloneListener: FC = () => { + const { route } = useParams<{ route?: string }>(); + useEffect(() => { + if (route) useAppStore.setState({ standalone: route }); + }, [route]); + return null; +}; + const BootstrapperRouter: FC = () => ( + {IS_STANDALONE && ( + + + + + + )} ); - export default BootstrapperRouter; diff --git a/src/boot/init.ts b/src/boot/init.ts index 92d49383..0588661b 100644 --- a/src/boot/init.ts +++ b/src/boot/init.ts @@ -13,7 +13,7 @@ import StoreFactory from '../redux/store-factory'; import { getInfo } from '../network/get-info'; export const init = (_i18nFactory: I18nFactory, _storeFactory: StoreFactory): void => { - getInfo().then(() => { + getInfo().finally(() => { _i18nFactory.setLocale( ( (useAccountStore.getState().settings?.prefs?.zimbraPrefLocale as string) ?? diff --git a/src/constants/index.ts b/src/constants/index.ts index 752cbead..6919327a 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -75,9 +75,16 @@ export const darkReaderDynamicThemeFixes: DynamicThemeFix = { disableStyleSheetsProxy: false }; -export const BASENAME = `/carbonio/`; +const base = '/carbonio/'; + +const standaloneBase = `${base}standalone`; + +export const IS_STANDALONE = window.location.pathname.startsWith(standaloneBase); +export const BASENAME = IS_STANDALONE ? standaloneBase : base; export const EMAIL_VALIDATION_REGEX = // eslint-disable-next-line @typescript-eslint/no-unused-vars, max-len, no-control-regex /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/; export const ROOT_NAME = 'USER_ROOT'; + +export const DR_VALUES = ['auto', 'enabled', 'disabled']; diff --git a/src/i18n/i18n-factory.ts b/src/i18n/i18n-factory.ts index 488ba6a7..23070155 100644 --- a/src/i18n/i18n-factory.ts +++ b/src/i18n/i18n-factory.ts @@ -51,6 +51,7 @@ export default class I18nFactory implements II18nFactory { // for all options read: https://www.i18next.com/overview/configuration-options .init({ returnEmptyString: true, + compatibilityJSON: 'v3', lng: this.locale, fallbackLng: 'en', debug: false, diff --git a/src/index.template.html b/src/index.template.html index 50ea337c..5db1d3f7 100644 --- a/src/index.template.html +++ b/src/index.template.html @@ -4,24 +4,25 @@ SPDX-License-Identifier: AGPL-3.0-only --> - + + - Carbonio Client - - - - - - - - - - + Carbonio Client + + + + + + + + + + + + -
+ diff --git a/src/index.tsx b/src/index.tsx index bca6b86e..aee6ca75 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -10,6 +10,10 @@ import './index.css'; import React, { lazy, Suspense } from 'react'; import { render } from 'react-dom'; import LoadingView from './boot/splash'; +import '@fontsource/roboto/300.css'; +import '@fontsource/roboto/400.css'; +import '@fontsource/roboto/500.css'; +import '@fontsource/roboto/700.css'; window.addEventListener('contextmenu', (ev) => { if ( @@ -31,7 +35,6 @@ window.addEventListener('contextmenu', (ev) => { // @ts-ignore works as intended, but it's tampering with the window window.__CARBONIO_DEV__ = !!new URL(window.location).searchParams.get('dev'); const Bootstrapper = lazy(() => import('./boot/bootstrapper')); - if (module.hot) module.hot.accept(); render( }> diff --git a/src/network/edit-settings.ts b/src/network/edit-settings.ts index b3fc4cb3..9855fd87 100644 --- a/src/network/edit-settings.ts +++ b/src/network/edit-settings.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { filter, find, findIndex, forEach, map, reduce } from 'lodash'; +import { filter, find, findIndex, forEach, map, reduce, isArray } from 'lodash'; import { SHELL_APP_ID } from '../constants'; import { useAccountStore } from '../store/account/store'; import { AccountState, Mods, Account } from '../../types'; @@ -22,9 +22,10 @@ export const editSettings = (mods: Mods, appId: string = SHELL_APP_ID): Promise< : '' }${ mods.prefs - ? `${map( - mods.prefs, - (pref, key) => `${pref}` + ? `${map(mods.prefs, (pref, key) => + isArray(pref) + ? map(pref, (p) => `${p}`).join('') + : `${pref}` ).join('')}` : '' }${ diff --git a/src/network/fetch.ts b/src/network/fetch.ts index 90320680..91b17cbc 100644 --- a/src/network/fetch.ts +++ b/src/network/fetch.ts @@ -10,9 +10,10 @@ import { Account, ErrorSoapResponse, SoapContext, SoapResponse } from '../../typ import { userAgent } from './user-agent'; import { report } from '../reporting'; import { useAccountStore } from '../store/account'; -import { SHELL_APP_ID } from '../constants'; +import { IS_STANDALONE, SHELL_APP_ID } from '../constants'; import { useNetworkStore } from '../store/network'; import { handleSync } from '../store/network/utils'; +import { useAppStore } from '../store/app'; export const noOp = (): void => { // eslint-disable-next-line @typescript-eslint/no-use-before-define @@ -98,12 +99,18 @@ const handleResponse = (api: string, res: SoapResponse): R => { (code) => code === (res).Body.Fault.Detail?.Error?.Code ) ) { - goToLogin(); + if (IS_STANDALONE) { + useAccountStore.setState({ authenticated: false }); + } else { + goToLogin(); + } } - throw new Error( - `${(res).Body.Fault.Detail?.Error?.Detail}: ${ - (res).Body.Fault.Reason?.Text - }` + console.error( + new Error( + `${(res).Body.Fault.Detail?.Error?.Detail}: ${ + (res).Body.Fault.Reason?.Text + }` + ) ); } if (res.Header?.context) { diff --git a/src/network/get-info.ts b/src/network/get-info.ts index 27742b90..b05925d6 100644 --- a/src/network/get-info.ts +++ b/src/network/get-info.ts @@ -28,36 +28,38 @@ const parsePollingInterval = (settings: AccountSettings): number => { } return pollingValue * 1000; }; + export const getInfo = (): Promise => - getSoapFetch(SHELL_APP_ID)<{ _jsns: string; rights: string }, GetInfoResponse>('GetInfo', { - _jsns: 'urn:zimbraAccount', - rights: 'sendAs,sendAsDistList,viewFreeBusy,sendOnBehalfOf,sendOnBehalfOfDistList' - }) - .then((res: any): void => { + fetch('/static/iris/components.json') + .then((r) => r.json()) + .then(({ components }: { components: Array }) => { + useAppStore.getState().setters.addApps( + filter(components, ({ type }) => { + if (type === 'shell' || type === 'carbonio') return true; + return false; + }) + ); + }) + .then(() => + getSoapFetch(SHELL_APP_ID)<{ _jsns: string; rights: string }, GetInfoResponse>('GetInfo', { + _jsns: 'urn:zimbraAccount', + rights: 'sendAs,sendAsDistList,viewFreeBusy,sendOnBehalfOf,sendOnBehalfOfDistList' + }) + ) + .then((res: GetInfoResponse): void => { if (res) { const { account, settings, version } = normalizeAccount(res); useNetworkStore.setState({ pollingInterval: parsePollingInterval(settings) }); useAccountStore.setState({ + authenticated: true, account, settings, zimbraVersion: version }); } }) - .then(() => fetch('/static/iris/components.json')) - .then((r: any) => r.json()) - .then(({ components }: { components: Array }) => { - useAppStore.getState().setters.addApps( - filter(components, ({ type }) => { - if (type === 'shell' || type === 'carbonio') return true; - return false; - }) - ); - }) - .catch((err: unknown) => { - console.log('there was an error checking user data'); + .catch((err: Error) => { console.error(err); - goToLogin(); }); diff --git a/src/search/search-bar.tsx b/src/search/search-bar.tsx index 4cf730b1..99005592 100644 --- a/src/search/search-bar.tsx +++ b/src/search/search-bar.tsx @@ -245,6 +245,8 @@ export const SearchBar: FC = ({ const [triggerSearch, setTriggerSearch] = useState(false); const containerRef = useRef(); + const addFocus = useCallback(() => setInputHasFocus(true), []); + const removeFocus = useCallback(() => setInputHasFocus(false), []); // useEffect(() => { // const handler = (event: KeyboardEvent): unknown => @@ -265,7 +267,10 @@ export const SearchBar: FC = ({ const ref = inputRef.current; const searchCb = (ev: any): void => { if (ev.key === 'Enter') { - setTimeout(() => setTriggerSearch(true), 0); + setTimeout(() => { + setTriggerSearch(true); + removeFocus(); + }, 0); } }; if (ref) { @@ -276,7 +281,7 @@ export const SearchBar: FC = ({ ref.removeEventListener('keyup', searchCb); } }; - }, [onSearch]); + }, [onSearch, removeFocus]); useEffect(() => { if (triggerSearch) { @@ -354,8 +359,6 @@ export const SearchBar: FC = ({ setInputState(map(query, (q) => ({ ...q, disabled: searchDisabled }))); }, [searchDisabled, query]); - const addFocus = useCallback(() => setInputHasFocus(true), []); - const removeFocus = useCallback(() => setInputHasFocus(false), []); const disableClearButton = useMemo(() => (isTyping ? false : !showClear), [showClear, isTyping]); return ( diff --git a/src/settings/components/general-settings/appearance-settings.tsx b/src/settings/components/general-settings/appearance-settings.tsx index fbbecc18..a975d867 100644 --- a/src/settings/components/general-settings/appearance-settings.tsx +++ b/src/settings/components/general-settings/appearance-settings.tsx @@ -10,7 +10,7 @@ import { FormSubSection, Select } from '@zextras/carbonio-design-system'; import { find } from 'lodash'; import { ThemeCallbacksContext } from '../../../boot/theme-provider'; import { AccountSettings, DRPropValues } from '../../../../types'; -import { SHELL_APP_ID } from '../../../constants'; +import { DR_VALUES, SHELL_APP_ID } from '../../../constants'; import { themeSubSection } from '../../general-settings-sub-sections'; const AppearanceSettings: FC<{ @@ -48,10 +48,12 @@ const AppearanceSettings: FC<{ ); const onSelectionChange = useCallback( (v) => { - setDarkReaderState(v); - addMod('props', 'zappDarkreaderMode', { app: SHELL_APP_ID, value: v }); + if (DR_VALUES.includes(v) && v !== currentDRMSetting) { + setDarkReaderState(v); + addMod('props', 'zappDarkreaderMode', { app: SHELL_APP_ID, value: v }); + } }, - [addMod, setDarkReaderState] + [addMod, currentDRMSetting, setDarkReaderState] ); const subSection = useMemo(() => themeSubSection(t), [t]); return ( diff --git a/src/shell/shell-primary-bar.tsx b/src/shell/shell-primary-bar.tsx index 2cf19e73..41f8d522 100644 --- a/src/shell/shell-primary-bar.tsx +++ b/src/shell/shell-primary-bar.tsx @@ -4,15 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { - Container, - IconButton, - Row, - Tooltip, - Text, - Padding, - Icon -} from '@zextras/carbonio-design-system'; +import { Container, IconButton, Row, Tooltip } from '@zextras/carbonio-design-system'; import { map, isEmpty, trim, filter, sortBy } from 'lodash'; import React, { useContext, FC, useState, useEffect, useMemo } from 'react'; import styled from 'styled-components'; @@ -26,18 +18,7 @@ import { AppRoute, PrimaryAccessoryView, PrimaryBarView } from '../../types'; import BadgeWrap from './badge-wrap'; import AppContextProvider from '../boot/app/app-context-provider'; import { checkRoute } from '../utility-bar/utils'; - -const PrimaryContainer = styled(Container)<{ active: boolean }>` - background: ${({ theme, active }): string => theme.palette[active ? 'gray4' : 'gray6'].regular}; - cursor: pointer; - transition: background 0.2s ease-out; - &:hover { - background: ${({ theme, active }): string => theme.palette[active ? 'gray4' : 'gray6'].hover}; - } - &:focus { - background: ${({ theme, active }): string => theme.palette[active ? 'gray4' : 'gray6'].focus}; - } -`; +import { IS_STANDALONE } from '../constants'; const ContainerWithDivider = styled(Container)` border-right: 1px solid ${({ theme }): string => theme.palette.gray3.regular}; @@ -107,7 +88,6 @@ const PrimaryBarAccessoryElement: FC = ({ view }) const ShellPrimaryBar: FC<{ activeRoute: AppRoute }> = ({ activeRoute }) => { const primaryBarViews = useAppStore((s) => s.views.primaryBar); - const [routes, setRoutes] = useState>({}); const history = useHistory(); @@ -137,6 +117,9 @@ const ShellPrimaryBar: FC<{ activeRoute: AppRoute }> = ({ activeRoute }) => { ), [activeRoute, primaryBarAccessoryViews] ); + if (IS_STANDALONE && activeRoute?.standalone?.hidePrimaryBar) { + return null; + } return ( { + const auth = useAccountStore((s) => s.authenticated); + useEffect(() => { + if (IS_STANDALONE && !auth && activeRoute && !activeRoute.standalone?.allowUnauthenticated) { + goToLogin(); + } + }, [activeRoute, auth]); +}; + export const Shell: FC = () => { const [mobileNavOpen, setMobileNavOpen] = useState(false); const activeRoute = useCurrentRoute() as AppRoute; + useLoginRedirection(activeRoute); return ( {/* */} - setMobileNavOpen(!mobileNavOpen)} - > - - + {!(IS_STANDALONE && activeRoute?.standalone?.hideShellHeader) && ( + setMobileNavOpen(!mobileNavOpen)} + > + + + )} diff --git a/src/store/account/store.ts b/src/store/account/store.ts index c9e9adac..0d7d815e 100644 --- a/src/store/account/store.ts +++ b/src/store/account/store.ts @@ -10,6 +10,7 @@ import { AccountState } from '../../../types'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore export const useAccountStore = create(() => ({ + authenticated: false, account: undefined, version: '', settings: { diff --git a/src/store/app/store.ts b/src/store/app/store.ts index 69c99bd3..b8b417a8 100644 --- a/src/store/app/store.ts +++ b/src/store/app/store.ts @@ -28,7 +28,9 @@ import { normalizeApp } from './utils'; const filterById = (items: Array, id: string): Array => filter(items, (item) => item.id !== id); -export const useAppStore = create((set) => ({ +const STANDALONE_RESPONSE = 'standalone'; +export const useAppStore = create((set, get) => ({ + standalone: false, apps: {}, appContexts: {}, shell: { @@ -88,6 +90,10 @@ export const useAppStore = create((set) => ({ }, // add route (id route primaryBar secondaryBar app) addRoute: (routeData: AppRouteDescriptor): string => { + const { standalone } = get(); + if (standalone && routeData.route !== standalone) { + return STANDALONE_RESPONSE; + } set( produce((state: AppState) => { state.routes[routeData.id] = routeData; @@ -187,6 +193,10 @@ export const useAppStore = create((set) => ({ // add settings addSettingsView: (data: SettingsView): string => { + const { standalone } = get(); + if (standalone && data.route !== standalone) { + return STANDALONE_RESPONSE; + } set( produce((state: AppState) => { state.views.settings = sortBy(unionBy([data], state.views.settings, 'id'), 'position'); @@ -206,6 +216,10 @@ export const useAppStore = create((set) => ({ // // add search addSearchView: (data: SearchView): string => { + const { standalone } = get(); + if (standalone && data.route !== standalone) { + return STANDALONE_RESPONSE; + } set( produce((state: AppState) => { state.views.search = sortBy(unionBy([data], state.views.search, 'id'), 'position'); diff --git a/src/store/app/utils.tsx b/src/store/app/utils.tsx index 8d37438c..7eb74456 100644 --- a/src/store/app/utils.tsx +++ b/src/store/app/utils.tsx @@ -57,7 +57,12 @@ export const normalizeRoute = ( label: data?.label ?? '', primaryBar: data.primaryBar ?? app.icon ?? 'CubeOutline', secondaryBar: data.secondaryBar, - appView: data.appView ?? FallbackView + appView: data.appView ?? FallbackView, + standalone: { + hidePrimaryBar: data?.standalone?.hidePrimaryBar, + hideShellHeader: data?.standalone?.hideShellHeader, + allowUnauthenticated: data?.standalone?.allowUnauthenticated + } }; }; diff --git a/src/store/network/store.ts b/src/store/network/store.ts index f1f6a8bf..9bae17ef 100644 --- a/src/store/network/store.ts +++ b/src/store/network/store.ts @@ -10,7 +10,6 @@ import { NetworkState } from '../../../types'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore export const useNetworkStore = create(() => ({ - noOpTimeout: undefined, pollingInterval: 30000, seq: 0 })) as UseBoundStore>; diff --git a/src/utility-bar/utils.ts b/src/utility-bar/utils.ts index 9a946636..e15dafc9 100644 --- a/src/utility-bar/utils.ts +++ b/src/utility-bar/utils.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { filter, intersection } from 'lodash'; +import { filter, intersection, omit } from 'lodash'; import { useMemo } from 'react'; import { AppRoute, PrimaryAccessoryView, SecondaryAccessoryView, UtilityView } from '../../types'; import { useCurrentRoute } from '../history/hooks'; @@ -17,7 +17,7 @@ export const checkRoute = ( view: UtilityView | PrimaryAccessoryView | SecondaryAccessoryView, activeRoute?: AppRoute ): boolean => { - const activeRouteValues = Object.values(activeRoute ?? {}); + const activeRouteValues = Object.values(omit(activeRoute, 'standalone') ?? {}); if (view.blacklistRoutes) return !checkList(activeRouteValues, view.blacklistRoutes); if (view.whitelistRoutes) return checkList(activeRouteValues, view.whitelistRoutes); return true; diff --git a/translations/de.json b/translations/de.json index 1e2f1097..22eba00d 100644 --- a/translations/de.json +++ b/translations/de.json @@ -376,7 +376,63 @@ "pacific_apia": "{{value}} Samoa", "pacific_honolulu": "{{value}} Hawaii", "pacific_midway": "{{value}} Samoa", - "utc": "{{value}} Koordinierte Weltzeit" + "utc": "{{value}} Koordinierte Weltzeit", + "africa_juba": "{{value}} Juba", + "africa_khartoum": "{{value}} Khartoum", + "africa_sao_tome": "{{value}} Sao Tome", + "africa_tripoli": "{{value}} Tripoli", + "america_adak": "{{value}} Adak", + "america_bahia": "{{value}} Salvador", + "america_cancun": "{{value}} Cancun, Chetumal", + "america_havana": "{{value}} Havanna", + "america_miquelon": "{{value}} Miquelon", + "america_port-au-prince": "{{value}} Port-au-Prince", + "america_punta_arenas": "{{value}} Punta_Arenas", + "america_whitehorse": "{{value}} Yukon", + "asia_barnaul": "{{value}} Barnaul", + "asia_almaty": "{{value}} Astana", + "asia_gaza": "{{value}} Gaza", + "asia_hovd": "{{value}} Hovd", + "asia_karachi": "{{value}} Islamabad, Karachi", + "asia_kuala_lumpur": "{{value}} Kuala Lumpur", + "asia_novosibirsk": "{{value}} Novosibirsk (RTZ 5)", + "asia_omsk": "{{value}} Omsk", + "asia_qyzylorda": "{{value}} Qyzylorda", + "asia_sakhalin": "{{value}} Sakhalin", + "asia_singapore": "{{value}} Singapur", + "asia_srednekolymsk": "{{value}} Chokurdakh (RTZ 10)", + "asia_taipei": "{{value}} Taipeh", + "asia_tashkent": "{{value}} Tashkent", + "asia_tokyo": "{{value}} Japan", + "asia_tomsk": "{{value}} Tomsk", + "asia_ulaanbaatar": "{{value}} Ulaanbaatar", + "asia_vladivostok": "{{value}} Vladivostok, Magadan (RTZ 9)", + "asia_yakutsk": "{{value}} Yakutsk (RTZ 8)", + "australia_adelaide": "{{value}} Adelaide", + "australia_brisbane": "{{value}} Brisbane", + "australia_hobart": "{{value}} Hobart", + "australia_lord_howe": "{{value}} Lord_Howe", + "australia_perth": "{{value}} Perth", + "australia_sydney": "{{value}} Canberra, Melbourne, Sydney", + "europe_astrakhan": "{{value}} Astrakhan", + "europe_bucharest": "{{value}} Bukarest", + "europe_chisinau": "{{value}} Chisinau", + "europe_samara": "{{value}} Izhevsk, Samara (RTZ 3)", + "europe_saratov": "{{value}} Saratov", + "europe_volgograd": "{{value}} Wolgograd", + "pacific_auckland": "{{value}} Neuseeland", + "pacific_bougainville": "{{value}} Bougainville Standardzeit", + "pacific_chatham": "{{value}} Chatham", + "pacific_fiji": "{{value}} Fiji", + "pacific_easter": "{{value}} Osterinsel", + "pacific_guadalcanal": "{{value}} Solomoninseln/ Neukaledonien", + "pacific_guam": "{{value}} Guam, Port Moresby", + "pacific_kiritimati": "{{value}} Kiritimati Insel", + "pacific_marquesas": "{{value}} Marquesas Inseln", + "pacific_norfolk": "{{value}} Norfolk", + "america_araguaina": "{{value}} Araguaina", + "america_fort_nelson": "{{value}} Fort Nelson", + "america_grand_turk": "{{value}} Turks- und Caicosinseln" }, "board": { "close_tab": "Reiter schließen", diff --git a/translations/es.json b/translations/es.json index f51d4b59..d52f2d4f 100644 --- a/translations/es.json +++ b/translations/es.json @@ -99,7 +99,7 @@ "message": { "snackbar": { "settings_saved": "Ediciones guardadas correctamente", - "identities_quota_exceeded": "La identidad no pudo crearse porque has superado tu cuota de tu identidad" + "identities_quota_exceeded": "La identidad no pudo crearse porque has superado la cuota de identidades" }, "reload": "\t¿Te gustaría volver a cargar la aplicación ahora para mostrar el nuevo idioma?\n(De lo contrario, el cambio se aplicará la próxima vez que inicies sesión)" }, @@ -121,7 +121,7 @@ "limited": "Has ocupado {{quota}}% del espacio disponible", "unlimited": "Tienes espacio ilimitado disponible", "space_full": "Parece que todo el espacio disponible está ocupado", - "title": "Cuota del usuario" + "title": "Cuota de correo" }, "label": { "cancel": "Cancelar", diff --git a/translations/fi.json b/translations/fi.json new file mode 100644 index 00000000..02f6eaa5 --- /dev/null +++ b/translations/fi.json @@ -0,0 +1,5 @@ +{ + "label": { + "remove": "poista" + } +} diff --git a/translations/tr.json b/translations/tr.json index 0e84c1f9..9b715f54 100644 --- a/translations/tr.json +++ b/translations/tr.json @@ -191,7 +191,7 @@ "recovery_email": "Kurtarma E-postası", "remove": "kaldır", "replyToFieldExample": "ör. Dilara Can", - "reply_to_field_example": "ör. Dilara Can", + "reply_to_field_example": "ör. Dilara Can Ute", "save": "Kaydet", "save_and_leave": "Kaydet ve çık", "save_delegate_folder": "Gönderilen mesajların bir kopyasını yardımcımın Gönderilenler klasörüne kaydet", diff --git a/translations/vi.json b/translations/vi.json index 0967ef42..027c82a5 100644 --- a/translations/vi.json +++ b/translations/vi.json @@ -1 +1,387 @@ -{} +{ + "label": { + "filter_folders": "Lọc thư mục", + "go_back": "Quay trở lại", + "feedback": "Phản hồi", + "folder": "Thư mục: Tài khoản mở rộng 3", + "folders": "Thư mục", + "from_name": "Từ: \"Tên\"", + "imap": "IMAP", + "inbox": "Hộp thư đến", + "language": "Ngôn ngữ", + "new_folder": "Thư mục mới", + "leave_anyway": "Vẫn tiếp tục rời khỏi", + "logout": "Đăng xuất", + "no": "Không", + "no_results": "Không tìm thấy kết quả", + "out_of_office": "Không ở văn phòng", + "password": "Mật khẩu", + "passwords_recovery_settings": "Cài đặt mật khẩu khôi phục tài khoản", + "permanent_delete_title": "Bạn có chắc chắn xóa vĩnh viễn người này không?", + "pop_port": "Cổng POP", + "primary": "Đầu tiên", + "recovery_email": "Thư điện tử dùng để khôi phục", + "reload": "Tải lại", + "remove": "xoá", + "save": "Lưu", + "reply_to_field_example": "Vi dụ Bob Smith", + "save_and_leave": "Lưu và thoát", + "save_to_my_sent_folder": "Lưu bản sao của các tin nhắn đã gửi vào thư mục Đã gửi", + "secondary": "Thứ nhì", + "set_reply_to_field": "Cài đặt trường \"Trả lời đến\" trong thư:", + "settings_sent_messages": "Cài đặt gửi thư đi", + "test_settings": "Cài đặt test", + "time_zone": "Múi giờ", + "unsaved_changes": "Bạn không lưu thay đổi", + "unsaved_changes_line1": "Bạn có chắc muốn rời khỏi trang này mà không lưu không?", + "update_view": "Xem cập nhập", + "use_persona": "Sử dụng cá nhân này", + "use_personas_line2": "Ví dụ, nếu đôi khi bạn gửi thư trong một vai trò nào đó tại nơi làm việc, hãy tạo một cá nhân hoá riêng cho vai trò đó.", + "use_ssl": "Sử dụng kết nối mã hoá (SSL) khi truy cập vào máy chủ này", + "when_composing": "Khi soạn, trả lời hoặc chuyển tiếp thư trong thư mục:", + "when_replying": "Khi trả lời hoặc chuyển tiếp, thư sẽ gửi đến:", + "account_menu": "Menu tài khoản", + "account_name": "Tên tài khoản", + "account_type": "Kiểu tài khoản", + "accounts": "Tài khoản", + "cancel": "Huỷ", + "accounts_list": "Danh sách tài khoản", + "add_delegate": "Thêm quyền", + "add_external_account": "Thêm tài khoản mở rộng", + "add_new_email": "Thêm địa chỉ thư mới", + "add_persona": "Thêm thông tin cá nhân", + "add_recovery_email": "Thêm thư khôi phục", + "address": "Địa chỉ", + "advanced_settings": "Cài đặt nâng cao", + "change_pop": "Thay đổi cổng POP", + "choose_account": "Chọn một tài khoản", + "persona_settings": "Cài đặt", + "pop3": "POP3", + "persona_name": "Tên", + "save_delegate_folder": "Lưu bản sao của các tin nhắn đã gửi vào thư mục Đã gửi được chỉ định", + "delete_permanently": "Xoá vĩnh viễn", + "delete": "Xoá", + "discard_changes": "BỎ QUA CÁC THAY ĐỔI", + "button": "Nút bấm", + "choose_folder": "Chọn thư mục", + "clear_search_query": "XOÁ TÌM KIẾM", + "click_to_copy": "Bấm để sao chép", + "delegates": "Phân quyền", + "documentation": "Tài liệu", + "edit_permissions": "Chỉnh sửa quyền", + "edit_to_start_search": "Chỉnh sửa tìm kiếm của bạn để bắt đầu một cái mới", + "email_address": "Địa chỉ thư điện tử", + "email_server": "Máy chủ thư điện tử", + "delete_after_download": "Xoá tin nhắn từ máy chủ sau khi tải về", + "dont_save": "Không lưu bản sao tin nhắn gửi đi", + "download_messages": "Tải tin nhắn về:", + "account_username": "Tên đăng nhập tài khoản", + "external_account_settings": "Cài đặt tài khoản mở rộng", + "persona": "Người", + "recipents": "Người nhận", + "save_both_folders": "Lưu bản sao của các tin nhắn đã gửi vào thư mục Đã gửi của tôi và thư mục chỉ định", + "unsaved_changes_line2": "Tất cả các thay đổi chưa được lưu của bạn sẽ bị mất", + "use_personas_line1": "Sử dụng cá nhân hoá để nhanh chóng thay đổi nhiều cài đặt khi gửi thư." + }, + "locale": { + "English": "Tiếng Anh", + "chinese_china": "Tiếng Trung", + "german": "Tiếng Đức", + "dutch": "Tiếng Hà Lan", + "hindi": "Tiếng Hindi", + "italian": "Tiếng Ý", + "japanese": "Tiếng Nhật", + "label_chinese": "Tiếng Trung (China) - {{value}}", + "label_dutch": "Tiếng Hà Lan- {{value}}", + "label_english": "Tiếng Anh - {{value}}", + "label_french": "Tiếng Pháp - {{value}}", + "label_german": "Tiếng Đức - {{value}}", + "label_hindi": "Tiếng Hindi - {{value}}", + "label_italian": "Tiếng Ý - {{value}}", + "label_portuguese": "Tiếng Bồ - {{value}}", + "label_romanian": "Tiếng Rumani - {{value}}", + "label_russian": "Tiếng Nga - {{value}}", + "label_spanish": "Tiếng Tây Ban nha - {{value}}", + "label_thai": "Tiếng Thái - {{value}}", + "portuguese": "Người Bồ Đào Nha", + "portuguese_brazil": "Bồ Đào Nha (Brazil)", + "romanian": "Romania", + "russian": "Nga", + "spanish": "Tây Ban Nha", + "turkish": "Thổ Nhĩ Kỳ", + "french": "Tiếng Pháp", + "thai": "Thái", + "label_portuguese_brazil": "Tiếng Bồ - {{value}}", + "label_japanese": "Tiếng Nhật - {{value}}", + "label_turkish": "Tiếng Thổ - {{value}}" + }, + "message": { + "snackbar": { + "settings_saved": "Chỉnh sửa được lưu chính xác", + "identities_quota_exceeded": "Không thể tạo danh tính vì bạn đã vượt quá hạn ngạch nhận dạng" + }, + "reload": "\tBạn có muốn tải lại ứng dụng ngay bây giờ để hiển thị ngôn ngữ mới không?\n(Nếu không, ngôn ngữ mới sẽ xuất hiện vào lần đăng nhập tiếp theo của bạn.)" + }, + "messages": { + "permanent_delete_title": "Bạn có chắc chắn xóa vĩnh viễn phần tử này không?", + "permanent_delete_body": "Nếu bạn xóa vĩnh viễn cá nhân hoá này, bạn sẽ không thể khôi phục nó. Tiếp tục?" + }, + "module": { + "app": { + "version": "Phiên bản ứng dụng" + } + }, + "new_tab": "Thẻ mới", + "passwords_recovery_settings": { + "title": "Cài đặt mật khẩu khôi phục tài khoản" + }, + "search": { + "active_input_label": "Phân tách từ khoá của bạn bằng dấu phẩu hoặc nhấn TAB", + "already_clear": "Dữ liệu nhập vào để tìm kiếm đã được xoá", + "app": "Tìm kiếm", + "clear": "Xoá dữ liệu nhập vào tìm kiếm", + "type_or_choose_suggestion": "Nhập hoặc chọn một số từ khóa để bắt đầu tìm kiếm", + "module": "Mô-đun", + "start": "Bắt đầu tìm kiếm", + "type_to_start_search": "Nhập một số từ khóa để bắt đầu tìm kiếm", + "unable_to_parse_query": "Không thể hoàn tất tìm kiếm, hãy xóa nó và thử lại", + "idle_input_label": "Tìm kiếm trong {{module}}" + }, + "settings": { + "accounts": "Tài khoản", + "choose": "Chọn", + "app": "Cài đặt", + "button": { + "confirm": "Xác nhận", + "primary": "Lưu" + }, + "general": { + "account": "Tài khoản", + "account_logout": "Đăng xuất", + "dark_mode": "Chế độ tối", + "general": "Cài đặt chung", + "theme_auto": "Tự động", + "theme_enabled": "Cho phép", + "theme_options": "Tuỳ chọn mẫu", + "theme_disabled": "Vô hiệu hoá" + }, + "out_of_office": { + "labels": { + "out_of_office_status": "Trạng thái \"Không ở văn phòng\":", + "send_auto_reply_period": "Gửi trả lời tự động trong khoảng thời gian sau:", + "start_date": "Ngày bắt đầu", + "start_time": "Thời gian bắt đầu:", + "all_day": "Tất cả các ngày:", + "create_appointment": "Tạo cuộc hẹn:", + "end_date": "Ngày kết thúc", + "end_time": "Thời gian kết thúc:", + "external_senders": "Người gửi bên ngoài", + "auto_reply_message": "Tin nhắn trả lời tự động:", + "auto_reply_message_external": "Tin nhắn trả lời tự động gửi cho người gửi bên ngoài:" + }, + "send_auto_replies": "Gửi trả lời tự động", + "status": { + "busy": "Bận" + }, + "external_senders": { + "send_custom_not_in_organisation": "Gửi tin nhắn tuỳ chỉnh đến những người không nằm trong tổ chức và trong danh bạ", + "do_not_send_to_external": "Không gửi trả lời tự động cho người gửi bên ngoài", + "send_standard_auto_reply": "Gửi thư trả lời tự động (auto-reply) chuẩn", + "send_custom_in_organisation": "Gửi thông điệp tuỳ chỉnh đến những người không nằm trong tổ chức" + }, + "headings": { + "create_appointment": "Lịch hẹn", + "settings_label": "Đang không ở văn phòng", + "time_period": "Khoảng thời gian" + }, + "do_not_send_auto_replies": "Không gửi trả lời tự động" + }, + "search_settings": { + "labels": { + "include_search_in_shared_folder": "Bao gồm thư mục chia sẻ trong tìm kiếm", + "include_search_in_spam_folder": "Bao gồm thư mục chứa Spam trong tìm kiếm", + "include_search_in_trash_folder": "Bao gồm thùng rác trong tìm kiếm" + } + }, + "timezone_and_language": "Múi giờ và ngôn ngữ", + "general_tab": "Chung" + }, + "share": { + "is_contained_in": "Được chứa trong" + }, + "timezone": { + "africa_cairo": "{{value}} Ai Cập", + "africa_casablanca": "{{value}} Casablanca", + "africa_harare": "{{value}} Harare, Pretoria", + "africa_juba": "{{value}} Juba", + "africa_algiers": "{{value}} Tây Trung Phi", + "africa_khartoum": "{{value}} Khartoum", + "africa_windhoek": "{{value}} Namibia", + "africa_monrovia": "{{value}} Monrovia", + "africa_nairobi": "{{value}} Nairobi", + "africa_sao_tome": "{{value}} Sao Tome", + "africa_tripoli": "{{value}} Tripoli", + "america_adak": "{{value}} Adak", + "america_anchorage": "{{value}} Alaska", + "america_araguaina": "{{value}} Araguaina", + "america_argentina_buenos_aires": "{{value}} Argentina", + "america_bogota": "{{value}} Colombia", + "america_asuncion": "{{value}} Asuncion", + "america_bahia": "{{value}} Salvador", + "america_cancun": "{{value}} Cancun, Chetumal", + "america_caracas": "{{value}} Caracas", + "america_cayenne": "{{value}} Cayenne, Fortaleza", + "america_chicago": "{{value}} US/Canada Central", + "america_chihuahua": "{{value}} Chihuahua, La Paz, Mazatlan", + "america_cuiaba": "{{value}} Cuiaba", + "america_denver": "{{value}} US/Canada Mountain", + "america_fort_nelson": "{{value}} Fort Nelson", + "america_grand_turk": "{{value}} Turks and Caicos Islands", + "america_guatemala": "{{value}} Central America", + "america_guyana": "{{value}} Georgetown, La Paz, Manaus, San Juan", + "america_halifax": "{{value}} Atlantic Time (Canada)", + "america_havana": "{{value}} Havana", + "america_indiana_indianapolis": "{{value}} Indiana (East)", + "america_los_angeles": "{{value}} US/Canada Pacific", + "america_mexico_city": "{{value}} Guadalajara, Mexico City, Monterrey", + "america_miquelon": "{{value}} Miquelon", + "america_montevideo": "{{value}} Montevideo", + "america_new_york": "{{value}} US/Canada Eastern", + "america_phoenix": "{{value}} Arizona", + "america_port-au-prince": "{{value}} Port-au-Prince", + "america_punta_arenas": "{{value}} Punta_Arenas", + "america_regina": "{{value}} Saskatchewan", + "america_santiago": "{{value}} Pacific South America", + "america_sao_paulo": "{{value}} Brasilia", + "america_st_johns": "{{value}} Newfoundland", + "america_tijuana": "{{value}} Baja California", + "america_whitehorse": "{{value}} Yukon", + "asia_almaty": "{{value}} Astana", + "asia_amman": "{{value}} Jordan", + "asia_baghdad": "{{value}} Iraq", + "asia_bangkok": "{{value}} Bangkok, Hanoi, Jakarta", + "asia_barnaul": "{{value}} Barnaul", + "asia_beirut": "{{value}} Beirut", + "asia_chita": "{{value}} Chita", + "asia_colombo": "{{value}} Sri Jayawardenepura Kotte", + "asia_damascus": "{{value}} Damascus", + "asia_dhaka": "{{value}} Dhaka", + "asia_gaza": "{{value}} Gaza", + "asia_hong_kong": "{{value}} Beijing, Chongqing, Hong Kong, Urumqi", + "asia_hovd": "{{value}} Hovd", + "asia_irkutsk": "{{value}} Irkutsk (RTZ 7)", + "asia_kabul": "{{value}} Kabul", + "asia_kamchatka": "{{value}} Anadyr, Petropavlovsk-Kamchatsky (RTZ 11)", + "asia_karachi": "{{value}} Islamabad, Karachi", + "asia_kathmandu": "{{value}} Kathmandu", + "asia_kolkata": "{{value}} Chennai, Kolkata, Mumbai, New Delhi", + "asia_krasnoyarsk": "{{value}} Krasnoyarsk (RTZ 6)", + "asia_kuala_lumpur": "{{value}} Kuala Lumpur", + "asia_magadan": "{{value}} Magadan", + "asia_muscat": "{{value}} Abu Dhabi, Muscat", + "asia_novosibirsk": "{{value}} Novosibirsk (RTZ 5)", + "asia_omsk": "{{value}} Omsk", + "asia_pyongyang": "{{value}} Pyongyang", + "asia_sakhalin": "{{value}} Sakhalin", + "asia_seoul": "{{value}} Korea", + "asia_singapore": "{{value}} Singapore", + "asia_srednekolymsk": "{{value}} Chokurdakh (RTZ 10)", + "asia_taipei": "{{value}} Taipei", + "asia_tashkent": "{{value}} Tashkent", + "asia_tbilisi": "{{value}} Tbilisi", + "asia_tehran": "{{value}} Tehran", + "asia_tokyo": "{{value}} Japan", + "asia_tomsk": "{{value}} Tomsk", + "asia_ulaanbaatar": "{{value}} Ulaanbaatar", + "asia_vladivostok": "{{value}} Vladivostok, Magadan (RTZ 9)", + "asia_yakutsk": "{{value}} Yakutsk (RTZ 8)", + "asia_yangon": "{{value}} Yangon", + "asia_yekaterinburg": "{{value}} Ekaterinburg (RTZ 4)", + "asia_yerevan": "{{value}} Yerevan", + "atlantic_azores": "{{value}} Azores", + "atlantic_cape_verde": "{{value}} Cape Verde Is.", + "atlantic_south_georgia": "{{value}} Mid-Atlantic", + "australia_brisbane": "{{value}} Brisbane", + "australia_darwin": "{{value}} Darwin", + "australia_eucla": "{{value}} Eucla", + "australia_hobart": "{{value}} Hobart", + "australia_lord_howe": "{{value}} Lord_Howe", + "australia_perth": "{{value}} Perth", + "australia_sydney": "{{value}} Canberra, Melbourne, Sydney", + "etc_gmt+12": "{{value}} Dateline", + "europe_athens": "{{value}} Athens, Beirut, Bucharest, Istanbul", + "europe_belgrade": "{{value}} Belgrade, Bratislava, Budapest, Ljubljana, Prague", + "europe_berlin": "{{value}} Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna", + "europe_bucharest": "{{value}} Bucharest", + "europe_chisinau": "{{value}} Chisinau", + "europe_helsinki": "{{value}} Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius", + "europe_istanbul": "{{value}} Istanbul", + "europe_kaliningrad": "{{value}} Kaliningrad (RTZ 1)", + "europe_london": "{{value}} Britain, Ireland, Portugal", + "europe_minsk": "{{value}} Minsk", + "europe_moscow": "{{value}} Moscow, St. Petersburg, Volgograd (RTZ 2)", + "europe_samara": "{{value}} Izhevsk, Samara (RTZ 3)", + "europe_saratov": "{{value}} Saratov", + "europe_volgograd": "{{value}} Volgograd", + "europe_warsaw": "{{value}} Sarajevo, Skopje, Warsaw, Zagreb", + "indian_mauritius": "{{value}} Port Louis", + "pacific_apia": "{{value}} Samoa", + "pacific_auckland": "{{value}} New Zealand", + "pacific_bougainville": "{{value}} Bougainville Standard Time", + "pacific_chatham": "{{value}} Chatham", + "pacific_easter": "{{value}} Easter", + "pacific_fiji": "{{value}} Fiji", + "pacific_guadalcanal": "{{value}} Solomon Is. / New Caledonia", + "pacific_guam": "{{value}} Guam, Port Moresby", + "pacific_honolulu": "{{value}} Hawaii", + "pacific_kiritimati": "{{value}} Kiritimati Island", + "pacific_marquesas": "{{value}} Marquesas", + "pacific_midway": "{{value}} Samoa", + "pacific_norfolk": "{{value}} Norfolk", + "pacific_tongatapu": "{{value}} Nuku’alofa", + "utc": "{{value}} Coordinated Universal Time", + "asia_baku": "{{value}} Baku", + "asia_jerusalem": "{{value}} Jerusalem", + "asia_kuwait": "{{value}} Kuwait, Riyadh", + "asia_qyzylorda": "{{value}} Qyzylorda", + "australia_adelaide": "{{value}} Adelaide", + "europe_astrakhan": "{{value}} Astrakhan", + "europe_brussels": "{{value}} Brussels, Copenhagen, Madrid, Paris" + }, + "snackbar": { + "error": "Có lỗi, vui lòng thử lại" + }, + "board": { + "close_tab": "Đóng thẻ", + "close_tabs": "Đóng tất cả các thẻ", + "enlarge": "Phóng to bảng", + "hide": "Ẩn bảng", + "reduce": "Thu nhỏ bảng", + "show_tabs": "Hiện các thẻ khác" + }, + "user_quota": { + "limited": "Bạn đã sử dụng hết {{quota}}% không gian có sẵn", + "space_full": "Có vẻ không gian lưu trữ bạn đã đầy", + "title": "Dung lượng lưu trữ của người dùng", + "unlimited": "Bạn được cấp phép dung lượng lưu trữ không giới hạn" + }, + "feedback": { + "behaviors": "Hành vi", + "hint": "Hãy nhớ: xác định chủ đề bằng cách sử dụng mô-đun và bộ chọn vùng macro trước khi viết phản hồi của bạn. Cảm ơn bạn đã giúp đỡ.", + "missing_features": "Các tính năng bị thiếu", + "report_something": "Bạn có muốn báo cáo điều gì đó không?", + "select_a_module": "Chọn một Mô-đun", + "send": "GỬI", + "success": "Cảm ơn phản hồi của bạn", + "user_interface": "Giao diện người dùng", + "write_here": "Viết phản hồi của bạn ở đây", + "other": "Khác", + "error": "Đã xảy ra lỗi khi gửi phản hồi của bạn", + "explanation": "Vui lòng cung cấp cho chúng tôi phản hồi của bạn về trải nghiệm mới của bạn với Zextras. Ý kiến của bạn rất có ý nghĩa để chúng tôi cải thiện sản phẩm của mình. Hãy để chúng tôi biết những suy nghĩ của bạn.", + "select_a_topic": "Chọn một chủ đề" + }, + "new": "Mới", + "primary_account": { + "title": "Cài đặt tài khoản chính" + } +} diff --git a/types/account/index.d.ts b/types/account/index.d.ts index 094f9553..a67f93a8 100644 --- a/types/account/index.d.ts +++ b/types/account/index.d.ts @@ -18,6 +18,7 @@ export type SoapFetch = ( ) => Promise; export type AccountState = { + authenticated: boolean; account?: Account; settings: AccountSettings; zimbraVersion: string; diff --git a/types/apps/index.d.ts b/types/apps/index.d.ts index d1a10603..d019c450 100644 --- a/types/apps/index.d.ts +++ b/types/apps/index.d.ts @@ -23,11 +23,18 @@ export type CarbonioModule = { sentryDsn?: string; }; +export type StandaloneFlags = { + hidePrimaryBar?: boolean; + hideShellHeader?: boolean; + allowUnauthenticated?: boolean; +}; + export type AppRoute = { // persist?: boolean; id: string; route: string; app: string; + standalone?: StandaloneFlags; }; export type AppRouteData = AppRoute & { @@ -126,6 +133,7 @@ export type AppRouteDescriptor = { label: string; secondaryBar?: ComponentType; appView: ComponentType; + standalone?: StandaloneFlags; }; export type AppSetters = { addApps: (apps: Array>) => void; @@ -171,6 +179,7 @@ export type AppSetters = { setAppContext: (app: string) => (context: unknown) => void; }; export type AppState = { + standalone: false | string; apps: Record; appContexts: Record; entryPoints: Record; diff --git a/types/exports/index.d.ts b/types/exports/index.d.ts index cd405fc6..b4713824 100644 --- a/types/exports/index.d.ts +++ b/types/exports/index.d.ts @@ -62,6 +62,9 @@ export const ACTION_TYPES: { }; export const SHELL_MODES: Record; export const BASENAME: string; + +export const IS_STANDALONE: boolean; + export const getIntegratedHook: (id: string) => [Function, boolean]; export const getIntegratedFunction: (id: string) => [Function, boolean]; export const getIntegratedComponent: (id: string) => [ComponentType, boolean]; diff --git a/types/network/index.d.ts b/types/network/index.d.ts index 9e561f18..b3fad7ca 100644 --- a/types/network/index.d.ts +++ b/types/network/index.d.ts @@ -105,7 +105,7 @@ export type AvailableLocalesResponse = { }; export type NetworkState = SoapContext & { - noOpTimeout: unknown; + noOpTimeout?: Timeout; pollingInterval: number; seq: number; };