├── i18n ├── bg.json ├── fa.json ├── it.json ├── ko.json ├── ro.json ├── uk.json ├── zh-TW.json └── ru.json ├── src ├── .storybook │ ├── addons.js │ ├── config.js │ └── webpack.config.js ├── dev-app-update.yml ├── assets │ ├── icon.icns │ ├── icon.ico │ ├── loading.gif │ ├── appicon_48.png │ ├── loading@2x.gif │ ├── osx │ │ ├── DMG_BG.png │ │ ├── osx_icon.icns │ │ └── menuIcons │ │ │ ├── MenuIcon16Template.png │ │ │ ├── MenuIcon20Template.png │ │ │ ├── MenuIcon24Template.png │ │ │ ├── MenuIcon32Template.png │ │ │ ├── MenuIcon16Template@2x.png │ │ │ ├── MenuIcon20Template@2x.png │ │ │ ├── MenuIcon24Template@2x.png │ │ │ ├── MenuIcon32Template@2x.png │ │ │ ├── MenuIconUnread16Template.png │ │ │ ├── MenuIconUnread20Template.png │ │ │ ├── MenuIconUnread24Template.png │ │ │ ├── MenuIconUnread32Template.png │ │ │ ├── MenuIconUnread16Template@2x.png │ │ │ ├── MenuIconUnread20Template@2x.png │ │ │ ├── MenuIconUnread24Template@2x.png │ │ │ └── MenuIconUnread32Template@2x.png │ ├── sounds │ │ ├── bing.mp3 │ │ ├── ding.mp3 │ │ ├── down.mp3 │ │ ├── hello.mp3 │ │ ├── crackle.mp3 │ │ ├── ripple.mp3 │ │ └── upstairs.mp3 │ ├── linux │ │ ├── app_icon.png │ │ ├── top_bar_dark_16.png │ │ ├── top_bar_light_16.png │ │ ├── top_bar_dark_16@2x.png │ │ ├── top_bar_light_16@2x.png │ │ ├── top_bar_dark_mention_16.png │ │ ├── top_bar_dark_unread_16.png │ │ ├── top_bar_light_mention_16.png │ │ ├── top_bar_light_unread_16.png │ │ ├── top_bar_dark_mention_16@2x.png │ │ ├── top_bar_dark_unread_16@2x.png │ │ ├── top_bar_light_unread_16@2x.png │ │ ├── top_bar_light_mention_16@2x.png │ │ ├── ICONS.md │ │ └── create_desktop_file.sh │ ├── windows │ │ ├── tray_dark.ico │ │ ├── tray_light.ico │ │ ├── tray_dark_mention.ico │ │ ├── tray_dark_unread.ico │ │ ├── tray_light_unread.ico │ │ └── tray_light_mention.ico │ ├── loader │ │ └── StippleMask.jpg │ ├── fonts │ │ ├── OpenSans-Light.woff2 │ │ ├── Metropolis-Light.woff │ │ ├── Metropolis-Regular.woff │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── Metropolis-SemiBold.woff │ │ ├── fontawesome-webfont.woff │ │ ├── fontawesome-webfont.woff2 │ │ ├── Metropolis-LightItalic.woff │ │ ├── Metropolis-RegularItalic.woff │ │ ├── Metropolis-SemiBoldItalic.woff │ │ ├── open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300.woff2 │ │ ├── open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600.woff2 │ │ ├── open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-italic.woff2 │ │ ├── open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-regular.woff2 │ │ ├── open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300italic.woff2 │ │ └── open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600italic.woff2 │ ├── appicon_with_spacing_32.png │ ├── appicon_with_spacing_96.png │ ├── titlebar │ │ ├── chrome-minimize.svg │ │ ├── chrome-maximize.svg │ │ ├── chrome-restore.svg │ │ └── chrome-close.svg │ └── icon-session-expired.svg ├── renderer │ ├── css │ │ ├── components │ │ │ ├── NewTeamModal.css │ │ │ ├── CarouselButton.scss │ │ │ ├── MainPage.css │ │ │ ├── UpdaterPage.css │ │ │ ├── index.css │ │ │ ├── ErrorView.css │ │ │ ├── MattermostView.css │ │ │ ├── ExtraBar.css │ │ │ ├── HoveringURL.css │ │ │ ├── Header.scss │ │ │ ├── LoadingSpinner.scss │ │ │ ├── WelcomeScreen.scss │ │ │ ├── UpgradeButton.scss │ │ │ ├── Carousel.scss │ │ │ ├── PermissionRequestDialog.css │ │ │ ├── WelcomeScreenSlide.scss │ │ │ ├── LoadingScreen.css │ │ │ └── CarouselPaginationIndicator.scss │ │ ├── lazy │ │ │ ├── modals-dark.lazy.css │ │ │ └── settings-dark.lazy.css │ │ ├── modals.css │ │ ├── settings.css │ │ ├── _css_variables.scss │ │ └── _mixins.scss │ ├── assets │ │ └── fonts │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ ├── fontawesome-webfont.woff2 │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ ├── glyphicons-halflings-regular.woff2 │ │ │ ├── open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300.woff2 │ │ │ ├── open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600.woff2 │ │ │ ├── open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-italic.woff2 │ │ │ ├── open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300italic.woff2 │ │ │ ├── open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600italic.woff2 │ │ │ └── open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-regular.woff2 │ ├── modals │ │ ├── urlView │ │ │ ├── index.html │ │ │ └── urlView.tsx │ │ ├── darkMode.ts │ │ ├── certificate │ │ │ └── certificate.tsx │ │ ├── login │ │ │ └── login.tsx │ │ ├── permission │ │ │ └── permission.tsx │ │ └── removeServer │ │ │ └── removeServer.tsx │ ├── components │ │ ├── Header │ │ │ ├── index.ts │ │ │ └── Header.tsx │ │ ├── Carousel │ │ │ ├── index.ts │ │ │ └── CarouselButton.tsx │ │ ├── LoadingScreen │ │ │ └── index.ts │ │ ├── WelcomeScreen │ │ │ ├── index.ts │ │ │ └── WelcomeScreenSlide.tsx │ │ ├── LoadingAnimation │ │ │ └── index.ts │ │ ├── SaveButton │ │ │ ├── LoadingWrapper.tsx │ │ │ ├── LoadingSpinner.tsx │ │ │ └── SaveButton.tsx │ │ ├── urlDescription.tsx │ │ ├── Button │ │ │ └── Button.stories.tsx │ │ ├── DestructiveConfirmModal.tsx │ │ ├── ExtraBar.tsx │ │ ├── RemoveServerModal.tsx │ │ └── AutoSaveIndicator.tsx │ ├── index.html │ ├── notificationSounds.ts │ ├── settings.tsx │ ├── intl_provider.tsx │ └── hooks │ │ ├── useAnimationEnd.ts │ │ └── useTransitionEnd.ts ├── types │ ├── args.ts │ ├── modals.ts │ ├── external │ │ ├── file-types.d.ts │ │ ├── winreg-utf8.d.ts │ │ └── tuple-record-polyfill.d.ts │ ├── trustedOrigin.ts │ ├── mainWindow.ts │ ├── appState.ts │ ├── auth.ts │ ├── notification.ts │ ├── server.ts │ ├── certificate.ts │ ├── utils.ts │ ├── global.d.ts │ └── window.ts ├── common │ ├── permissions.ts │ ├── servers │ │ └── MattermostServer.ts │ ├── tabs │ │ ├── MessagingTabView.ts │ │ ├── FocalboardTabView.ts │ │ ├── PlaybooksTabView.ts │ │ ├── BaseTabView.ts │ │ └── TabView.test.js │ ├── utils │ │ ├── requests.ts │ │ └── util.ts │ ├── config │ │ ├── migrationPreferences.ts │ │ ├── migrationPreferences.test.js │ │ ├── defaultPreferences.ts │ │ ├── pastDefaultPreferences.ts │ │ ├── buildConfig.ts │ │ └── upgradePreferences.ts │ └── JsonFileManager.ts ├── main │ ├── app │ │ └── index.ts │ ├── preload │ │ ├── urlView.js │ │ ├── mainWindow.js │ │ └── loadingScreenPreload.js │ ├── AppVersionManager.test.js │ ├── ParseArgs.test.js │ ├── constants.ts │ ├── windows │ │ └── settingsWindow.ts │ ├── AutoLauncher.test.js │ ├── menus │ │ ├── tray.test.js │ │ └── tray.ts │ ├── notifications │ │ ├── Download.ts │ │ ├── Upgrade.ts │ │ └── Mention.ts │ ├── AppVersionManager.ts │ ├── AutoLauncher.ts │ ├── i18nManager.ts │ └── contextMenu.ts └── jestSetup.js ├── scripts ├── wix311.sha ├── .eslintrc.json ├── patch_updater_yml.sh ├── extract_dict.js ├── msi_installer_files_set_win64.xslt ├── patch_mas_version.sh ├── msi_installer_files_replace_id.xslt ├── check_build_config.js ├── afterbuild.js ├── beforepack.js ├── watch_main_and_preload.js ├── notarize.js ├── manipulate_windows_zip.js ├── afterpack.js └── generate_release_markdown.sh ├── .eslintignore ├── resources └── windows │ ├── msi_up.ico │ ├── msi_modify.ico │ ├── msi_remove.ico │ ├── msi_repair.ico │ ├── msi_question.ico │ ├── msi_warning.ico │ ├── msi_create_folder.ico │ ├── msi_dialog_banner.png │ ├── msi_dialog_banner_small.png │ └── gpo │ ├── en-US │ └── mattermost.adml │ └── mattermost.admx ├── .gitlab-ci.yml ├── CHANGELOG.md ├── .editorconfig ├── e2e ├── utils │ └── constants.js ├── modules │ ├── test.html │ └── utils.js ├── .eslintrc.json └── specs │ ├── menu_bar │ ├── menu.test.js │ └── history_menu.test.js │ ├── relative_url │ └── relative_url.test.js │ ├── deep_linking │ └── deeplink.test.js │ └── mattermost │ └── copy_link.test.js ├── .github ├── ISSUE_TEMPLATE │ ├── troubleshooting-question.md │ ├── feature_request.md │ ├── upgrade-dependencies.md │ ├── beta_report.md │ └── bug_report.md ├── workflows │ ├── codeql-analysis.yml │ └── scorecards-analysis.yml └── PULL_REQUEST_TEMPLATE.md ├── entitlements.mas.inherit.plist ├── .vscode ├── extensions.json ├── settings.json ├── tasks.json └── launch.json ├── .gitignore ├── .config └── notice-file │ ├── config.yaml │ └── Readme.md ├── entitlements.mac.plist ├── babel.config.js ├── PULL_REQUEST_TEMPLATE.md ├── tsconfig.json ├── fastlane └── Fastfile ├── entitlements.mas.plist ├── webpack.config.test.js ├── Makefile ├── ISSUE_TEMPLATE.md ├── webpack.config.main.js ├── SECURITY.md └── webpack.config.base.js /i18n/bg.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /i18n/fa.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /i18n/it.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /i18n/ko.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /i18n/ro.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /i18n/uk.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /i18n/zh-TW.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /src/.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-actions/register'; 2 | -------------------------------------------------------------------------------- /i18n/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "main.menus.app.history.forward": "Переслать" 3 | } 4 | -------------------------------------------------------------------------------- /src/dev-app-update.yml: -------------------------------------------------------------------------------- 1 | provider: generic 2 | url: 'http://localhost:8081/' 3 | -------------------------------------------------------------------------------- /scripts/wix311.sha: -------------------------------------------------------------------------------- 1 | 3f619089b46df893f55e58832ce442678fb0635f *./wix311.exe 2 | 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*_bundle.js 2 | node_modules/ 3 | release/ 4 | src/node_modules/ 5 | -------------------------------------------------------------------------------- /scripts/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "import/no-commonjs": 0 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/icon.icns -------------------------------------------------------------------------------- /src/assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/icon.ico -------------------------------------------------------------------------------- /src/assets/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/loading.gif -------------------------------------------------------------------------------- /src/assets/appicon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/appicon_48.png -------------------------------------------------------------------------------- /src/assets/loading@2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/loading@2x.gif -------------------------------------------------------------------------------- /src/assets/osx/DMG_BG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/DMG_BG.png -------------------------------------------------------------------------------- /src/assets/sounds/bing.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/sounds/bing.mp3 -------------------------------------------------------------------------------- /src/assets/sounds/ding.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/sounds/ding.mp3 -------------------------------------------------------------------------------- /src/assets/sounds/down.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/sounds/down.mp3 -------------------------------------------------------------------------------- /src/assets/sounds/hello.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/sounds/hello.mp3 -------------------------------------------------------------------------------- /resources/windows/msi_up.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/resources/windows/msi_up.ico -------------------------------------------------------------------------------- /src/assets/linux/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/linux/app_icon.png -------------------------------------------------------------------------------- /src/assets/osx/osx_icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/osx_icon.icns -------------------------------------------------------------------------------- /src/assets/sounds/crackle.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/sounds/crackle.mp3 -------------------------------------------------------------------------------- /src/assets/sounds/ripple.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/sounds/ripple.mp3 -------------------------------------------------------------------------------- /src/assets/sounds/upstairs.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/sounds/upstairs.mp3 -------------------------------------------------------------------------------- /resources/windows/msi_modify.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/resources/windows/msi_modify.ico -------------------------------------------------------------------------------- /resources/windows/msi_remove.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/resources/windows/msi_remove.ico -------------------------------------------------------------------------------- /resources/windows/msi_repair.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/resources/windows/msi_repair.ico -------------------------------------------------------------------------------- /src/assets/windows/tray_dark.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/windows/tray_dark.ico -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | include: 4 | - project: mattermost/ci/desktop 5 | ref: main 6 | file: private.yml 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/windows/msi_question.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/resources/windows/msi_question.ico -------------------------------------------------------------------------------- /resources/windows/msi_warning.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/resources/windows/msi_warning.ico -------------------------------------------------------------------------------- /src/assets/loader/StippleMask.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/loader/StippleMask.jpg -------------------------------------------------------------------------------- /src/assets/windows/tray_light.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/windows/tray_light.ico -------------------------------------------------------------------------------- /src/assets/fonts/OpenSans-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/fonts/OpenSans-Light.woff2 -------------------------------------------------------------------------------- /src/assets/linux/top_bar_dark_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/linux/top_bar_dark_16.png -------------------------------------------------------------------------------- /src/assets/linux/top_bar_light_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/linux/top_bar_light_16.png -------------------------------------------------------------------------------- /src/renderer/css/components/NewTeamModal.css: -------------------------------------------------------------------------------- 1 | .NewTeamModal-noBottomSpace { 2 | padding-bottom: 0px; 3 | margin-bottom: 0px; 4 | } 5 | -------------------------------------------------------------------------------- /resources/windows/msi_create_folder.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/resources/windows/msi_create_folder.ico -------------------------------------------------------------------------------- /resources/windows/msi_dialog_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/resources/windows/msi_dialog_banner.png -------------------------------------------------------------------------------- /src/assets/appicon_with_spacing_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/appicon_with_spacing_32.png -------------------------------------------------------------------------------- /src/assets/appicon_with_spacing_96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/appicon_with_spacing_96.png -------------------------------------------------------------------------------- /src/assets/fonts/Metropolis-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/fonts/Metropolis-Light.woff -------------------------------------------------------------------------------- /src/assets/fonts/Metropolis-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/fonts/Metropolis-Regular.woff -------------------------------------------------------------------------------- /src/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/assets/linux/top_bar_dark_16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/linux/top_bar_dark_16@2x.png -------------------------------------------------------------------------------- /src/assets/linux/top_bar_light_16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/linux/top_bar_light_16@2x.png -------------------------------------------------------------------------------- /src/assets/windows/tray_dark_mention.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/windows/tray_dark_mention.ico -------------------------------------------------------------------------------- /src/assets/windows/tray_dark_unread.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/windows/tray_dark_unread.ico -------------------------------------------------------------------------------- /src/assets/windows/tray_light_unread.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/windows/tray_light_unread.ico -------------------------------------------------------------------------------- /src/assets/fonts/Metropolis-SemiBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/fonts/Metropolis-SemiBold.woff -------------------------------------------------------------------------------- /src/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/assets/windows/tray_light_mention.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/windows/tray_light_mention.ico -------------------------------------------------------------------------------- /resources/windows/msi_dialog_banner_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/resources/windows/msi_dialog_banner_small.png -------------------------------------------------------------------------------- /src/assets/fonts/Metropolis-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/fonts/Metropolis-LightItalic.woff -------------------------------------------------------------------------------- /src/assets/linux/top_bar_dark_mention_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/linux/top_bar_dark_mention_16.png -------------------------------------------------------------------------------- /src/assets/linux/top_bar_dark_unread_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/linux/top_bar_dark_unread_16.png -------------------------------------------------------------------------------- /src/assets/linux/top_bar_light_mention_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/linux/top_bar_light_mention_16.png -------------------------------------------------------------------------------- /src/assets/linux/top_bar_light_unread_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/linux/top_bar_light_unread_16.png -------------------------------------------------------------------------------- /src/renderer/css/lazy/modals-dark.lazy.css: -------------------------------------------------------------------------------- 1 | @import '~bootstrap-dark/src/bootstrap-dark.css'; 2 | 3 | body { 4 | background-color: transparent; 5 | } -------------------------------------------------------------------------------- /src/assets/fonts/Metropolis-RegularItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/fonts/Metropolis-RegularItalic.woff -------------------------------------------------------------------------------- /src/assets/fonts/Metropolis-SemiBoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/fonts/Metropolis-SemiBoldItalic.woff -------------------------------------------------------------------------------- /src/assets/linux/top_bar_dark_mention_16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/linux/top_bar_dark_mention_16@2x.png -------------------------------------------------------------------------------- /src/assets/linux/top_bar_dark_unread_16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/linux/top_bar_dark_unread_16@2x.png -------------------------------------------------------------------------------- /src/assets/linux/top_bar_light_unread_16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/linux/top_bar_light_unread_16@2x.png -------------------------------------------------------------------------------- /src/assets/osx/menuIcons/MenuIcon16Template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/menuIcons/MenuIcon16Template.png -------------------------------------------------------------------------------- /src/assets/osx/menuIcons/MenuIcon20Template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/menuIcons/MenuIcon20Template.png -------------------------------------------------------------------------------- /src/assets/osx/menuIcons/MenuIcon24Template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/menuIcons/MenuIcon24Template.png -------------------------------------------------------------------------------- /src/assets/osx/menuIcons/MenuIcon32Template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/menuIcons/MenuIcon32Template.png -------------------------------------------------------------------------------- /src/assets/linux/top_bar_light_mention_16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/linux/top_bar_light_mention_16@2x.png -------------------------------------------------------------------------------- /src/assets/osx/menuIcons/MenuIcon16Template@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/menuIcons/MenuIcon16Template@2x.png -------------------------------------------------------------------------------- /src/assets/osx/menuIcons/MenuIcon20Template@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/menuIcons/MenuIcon20Template@2x.png -------------------------------------------------------------------------------- /src/assets/osx/menuIcons/MenuIcon24Template@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/menuIcons/MenuIcon24Template@2x.png -------------------------------------------------------------------------------- /src/assets/osx/menuIcons/MenuIcon32Template@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/menuIcons/MenuIcon32Template@2x.png -------------------------------------------------------------------------------- /src/renderer/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/renderer/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/renderer/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/renderer/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/renderer/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/renderer/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/renderer/assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/renderer/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/assets/osx/menuIcons/MenuIconUnread16Template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/menuIcons/MenuIconUnread16Template.png -------------------------------------------------------------------------------- /src/assets/osx/menuIcons/MenuIconUnread20Template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/menuIcons/MenuIconUnread20Template.png -------------------------------------------------------------------------------- /src/assets/osx/menuIcons/MenuIconUnread24Template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/menuIcons/MenuIconUnread24Template.png -------------------------------------------------------------------------------- /src/assets/osx/menuIcons/MenuIconUnread32Template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/menuIcons/MenuIconUnread32Template.png -------------------------------------------------------------------------------- /src/renderer/modals/urlView/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | -------------------------------------------------------------------------------- /src/assets/osx/menuIcons/MenuIconUnread16Template@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/menuIcons/MenuIconUnread16Template@2x.png -------------------------------------------------------------------------------- /src/assets/osx/menuIcons/MenuIconUnread20Template@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/menuIcons/MenuIconUnread20Template@2x.png -------------------------------------------------------------------------------- /src/assets/osx/menuIcons/MenuIconUnread24Template@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/menuIcons/MenuIconUnread24Template@2x.png -------------------------------------------------------------------------------- /src/assets/osx/menuIcons/MenuIconUnread32Template@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/osx/menuIcons/MenuIconUnread32Template@2x.png -------------------------------------------------------------------------------- /src/renderer/css/modals.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: transparent; 3 | } 4 | 5 | .btn-primary { 6 | background-color: #166de0; 7 | border-color: #166de0; 8 | } 9 | -------------------------------------------------------------------------------- /src/renderer/assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/renderer/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/renderer/assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/renderer/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/renderer/assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/renderer/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/renderer/assets/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/renderer/assets/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/types/args.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | export type Args = typeof global.args; 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Mattermost Desktop Application Changelog 2 | 3 | The Desktop App changelog has moved to the [Admin Documentation](https://docs.mattermost.com/help/apps/desktop-changelog.html). 4 | -------------------------------------------------------------------------------- /src/assets/titlebar/chrome-minimize.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/renderer/components/Header/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | export {default} from './Header'; 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/renderer/components/Carousel/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | export {default} from './Carousel'; 5 | -------------------------------------------------------------------------------- /src/assets/titlebar/chrome-maximize.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/renderer/components/LoadingScreen/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | export {default} from './LoadingScreen'; 5 | -------------------------------------------------------------------------------- /src/renderer/components/WelcomeScreen/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | export {default} from './WelcomeScreen'; 5 | -------------------------------------------------------------------------------- /src/renderer/components/LoadingAnimation/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | export {default} from './LoadingAnimation'; 5 | -------------------------------------------------------------------------------- /src/types/modals.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | export type ModalMessage = { 5 | type: string; 6 | data: T; 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/linux/ICONS.md: -------------------------------------------------------------------------------- 1 | ## Figma 2 | 3 | Canonical vector source images for icons can be found on [Figma](https://www.figma.com/file/7V9FN9l9c4zHIls2ZUCoPU/MM-30584-Desktop-App-Icon-Update?node-id=9%3A1170). The files on GitHub are exports. -------------------------------------------------------------------------------- /src/types/external/file-types.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | declare module '*.mp3'; 5 | declare module '*.svg'; 6 | declare module '*.lazy.css'; 7 | -------------------------------------------------------------------------------- /src/renderer/css/components/CarouselButton.scss: -------------------------------------------------------------------------------- 1 | @import '~@mattermost/compass-icons/css/compass-icons.css'; 2 | 3 | .CarouselButton { 4 | height: 32px; 5 | padding: 8px; 6 | 7 | &:focus { 8 | padding: 6px; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/types/trustedOrigin.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | export type PermissionType = 'canBasicAuth'; 5 | export type TrustedOrigin = Record; 6 | -------------------------------------------------------------------------------- /e2e/utils/constants.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | const MOCHAWESOME_REPORT_DIR = './mochawesome-report'; 5 | 6 | module.exports = { 7 | MOCHAWESOME_REPORT_DIR, 8 | }; 9 | -------------------------------------------------------------------------------- /src/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600.woff2 -------------------------------------------------------------------------------- /src/assets/titlebar/chrome-restore.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/types/mainWindow.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | export type SavedWindowState = Electron.Rectangle & { 5 | maximized: boolean; 6 | fullscreen: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-regular.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600italic.woff2 -------------------------------------------------------------------------------- /src/types/appState.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | export type AppState = { 5 | lastAppVersion?: string; 6 | skippedVersion?: string; 7 | updateCheckedDate?: string; 8 | }; 9 | -------------------------------------------------------------------------------- /src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300.woff2 -------------------------------------------------------------------------------- /src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600.woff2 -------------------------------------------------------------------------------- /src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-italic.woff2 -------------------------------------------------------------------------------- /src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-300italic.woff2 -------------------------------------------------------------------------------- /src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-600italic.woff2 -------------------------------------------------------------------------------- /src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edlerd/mattermost-desktop/master/src/renderer/assets/fonts/open-sans-v13-latin-ext_latin_cyrillic-ext_greek-ext_greek_cyrillic_vietnamese-regular.woff2 -------------------------------------------------------------------------------- /src/renderer/css/components/MainPage.css: -------------------------------------------------------------------------------- 1 | /*.mainPage,.mainPage > .container-fluid, .mainPage-viewsRow { 2 | height: 100%; 3 | }*/ 4 | 5 | .MainPage .HoveringURL { 6 | max-width: 95%; 7 | position: absolute; 8 | bottom: 0px; 9 | } 10 | 11 | div[id*="-permissionDialog"] { 12 | max-width: 350px; 13 | } 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/troubleshooting-question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Troubleshooting question 3 | about: Ask a question to solve installation and configuration issues 4 | 5 | --- 6 | 7 | For troubleshooting, see [https://docs.mattermost.com/install/troubleshooting.html](https://docs.mattermost.com/install/troubleshooting.html). 8 | -------------------------------------------------------------------------------- /src/renderer/css/components/UpdaterPage.css: -------------------------------------------------------------------------------- 1 | .UpdaterPage-heading { 2 | font-size: 20pt; 3 | margin: 10px 0px; 4 | } 5 | 6 | .UpdaterPage-skipButton { 7 | padding-left: 0px; 8 | } 9 | 10 | .UpdaterPage-footer { 11 | padding: 1em 0; 12 | } 13 | 14 | .UpdaterPage .progress-bar { 15 | min-width: 2em; 16 | } 17 | -------------------------------------------------------------------------------- /src/types/auth.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import {AuthenticationResponseDetails, AuthInfo} from 'electron/common'; 5 | 6 | export type LoginModalData = { 7 | request: AuthenticationResponseDetails; 8 | authInfo: AuthInfo; 9 | } 10 | -------------------------------------------------------------------------------- /src/.storybook/config.js: -------------------------------------------------------------------------------- 1 | import {configure} from '@storybook/react'; 2 | import 'bootstrap/dist/css/bootstrap.min.css'; 3 | 4 | const req = require.context('../browser/components', true, /\.stories\.jsx$/) 5 | 6 | function loadStories() { 7 | req.keys().forEach((filename) => req(filename)) 8 | } 9 | 10 | configure(loadStories, module); 11 | -------------------------------------------------------------------------------- /entitlements.mas.inherit.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.inherit 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint", 6 | "msjsdiag.debugger-for-chrome", 7 | "streetsidesoftware.code-spell-checker", 8 | "lokalise.i18n-ally", 9 | "EditorConfig.EditorConfig" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | .DS_Store 3 | *.log 4 | 5 | node_modules/ 6 | *_bundle.js 7 | release*/ 8 | npm-debug.log* 9 | 10 | build/ 11 | coverage/ 12 | dist/ 13 | mochawesome-report/ 14 | 15 | test-results.xml 16 | test_config.json 17 | .idea 18 | testUserData 19 | 20 | .gitattributes 21 | 22 | fastlane/README.md 23 | fastlane/report.xml 24 | 25 | *.provisionprofile 26 | -------------------------------------------------------------------------------- /src/renderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= htmlWebpackPlugin.options.title %> 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /src/types/notification.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import {NotificationConstructorOptions} from 'electron/common'; 5 | 6 | export type MentionData = { 7 | soundName: string; 8 | } 9 | 10 | export type MentionOptions = NotificationConstructorOptions & { 11 | data: MentionData; 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "i18n-ally.localesPaths": [ 3 | "i18n" 4 | ], 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll.eslint": true 7 | }, 8 | "cSpell.words": [ 9 | "browserview", 10 | "deauthorize", 11 | "loadscreen", 12 | "Ochiai", 13 | "UNCLOSEABLE", 14 | "webcontents", 15 | "Yuya" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/common/permissions.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | // Permission types that can be requested 5 | export const BASIC_AUTH_PERMISSION = 'canBasicAuth'; 6 | 7 | // Permission descriptions 8 | export const PERMISSION_DESCRIPTION = { 9 | [BASIC_AUTH_PERMISSION]: 'common.permissions.canBasicAuth', 10 | }; 11 | -------------------------------------------------------------------------------- /e2e/modules/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mattermost Desktop testing html 5 | 6 | 7 | 8 |

window.open() test

9 |

10 | 11 |

12 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /e2e/modules/utils.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | 5 | function asyncSleep(timeout) { 6 | return new Promise((resolve) => { 7 | setTimeout(() => { 8 | resolve(); 9 | }, timeout); 10 | }); 11 | } 12 | 13 | module.exports = { 14 | asyncSleep, 15 | }; 16 | -------------------------------------------------------------------------------- /src/types/server.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | export type RemoteInfo = { 5 | name: string; 6 | serverVersion?: string; 7 | siteURL?: string; 8 | hasFocalboard?: boolean; 9 | hasPlaybooks?: boolean; 10 | }; 11 | 12 | export type ClientConfig = { 13 | Version: string; 14 | SiteURL: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/assets/titlebar/chrome-close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/types/certificate.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import {Certificate} from 'electron/common'; 5 | 6 | export type ComparableCertificate = { 7 | data: string; 8 | issuerName: string; 9 | dontTrust: boolean; 10 | } 11 | 12 | export type CertificateModalData = { 13 | url: string; 14 | list: Certificate[]; 15 | } 16 | -------------------------------------------------------------------------------- /e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "open_window": true 7 | }, 8 | "rules": { 9 | "import/no-commonjs": 0, 10 | "func-names": 0, 11 | "global-require": 0, 12 | "max-nested-callbacks": 0, 13 | "no-eval": 0, 14 | "no-magic-numbers": 0, 15 | "no-unused-expressions": 0, 16 | "prefer-arrow-callback": 0 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/renderer/css/components/index.css: -------------------------------------------------------------------------------- 1 | @import url("ErrorView.css"); 2 | @import url("HoveringURL.css"); 3 | @import url("MainPage.css"); 4 | @import url("MattermostView.css"); 5 | @import url("NewTeamModal.css"); 6 | @import url("PermissionRequestDialog.css"); 7 | @import url("TabBar.css"); 8 | @import url("UpdaterPage.css"); 9 | @import url("CertificateModal.css"); 10 | @import url("ExtraBar.css"); 11 | @import url("LoadingScreen.css"); 12 | @import url("LoadingAnimation.css"); 13 | -------------------------------------------------------------------------------- /src/types/utils.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | export type ServerFromURL = { 5 | name: string; 6 | url: string; 7 | } 8 | 9 | export type Boundaries = { 10 | maxX: number; 11 | maxY: number; 12 | minX: number; 13 | minY: number; 14 | maxWidth: number; 15 | maxHeight: number; 16 | } 17 | 18 | export type DeepPartial = { 19 | [P in keyof T]?: DeepPartial; 20 | } 21 | -------------------------------------------------------------------------------- /src/types/global.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | declare namespace NodeJS { 5 | export interface Global { 6 | willAppQuit: boolean; 7 | isDev: boolean; 8 | args: { 9 | hidden?: boolean; 10 | disableDevMode?: boolean; 11 | dataDir?: string; 12 | version?: boolean; 13 | fullscreen?: boolean; 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/assets/icon-session-expired.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.config/notice-file/config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: "Mattermost Desktop" 4 | copyright: "© 2016-2017 Mattermost, Inc. All Rights Reserved. See LICENSE.txt for license information." 5 | description: "This document includes a list of open source components used in Mattermost Desktop, including those that have been modified." 6 | reviewers: 7 | - mattermost/release-managers 8 | - mattermost/team-desktop 9 | search: 10 | - "package.json" 11 | dependencies: 12 | - "wix" 13 | devDependencies: [] 14 | 15 | ... 16 | -------------------------------------------------------------------------------- /src/assets/linux/create_desktop_file.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | WORKING_DIR=`pwd` 4 | THIS_PATH=`readlink -f $0` 5 | cd `dirname ${THIS_PATH}` 6 | FULL_PATH=`pwd` 7 | cd ${WORKING_DIR} 8 | cat < Mattermost.desktop 9 | [Desktop Entry] 10 | Name=Mattermost 11 | Comment=Mattermost Desktop application for Linux 12 | Exec="${FULL_PATH}/mattermost-desktop" 13 | Terminal=false 14 | Type=Application 15 | Icon=${FULL_PATH}/app_icon.png 16 | Categories=Network;InstantMessaging; 17 | EOS 18 | chmod +x Mattermost.desktop 19 | -------------------------------------------------------------------------------- /src/main/app/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | /* istanbul ignore file */ 5 | 6 | import {initialize} from './initialize'; 7 | 8 | if (process.env.NODE_ENV !== 'production' && module.hot) { 9 | module.hot.accept(); 10 | } 11 | 12 | // attempt to initialize the application 13 | try { 14 | initialize(); 15 | } catch (error: any) { 16 | throw new Error(`App initialization failed: ${error.toString()}`); 17 | } 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | Suggest features in the Mattermost feature idea forum so it can be discussed, upvoted and considered for a [help wanted ticket](https://docs.mattermost.com/process/help-wanted.html). 8 | 9 | [https://mattermost.uservoice.com](https://mattermost.uservoice.com/forums/306457-general?category_id=159795) 10 | 11 | You get 10 votes in the feature idea forum, and each one influences the future of the project. 12 | -------------------------------------------------------------------------------- /src/common/servers/MattermostServer.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import urlUtils from 'common/utils/url'; 5 | 6 | export class MattermostServer { 7 | name: string; 8 | url: URL; 9 | constructor(name: string, serverUrl: string) { 10 | this.name = name; 11 | this.url = urlUtils.parseURL(serverUrl)!; 12 | if (!this.url) { 13 | throw new Error('Invalid url for creating a server'); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/common/tabs/MessagingTabView.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import BaseTabView from './BaseTabView'; 5 | import {TabType, TAB_MESSAGING} from './TabView'; 6 | 7 | export default class MessagingTabView extends BaseTabView { 8 | get url(): URL { 9 | return this.server.url; 10 | } 11 | 12 | get type(): TabType { 13 | return TAB_MESSAGING; 14 | } 15 | 16 | get shouldNotify(): boolean { 17 | return true; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/upgrade-dependencies.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Upgrade dependencies 3 | about: A checklist to upgrade dependencies after cutting release 4 | 5 | --- 6 | 7 | ### package.json 8 | 9 | #### Electron 10 | - [ ] Electron and Spectron 11 | - [ ] electron-builder 12 | 13 | #### Build tools 14 | - [ ] Babel 15 | - [ ] Webpack 16 | 17 | #### Test tools 18 | - [ ] ESLint 19 | - [ ] Other test tools 20 | 21 | #### Others 22 | - [ ] Other devDependencies 23 | 24 | ### src/package.json 25 | 26 | #### React 27 | - [ ] React 28 | - [ ] React-Bootstrap 29 | 30 | #### Others 31 | - [ ] Other dependencies 32 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Build sources", 8 | "type": "npm", 9 | "script": "build", 10 | "problemMatcher": [] 11 | }, 12 | { 13 | "label": "prepare-e2e", 14 | "type": "shell", 15 | "command": "npm run build; npm run build-robotjs; npm run test:e2e:build", 16 | "problemMatcher": [] 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/common/tabs/FocalboardTabView.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import {getFormattedPathName} from 'common/utils/url'; 5 | 6 | import BaseTabView from './BaseTabView'; 7 | import {TabType, TAB_FOCALBOARD} from './TabView'; 8 | 9 | export default class FocalboardTabView extends BaseTabView { 10 | get url(): URL { 11 | return new URL(`${this.server.url.origin}${getFormattedPathName(this.server.url.pathname)}boards`); 12 | } 13 | 14 | get type(): TabType { 15 | return TAB_FOCALBOARD; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/common/tabs/PlaybooksTabView.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import {getFormattedPathName} from 'common/utils/url'; 5 | 6 | import BaseTabView from './BaseTabView'; 7 | import {TabType, TAB_PLAYBOOKS} from './TabView'; 8 | 9 | export default class PlaybooksTabView extends BaseTabView { 10 | get url(): URL { 11 | return new URL(`${this.server.url.origin}${getFormattedPathName(this.server.url.pathname)}playbooks`); 12 | } 13 | 14 | get type(): TabType { 15 | return TAB_PLAYBOOKS; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.device.microphone 6 | 7 | com.apple.security.device.camera 8 | 9 | com.apple.security.device.audio-input 10 | 11 | com.apple.security.cs.allow-jit 12 | 13 | com.apple.security.cs.allow-unsigned-executable-memory 14 | 15 | com.apple.security.cs.allow-dyld-environment-variables 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/common/utils/requests.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import * as http from 'http'; 5 | import * as https from 'https'; 6 | 7 | type Method = 'GET' | 'POST' | 'DELETE' | 'PUT' | 'OPTIONS' | 'HEAD'; 8 | 9 | export async function ping(url: URL, method: Method = 'GET'): Promise { 10 | const f = url.protocol === 'http:' ? http.request : https.request; 11 | return new Promise((yes, no) => { 12 | const req = f(url, {method}); 13 | req.on('error', no); 14 | req.on('response', yes); 15 | req.end(); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /src/renderer/css/components/ErrorView.css: -------------------------------------------------------------------------------- 1 | .ErrorView { 2 | position: absolute; 3 | top: 32px; 4 | right: 0px; 5 | bottom: 0px; 6 | left: 0px; 7 | } 8 | 9 | .ErrorView-hidden { 10 | visibility: hidden; 11 | } 12 | 13 | .ErrorView-tableStyle { 14 | display: table; 15 | width: 100%; 16 | height: 100%; 17 | position: absolute; 18 | top: 0; 19 | left: 0; 20 | } 21 | 22 | .ErrorView-cellStyle { 23 | display: table-cell; 24 | vertical-align: top; 25 | padding-top: 2em; 26 | } 27 | 28 | .ErrorView-bullets { 29 | padding-left: 15px; 30 | line-height: 1.7; 31 | } 32 | 33 | .ErrorView-techInfo { 34 | font-size: 12px; 35 | color: #aaa; 36 | } 37 | -------------------------------------------------------------------------------- /scripts/patch_updater_yml.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | VERSION="$(jq -r '.version' /dev/null; then 16 | for i in ./release/${RELEASE_VERSION}*.yml; do 17 | VERSION=$VERSION yq eval -i '.files[].url |= strenv(VERSION) + "/" + .' $i 18 | done 19 | fi 20 | 21 | -------------------------------------------------------------------------------- /scripts/extract_dict.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | 5 | // This file uses process.exit(). 6 | /* eslint-disable no-process-exit */ 7 | 8 | const {spawn} = require('child_process'); 9 | 10 | const {path7za} = require('7zip-bin'); 11 | 12 | const cwd = process.argv[2]; 13 | 14 | spawn(path7za, ['e', '-y', '*.zip'], { 15 | cwd, 16 | stdio: 'inherit', 17 | }).on('error', (err) => { 18 | console.error(err); 19 | process.exit(1); 20 | }).on('close', (code) => { 21 | process.exit(code); 22 | }); 23 | 24 | /* eslint-enable no-process-exit */ 25 | -------------------------------------------------------------------------------- /src/renderer/css/components/MattermostView.css: -------------------------------------------------------------------------------- 1 | .mattermostView { 2 | text-align: center; 3 | z-index: 1; 4 | opacity: 1; 5 | visibility: visible; 6 | } 7 | 8 | .mattermostView-hidden { 9 | z-index: -1; 10 | opacity: 0; 11 | visibility: hidden; 12 | } 13 | 14 | .mattermostView .ErrorView { 15 | text-align: left; 16 | } 17 | 18 | .mattermostView webview { 19 | position: absolute; 20 | top: 40px; 21 | right: 0px; 22 | bottom: 0px; 23 | left: 0px; 24 | } 25 | 26 | .mattermostView-hidden webview { 27 | visibility: hidden; 28 | z-index: -1; 29 | } 30 | 31 | .mattermostView-error webview { 32 | z-index: -1; 33 | } 34 | 35 | .allow-extra-bar webview { 36 | top: 76px; 37 | } 38 | -------------------------------------------------------------------------------- /src/main/preload/urlView.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | // Copyright (c) 2015-2016 Yuya Ochiai 4 | 5 | 'use strict'; 6 | 7 | import {ipcRenderer} from 'electron'; 8 | 9 | import {UPDATE_URL_VIEW_WIDTH} from 'common/communication'; 10 | 11 | console.log('preloaded for the url view'); 12 | 13 | window.addEventListener('message', async (event) => { 14 | switch (event.data.type) { 15 | case UPDATE_URL_VIEW_WIDTH: 16 | ipcRenderer.send(UPDATE_URL_VIEW_WIDTH, event.data.data); 17 | break; 18 | default: 19 | console.log(`got a message: ${event}`); 20 | console.log(event); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /src/renderer/css/components/ExtraBar.css: -------------------------------------------------------------------------------- 1 | #extra-bar { 2 | max-height: 76px; 3 | transition: max-height 0.25s ease; 4 | background-color: #f5f5f5; 5 | -webkit-font-smoothing: antialiased; 6 | } 7 | 8 | #extra-bar div { 9 | padding: 0 0.93em 0.2em; 10 | } 11 | 12 | #extra-bar.hidden { 13 | display: none; 14 | } 15 | 16 | span.backLabel { 17 | font-family: "Open Sans", sans-serif; 18 | font-weight: normal; 19 | line-height: 30px; 20 | font-size: 14px; 21 | padding-top: 0px; 22 | padding-bottom: 0px; 23 | color: #166de0; 24 | } 25 | 26 | span.backIcon { 27 | margin-right: 4px; 28 | } 29 | 30 | .container-fluid button:first-child { 31 | padding-left: 0px; 32 | } 33 | -------------------------------------------------------------------------------- /src/renderer/modals/urlView/urlView.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import 'renderer/css/components/HoveringURL.css'; 5 | 6 | const queryString = window.location.search; 7 | const urlParams = new URLSearchParams(queryString); 8 | 9 | import React from 'react'; 10 | import ReactDOM from 'react-dom'; 11 | 12 | import UrlDescription from '../../components/urlDescription'; 13 | 14 | const start = async () => { 15 | ReactDOM.render( 16 | , 19 | document.getElementById('app'), 20 | ); 21 | }; 22 | 23 | start(); 24 | -------------------------------------------------------------------------------- /src/.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | const rendererConfig = require('../../webpack.config.renderer'); 4 | 5 | // https://storybook.js.org/configurations/custom-webpack-config/#full-control-mode--default 6 | module.exports = (storybookBaseConfig, configType) => { 7 | // Avoid conflicting two instances of React due to two package.json structure 8 | storybookBaseConfig.resolve.modules.unshift(path.resolve(__dirname, '../node_modules')); 9 | 10 | // Use same rules 11 | storybookBaseConfig.module.rules = rendererConfig.module.rules.concat({ 12 | test: /\.(ttf|woff2?|eot|svg)/, 13 | use: { 14 | loader: 'file-loader' 15 | } 16 | }); 17 | return storybookBaseConfig; 18 | } 19 | -------------------------------------------------------------------------------- /scripts/msi_installer_files_set_win64.xslt: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | yes 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/renderer/modals/darkMode.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import {DARK_MODE_CHANGE, GET_DARK_MODE} from 'common/communication'; 5 | 6 | import darkStyles from 'renderer/css/lazy/modals-dark.lazy.css'; 7 | 8 | export default function addDarkModeListener() { 9 | window.addEventListener('message', async (event) => { 10 | if (event.data.type === DARK_MODE_CHANGE) { 11 | if (event.data.data) { 12 | darkStyles.use(); 13 | } else { 14 | darkStyles.unuse(); 15 | } 16 | } 17 | }); 18 | window.postMessage({type: GET_DARK_MODE}, window.location.href); 19 | } 20 | -------------------------------------------------------------------------------- /src/renderer/css/lazy/settings-dark.lazy.css: -------------------------------------------------------------------------------- 1 | @import '~bootstrap-dark/src/bootstrap-dark.css'; 2 | 3 | .TeamListItem:hover { 4 | background: #242a30; 5 | } 6 | 7 | .SettingsPage__spellCheckerLocalesDropdown .SettingsPage__spellCheckerLocalesDropdown__control { 8 | background: #242a30; 9 | } 10 | 11 | .SettingsPage__spellCheckerLocalesDropdown .SettingsPage__spellCheckerLocalesDropdown__menu { 12 | background: #242a30; 13 | } 14 | 15 | .SettingsPage__spellCheckerLocalesDropdown .SettingsPage__spellCheckerLocalesDropdown__option { 16 | background: #242a30; 17 | } 18 | 19 | .SettingsPage__spellCheckerLocalesDropdown .SettingsPage__spellCheckerLocalesDropdown__option:hover { 20 | background: rgba(255, 255, 255, 0.16); 21 | } 22 | -------------------------------------------------------------------------------- /src/types/window.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import {ipcRenderer} from 'electron/renderer'; 5 | 6 | declare global { 7 | interface Window { 8 | ipcRenderer: { 9 | send: typeof ipcRenderer.send; 10 | on: (channel: string, listener: (...args: any[]) => void) => void; 11 | invoke: typeof ipcRenderer.invoke; 12 | }; 13 | process: { 14 | platform: NodeJS.Platform; 15 | env: { 16 | user?: string; 17 | username?: string; 18 | }; 19 | }; 20 | timers: { 21 | setImmediate: typeof setImmediate; 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /scripts/patch_mas_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | STABLE_VERSION=$(./node_modules/.bin/semver $(jq -r .version package.json) -c) 6 | BUILD_VERSION=$(jq -r .version package.json | sed "s/$STABLE_VERSION-.*\.//g") 7 | 8 | if [ "$BUILD_VERSION" == "" ]; then 9 | BUILD_VERSION=$STABLE_VERSION 10 | fi 11 | 12 | if [ "$CIRCLE_BUILD_NUM" != "" ]; then 13 | BUILD_VERSION=$CIRCLE_BUILD_NUM 14 | fi 15 | 16 | temp_file="$(mktemp -t electron-builder.json)" 17 | jq -r --arg version "$STABLE_VERSION" '.mac.bundleShortVersion = $version' electron-builder.json > "${temp_file}" && mv "${temp_file}" electron-builder.json 18 | jq -r --arg version "$BUILD_VERSION" '.mac.bundleVersion = $version' electron-builder.json > "${temp_file}" && mv "${temp_file}" electron-builder.json -------------------------------------------------------------------------------- /src/renderer/css/components/HoveringURL.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | .HoveringURL { 6 | position: fixed; 7 | bottom: 0; 8 | left: 0; 9 | color: gray; 10 | background-color: whitesmoke; 11 | white-space: nowrap; 12 | overflow: hidden; 13 | text-overflow: ellipsis; 14 | padding-left: 0px; 15 | padding-right: 16px; 16 | padding-top: 2px; 17 | padding-bottom: 2px; 18 | border-top: solid thin lightgray; 19 | pointer-events: none; 20 | margin: 0; 21 | } 22 | 23 | .HoveringURL-left { 24 | border-top-right-radius: 4px; 25 | border-right: solid thin lightgray; 26 | } 27 | 28 | .HoveringURL p { 29 | margin: 0; 30 | font-size: small; 31 | font-family: "Open Sans", sans-serif; 32 | padding-left: 4px; 33 | padding-right: 4px; 34 | } 35 | -------------------------------------------------------------------------------- /scripts/msi_installer_files_replace_id.xslt: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | MattermostDesktopEXE 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/common/config/migrationPreferences.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import {Config, MigrationInfo} from 'types/config'; 5 | 6 | import JsonFileManager from 'common/JsonFileManager'; 7 | 8 | import {migrationInfoPath} from 'main/constants'; 9 | 10 | export default function migrateConfigItems(config: Config) { 11 | const migrationPrefs = new JsonFileManager(migrationInfoPath); 12 | let didMigrate = false; 13 | 14 | if (!migrationPrefs.getValue('updateTrayIconWin32') && process.platform === 'win32') { 15 | config.trayIconTheme = 'use_system'; 16 | migrationPrefs.setValue('updateTrayIconWin32', true); 17 | didMigrate = true; 18 | } 19 | 20 | return didMigrate; 21 | } 22 | -------------------------------------------------------------------------------- /scripts/check_build_config.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | const buildConfig = require('../dist/buildConfig'); 5 | 6 | function validateBuildConfig(config) { 7 | if (config.enableServerManagement === false && config.defaultTeams && config.defaultTeams.length === 0) { 8 | return { 9 | result: false, 10 | message: `Specify at least one server for "defaultTeams" in buildConfig.js when "enableServerManagement is set to false.\n${JSON.stringify(config, null, 2)}`, 11 | }; 12 | } 13 | return {result: true}; 14 | } 15 | 16 | const ret = validateBuildConfig(buildConfig); 17 | if (ret.result === false) { 18 | throw new Error(ret.message); 19 | } 20 | -------------------------------------------------------------------------------- /src/jestSetup.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | jest.mock('main/constants', () => ({ 5 | configPath: 'configPath', 6 | allowedProtocolFile: 'allowedProtocolFile', 7 | appVersionJson: 'appVersionJson', 8 | certificateStorePath: 'certificateStorePath', 9 | trustedOriginsStoreFile: 'trustedOriginsStoreFile', 10 | boundsInfoPath: 'boundsInfoPath', 11 | 12 | updatePaths: jest.fn(), 13 | })); 14 | 15 | jest.mock('electron-log', () => ({ 16 | error: jest.fn(), 17 | warn: jest.fn(), 18 | info: jest.fn(), 19 | verbose: jest.fn(), 20 | debug: jest.fn(), 21 | silly: jest.fn(), 22 | transports: { 23 | file: { 24 | level: '', 25 | }, 26 | }, 27 | })); 28 | 29 | -------------------------------------------------------------------------------- /src/renderer/css/settings.css: -------------------------------------------------------------------------------- 1 | .CloseButton:hover span { 2 | color: #333; 3 | } 4 | 5 | .IndicatorContainer { 6 | display: inline-block; 7 | padding: 1em; 8 | } 9 | 10 | .AutoSaveIndicator { 11 | padding: 5px 15px; 12 | margin: 0 0 0 10px; 13 | } 14 | 15 | .AutoSaveIndicator.AutoSaveIndicator-Leave { 16 | opacity: 0; 17 | transition: opacity 1s cubic-bezier(0.19, 1, 0.22, 1); 18 | } 19 | 20 | .checkbox > label { 21 | width: 100%; 22 | } 23 | 24 | body { 25 | overflow-x: hidden; 26 | overflow-y: scroll; 27 | height: 100%; 28 | } 29 | 30 | #content { 31 | height: 100%; 32 | } 33 | 34 | .btn-primary { 35 | background-color: #166de0; 36 | border-color: #166de0; 37 | } 38 | 39 | .SettingsPage__spellCheckerLocalesDropdown { 40 | margin-top: 8px; 41 | margin-left: 16px; 42 | width: 50%; 43 | } 44 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | 5 | module.exports = (api) => { // eslint-disable-line import/no-commonjs 6 | api.cache.forever(); 7 | return { 8 | presets: [ 9 | ['@babel/preset-env', { 10 | targets: { 11 | browsers: ['Electron >= 2.0'], 12 | node: '8.9', 13 | }, 14 | }], 15 | '@babel/preset-react', 16 | ['@babel/typescript', { 17 | allExtensions: true, 18 | isTSX: true, 19 | }], 20 | ], 21 | plugins: ['@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-class-properties'], 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /src/renderer/components/SaveButton/LoadingWrapper.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import React from 'react'; 5 | 6 | import LoadingSpinner from './LoadingSpinner'; 7 | 8 | type Props = { 9 | loading: boolean; 10 | text: React.ReactNode; 11 | children: React.ReactNode; 12 | } 13 | 14 | export default class LoadingWrapper extends React.PureComponent { 15 | public static defaultProps: Props = { 16 | loading: true, 17 | text: null, 18 | children: null, 19 | } 20 | 21 | public render() { 22 | const {text, loading, children} = this.props; 23 | if (!loading) { 24 | return children; 25 | } 26 | 27 | return ; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/renderer/components/SaveButton/LoadingSpinner.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import React from 'react'; 5 | 6 | import 'renderer/css/components/LoadingSpinner.scss'; 7 | 8 | type Props = { 9 | text: React.ReactNode; 10 | } 11 | 12 | export default class LoadingSpinner extends React.PureComponent { 13 | public static defaultProps: Props = { 14 | text: null, 15 | } 16 | 17 | public render() { 18 | return ( 19 | 23 | 24 | {this.props.text} 25 | 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Before submitting, please confirm you've 2 | - [ ] read and understood our [Contributing Guidelines](https://github.com/mattermost/desktop/blob/master/CONTRIBUTING.md) 3 | - [ ] completed [Mattermost Contributor Agreement](https://mattermost.com/contribute/) 4 | - [ ] executed `npm run lint:js` for proper code formatting 5 | - [ ] executed `npm run check-types` for proper type checking 6 | - [ ] executed `npm run test` to ensure all automated tests pass 7 | 8 | Please provide the following information: 9 | 10 | **Summary** 11 | 14 | 15 | **Issue link** 16 | 19 | 20 | **Test Cases** 21 | 22 | **Additional Notes** 23 | -------------------------------------------------------------------------------- /src/renderer/components/urlDescription.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import React, {useEffect} from 'react'; 5 | 6 | import {UPDATE_URL_VIEW_WIDTH} from 'common/communication'; 7 | 8 | export default function UrlDescription(props: {url: string}) { 9 | const urlRef = React.createRef(); 10 | 11 | useEffect(() => { 12 | window.postMessage({type: UPDATE_URL_VIEW_WIDTH, data: urlRef.current?.scrollWidth}, window.location.href); 13 | }, []); 14 | 15 | if (props.url) { 16 | return ( 17 |
21 |

{props.url}

22 |
23 | ); 24 | } 25 | 26 | return null; 27 | } 28 | -------------------------------------------------------------------------------- /src/renderer/css/components/Header.scss: -------------------------------------------------------------------------------- 1 | @import url("../_css_variables.scss"); 2 | 3 | .Header { 4 | position: relative; 5 | display: flex; 6 | width: 100%; 7 | min-height: 100px; 8 | padding: 0 40px; 9 | 10 | .Header__main { 11 | display: flex; 12 | width: 100%; 13 | height: 100%; 14 | flex-flow: wrap; 15 | align-items: center; 16 | justify-content: space-between; 17 | 18 | .Header__logo { 19 | width: 170px; 20 | 21 | path { 22 | fill: var(--center-channel-text); 23 | } 24 | } 25 | } 26 | 27 | &.Header--darkMode { 28 | .Header__main { 29 | .Header__logo { 30 | path { 31 | fill: var(--button-color); 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "strictNullChecks": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "module": "commonjs", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "noEmit": true, 20 | "experimentalDecorators": true, 21 | "jsx": "react", 22 | "baseUrl": "./src", 23 | }, 24 | "include": [ 25 | "./src/**/*" 26 | ], 27 | "exclude": [ 28 | "dist", 29 | "e2e", 30 | "!node_modules/@types", 31 | "storybook-static", 32 | "coverage" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /scripts/afterbuild.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | const {spawnSync} = require('child_process'); 5 | 6 | const {path7za} = require('7zip-bin'); 7 | 8 | const windowsZipRegex = /win-[A-Za-z0-9]+\.zip$/g; 9 | 10 | async function removeAppUpdate(path) { 11 | const result = await spawnSync(path7za, ['d', path, 'resources/app-update.yml', '-r']); 12 | if (result.error) { 13 | throw new Error( 14 | `Failed to remove files from ${path}: ${result.error} ${result.stderr} ${result.stdout}`, 15 | ); 16 | } 17 | } 18 | 19 | exports.default = async function afterAllArtifactBuild(context) { 20 | await context.artifactPaths.forEach(async (path) => { 21 | if (path.match(windowsZipRegex)) { 22 | await removeAppUpdate(path); 23 | } 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /src/renderer/css/components/LoadingSpinner.scss: -------------------------------------------------------------------------------- 1 | @import url("../fonts.css"); 2 | 3 | @keyframes spin-fw { 4 | 0% { 5 | transform: rotate(0deg); 6 | } 7 | 100% { 8 | transform: rotate(359deg); 9 | } 10 | } 11 | 12 | .LoadingSpinner { 13 | .spinner { 14 | margin-right: 3px; 15 | animation: spin-fw 1s infinite steps(8); 16 | width: 1.28571429em; 17 | text-align: center; 18 | display: inline-block; 19 | font-family: 'FontAwesome'; 20 | font-size: inherit; 21 | text-rendering: auto; 22 | -webkit-font-smoothing: antialiased; 23 | 24 | &::before { 25 | content: '\f110'; 26 | display: inline-block; 27 | box-sizing: border-box; 28 | } 29 | } 30 | 31 | &.with-text { 32 | .spinner { 33 | margin-right: 5px; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /scripts/beforepack.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | const {exec} = require('child_process'); 5 | 6 | exports.default = async function beforePack(context) { 7 | return new Promise((resolve, reject) => { 8 | const arch = getArch(context.arch); 9 | exec(`npm run postinstall -- --arch ${arch}`, (error) => { 10 | if (error) { 11 | reject(error); 12 | } else { 13 | resolve(); 14 | } 15 | }); 16 | }); 17 | }; 18 | 19 | function getArch(arch) { 20 | switch (arch) { 21 | case 0: 22 | return 'ia32'; 23 | case 1: 24 | return 'x64'; 25 | case 2: 26 | return 'armv7l'; 27 | case 3: 28 | return 'arm64'; 29 | case 4: 30 | return 'universal'; 31 | default: 32 | return ''; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/renderer/components/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import React from 'react'; 5 | import classNames from 'classnames'; 6 | 7 | import Logo from 'renderer/components/Logo'; 8 | 9 | import 'renderer/css/components/Header.scss'; 10 | 11 | type HeaderProps = { 12 | alternateLink?: React.ReactElement; 13 | darkMode?: boolean; 14 | } 15 | 16 | const Header = ({ 17 | alternateLink, 18 | darkMode, 19 | }: HeaderProps) => ( 20 |
26 |
27 |
28 | 29 |
30 | {alternateLink} 31 |
32 |
33 | ); 34 | 35 | export default Header; 36 | -------------------------------------------------------------------------------- /src/types/external/winreg-utf8.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | declare module 'winreg-utf8' { 5 | import WindowsRegistry from 'winreg'; 6 | export = WindowsRegistry; 7 | } 8 | 9 | declare namespace Winreg { 10 | export interface Options { 11 | 12 | /** 13 | * Optional hostname, must start with '\\' sequence. 14 | */ 15 | host?: string; 16 | 17 | /** 18 | * Optional hive ID, default is HKLM. 19 | */ 20 | hive?: string; 21 | 22 | /** 23 | * Optional key, default is the root key. 24 | */ 25 | key?: string; 26 | 27 | /** 28 | * Optional registry hive architecture ('x86' or 'x64'; only valid on Windows 64 Bit Operating Systems). 29 | */ 30 | arch?: string; 31 | 32 | utf8?: boolean; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/renderer/css/_css_variables.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --away-indicator-dark: #c79e3f; 3 | --button-bg: #166de0; 4 | --button-color: #fff; 5 | --center-channel-bg: #fff; 6 | --center-channel-color: #3d3c40; 7 | --center-channel-text: #3f4350; 8 | --error-text: #d24b4e; 9 | --link-color: #2389d7; 10 | --online-indicator: #06d6a0; 11 | --sidebar-text-active-border: #579eff; 12 | --denim-button-bg: #1c58d9; 13 | --denim-sidebar-active-border: #5d89ea; 14 | --denim-sidebar-header-bg: #192A4D; 15 | --title-color-indigo-500: #1e325c; 16 | 17 | --button-color-rgb: 255, 255, 255; 18 | --center-channel-color-rgb: 61, 60, 64; 19 | --center-channel-text-rgb: 63, 67, 80; 20 | --link-color-inverted-rgb: 129, 163, 239; 21 | --denim-button-bg-rgb: 28, 88, 217; 22 | --denim-sidebar-header-bg-rgb: 25, 42, 77; 23 | --secondary-blue-rgb: 34, 64, 109; 24 | --onyx-center-channel-text-rgb: 221, 223, 228; 25 | 26 | --elevation-5: 0 12px 32px 0 rgba(0, 0, 0, 0.12); 27 | } 28 | -------------------------------------------------------------------------------- /src/common/tabs/BaseTabView.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import {Tuple as tuple} from '@bloomberg/record-tuple-polyfill'; 5 | 6 | import {MattermostServer} from 'common/servers/MattermostServer'; 7 | 8 | import {getTabViewName, TabType, TabView, TabTuple} from './TabView'; 9 | 10 | export default abstract class BaseTabView implements TabView { 11 | server: MattermostServer; 12 | 13 | constructor(server: MattermostServer) { 14 | this.server = server; 15 | } 16 | get name(): string { 17 | return getTabViewName(this.server.name, this.type); 18 | } 19 | get urlTypeTuple(): TabTuple { 20 | return tuple(this.server.url.href, this.type) as TabTuple; 21 | } 22 | get url(): URL { 23 | throw new Error('Not implemented'); 24 | } 25 | get type(): TabType { 26 | throw new Error('Not implemented'); 27 | } 28 | get shouldNotify(): boolean { 29 | return false; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /scripts/watch_main_and_preload.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | const webpack = require('webpack'); 5 | const electron = require('electron-connect').server.create({path: 'dist/'}); 6 | 7 | const mainConfig = require('../webpack.config.main.js'); 8 | const rendererConfig = require('../webpack.config.renderer.js'); 9 | 10 | let started = false; 11 | 12 | const mainCompiler = webpack(mainConfig); 13 | mainCompiler.watch({}, (err, stats) => { 14 | process.stdout.write(stats.toString({colors: true})); 15 | process.stdout.write('\n'); 16 | if (!stats.hasErrors()) { 17 | if (started) { 18 | electron.restart(); 19 | } else { 20 | electron.start(); 21 | started = true; 22 | } 23 | } 24 | }); 25 | 26 | const preloadCompiler = webpack(rendererConfig); 27 | preloadCompiler.watch({}, (err) => { 28 | if (err) { 29 | console.log(err); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /src/renderer/components/Button/Button.stories.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | 5 | import React from 'react'; 6 | import {storiesOf} from '@storybook/react'; 7 | 8 | import {action} from '@storybook/addon-actions'; 9 | import {Button, ButtonToolbar} from 'react-bootstrap'; 10 | 11 | storiesOf('Button', module). 12 | add('bsStyle', () => ( 13 | 14 | 15 | 19 | 23 | 27 | 28 | )); 29 | -------------------------------------------------------------------------------- /src/renderer/css/components/WelcomeScreen.scss: -------------------------------------------------------------------------------- 1 | @import '../_mixins.scss'; 2 | 3 | .WelcomeScreen { 4 | flex-direction: column; 5 | z-index: 20; 6 | 7 | * { 8 | z-index: 21; 9 | } 10 | 11 | .WelcomeScreen__body { 12 | display: flex; 13 | flex: 1; 14 | width: 100%; 15 | align-items: center; 16 | justify-content: center; 17 | -webkit-font-smoothing: antialiased; 18 | 19 | .WelcomeScreen__content { 20 | display: flex; 21 | flex-direction: column; 22 | width: 100%; 23 | align-items: center; 24 | justify-content: center; 25 | 26 | .WelcomeScreen__button { 27 | margin-top: 22px; 28 | } 29 | } 30 | 31 | &.outToLeft { 32 | @include outToLeft(40); 33 | 34 | animation: outToLeft40 0.4s ease-in-out; 35 | opacity: 0; 36 | } 37 | } 38 | 39 | .WelcomeScreen__footer { 40 | display: block; 41 | width: 100%; 42 | height: 100px; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/AppVersionManager.test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import fs from 'fs'; 5 | 6 | import * as Validator from 'main/Validator'; 7 | 8 | import {AppVersionManager} from './AppVersionManager'; 9 | 10 | jest.mock('electron', () => ({ 11 | ipcMain: { 12 | on: jest.fn(), 13 | }, 14 | })); 15 | 16 | jest.mock('fs', () => ({ 17 | readFileSync: jest.fn(), 18 | writeFile: jest.fn(), 19 | })); 20 | 21 | jest.mock('main/Validator', () => ({ 22 | validateAppState: jest.fn(), 23 | })); 24 | 25 | describe('main/AppVersionManager', () => { 26 | it('should wipe out JSON file when validation fails', () => { 27 | fs.readFileSync.mockReturnValue('some bad JSON'); 28 | Validator.validateAppState.mockReturnValue(false); 29 | 30 | // eslint-disable-next-line no-unused-vars 31 | const appVersionManager = new AppVersionManager('somefilename.txt'); 32 | 33 | expect(fs.writeFile).toBeCalledWith('somefilename.txt', '{}', expect.any(Function)); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/main/ParseArgs.test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | const dummyVersion = '1.2.3-test'; 5 | 6 | jest.mock('electron', () => ({ 7 | app: { 8 | getVersion: () => dummyVersion, 9 | }, 10 | })); 11 | 12 | import parse from 'main/ParseArgs'; 13 | 14 | describe('main/ParseArgs', () => { 15 | it('should remove arguments following a deeplink', () => { 16 | const args = parse(['mattermost', '--disableDevMode', 'mattermost://server-1.com']); 17 | expect(args.disableDevMode).toBe(true); 18 | }); 19 | 20 | it('should show version and exit when specified', async () => { 21 | jest.spyOn(process.stdout, 'write').mockImplementation(() => {}); 22 | const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {}); 23 | const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); 24 | parse(['mattermost', '--version', 'mattermost://server-1.com']); 25 | expect(exitSpy).toHaveBeenCalledWith(0); 26 | expect(logSpy).toHaveBeenCalledWith(dummyVersion); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/renderer/notificationSounds.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import ding from 'static/sounds/ding.mp3'; 5 | import bing from 'static/sounds/bing.mp3'; 6 | import crackle from 'static/sounds/crackle.mp3'; 7 | import down from 'static/sounds/down.mp3'; 8 | import hello from 'static/sounds/hello.mp3'; 9 | import ripple from 'static/sounds/ripple.mp3'; 10 | import upstairs from 'static/sounds/upstairs.mp3'; 11 | 12 | const DEFAULT_WIN7 = 'Ding'; 13 | const notificationSounds = new Map([ 14 | [DEFAULT_WIN7, ding], 15 | ['Bing', bing], 16 | ['Crackle', crackle], 17 | ['Down', down], 18 | ['Hello', hello], 19 | ['Ripple', ripple], 20 | ['Upstairs', upstairs], 21 | ]); 22 | 23 | let canPlaySound = true; 24 | 25 | export const playSound = (soundName: string) => { 26 | if (soundName && canPlaySound) { 27 | canPlaySound = false; 28 | setTimeout(() => { 29 | canPlaySound = true; 30 | }, 3000); 31 | const audio = new Audio(notificationSounds.get(soundName)); 32 | audio.play(); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /scripts/notarize.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | // inspired by https://kilianvalkhof.com/2019/electron/notarizing-your-electron-application/ 5 | require('dotenv').config(); 6 | const {notarize} = require('electron-notarize'); 7 | 8 | const config = require('../electron-builder.json'); 9 | 10 | exports.default = async function notarizing(context) { 11 | const {electronPlatformName, appOutDir} = context; 12 | 13 | if (electronPlatformName !== 'darwin' || process.platform !== 'darwin') { 14 | return; 15 | } 16 | 17 | const appName = context.packager.appInfo.productFilename; 18 | if (typeof process.env.APPLEID === 'undefined') { 19 | console.log('skipping notarization, remember to setup environment variables for APPLEID and APPLEIDPASS if you want to notarize'); 20 | return; 21 | } 22 | await notarize({ 23 | 24 | // should we change it to appBundleId: 'com.mattermost.desktop', 25 | appBundleId: config.appId, 26 | appPath: `${appOutDir}/${appName}.app`, 27 | appleId: process.env.APPLEID, 28 | appleIdPassword: process.env.APPLEIDPASS, 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /src/main/preload/mainWindow.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | // Copyright (c) 2015-2016 Yuya Ochiai 4 | 5 | 'use strict'; 6 | 7 | import {ipcRenderer, contextBridge} from 'electron'; 8 | 9 | import { 10 | GET_LANGUAGE_INFORMATION, 11 | RETRIEVED_LANGUAGE_INFORMATION, 12 | } from 'common/communication'; 13 | 14 | contextBridge.exposeInMainWorld('ipcRenderer', { 15 | send: ipcRenderer.send, 16 | on: (channel, listener) => ipcRenderer.on(channel, (_, ...args) => listener(null, ...args)), 17 | invoke: ipcRenderer.invoke, 18 | }); 19 | 20 | contextBridge.exposeInMainWorld('process', { 21 | platform: process.platform, 22 | env: { 23 | user: process.env.USER, 24 | username: process.env.USERNAME, 25 | }, 26 | }); 27 | 28 | contextBridge.exposeInMainWorld('timers', { 29 | setImmediate, 30 | }); 31 | 32 | window.addEventListener('message', async (event) => { 33 | switch (event.data.type) { 34 | case GET_LANGUAGE_INFORMATION: 35 | window.postMessage({type: RETRIEVED_LANGUAGE_INFORMATION, data: await ipcRenderer.invoke(GET_LANGUAGE_INFORMATION)}); 36 | break; 37 | } 38 | }); 39 | 40 | -------------------------------------------------------------------------------- /src/types/external/tuple-record-polyfill.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | declare module '@bloomberg/record-tuple-polyfill' { 5 | export function Tuple(A): [A]; 6 | export function Tuple(a: A, b: B): [A, B]; 7 | export function Tuple(a: A, b: B, c: C): [A, B, C]; 8 | export function Tuple(a: A, b: B, c: C, d: D): [A, B, C, D]; 9 | export function Tuple(a: A, b: B, c: C, d: D, e: E): [A, B, C, D, E]; 10 | export function Tuple(a: A, b: B, c: C, d: D, e: E, f: F): [A, B, C, D, E, F]; 11 | export function Tuple(a: A, b: B, c: C, d: D, e: E, f: F, g: G): [A, B, C, D, E, F, G]; 12 | export function Tuple(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H): [A, B, C, D, E, F, G, H]; 13 | export function Tuple(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I): [A, B, C, D, E, F, G, H, I]; 14 | export function Tuple(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J): [A, B, C, D, E, F, G, H, I, J]; 15 | export function Record(x: {[key: string]: T}): {[key: string]: T}; 16 | } 17 | -------------------------------------------------------------------------------- /src/renderer/css/components/UpgradeButton.scss: -------------------------------------------------------------------------------- 1 | .upgrade-btns { 2 | width: 32px; 3 | height: 32px; 4 | font-size: 18px; 5 | line-height: 18px; 6 | border-radius: 4px; 7 | margin-top: 4px; 8 | margin-right: 4px; 9 | text-align: center; 10 | color: rgba(63, 67, 80, 0.56); 11 | background: none; 12 | border: none; 13 | 14 | &:hover { 15 | background-color: rgba(63, 67, 80, 0.08); 16 | } 17 | } 18 | 19 | .upgrade-button { 20 | height: 18px; 21 | cursor: pointer; 22 | 23 | > i::before { 24 | margin: 0; 25 | } 26 | } 27 | 28 | .rotate { 29 | animation: rotation 2s infinite linear; 30 | 31 | > i::before { 32 | transform: scaleX(-1); 33 | } 34 | } 35 | 36 | @keyframes rotation { 37 | from { 38 | transform: rotate(0deg); 39 | } 40 | to { 41 | transform: rotate(359deg); 42 | } 43 | } 44 | 45 | .circle { 46 | background: #F74343; 47 | border-radius: 100px; 48 | width: 12px; 49 | height: 12px; 50 | position: relative; 51 | top: -22px; 52 | right: -10px; 53 | border: 2px solid #efefef; 54 | } 55 | 56 | .upgrade-btns.darkMode { 57 | color: rgba(221, 221, 221, 0.56); 58 | 59 | &:hover { 60 | background-color: rgba(175, 188, 192, 0.08); 61 | } 62 | 63 | .circle { 64 | border-color: #2e2e2e; 65 | } 66 | } -------------------------------------------------------------------------------- /src/renderer/components/WelcomeScreen/WelcomeScreenSlide.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import React from 'react'; 5 | import classNames from 'classnames'; 6 | 7 | import 'renderer/css/components/WelcomeScreenSlide.scss'; 8 | 9 | type WelcomeScreenSlideProps = { 10 | title: string; 11 | subtitle: string | React.ReactElement; 12 | image: React.ReactNode; 13 | isMain?: boolean; 14 | darkMode?: boolean; 15 | }; 16 | 17 | const WelcomeScreenSlide = ({ 18 | title, 19 | subtitle, 20 | image, 21 | isMain, 22 | darkMode, 23 | }: WelcomeScreenSlideProps) => ( 24 |
33 |
34 | {image} 35 |
36 |
37 | {title} 38 |
39 |
40 | {subtitle} 41 |
42 |
43 | ); 44 | 45 | export default WelcomeScreenSlide; 46 | -------------------------------------------------------------------------------- /src/main/constants.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | /* istanbul ignore file */ 5 | 6 | import path from 'path'; 7 | 8 | import {app, ipcMain} from 'electron'; 9 | 10 | import {UPDATE_PATHS} from 'common/communication'; 11 | 12 | let userDataPath; 13 | 14 | export let configPath = ''; 15 | export let allowedProtocolFile = ''; 16 | export let appVersionJson = ''; 17 | export let certificateStorePath = ''; 18 | export let trustedOriginsStoreFile = ''; 19 | export let boundsInfoPath = ''; 20 | export let migrationInfoPath = ''; 21 | 22 | export function updatePaths(emit = false) { 23 | userDataPath = app.getPath('userData'); 24 | 25 | configPath = `${userDataPath}/config.json`; 26 | allowedProtocolFile = path.resolve(userDataPath, 'allowedProtocols.json'); 27 | appVersionJson = path.join(userDataPath, 'app-state.json'); 28 | certificateStorePath = path.resolve(userDataPath, 'certificate.json'); 29 | trustedOriginsStoreFile = path.resolve(userDataPath, 'trustedOrigins.json'); 30 | boundsInfoPath = path.join(userDataPath, 'bounds-info.json'); 31 | migrationInfoPath = path.resolve(userDataPath, 'migration-info.json'); 32 | 33 | if (emit) { 34 | ipcMain.emit(UPDATE_PATHS); 35 | } 36 | } 37 | 38 | updatePaths(); 39 | -------------------------------------------------------------------------------- /src/renderer/css/components/Carousel.scss: -------------------------------------------------------------------------------- 1 | @import '../_mixins.scss'; 2 | 3 | .Carousel { 4 | display: flex; 5 | flex: 1; 6 | flex-flow: column; 7 | align-items: center; 8 | justify-content: center; 9 | width: 100%; 10 | 11 | .Carousel__slides { 12 | min-height: 380px; 13 | width: 100%; 14 | position: relative; 15 | display: flex; 16 | justify-content: center; 17 | 18 | .Carousel__slide { 19 | position: absolute; 20 | bottom: 0; 21 | opacity: 0; 22 | } 23 | 24 | .Carousel__slide-current { 25 | opacity: 1; 26 | } 27 | } 28 | 29 | .Carousel__pagination { 30 | display: flex; 31 | flex: 1; 32 | align-items: center; 33 | justify-content: center; 34 | margin-top: 22px; 35 | } 36 | } 37 | 38 | .inFromRight { 39 | @include inFromRight(30); 40 | 41 | animation: inFromRight30 0.4s ease-in-out; 42 | } 43 | 44 | .inFromLeft { 45 | @include inFromLeft(30); 46 | 47 | animation: inFromLeft30 0.4s ease-in-out; 48 | } 49 | 50 | .outToRight { 51 | @include outToRight(30); 52 | 53 | animation: outToRight30 0.4s ease-in-out; 54 | } 55 | 56 | .outToLeft { 57 | @include outToLeft(30); 58 | 59 | animation: outToLeft30 0.4s ease-in-out; 60 | } 61 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | fastlane_version '2.71.0' 2 | fastlane_require 'aws-sdk-s3' 3 | fastlane_require 'erb' 4 | fastlane_require 'json' 5 | fastlane_require 'pathname' 6 | 7 | lane :publish_test do |options| 8 | api_key = '' 9 | unless ENV['MACOS_API_KEY_ID'].nil? || ENV['MACOS_API_KEY_ID'].empty? || 10 | ENV['MACOS_API_ISSUER_ID'].nil? || ENV['MACOS_API_ISSUER_ID'].empty? || 11 | ENV['MACOS_API_KEY'].nil? || ENV['MACOS_API_KEY'].empty? 12 | api_key_path = "#{ENV['MACOS_API_KEY_ID']}.p8" 13 | File.open("../#{api_key_path}", 'w') do |f| 14 | key_string = ENV['MACOS_API_KEY'] 15 | p8_array = key_string.split('\n') 16 | p8_array.each_with_index do |value, index| 17 | f.write(value) 18 | f.write("\n") unless index == p8_array.length - 1 19 | end 20 | end 21 | 22 | api_key = app_store_connect_api_key( 23 | key_id: ENV['MACOS_API_KEY_ID'], 24 | issuer_id: ENV['MACOS_API_ISSUER_ID'], 25 | key_filepath: "./#{api_key_path}", 26 | in_house: ENV['MACOS_IN_HOUSE'] == 'true', # optional but may be required if using match/sigh 27 | ) 28 | 29 | File.delete("../#{api_key_path}") 30 | end 31 | pilot( 32 | pkg: options[:path], 33 | skip_waiting_for_build_processing: ENV['CI'] === 'true', 34 | api_key: api_key 35 | ) 36 | end -------------------------------------------------------------------------------- /src/renderer/modals/certificate/certificate.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | import {Certificate} from 'electron/renderer'; 7 | 8 | import {MODAL_CANCEL, MODAL_RESULT, RETRIEVE_MODAL_INFO} from 'common/communication'; 9 | 10 | import 'bootstrap/dist/css/bootstrap.min.css'; 11 | import 'renderer/css/modals.css'; 12 | import 'renderer/css/components/CertificateModal.css'; 13 | 14 | import setupDarkMode from '../darkMode'; 15 | 16 | import SelectCertificateModal from './certificateModal'; 17 | 18 | setupDarkMode(); 19 | 20 | const handleCancel = () => { 21 | window.postMessage({type: MODAL_CANCEL}, window.location.href); 22 | }; 23 | 24 | const handleSelect = (cert: Certificate) => { 25 | window.postMessage({type: MODAL_RESULT, data: {cert}}, window.location.href); 26 | }; 27 | 28 | const getCertInfo = () => { 29 | window.postMessage({type: RETRIEVE_MODAL_INFO}, window.location.href); 30 | }; 31 | 32 | const start = async () => { 33 | ReactDOM.render( 34 | , 39 | document.getElementById('app'), 40 | ); 41 | }; 42 | 43 | start(); 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/beta_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Beta Testing Report 3 | about: Report issues found while using a beta version of the Mattermost Desktop App 4 | 5 | --- 6 | 7 | I confirm (by marking "x" in the [ ] below: [x]): 8 | 9 | - [ ] I have confirmed this is an issue on the newest beta build [available here](https://github.com/mattermost/desktop/tags) 10 | - [ ] This issue doesn't reproduce on web browsers (such as in Chrome). If it does, [issue reports go to the Mattermost Server repository](https://github.com/mattermost/platform/issues). 11 | - [ ] I have read [contributing guidelines](https://github.com/mattermost/desktop/blob/master/CONTRIBUTING.md). 12 | 13 | --- 14 | 15 | **Summary** 16 | 19 | 20 | **Environment** 21 | - Operating System: 22 | - Mattermost Desktop App version: X.X.X 23 | - Mattermost Server version: X.X.X 24 | 25 | **Steps to reproduce** 26 | 27 | **Expected behavior** 28 | 29 | **Observed behavior** 30 | 31 | 34 | 35 | **Possible fixes** 36 | 37 | 40 | -------------------------------------------------------------------------------- /src/renderer/css/components/PermissionRequestDialog.css: -------------------------------------------------------------------------------- 1 | .PermissionRequestDialog-content{ 2 | min-width: 250px; 3 | } 4 | 5 | .PermissionRequestDialog-content .popover-content { 6 | padding: 14px; 7 | } 8 | 9 | .PermissionRequestDialog-content > *:nth-child(1) { 10 | margin-right: 14px; 11 | } 12 | 13 | .PermissionRequestDialog-content .PermissionRequestDialog-content-description { 14 | text-indent: 0.25em; 15 | } 16 | 17 | .PermissionRequestDialog-content .PermissionRequestDialog-content-description .glyphicon { 18 | margin-right: 0.5em; 19 | } 20 | 21 | .PermissionRequestDialog-content .PermissionRequestDialog-content-buttons { 22 | margin-bottom: 0; 23 | text-align: right; 24 | } 25 | 26 | .PermissionRequestDialog-content .PermissionRequestDialog-content-buttons > * { 27 | margin-right: 7px; 28 | } 29 | 30 | .PermissionRequestDialog-content .PermissionRequestDialog-content-buttons > *:last-child { 31 | margin-right: 0; 32 | } 33 | 34 | .PermissionRequestDialog-content .PermissionRequestDialog-content-close { 35 | position:absolute; 36 | top: 0px; 37 | right:0px; 38 | color: gray; 39 | text-decoration-line: none; 40 | } 41 | 42 | .PermissionRequestDialog-content .PermissionRequestDialog-content-close:hover { 43 | color: black; 44 | } 45 | 46 | .permission-modal .modal-dialog { 47 | max-width: 580px; 48 | } 49 | .permission-modal .remove-border { 50 | border: none; 51 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report about an issue you found 4 | 5 | --- 6 | 7 | I confirm (by marking "x" in the [ ] below: [x]): 8 | 9 | - [ ] This is not a troubleshooting question. [Troubleshooting questions go here: https://docs.mattermost.com/install/troubleshooting.html](https://docs.mattermost.com/install/troubleshooting.html). 10 | - [ ] This doesn't reproduce on web browsers (such as in Chrome). If it does, [issue reports go to the Mattermost Server repository](https://github.com/mattermost/platform/issues). 11 | - [ ] I have read [contributing guidelines](https://github.com/mattermost/desktop/blob/master/CONTRIBUTING.md). 12 | 13 | --- 14 | 15 | **Summary** 16 | 19 | 20 | **Environment** 21 | - Operating System: 22 | - Mattermost Desktop App version: X.X.X 23 | - Mattermost Server version: X.X.X 24 | 25 | **Steps to reproduce** 26 | 27 | **Expected behavior** 28 | 29 | **Observed behavior** 30 | 31 | 34 | 35 | **Possible fixes** 36 | 37 | 40 | -------------------------------------------------------------------------------- /scripts/manipulate_windows_zip.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | 'use strict'; 5 | 6 | const spawnSync = require('child_process').spawnSync; 7 | 8 | const path = require('path'); 9 | 10 | const path7za = require('7zip-bin').path7za; 11 | 12 | const pkg = require('../package.json'); 13 | const appVersion = pkg.version; 14 | const name = pkg.name; 15 | 16 | function disableInstallUpdate(zipPath) { 17 | const zipFullPath = path.resolve(__dirname, '..', zipPath); 18 | const appUpdaterConfigFile = 'app-updater-config.json'; 19 | 20 | const addResult = spawnSync(path7za, ['a', zipFullPath, appUpdaterConfigFile], {cwd: 'resources/windows'}); 21 | if (addResult.status !== 0) { 22 | throw new Error(`7za a returned non-zero exit code for ${zipPath}`); 23 | } 24 | 25 | const renameResult = spawnSync(path7za, ['rn', zipFullPath, appUpdaterConfigFile, `resources/${appUpdaterConfigFile}`]); 26 | if (renameResult.status !== 0) { 27 | throw new Error(`7za rn returned non-zero exit code for ${zipPath}`); 28 | } 29 | } 30 | 31 | console.log('Manipulating 64-bit zip...'); 32 | disableInstallUpdate(`release/${name}-${appVersion}-win-x64.zip`); 33 | console.log('Manipulating 32-bit zip...'); 34 | disableInstallUpdate(`release/${name}-${appVersion}-win-ia32.zip`); 35 | -------------------------------------------------------------------------------- /src/common/config/migrationPreferences.test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import JsonFileManager from 'common/JsonFileManager'; 5 | 6 | import migrateConfigItems from './migrationPreferences'; 7 | 8 | jest.mock('common/JsonFileManager', () => jest.fn()); 9 | 10 | describe('common/config/migrationPreferences', () => { 11 | describe('migrateConfigItems', () => { 12 | afterEach(() => { 13 | jest.resetAllMocks(); 14 | }); 15 | 16 | it('should not migrate if all items migrated', () => { 17 | JsonFileManager.mockImplementation(() => ({ 18 | getValue: () => true, 19 | })); 20 | expect(migrateConfigItems({})).toBe(false); 21 | }); 22 | 23 | it('should migrate if items are not migrated', () => { 24 | const originalPlatform = process.platform; 25 | Object.defineProperty(process, 'platform', { 26 | value: 'win32', 27 | }); 28 | JsonFileManager.mockImplementation(() => ({ 29 | getValue: () => false, 30 | setValue: jest.fn(), 31 | })); 32 | expect(migrateConfigItems({})).toBe(true); 33 | Object.defineProperty(process, 'platform', { 34 | value: originalPlatform, 35 | }); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/renderer/settings.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | 5 | import 'bootstrap/dist/css/bootstrap.min.css'; 6 | import 'renderer/css/index.css'; 7 | import 'renderer/css/settings.css'; 8 | 9 | import React from 'react'; 10 | import ReactDOM from 'react-dom'; 11 | 12 | import {DARK_MODE_CHANGE, GET_DARK_MODE} from 'common/communication'; 13 | 14 | import darkStyles from 'renderer/css/lazy/settings-dark.lazy.css'; 15 | 16 | import SettingsPage from './components/SettingsPage'; 17 | import IntlProvider from './intl_provider'; 18 | 19 | const setDarkMode = (darkMode: boolean) => { 20 | if (darkMode) { 21 | darkStyles.use(); 22 | } else { 23 | darkStyles.unuse(); 24 | } 25 | }; 26 | 27 | window.ipcRenderer.on(DARK_MODE_CHANGE, (_, darkMode) => setDarkMode(darkMode)); 28 | window.ipcRenderer.invoke(GET_DARK_MODE).then(setDarkMode); 29 | 30 | const start = async () => { 31 | ReactDOM.render( 32 | ( 33 | 34 | 35 | 36 | ) 37 | , 38 | document.getElementById('app'), 39 | ); 40 | }; 41 | 42 | // Deny drag&drop navigation in mainWindow. 43 | document.addEventListener('dragover', (event) => event.preventDefault()); 44 | document.addEventListener('drop', (event) => event.preventDefault()); 45 | 46 | start(); 47 | -------------------------------------------------------------------------------- /src/main/windows/settingsWindow.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import {BrowserWindow} from 'electron'; 5 | import log from 'electron-log'; 6 | 7 | import Config from 'common/config'; 8 | 9 | import ContextMenu from '../contextMenu'; 10 | import {getLocalPreload, getLocalURLString} from '../utils'; 11 | 12 | export function createSettingsWindow(mainWindow: BrowserWindow, withDevTools: boolean) { 13 | const preload = getLocalPreload('mainWindow.js'); 14 | const spellcheck = (typeof Config.useSpellChecker === 'undefined' ? true : Config.useSpellChecker); 15 | const settingsWindow = new BrowserWindow({ 16 | parent: mainWindow, 17 | title: 'Desktop App Settings', 18 | fullscreen: false, 19 | webPreferences: { 20 | preload, 21 | spellcheck, 22 | }}); 23 | 24 | const contextMenu = new ContextMenu({}, settingsWindow); 25 | contextMenu.reload(); 26 | 27 | const localURL = getLocalURLString('settings.html'); 28 | settingsWindow.setMenuBarVisibility(false); 29 | settingsWindow.loadURL(localURL).catch( 30 | (reason) => { 31 | log.error(`Settings window failed to load: ${reason}`); 32 | log.info(process.env); 33 | }); 34 | settingsWindow.show(); 35 | 36 | if (withDevTools) { 37 | settingsWindow.webContents.openDevTools({mode: 'detach'}); 38 | } 39 | return settingsWindow; 40 | } 41 | -------------------------------------------------------------------------------- /src/common/JsonFileManager.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | import fs from 'fs'; 5 | 6 | export default class JsonFileManager { 7 | jsonFile: string; 8 | json: T; 9 | 10 | constructor(file: string) { 11 | this.jsonFile = file; 12 | try { 13 | this.json = JSON.parse(fs.readFileSync(file, 'utf-8')); 14 | } catch (err) { 15 | this.json = {} as T; 16 | } 17 | } 18 | 19 | writeToFile(): Promise { 20 | return new Promise((resolve, reject) => { 21 | fs.writeFile(this.jsonFile, JSON.stringify(this.json, undefined, 2), (err) => { 22 | if (err) { 23 | // No real point in bringing electron-log into this otherwise electron-free file 24 | // eslint-disable-next-line no-console 25 | console.error(err); 26 | reject(err); 27 | return; 28 | } 29 | resolve(); 30 | }); 31 | }); 32 | } 33 | 34 | setJson(json: T): void { 35 | this.json = json; 36 | this.writeToFile(); 37 | } 38 | 39 | setValue(key: keyof T, value: T[keyof T]): void { 40 | this.json[key] = value; 41 | this.writeToFile(); 42 | } 43 | 44 | getValue(key: keyof T): T[keyof T] { 45 | return this.json[key]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /entitlements.mas.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.device.microphone 6 | 7 | com.apple.security.device.camera 8 | 9 | com.apple.security.device.audio-input 10 | 11 | com.apple.security.cs.allow-jit 12 | 13 | com.apple.security.cs.allow-unsigned-executable-memory 14 | 15 | com.apple.security.cs.allow-dyld-environment-variables 16 | 17 | com.apple.security.network.client 18 | 19 | com.apple.security.network.server 20 | 21 | com.apple.security.files.user-selected.read-write 22 | 23 | com.apple.security.files.downloads.read-write 24 | 25 | com.apple.security.assets.pictures.read-write 26 | 27 | com.apple.security.assets.music.read-write 28 | 29 | com.apple.security.assets.movies.read-write 30 | 31 | com.apple.security.app-sandbox 32 | 33 | com.apple.security.application-groups 34 | 35 | UQ8HT4Q2XM.Mattermost.Desktop 36 | 37 | com.apple.application-identifier 38 | UQ8HT4Q2XM.Mattermost.Desktop 39 | com.apple.developer.team-identifier 40 | UQ8HT4Q2XM 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/renderer/components/Carousel/CarouselButton.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import React from 'react'; 5 | import classNames from 'classnames'; 6 | 7 | import 'renderer/css/components/Button.scss'; 8 | import 'renderer/css/components/CarouselButton.scss'; 9 | 10 | export enum ButtonDirection { 11 | NEXT = 'next', 12 | PREV = 'prev', 13 | } 14 | 15 | type CarouselButtonProps = { 16 | direction: ButtonDirection; 17 | disabled?: boolean; 18 | darkMode?: boolean; 19 | onClick?: () => void; 20 | }; 21 | 22 | function CarouselButton({ 23 | direction = ButtonDirection.NEXT, 24 | disabled = false, 25 | darkMode = false, 26 | onClick = () => null, 27 | }: CarouselButtonProps) { 28 | const handleOnClick = () => { 29 | onClick(); 30 | }; 31 | 32 | return ( 33 | 48 | ); 49 | } 50 | 51 | export default CarouselButton; 52 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ master ] 9 | schedule: 10 | - cron: '00 00 * * 0' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | language: [ 'javascript' ] 21 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 22 | 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v2 26 | 27 | # Initializes the CodeQL tools for scanning. 28 | - name: Initialize CodeQL 29 | uses: github/codeql-action/init@v1 30 | with: 31 | languages: ${{ matrix.language }} 32 | # If you wish to specify custom queries, you can do so here or in a config file. 33 | # By default, queries listed here will override any specified in a config file. 34 | # Prefix the list here with "+" to use these queries and those in the config file. 35 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 36 | 37 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 38 | # If this step fails, then you should remove it and run the build manually (see below) 39 | - name: Autobuild 40 | uses: github/codeql-action/autobuild@v1 41 | 42 | - name: Perform CodeQL Analysis 43 | uses: github/codeql-action/analyze@v1 44 | -------------------------------------------------------------------------------- /src/renderer/components/DestructiveConfirmModal.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | 5 | import React from 'react'; 6 | import {Button, Modal} from 'react-bootstrap'; 7 | 8 | type Props = { 9 | title: string; 10 | body: React.ReactNode; 11 | acceptLabel: string; 12 | cancelLabel: string; 13 | onHide: () => void; 14 | onAccept: React.MouseEventHandler; 15 | onCancel: React.MouseEventHandler; 16 | }; 17 | 18 | export default function DestructiveConfirmationModal(props: Props) { 19 | const { 20 | title, 21 | body, 22 | acceptLabel, 23 | cancelLabel, 24 | onAccept, 25 | onCancel, 26 | onHide, 27 | ...rest} = props; 28 | return ( 29 | 33 | 34 | {title} 35 | 36 | {body} 37 | 38 | 42 | 46 | 47 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /src/common/config/defaultPreferences.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | 5 | import path from 'path'; 6 | import os from 'os'; 7 | 8 | /** 9 | * Default user preferences. End-users can change these parameters by editing config.json 10 | * @param {number} version - Scheme version. (Not application version) 11 | */ 12 | 13 | import {ConfigV3} from 'types/config'; 14 | 15 | export const getDefaultDownloadLocation = (): string | undefined => { 16 | // eslint-disable-next-line no-undef 17 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 18 | // @ts-ignore 19 | if (__IS_MAC_APP_STORE__) { 20 | return undefined; 21 | } 22 | return path.join(os.homedir(), 'Downloads'); 23 | }; 24 | 25 | const defaultPreferences: ConfigV3 = { 26 | version: 3, 27 | teams: [], 28 | showTrayIcon: true, 29 | trayIconTheme: 'use_system', 30 | minimizeToTray: process.platform !== 'linux', 31 | notifications: { 32 | flashWindow: 2, 33 | bounceIcon: true, 34 | bounceIconType: 'informational', 35 | }, 36 | showUnreadBadge: true, 37 | useSpellChecker: true, 38 | enableHardwareAcceleration: true, 39 | autostart: true, 40 | hideOnStart: false, 41 | spellCheckerLocales: [], 42 | darkMode: false, 43 | lastActiveTeam: 0, 44 | downloadLocation: getDefaultDownloadLocation(), 45 | startInFullscreen: false, 46 | }; 47 | 48 | export default defaultPreferences; 49 | -------------------------------------------------------------------------------- /src/renderer/modals/login/login.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | import {AuthenticationResponseDetails} from 'electron/renderer'; 7 | 8 | import {MODAL_CANCEL, MODAL_RESULT, RETRIEVE_MODAL_INFO} from 'common/communication'; 9 | 10 | import IntlProvider from 'renderer/intl_provider'; 11 | 12 | import 'bootstrap/dist/css/bootstrap.min.css'; 13 | import 'renderer/css/modals.css'; 14 | 15 | import setupDarkMode from '../darkMode'; 16 | 17 | import LoginModal from './loginModal'; 18 | 19 | setupDarkMode(); 20 | 21 | const handleLoginCancel = (request: AuthenticationResponseDetails) => { 22 | window.postMessage({type: MODAL_CANCEL, data: {request}}, window.location.href); 23 | }; 24 | 25 | const handleLogin = (request: AuthenticationResponseDetails, username: string, password: string) => { 26 | window.postMessage({type: MODAL_RESULT, data: {request, username, password}}, window.location.href); 27 | }; 28 | 29 | const getAuthInfo = () => { 30 | window.postMessage({type: RETRIEVE_MODAL_INFO}, window.location.href); 31 | }; 32 | 33 | const start = async () => { 34 | ReactDOM.render( 35 | 36 | 41 | , 42 | document.getElementById('app'), 43 | ); 44 | }; 45 | 46 | start(); 47 | -------------------------------------------------------------------------------- /webpack.config.test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | // This file uses CommonJS. 5 | /* eslint-disable import/no-commonjs */ 6 | 'use strict'; 7 | 8 | const path = require('path'); 9 | 10 | const glob = require('glob'); 11 | const {merge} = require('webpack-merge'); 12 | 13 | const base = require('./webpack.config.base'); 14 | 15 | const WEBSERVER_PORT = process.env.WEBSERVER_PORT ?? 9001; 16 | 17 | module.exports = merge(base, { 18 | entry: { 19 | e2e: glob.sync('./e2e/specs/**/*.test.js'), 20 | }, 21 | output: { 22 | path: path.resolve(__dirname, 'dist/tests'), 23 | filename: '[name]_bundle.js', 24 | }, 25 | module: { 26 | rules: [{ 27 | test: /\.(js|jsx|ts|tsx)?$/, 28 | use: ['babel-loader', 'shebang-loader'], 29 | }], 30 | }, 31 | externals: { 32 | fs: 'require("fs")', 33 | ws: 'require("ws")', 34 | child_process: 'require("child_process")', 35 | dns: 'require("dns")', 36 | http2: 'require("http2")', 37 | net: 'require("net")', 38 | repl: 'require("repl")', 39 | tls: 'require("tls")', 40 | playwright: 'require("playwright")', 41 | robotjs: 'require("robotjs")', 42 | }, 43 | node: { 44 | __filename: false, 45 | __dirname: false, 46 | }, 47 | devServer: { 48 | port: WEBSERVER_PORT, 49 | }, 50 | target: 'electron-main', 51 | }); 52 | 53 | /* eslint-enable import/no-commonjs */ 54 | -------------------------------------------------------------------------------- /src/main/preload/loadingScreenPreload.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | // Copyright (c) 2015-2016 Yuya Ochiai 4 | 5 | 'use strict'; 6 | 7 | import {ipcRenderer} from 'electron'; 8 | 9 | import { 10 | RECEIVED_LOADING_SCREEN_DATA, 11 | GET_LOADING_SCREEN_DATA, 12 | LOADING_SCREEN_ANIMATION_FINISHED, 13 | TOGGLE_LOADING_SCREEN_VISIBILITY, 14 | CLOSE_TEAMS_DROPDOWN, 15 | } from 'common/communication'; 16 | 17 | console.log('preloaded for the loading screen!'); 18 | 19 | window.addEventListener('message', async (event) => { 20 | switch (event.data.type) { 21 | case GET_LOADING_SCREEN_DATA: 22 | window.postMessage({type: RECEIVED_LOADING_SCREEN_DATA, data: await ipcRenderer.invoke(GET_LOADING_SCREEN_DATA)}, window.location.href); 23 | break; 24 | case LOADING_SCREEN_ANIMATION_FINISHED: 25 | ipcRenderer.send(LOADING_SCREEN_ANIMATION_FINISHED); 26 | break; 27 | default: 28 | console.log(`got a message: ${event}`); 29 | console.log(event); 30 | } 31 | }); 32 | 33 | ipcRenderer.on(GET_LOADING_SCREEN_DATA, (_, result) => { 34 | window.postMessage({type: RECEIVED_LOADING_SCREEN_DATA, data: result}, window.location.href); 35 | }); 36 | 37 | ipcRenderer.on(TOGGLE_LOADING_SCREEN_VISIBILITY, (_, toggle) => { 38 | window.postMessage({type: TOGGLE_LOADING_SCREEN_VISIBILITY, data: toggle}, window.location.href); 39 | }); 40 | 41 | window.addEventListener('click', () => { 42 | ipcRenderer.send(CLOSE_TEAMS_DROPDOWN); 43 | }); 44 | -------------------------------------------------------------------------------- /src/main/AutoLauncher.test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import AutoLaunch from 'auto-launch'; 5 | 6 | import {AutoLauncher} from './AutoLauncher'; 7 | 8 | jest.mock('auto-launch', () => jest.fn()); 9 | jest.mock('electron', () => ({ 10 | app: { 11 | name: 'Mattermost', 12 | }, 13 | })); 14 | 15 | jest.mock('electron-is-dev', () => false); 16 | 17 | jest.mock('main/i18nManager', () => ({ 18 | localizeMessage: jest.fn(), 19 | })); 20 | 21 | describe('main/AutoLauncher', () => { 22 | let autoLauncher; 23 | const isEnabled = jest.fn(); 24 | const enable = jest.fn(); 25 | const disable = jest.fn(); 26 | 27 | beforeEach(() => { 28 | AutoLaunch.mockImplementation(() => ({ 29 | isEnabled, 30 | enable, 31 | disable, 32 | })); 33 | autoLauncher = new AutoLauncher(); 34 | }); 35 | 36 | it('should toggle enabled/disabled', async () => { 37 | isEnabled.mockReturnValue(true); 38 | await autoLauncher.enable(); 39 | expect(enable).not.toBeCalled(); 40 | 41 | isEnabled.mockReturnValue(false); 42 | await autoLauncher.enable(); 43 | expect(enable).toBeCalled(); 44 | 45 | isEnabled.mockReturnValue(false); 46 | await autoLauncher.disable(); 47 | expect(disable).not.toBeCalled(); 48 | 49 | isEnabled.mockReturnValue(true); 50 | await autoLauncher.disable(); 51 | expect(disable).toBeCalled(); 52 | }); 53 | }); 54 | 55 | -------------------------------------------------------------------------------- /src/main/menus/tray.test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | 'use strict'; 5 | 6 | import {createTemplate} from './tray'; 7 | 8 | jest.mock('main/i18nManager', () => ({ 9 | localizeMessage: jest.fn(), 10 | })); 11 | 12 | jest.mock('main/windows/windowManager', () => ({})); 13 | 14 | describe('main/menus/tray', () => { 15 | it('should show the first 9 servers (using order)', () => { 16 | const config = { 17 | teams: [...Array(15).keys()].map((key) => ({ 18 | name: `server-${key}`, 19 | url: `http://server-${key}.com`, 20 | order: (key + 5) % 15, 21 | lastActiveTab: 0, 22 | tab: [ 23 | { 24 | name: 'TAB_MESSAGING', 25 | isOpen: true, 26 | }, 27 | ], 28 | })), 29 | }; 30 | const menu = createTemplate(config); 31 | for (let i = 10; i < 15; i++) { 32 | const menuItem = menu.find((item) => item.label === `server-${i}`); 33 | expect(menuItem).not.toBe(undefined); 34 | } 35 | for (let i = 0; i < 4; i++) { 36 | const menuItem = menu.find((item) => item.label === `server-${i}`); 37 | expect(menuItem).not.toBe(undefined); 38 | } 39 | for (let i = 4; i < 10; i++) { 40 | const menuItem = menu.find((item) => item.label === `server-${i}`); 41 | expect(menuItem).toBe(undefined); 42 | } 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /src/main/notifications/Download.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import os from 'os'; 5 | import path from 'path'; 6 | 7 | import {app, Notification} from 'electron'; 8 | 9 | import Utils from 'common/utils/util'; 10 | 11 | import {localizeMessage} from 'main/i18nManager'; 12 | 13 | const assetsDir = path.resolve(app.getAppPath(), 'assets'); 14 | const appIconURL = path.resolve(assetsDir, 'appicon_48.png'); 15 | 16 | const defaultOptions = { 17 | title: localizeMessage('main.notifications.download.complete.title', 'Download Complete'), 18 | silent: false, 19 | icon: appIconURL, 20 | urgency: 'normal' as Notification['urgency'], 21 | body: '', 22 | }; 23 | 24 | export class DownloadNotification extends Notification { 25 | constructor(fileName: string, serverName: string) { 26 | const options = {...defaultOptions}; 27 | if (process.platform === 'darwin' || (process.platform === 'win32' && Utils.isVersionGreaterThanOrEqualTo(os.release(), '10.0'))) { 28 | // Notification Center shows app's icon, so there were two icons on the notification. 29 | Reflect.deleteProperty(options, 'icon'); 30 | } 31 | 32 | options.title = process.platform === 'win32' ? serverName : localizeMessage('main.notifications.download.complete.title', 'Download Complete'); 33 | options.body = process.platform === 'win32' ? localizeMessage('main.notifications.download.complete.body', 'Download Complete \n {fileName}', {fileName}) : fileName; 34 | 35 | super(options); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/renderer/css/components/WelcomeScreenSlide.scss: -------------------------------------------------------------------------------- 1 | @import url("../_css_variables.scss"); 2 | @import url("../fonts.css"); 3 | 4 | .WelcomeScreenSlide { 5 | display: flex; 6 | flex: 1; 7 | max-width: 540px; 8 | flex-flow: column; 9 | justify-content: flex-end; 10 | 11 | .WelcomeScreenSlide__image { 12 | align-self: center; 13 | } 14 | 15 | .WelcomeScreenSlide__title { 16 | color: var(--title-color-indigo-500); 17 | font-family: 'Metropolis'; 18 | font-size: 80px; 19 | font-weight: 600; 20 | letter-spacing: -0.05em; 21 | line-height: 88px; 22 | text-align: center; 23 | } 24 | 25 | .WelcomeScreenSlide__subtitle { 26 | color: rgba(var(--center-channel-text-rgb), 0.72); 27 | font-family: Open Sans; 28 | font-size: 16px; 29 | font-weight: 400; 30 | line-height: 24px; 31 | text-align: center; 32 | margin-top: 16px; 33 | } 34 | 35 | &.WelcomeScreenSlide--main { 36 | position: relative; 37 | height: 100%; 38 | 39 | .WelcomeScreenSlide__title { 40 | font-size: 128px; 41 | line-height: 128px; 42 | } 43 | 44 | .WelcomeScreenSlide__image { 45 | position: absolute; 46 | display: block; 47 | bottom: 115px; 48 | } 49 | } 50 | 51 | &.WelcomeScreenSlide--darkMode { 52 | .WelcomeScreenSlide__title { 53 | color: var(--button-color); 54 | } 55 | 56 | .WelcomeScreenSlide__subtitle { 57 | color: rgba(var(--button-color-rgb), 0.72); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/renderer/modals/permission/permission.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | 7 | import {MODAL_CANCEL, MODAL_RESULT, RETRIEVE_MODAL_INFO, MODAL_SEND_IPC_MESSAGE} from 'common/communication'; 8 | 9 | import IntlProvider from 'renderer/intl_provider'; 10 | 11 | import 'bootstrap/dist/css/bootstrap.min.css'; 12 | import 'renderer/css/modals.css'; 13 | 14 | import setupDarkMode from '../darkMode'; 15 | 16 | import PermissionModal from './permissionModal'; 17 | 18 | setupDarkMode(); 19 | 20 | const handleDeny = () => { 21 | window.postMessage({type: MODAL_CANCEL}, window.location.href); 22 | }; 23 | 24 | const handleGrant = () => { 25 | window.postMessage({type: MODAL_RESULT}, window.location.href); 26 | }; 27 | 28 | const getPermissionInfo = () => { 29 | window.postMessage({type: RETRIEVE_MODAL_INFO}, window.location.href); 30 | }; 31 | 32 | const openExternalLink = (protocol: string, url: string) => { 33 | window.postMessage({type: MODAL_SEND_IPC_MESSAGE, data: {type: 'confirm-protocol', args: [protocol, url]}}, window.location.href); 34 | }; 35 | 36 | const start = async () => { 37 | ReactDOM.render( 38 | 39 | 45 | , 46 | document.getElementById('app'), 47 | ); 48 | }; 49 | 50 | start(); 51 | -------------------------------------------------------------------------------- /src/main/menus/tray.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | 'use strict'; 5 | 6 | import {Menu, MenuItem, MenuItemConstructorOptions} from 'electron'; 7 | import {CombinedConfig} from 'types/config'; 8 | 9 | import WindowManager from 'main/windows/windowManager'; 10 | import {localizeMessage} from 'main/i18nManager'; 11 | 12 | export function createTemplate(config: CombinedConfig) { 13 | const teams = config.teams; 14 | const template = [ 15 | ...teams.sort((teamA, teamB) => teamA.order - teamB.order).slice(0, 9).map((team) => { 16 | return { 17 | label: team.name.length > 50 ? `${team.name.slice(0, 50)}...` : team.name, 18 | click: () => { 19 | WindowManager.switchServer(team.name); 20 | }, 21 | }; 22 | }), { 23 | type: 'separator', 24 | }, { 25 | label: process.platform === 'darwin' ? localizeMessage('main.menus.tray.preferences', 'Preferences...') : localizeMessage('main.menus.tray.settings', 'Settings'), 26 | click: () => { 27 | WindowManager.showSettingsWindow(); 28 | }, 29 | }, { 30 | type: 'separator', 31 | }, { 32 | role: 'quit', 33 | }, 34 | ]; 35 | return template; 36 | } 37 | 38 | export function createMenu(config: CombinedConfig) { 39 | // Electron is enforcing certain variables that it doesn't need 40 | return Menu.buildFromTemplate(createTemplate(config) as Array); 41 | } 42 | -------------------------------------------------------------------------------- /src/common/config/pastDefaultPreferences.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | import {ConfigV0, ConfigV1, ConfigV2} from 'types/config'; 5 | 6 | import defaultPreferences, {getDefaultDownloadLocation} from './defaultPreferences'; 7 | 8 | const pastDefaultPreferences = { 9 | 0: { 10 | url: '', 11 | } as ConfigV0, 12 | 1: { 13 | version: 1, 14 | teams: [], 15 | showTrayIcon: false, 16 | trayIconTheme: 'light', 17 | minimizeToTray: false, 18 | notifications: { 19 | flashWindow: 0, 20 | bounceIcon: false, 21 | bounceIconType: 'informational', 22 | }, 23 | showUnreadBadge: true, 24 | useSpellChecker: true, 25 | enableHardwareAcceleration: true, 26 | autostart: true, 27 | spellCheckerLocale: 'en-US', 28 | } as ConfigV1, 29 | 2: { 30 | version: 2, 31 | teams: [], 32 | showTrayIcon: true, 33 | trayIconTheme: 'light', 34 | minimizeToTray: true, 35 | notifications: { 36 | flashWindow: 2, 37 | bounceIcon: true, 38 | bounceIconType: 'informational', 39 | }, 40 | showUnreadBadge: true, 41 | useSpellChecker: true, 42 | enableHardwareAcceleration: true, 43 | autostart: true, 44 | spellCheckerLocale: 'en-US', 45 | darkMode: false, 46 | downloadLocation: getDefaultDownloadLocation(), 47 | } as ConfigV2, 48 | 3: defaultPreferences, 49 | }; 50 | 51 | export default pastDefaultPreferences; 52 | -------------------------------------------------------------------------------- /src/renderer/css/components/LoadingScreen.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: transparent; 3 | } 4 | 5 | .LoadingScreen { 6 | --background-color: #F4F4F6; 7 | --background-color-highlight: #FFFFFF; 8 | --stipple-color: #1E325C; 9 | --stipple-opacity: 0.08; 10 | 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | position: absolute; 15 | top: 0px; 16 | bottom: 0px; 17 | width: 100vw; 18 | height: 100vh; 19 | vertical-align: middle; 20 | opacity: 1; 21 | visibility: visible; 22 | z-index: 10; 23 | overflow:hidden; 24 | 25 | transition: opacity 150ms 0ms ease-out, visibility 150ms 0ms step-start; 26 | } 27 | 28 | .LoadingScreen--darkMode { 29 | --background-color-highlight: #28427B; 30 | --background-color: #1E325C; 31 | --stipple-color: #14213E; 32 | --stipple-opacity: 0.8; 33 | } 34 | 35 | .LoadingScreen__backgound, 36 | .LoadingScreen__backgound > svg 37 | { 38 | position: absolute; 39 | top: 0; 40 | left: 0; 41 | width: 100%; 42 | height: 100%; 43 | } 44 | 45 | .LoadingScreen__backgound { 46 | background: var(--background-color); 47 | } 48 | 49 | .LoadingScreen .Pill__stipple { 50 | fill: var(--stipple-color); 51 | fill-opacity: var(--stipple-opacity); 52 | } 53 | 54 | .LoadingScreen .Pill__gradient { 55 | stop-color: var(--background-color); 56 | } 57 | 58 | .LoadingScreen .Pill__gradientHighlight { 59 | stop-color: var(--background-color-highlight); 60 | } 61 | 62 | .LoadingScreen--loaded { 63 | opacity: 0; 64 | visibility: hidden; 65 | 66 | transition: opacity 150ms 0ms ease-in, visibility 150ms 0ms step-end; 67 | } 68 | -------------------------------------------------------------------------------- /src/common/tabs/TabView.test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import {MattermostServer} from 'common/servers/MattermostServer'; 5 | import * as TabView from 'common/tabs/TabView'; 6 | 7 | describe('common/tabs/TabView', () => { 8 | describe('getServerView', () => { 9 | it('should return correct URL on messaging tab', () => { 10 | const server = new MattermostServer('server-1', 'http://server-1.com'); 11 | const tab = {name: TabView.TAB_MESSAGING}; 12 | expect(TabView.getServerView(server, tab).url).toBe(server.url); 13 | }); 14 | 15 | it('should return correct URL on playbooks tab', () => { 16 | const server = new MattermostServer('server-1', 'http://server-1.com'); 17 | const tab = {name: TabView.TAB_PLAYBOOKS}; 18 | expect(TabView.getServerView(server, tab).url.toString()).toBe(`${server.url}playbooks`); 19 | }); 20 | 21 | it('should return correct URL on boards tab', () => { 22 | const server = new MattermostServer('server-1', 'http://server-1.com'); 23 | const tab = {name: TabView.TAB_FOCALBOARD}; 24 | expect(TabView.getServerView(server, tab).url.toString()).toBe(`${server.url}boards`); 25 | }); 26 | 27 | it('should throw error on bad tab name', () => { 28 | const server = new MattermostServer('server-1', 'http://server-1.com'); 29 | const tab = {name: 'not a real tab name'}; 30 | expect(() => { 31 | TabView.getServerView(server, tab); 32 | }).toThrow(Error); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /.config/notice-file/Readme.md: -------------------------------------------------------------------------------- 1 | # Notice.txt File Configuration 2 | 3 | We are automatically generating Notice.txt by using first-level dependencies of the project. The related pipeline uses `config.yaml` stored in this folder. 4 | 5 | 6 | ## Configuration 7 | 8 | Sample: 9 | 10 | ``` 11 | title: "Mattermost Desktop" 12 | copyright: "© 2016-2017 Mattermost, Inc. All Rights Reserved. See LICENSE.txt for license information." 13 | description: "This document includes a list of open source components used in Mattermost Desktop, including those that have been modified." 14 | reviewers: 15 | - mattermost/release-managers 16 | - mattermost/team-desktop 17 | search: 18 | - "package.json" 19 | dependencies: 20 | - "wix" 21 | devDependencies: 22 | - "webpack" 23 | ``` 24 | 25 | | Field | Type | Purpose | 26 | | :-- | :-- | :-- | 27 | | title | string | Field content will be used as a title of the application. See first line of `NOTICE.txt` file. | 28 | | copyright | string | Field content will be used as a copyright message. See second line of `NOTICE.txt` file. | 29 | | description | string | Field content will be used as notice file description. See third line of `NOTICE.txt` file. | 30 | | reviewers | array of GitHub user/teams | Those will be automatically assigned to the PRs as reviewers. | 31 | | dependencies | array | If any dependency name mentioned, it will be automatically added even if it is not a first-level dependency. | 32 | | devDependencies | array | If any dependency name mentioned, it will be added when it is referenced in devDependency section. | 33 | | search | array | Pipeline will search for package.json files mentioned here. Globstar format is supported ie. `packages/**/package.json`. | 34 | 35 | -------------------------------------------------------------------------------- /scripts/afterpack.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | const path = require('path'); 5 | 6 | const {spawn} = require('electron-notarize/lib/spawn.js'); 7 | 8 | const SETUID_PERMISSIONS = '4755'; 9 | 10 | const {flipFuses, FuseVersion, FuseV1Options} = require('@electron/fuses'); 11 | 12 | function fixSetuid(context) { 13 | return async (target) => { 14 | if (!['appimage', 'snap'].includes(target.name.toLowerCase())) { 15 | const result = await spawn('chmod', [SETUID_PERMISSIONS, path.join(context.appOutDir, 'chrome-sandbox')]); 16 | if (result.code !== 0) { 17 | throw new Error( 18 | `Failed to set proper permissions for linux arch on ${target.name}`, 19 | ); 20 | } 21 | } 22 | }; 23 | } 24 | 25 | function getAppFileName(context) { 26 | switch (context.electronPlatformName) { 27 | case 'win32': 28 | return 'Mattermost.exe'; 29 | case 'darwin': 30 | case 'mas': 31 | return 'Mattermost.app'; 32 | case 'linux': 33 | return context.packager.executableName; 34 | default: 35 | return ''; 36 | } 37 | } 38 | 39 | exports.default = async function afterPack(context) { 40 | await flipFuses( 41 | `${context.appOutDir}/${getAppFileName(context)}`, // Returns the path to the electron binary 42 | { 43 | version: FuseVersion.V1, 44 | [FuseV1Options.RunAsNode]: false, // Disables ELECTRON_RUN_AS_NODE 45 | }); 46 | 47 | if (context.electronPlatformName === 'linux') { 48 | context.targets.forEach(fixSetuid(context)); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /src/renderer/components/ExtraBar.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import React from 'react'; 5 | import {Row, Button} from 'react-bootstrap'; 6 | import {FormattedMessage} from 'react-intl'; 7 | 8 | type Props = { 9 | darkMode?: boolean; 10 | goBack?: () => void; 11 | show?: boolean; 12 | }; 13 | 14 | export default class ExtraBar extends React.PureComponent { 15 | handleBack = () => { 16 | if (this.props.goBack) { 17 | this.props.goBack(); 18 | } 19 | } 20 | render() { 21 | let barClass = 'clear-mode'; 22 | if (!this.props.show) { 23 | barClass = 'hidden'; 24 | } else if (this.props.darkMode) { 25 | barClass = 'dark-mode'; 26 | } 27 | 28 | return ( 29 | 33 |
37 | 49 |
50 |
51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/renderer/intl_provider.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import React from 'react'; 5 | import {IntlProvider as BaseIntlProvider} from 'react-intl'; 6 | 7 | import {GET_LANGUAGE_INFORMATION, RETRIEVED_LANGUAGE_INFORMATION} from 'common/communication'; 8 | 9 | import {Language} from '../../i18n/i18n'; 10 | 11 | type State = { 12 | language?: Language; 13 | } 14 | 15 | export default class IntlProvider extends React.PureComponent { 16 | constructor(props: any) { 17 | super(props); 18 | this.state = {}; 19 | } 20 | 21 | componentDidMount() { 22 | window.addEventListener('message', this.handleMessageEvent); 23 | window.postMessage({type: GET_LANGUAGE_INFORMATION}); 24 | } 25 | 26 | componentWillUnmount() { 27 | window.removeEventListener('message', this.handleMessageEvent); 28 | } 29 | 30 | handleMessageEvent = (event: MessageEvent<{type: string; data: Language}>) => { 31 | if (event.data.type === RETRIEVED_LANGUAGE_INFORMATION) { 32 | this.setState({ 33 | language: event.data.data, 34 | }); 35 | } 36 | } 37 | 38 | render() { 39 | if (!this.state.language) { 40 | return null; 41 | } 42 | 43 | return ( 44 | 51 | {this.props.children} 52 | 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/common/config/buildConfig.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | 5 | import {BuildConfig} from 'types/config'; 6 | 7 | // For detailed guides, please refer to https://docs.mattermost.com/deployment/desktop-app-deployment.html 8 | 9 | /** 10 | * Build-time configuration. End-users can't change these parameters. 11 | * @prop {Object[]} defaultTeams 12 | * @prop {string} defaultTeams[].name - The tab name for default team. 13 | * @prop {string} defaultTeams[].url - The URL for default team. 14 | * @prop {string} defaultTeams[].order - Sort order for team tabs (0, 1, 2) 15 | * @prop {string} helpLink - The URL for "Help->Learn More..." menu item. 16 | * If null is specified, the menu disappears. 17 | * @prop {boolean} enableServerManagement - Whether users can edit servers configuration. 18 | * Specify at least one server for "defaultTeams" 19 | * when "enableServerManagement is set to false 20 | * @prop {[]} managedResources - Defines which paths are managed 21 | * @prop {[]} allowedProtocols - Defines which protocols should be automatically allowed 22 | */ 23 | const buildConfig: BuildConfig = { 24 | defaultTeams: [/* 25 | { 26 | name: 'example', 27 | url: 'https://example.com' 28 | } 29 | */], 30 | helpLink: 'https://docs.mattermost.com/messaging/managing-desktop-app-servers.html', 31 | enableServerManagement: true, 32 | enableAutoUpdater: true, 33 | managedResources: ['trusted'], 34 | allowedProtocols: [ 35 | 'mattermost', 36 | 'ftp', 37 | 'mailto', 38 | 'tel', 39 | ], 40 | }; 41 | 42 | export default buildConfig; 43 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SIGNER?="origin" 2 | 3 | GPG=$(shell command which gpg || echo "N/A") 4 | DPKG_SIG=$(shell command which dpkg-sig || echo "N/A") 5 | 6 | define sign_debian_package 7 | dpkg-sig -k ${GPG_KEY_ID} --sign ${SIGNER} $1 8 | dpkg-sig --verify $1 9 | endef 10 | 11 | .PHONY: check-sign-linux-deb 12 | check-sign-linux-deb: ##Check running environment to sign debian packages 13 | ifeq ("$(GPG)","N/A") 14 | @echo "Path does not contain gpg executable. Consider install!" 15 | @exit 128 16 | else 17 | @echo "gpg Found in path!" 18 | endif 19 | ifeq ("$(DPKG_SIG)","N/A") 20 | @echo "Path does not contain dpkg_sig executable. Consider install!" 21 | @exit 128 22 | else 23 | @echo "dpkg_sig Found in path!" 24 | endif 25 | ifndef GPG_KEY_ID 26 | @echo "Please define GPG_KEY_ID environment variable!" 27 | @exit 128 28 | else 29 | @echo "GPG_KEY_ID is defined" 30 | endif 31 | 32 | .PHONY: npm-ci 33 | npm-ci: ## Install all npm dependencies 34 | PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm ci 35 | 36 | .PHONY: package 37 | package: package-linux-deb ## Generates packages for all environments 38 | 39 | .PHONY: package-linux-deb 40 | package-linux-deb: npm-ci ## Generates linux packages under build/linux folder 41 | npm run package:linux-deb 42 | mkdir -p artifacts 43 | find ./release -name '*.deb' -exec cp "{}" artifacts/ \; 44 | 45 | 46 | .PHONY: sign 47 | sign: sign-linux-deb ## Sign packages in artifacts directory 48 | 49 | .PHONY: sign-linux-deb 50 | sign-linux-deb: check-sign-linux-deb ## Sign debian packages 51 | $(foreach file, $(wildcard artifacts/*.deb), $(call sign_debian_package,${file});) 52 | 53 | ## Help documentation à la https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html 54 | help: 55 | @grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' ./Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 56 | 57 | -------------------------------------------------------------------------------- /src/common/utils/util.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | // Copyright (c) 2015-2016 Yuya Ochiai 4 | 5 | import {DEVELOPMENT, PRODUCTION} from './constants'; 6 | 7 | function runMode() { 8 | return process.env.NODE_ENV === PRODUCTION ? PRODUCTION : DEVELOPMENT; 9 | } 10 | 11 | const DEFAULT_MAX = 20; 12 | 13 | function shorten(string: string, max?: number) { 14 | const maxLength = (max && max >= 4) ? max : DEFAULT_MAX; 15 | if (string.length >= maxLength) { 16 | return `${string.slice(0, maxLength - 3)}...`; 17 | } 18 | return string; 19 | } 20 | 21 | function isVersionGreaterThanOrEqualTo(currentVersion: string, compareVersion: string): boolean { 22 | if (currentVersion === compareVersion) { 23 | return true; 24 | } 25 | 26 | // We only care about the numbers 27 | const currentVersionNumber = (currentVersion || '').split('.').filter((x) => (/^[0-9]+$/).exec(x) !== null); 28 | const compareVersionNumber = (compareVersion || '').split('.').filter((x) => (/^[0-9]+$/).exec(x) !== null); 29 | 30 | for (let i = 0; i < Math.max(currentVersionNumber.length, compareVersionNumber.length); i++) { 31 | const currentVersion = parseInt(currentVersionNumber[i], 10) || 0; 32 | const compareVersion = parseInt(compareVersionNumber[i], 10) || 0; 33 | if (currentVersion > compareVersion) { 34 | return true; 35 | } 36 | 37 | if (currentVersion < compareVersion) { 38 | return false; 39 | } 40 | } 41 | 42 | // If all components are equal, then return true 43 | return true; 44 | } 45 | 46 | export function t(s: string) { 47 | return s; 48 | } 49 | 50 | export default { 51 | runMode, 52 | shorten, 53 | isVersionGreaterThanOrEqualTo, 54 | }; 55 | -------------------------------------------------------------------------------- /e2e/specs/menu_bar/menu.test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 'use strict'; 4 | 5 | const fs = require('fs'); 6 | 7 | const robot = require('robotjs'); 8 | 9 | const env = require('../../modules/environment'); 10 | const {asyncSleep} = require('../../modules/utils'); 11 | 12 | describe('menu/menu', function desc() { 13 | this.timeout(30000); 14 | 15 | const config = env.demoConfig; 16 | 17 | beforeEach(async () => { 18 | env.cleanDataDir(); 19 | env.createTestUserDataDir(); 20 | env.cleanTestConfig(); 21 | fs.writeFileSync(env.configFilePath, JSON.stringify(config)); 22 | await asyncSleep(1000); 23 | this.app = await env.getApp(); 24 | }); 25 | 26 | afterEach(async () => { 27 | if (this.app) { 28 | await this.app.close(); 29 | } 30 | await env.clearElectronInstances(); 31 | }); 32 | 33 | if (process.platform !== 'darwin') { 34 | it('MM-T4404 should open the 3 dot menu with Alt', async () => { 35 | const mainWindow = this.app.windows().find((window) => window.url().includes('index')); 36 | mainWindow.should.not.be.null; 37 | 38 | await mainWindow.bringToFront(); 39 | await mainWindow.click('#app'); 40 | 41 | // Settings window should open if Alt works 42 | robot.keyTap('alt'); 43 | robot.keyTap('enter'); 44 | robot.keyTap('f'); 45 | robot.keyTap('s'); 46 | robot.keyTap('enter'); 47 | const settingsWindow = await this.app.waitForEvent('window', { 48 | predicate: (window) => window.url().includes('settings'), 49 | }); 50 | settingsWindow.should.not.be.null; 51 | }); 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /resources/windows/gpo/en-US/mattermost.adml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Requires Mattermost Desktop 4.3 or later 8 | Requires Mattermost Desktop 5.1 or later 9 | Mattermost 10 | 11 | EnableAutoUpdater 12 | If this policy is enabled, users will receive notifications when new versions of the desktop app are available. System Administrator privileges on the computer are required to install the update. 13 | 14 | EnableServerManagement 15 | If this policy is enabled, users can add or remove servers in their app settings, even if default servers are configured in DefaultServerList. 16 | 17 | If this policy is disabled, the Server Management section is hidden in the app settings and only servers defined by Group Policy can be used. 18 | DefaultServerList 19 | If this policy is enabled, you can define one or more Mattermost servers that will be pre-configured for users when they launch the app. "Value name" represents the name of the corresponding server tab and "Value" is the URL of the Mattermost server. 20 | 21 | 22 | 23 | List of default Mattermost servers: 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/renderer/hooks/useAnimationEnd.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import React from 'react'; 5 | 6 | /** 7 | * A custom hook to implement an animationend listener on the provided ref 8 | * @param {object} ref - A reference to a DOM element to add the listener to 9 | * @param {function} callback - A callback function that will be run for matching animation events 10 | * @param {string} animationName - The name of the animation to listen for 11 | * @param {boolean} listenForEventBubbling - A parameter that when true, listens for events on the target element and 12 | * bubbled from all descendent elements but when false, only listens for events coming from the target element and 13 | * ignores events bubbling up from descendent elements 14 | */ 15 | function useAnimationEnd( 16 | ref: React.RefObject, 17 | callback: (event: Event) => void, 18 | animationName: string, 19 | listenForEventBubbling = true, 20 | ): void { 21 | React.useEffect(() => { 22 | if (!ref.current) { 23 | return undefined; 24 | } 25 | 26 | function handleAnimationend(event: Event & {animationName?: string}) { 27 | if (!listenForEventBubbling && event.target !== ref.current) { 28 | return; 29 | } 30 | if (animationName && animationName !== event.animationName) { 31 | return; 32 | } 33 | callback(event); 34 | } 35 | 36 | ref.current.addEventListener('animationend', handleAnimationend); 37 | 38 | return () => { 39 | ref.current?.removeEventListener('animationend', handleAnimationend); 40 | }; 41 | }, [ref, callback, animationName, listenForEventBubbling]); 42 | } 43 | 44 | export default useAnimationEnd; 45 | -------------------------------------------------------------------------------- /src/main/AppVersionManager.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | // Copyright (c) 2015-2016 Yuya Ochiai 4 | 5 | import {ipcMain} from 'electron'; 6 | 7 | import {AppState} from 'types/appState'; 8 | 9 | import {UPDATE_PATHS} from 'common/communication'; 10 | import JsonFileManager from 'common/JsonFileManager'; 11 | 12 | import {appVersionJson} from 'main/constants'; 13 | 14 | import * as Validator from './Validator'; 15 | 16 | export class AppVersionManager extends JsonFileManager { 17 | constructor(file: string) { 18 | super(file); 19 | 20 | this.init(); 21 | } 22 | init = () => { 23 | // ensure data loaded from file is valid 24 | const validatedJSON = Validator.validateAppState(this.json); 25 | if (!validatedJSON) { 26 | this.setJson({}); 27 | } 28 | } 29 | 30 | set lastAppVersion(version) { 31 | this.setValue('lastAppVersion', version); 32 | } 33 | 34 | get lastAppVersion() { 35 | return this.getValue('lastAppVersion'); 36 | } 37 | 38 | set skippedVersion(version) { 39 | this.setValue('skippedVersion', version); 40 | } 41 | 42 | get skippedVersion() { 43 | return this.getValue('skippedVersion'); 44 | } 45 | 46 | set updateCheckedDate(date) { 47 | this.setValue('updateCheckedDate', date?.toISOString()); 48 | } 49 | 50 | get updateCheckedDate() { 51 | const date = this.getValue('updateCheckedDate'); 52 | if (date) { 53 | return new Date(date); 54 | } 55 | return null; 56 | } 57 | } 58 | 59 | let appVersionManager = new AppVersionManager(appVersionJson); 60 | export default appVersionManager; 61 | 62 | ipcMain.on(UPDATE_PATHS, () => { 63 | appVersionManager = new AppVersionManager(appVersionJson); 64 | }); 65 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | I confirm (by marking "x" in the [ ] below: [x]): 2 | 3 | - [ ] This is not a troubleshooting question. If you have having issues, please send us a message in the [Peer-to-peer Help](https://community-daily.mattermost.com/core/channels/peer-to-peer-help) or the [Developers: Desktop App](https://community-daily.mattermost.com/core/channels/desktop-app) channels on the Mattermost Community server 4 | - [ ] I have read [contributing guidelines](https://github.com/mattermost/desktop/blob/master/CONTRIBUTING.md). 5 | 6 | --- 7 | 8 | **Summary** 9 | 12 | 13 | **Steps to reproduce** 14 | 15 | 22 | 23 | **Expected behavior** 24 | 25 | **Observed behavior** 26 | 27 | 30 | 31 | **Possible fixes** 32 | 33 | 36 | 37 | --- 38 | 39 | [JIRA tickets](https://mattermost.atlassian.net/issues/?filter=-4&jql=component%20%3D%20%22Desktop%20App%22%20order%20by%20created%20DESC) are created for all valid bug reports filed here. Bugs and features that the Mattermost core team are not immediately working on are opened as [Help Wanted GitHub issues](https://developers.mattermost.com/contribute/getting-started/contribution-checklist/) and welcome to contributions. 40 | 41 | Our goal is to bring the Mattermost desktop repository into our day-to-day internal process with the hope of having more frequent desktop releases with faster feature development and turnaround time on bug fixes. 42 | -------------------------------------------------------------------------------- /src/main/AutoLauncher.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | 5 | import AutoLaunch from 'auto-launch'; 6 | import {app} from 'electron'; 7 | import isDev from 'electron-is-dev'; 8 | import log from 'electron-log'; 9 | 10 | export class AutoLauncher { 11 | appLauncher: AutoLaunch; 12 | 13 | constructor() { 14 | this.appLauncher = new AutoLaunch({ 15 | name: app.name, 16 | isHidden: true, 17 | }); 18 | } 19 | 20 | async upgradeAutoLaunch() { 21 | if (process.platform === 'darwin') { 22 | return; 23 | } 24 | const appLauncher = new AutoLaunch({ 25 | name: app.name, 26 | }); 27 | const enabled = await appLauncher.isEnabled(); 28 | if (enabled) { 29 | await appLauncher.enable(); 30 | } 31 | } 32 | 33 | isEnabled() { 34 | return this.appLauncher.isEnabled(); 35 | } 36 | 37 | async enable() { 38 | if (isDev) { 39 | log.warn('In development mode, autostart config never effects'); 40 | return Promise.resolve(null); 41 | } 42 | const enabled = await this.isEnabled(); 43 | if (!enabled) { 44 | return this.appLauncher.enable(); 45 | } 46 | return Promise.resolve(null); 47 | } 48 | 49 | async disable() { 50 | if (isDev) { 51 | log.warn('In development mode, autostart config never effects'); 52 | return Promise.resolve(null); 53 | } 54 | const enabled = await this.isEnabled(); 55 | if (enabled) { 56 | return this.appLauncher.disable(); 57 | } 58 | return Promise.resolve(null); 59 | } 60 | } 61 | 62 | const autoLauncher = new AutoLauncher(); 63 | export default autoLauncher; 64 | -------------------------------------------------------------------------------- /src/renderer/css/components/CarouselPaginationIndicator.scss: -------------------------------------------------------------------------------- 1 | @import url("../_css_variables.scss"); 2 | 3 | .CarouselPaginationIndicator { 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | margin: 0 23px; 8 | 9 | .indicatorDot { 10 | display: flex; 11 | align-items: center; 12 | justify-content: center; 13 | width: 12px; 14 | height: 12px; 15 | border-radius: 50%; 16 | background: none; 17 | box-shadow: 0 0 0 3px transparent; 18 | cursor: pointer; 19 | 20 | &:not(:first-child) { 21 | margin-left: 4px; 22 | } 23 | 24 | .dot { 25 | width: 6px; 26 | height: 6px; 27 | border-radius: 50%; 28 | background: rgba(var(--denim-button-bg-rgb), 0.32); 29 | } 30 | 31 | &.active, 32 | &.disabled { 33 | cursor: default; 34 | pointer-events: none; 35 | } 36 | 37 | &.active { 38 | background: rgba(var(--denim-button-bg-rgb), 0.16); 39 | 40 | .dot { 41 | background: var(--denim-button-bg); 42 | } 43 | } 44 | 45 | &:focus-visible { 46 | box-sizing: border-box; 47 | border: 2px solid var(--sidebar-text-active-border); 48 | outline: none; 49 | } 50 | } 51 | 52 | .indicatorDot-inverted { 53 | .dot { 54 | background: rgba(var(--button-color-rgb), 0.32); 55 | } 56 | 57 | &.active { 58 | background: rgba(var(--button-color-rgb), 0.16); 59 | 60 | .dot { 61 | background: var(--button-color); 62 | } 63 | } 64 | 65 | &:focus-visible { 66 | border: 2px solid var(--denim-sidebar-active-border); 67 | color: var(--denim-button-bg); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/i18nManager.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import {ipcMain} from 'electron'; 5 | import log from 'electron-log'; 6 | 7 | import {GET_AVAILABLE_LANGUAGES, GET_LANGUAGE_INFORMATION} from 'common/communication'; 8 | 9 | import {Language, languages} from '../../i18n/i18n'; 10 | 11 | export function localizeMessage(s: string, defaultString = '', values: any = {}) { 12 | let str = i18nManager.currentLanguage.url[s] || defaultString; 13 | for (const key of Object.keys(values)) { 14 | str = str.replace(new RegExp(`{${key}}`, 'g'), values[key]); 15 | } 16 | return str; 17 | } 18 | 19 | export class I18nManager { 20 | currentLanguage: Language; 21 | 22 | constructor() { 23 | this.currentLanguage = this.getLanguages().en; 24 | 25 | ipcMain.handle(GET_LANGUAGE_INFORMATION, this.getCurrentLanguage); 26 | ipcMain.handle(GET_AVAILABLE_LANGUAGES, this.getAvailableLanguages); 27 | } 28 | 29 | setLocale = (locale: string) => { 30 | log.debug('i18nManager.setLocale', locale); 31 | 32 | if (this.isLanguageAvailable(locale)) { 33 | this.currentLanguage = this.getLanguages()[locale]; 34 | log.info('Set new language', locale); 35 | return true; 36 | } 37 | 38 | log.warn('Failed to set new language', locale); 39 | return false; 40 | } 41 | 42 | getLanguages = () => { 43 | return languages; 44 | } 45 | 46 | getAvailableLanguages = () => { 47 | return Object.keys(languages); 48 | } 49 | 50 | isLanguageAvailable = (locale: string) => { 51 | return Boolean(this.getLanguages()[locale]); 52 | } 53 | 54 | getCurrentLanguage = () => { 55 | return this.currentLanguage; 56 | } 57 | } 58 | 59 | const i18nManager = new I18nManager(); 60 | export default i18nManager; 61 | -------------------------------------------------------------------------------- /src/renderer/css/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin inFromRight($start) { 2 | @keyframes inFromRight#{$start} { 3 | 0% { 4 | transform: translateX(#{$start + '%'}); 5 | opacity: 0; 6 | } 7 | 100% { 8 | transform: translateX(0%); 9 | opacity: 1; 10 | } 11 | } 12 | } 13 | 14 | @mixin inFromLeft($start) { 15 | @keyframes inFromLeft#{$start} { 16 | 0% { 17 | transform: translateX(#{'-' + $start + '%'}); 18 | opacity: 0; 19 | } 20 | 100% { 21 | transform: translateX(0%); 22 | opacity: 1; 23 | } 24 | } 25 | } 26 | 27 | @mixin outToLeft($end) { 28 | @keyframes outToLeft#{$end} { 29 | 0% { 30 | transform: translateX(0%); 31 | opacity: 1; 32 | } 33 | 100% { 34 | transform: translateX(#{'-' + $end + '%'}); 35 | opacity: 0; 36 | } 37 | } 38 | } 39 | 40 | @mixin outToRight($end) { 41 | @keyframes outToRight#{$end} { 42 | 0% { 43 | transform: translateX(0%); 44 | opacity: 1; 45 | } 46 | 100% { 47 | transform: translateX(#{$end + '%'}); 48 | opacity: 0; 49 | } 50 | } 51 | } 52 | 53 | @mixin shake-horizontally { 54 | animation: shake-horizontally 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; 55 | backface-visibility: hidden; 56 | perspective: 1000px; 57 | transform: translate3d(0, 0, 0); 58 | } 59 | 60 | @keyframes shake-horizontally { 61 | 10%, 62 | 90% { 63 | transform: translate3d(-1px, 0, 0); 64 | } 65 | 66 | 20%, 67 | 80% { 68 | transform: translate3d(2px, 0, 0); 69 | } 70 | 71 | 30%, 72 | 50%, 73 | 70% { 74 | transform: translate3d(-4px, 0, 0); 75 | } 76 | 77 | 40%, 78 | 60% { 79 | transform: translate3d(4px, 0, 0); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /webpack.config.main.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | // Copyright (c) 2015-2016 Yuya Ochiai 4 | 5 | // This files uses CommonJS. 6 | /* eslint-disable import/no-commonjs */ 7 | 'use strict'; 8 | 9 | const {merge} = require('webpack-merge'); 10 | 11 | const CopyPlugin = require('copy-webpack-plugin'); 12 | 13 | const base = require('./webpack.config.base'); 14 | 15 | module.exports = merge(base, { 16 | entry: { 17 | index: './src/main/app/index.ts', 18 | mainWindow: './src/main/preload/mainWindow.js', 19 | dropdown: './src/main/preload/dropdown.js', 20 | preload: './src/main/preload/mattermost.js', 21 | modalPreload: './src/main/preload/modalPreload.js', 22 | loadingScreenPreload: './src/main/preload/loadingScreenPreload.js', 23 | urlView: './src/main/preload/urlView.js', 24 | }, 25 | externals: { 26 | 'macos-notification-state': 'require("macos-notification-state")', 27 | 'windows-focus-assist': 'require("windows-focus-assist")', 28 | }, 29 | module: { 30 | rules: [{ 31 | test: /\.(js|ts)?$/, 32 | use: { 33 | loader: 'babel-loader', 34 | options: { 35 | include: ['@babel/plugin-proposal-class-properties'], 36 | }, 37 | }, 38 | }, { 39 | test: /\.mp3$/, 40 | type: 'asset/inline', 41 | }, 42 | { 43 | test: /\.node$/, 44 | loader: 'node-loader', 45 | }], 46 | }, 47 | plugins: [ 48 | new CopyPlugin({ 49 | patterns: [{ 50 | from: 'assets/**/*', 51 | context: 'src', 52 | }], 53 | }), 54 | ], 55 | node: { 56 | __filename: true, 57 | __dirname: true, 58 | }, 59 | target: 'electron-main', 60 | }); 61 | 62 | /* eslint-enable import/no-commonjs */ 63 | -------------------------------------------------------------------------------- /e2e/specs/relative_url/relative_url.test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 'use strict'; 4 | 5 | const fs = require('fs'); 6 | 7 | const {expect} = require('chai'); 8 | 9 | const env = require('../../modules/environment'); 10 | const {asyncSleep} = require('../../modules/utils'); 11 | 12 | describe('copylink', function desc() { 13 | this.timeout(30000); 14 | 15 | const config = env.demoMattermostConfig; 16 | 17 | beforeEach(async () => { 18 | env.cleanDataDir(); 19 | env.createTestUserDataDir(); 20 | env.cleanTestConfig(); 21 | fs.writeFileSync(env.configFilePath, JSON.stringify(config)); 22 | await asyncSleep(1000); 23 | this.app = await env.getApp(); 24 | this.serverMap = await env.getServerMap(this.app); 25 | }); 26 | 27 | afterEach(async () => { 28 | if (this.app) { 29 | await this.app.close(); 30 | } 31 | await env.clearElectronInstances(); 32 | }); 33 | 34 | it('MM-T1308 Check that external links dont open in the app', async () => { 35 | const loadingScreen = this.app.windows().find((window) => window.url().includes('loadingScreen')); 36 | await loadingScreen.waitForSelector('.LoadingScreen', {state: 'hidden'}); 37 | const firstServer = this.serverMap[`${config.teams[0].name}___TAB_MESSAGING`].win; 38 | await env.loginToMattermost(firstServer); 39 | await firstServer.waitForSelector('#sidebarItem_suscipit-4'); 40 | await firstServer.click('#sidebarItem_suscipit-4'); 41 | await firstServer.click('#post_textbox'); 42 | await firstServer.fill('#post_textbox', 'https://electronjs.org/apps/mattermost'); 43 | await firstServer.press('#post_textbox', 'Enter'); 44 | const newPageWindow = this.app.windows().find((window) => window.url().includes('apps/mattermost')); 45 | expect(newPageWindow === undefined); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | Security 2 | ======== 3 | 4 | Safety and data security is of the utmost priority for the Mattermost community. If you are a security researcher and have discovered a security vulnerability in our codebase, we would appreciate your help in disclosing it to us in a responsible manner. 5 | 6 | Reporting security issues 7 | ------------------------- 8 | 9 | **Please do not use GitHub issues for security-sensitive communication.** 10 | 11 | Security issues in the community test server, any of the open source codebases maintained by Mattermost, or any of our commercial offerings should be reported via email to [responsibledisclosure@mattermost.com](mailto:responsibledisclosure@mattermost.com). Mattermost is committed to working together with researchers and keeping them updated throughout the patching process. Researchers who responsibly report valid security issues will be publicly credited for their efforts (if they so choose). 12 | 13 | For a more detailed description of the disclosure process and a list of researchers who have previously contributed to the disclosure program, see [Report a Security Vulnerability](https://mattermost.com/security-vulnerability-report/) on the Mattermost website. 14 | 15 | Security updates 16 | ---------------- 17 | 18 | Mattermost has a mandatory upgrade policy, and updates are only provided for the latest release. Critical updates are delivered as dot releases. Details on security updates are announced 30 days after the availability of the update. 19 | 20 | For more details about the security content of past releases, see the [Security Updates](https://mattermost.com/security-updates/) page on the Mattermost website. For timely notifications about new security updates, subscribe to the [Security Bulletins Mailing List](https://mattermost.com/security-updates/#sign-up). 21 | 22 | Contributing to this policy 23 | --------------------------- 24 | 25 | If you have feedback or suggestions on improving this policy document, please [create an issue](https://github.com/mattermost/desktop/issues/new). 26 | -------------------------------------------------------------------------------- /src/main/contextMenu.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | 5 | import {BrowserView, BrowserWindow, ContextMenuParams, Event} from 'electron'; 6 | import electronContextMenu, {Options} from 'electron-context-menu'; 7 | 8 | import urlUtils from 'common/utils/url'; 9 | 10 | const defaultMenuOptions = { 11 | shouldShowMenu: (e: Event, p: ContextMenuParams) => { 12 | const isInternalLink = p.linkURL.endsWith('#') && p.linkURL.slice(0, -1) === p.pageURL; 13 | let isInternalSrc; 14 | try { 15 | const srcurl = urlUtils.parseURL(p.srcURL); 16 | isInternalSrc = srcurl?.protocol === 'file:'; 17 | } catch (err) { 18 | isInternalSrc = false; 19 | } 20 | return p.isEditable || (p.mediaType !== 'none' && !isInternalSrc) || (p.linkURL !== '' && !isInternalLink) || p.misspelledWord !== '' || p.selectionText !== ''; 21 | }, 22 | showLookUpSelection: true, 23 | showSearchWithGoogle: true, 24 | showCopyImage: true, 25 | showSaveImage: true, 26 | showSaveImageAs: true, 27 | showServices: true, 28 | }; 29 | 30 | export default class ContextMenu { 31 | view: BrowserWindow | BrowserView; 32 | menuOptions: Options; 33 | menuDispose?: () => void; 34 | 35 | constructor(options: Options, view: BrowserWindow | BrowserView) { 36 | const providedOptions: Options = options || {}; 37 | 38 | this.menuOptions = Object.assign({}, defaultMenuOptions, providedOptions); 39 | this.view = view; 40 | 41 | this.reload(); 42 | } 43 | 44 | dispose = () => { 45 | if (this.menuDispose) { 46 | this.menuDispose(); 47 | delete this.menuDispose; 48 | } 49 | } 50 | 51 | reload = () => { 52 | this.dispose(); 53 | 54 | const options = {window: this.view, ...this.menuOptions}; 55 | this.menuDispose = electronContextMenu(options); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/notifications/Upgrade.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import path from 'path'; 5 | 6 | import {app, Notification} from 'electron'; 7 | 8 | import {localizeMessage} from 'main/i18nManager'; 9 | 10 | const assetsDir = path.resolve(app.getAppPath(), 'assets'); 11 | const appIconURL = path.resolve(assetsDir, 'appicon_48.png'); 12 | 13 | const defaultOptions = { 14 | title: localizeMessage('main.notifications.upgrade.newVersion.title', 'New desktop version available'), 15 | body: localizeMessage('main.notifications.upgrade.newVersion.body', 'A new version is available for you to download now.'), 16 | silent: false, 17 | icon: appIconURL, 18 | urgency: 'normal' as Notification['urgency'], 19 | }; 20 | 21 | export class NewVersionNotification extends Notification { 22 | constructor() { 23 | const options = {...defaultOptions}; 24 | if (process.platform === 'win32') { 25 | options.icon = appIconURL; 26 | } else if (process.platform === 'darwin') { 27 | // Notification Center shows app's icon, so there were two icons on the notification. 28 | Reflect.deleteProperty(options, 'icon'); 29 | } 30 | 31 | super(options); 32 | } 33 | } 34 | 35 | export class UpgradeNotification extends Notification { 36 | constructor() { 37 | const options = {...defaultOptions}; 38 | options.title = localizeMessage('main.notifications.upgrade.readyToInstall.title', 'Click to restart and install update'); 39 | options.body = localizeMessage('main.notifications.upgrade.readyToInstall.body', 'A new desktop version is ready to install now.'); 40 | if (process.platform === 'win32') { 41 | options.icon = appIconURL; 42 | } else if (process.platform === 'darwin') { 43 | // Notification Center shows app's icon, so there were two icons on the notification. 44 | Reflect.deleteProperty(options, 'icon'); 45 | } 46 | 47 | super(options); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/renderer/components/SaveButton/SaveButton.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import React from 'react'; 5 | import {FormattedMessage} from 'react-intl'; 6 | import classNames from 'classnames'; 7 | 8 | import LoadingWrapper from './LoadingWrapper'; 9 | 10 | import 'renderer/css/components/Button.scss'; 11 | 12 | type Props = { 13 | saving: boolean; 14 | disabled?: boolean; 15 | id?: string; 16 | onClick: (e: React.MouseEvent) => void; 17 | savingMessage?: React.ReactNode; 18 | defaultMessage?: React.ReactNode; 19 | extraClasses?: string; 20 | darkMode?: boolean; 21 | } 22 | 23 | const SaveButton = ({ 24 | id, 25 | defaultMessage = ( 26 | 30 | ), 31 | disabled, 32 | extraClasses, 33 | saving, 34 | savingMessage = ( 35 | 39 | ), 40 | darkMode, 41 | onClick, 42 | }: Props) => { 43 | const handleOnClick = (e: React.MouseEvent) => { 44 | if (saving) { 45 | return; 46 | } 47 | 48 | onClick(e); 49 | }; 50 | 51 | return ( 52 | 71 | ); 72 | }; 73 | 74 | export default SaveButton; 75 | -------------------------------------------------------------------------------- /src/main/notifications/Mention.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import os from 'os'; 5 | import path from 'path'; 6 | 7 | import {app, Notification} from 'electron'; 8 | 9 | import {MentionOptions} from 'types/notification'; 10 | 11 | import Utils from 'common/utils/util'; 12 | 13 | import {localizeMessage} from 'main/i18nManager'; 14 | 15 | const assetsDir = path.resolve(app.getAppPath(), 'assets'); 16 | const appIconURL = path.resolve(assetsDir, 'appicon_48.png'); 17 | 18 | const defaultOptions = { 19 | title: localizeMessage('main.notifications.mention.title', 'Someone mentioned you'), 20 | silent: false, 21 | icon: appIconURL, 22 | urgency: 'normal' as Notification['urgency'], 23 | }; 24 | const DEFAULT_WIN7 = 'Ding'; 25 | 26 | export class Mention extends Notification { 27 | customSound: string; 28 | channel: {id: string}; // TODO: Channel from mattermost-redux 29 | teamId: string; 30 | 31 | constructor(customOptions: MentionOptions, channel: {id: string}, teamId: string) { 32 | const options = {...defaultOptions, ...customOptions}; 33 | if (process.platform === 'darwin' || (process.platform === 'win32' && Utils.isVersionGreaterThanOrEqualTo(os.release(), '10.0'))) { 34 | // Notification Center shows app's icon, so there were two icons on the notification. 35 | Reflect.deleteProperty(options, 'icon'); 36 | } 37 | const isWin7 = (process.platform === 'win32' && !Utils.isVersionGreaterThanOrEqualTo(os.release(), '6.3') && DEFAULT_WIN7); 38 | const customSound = String(!options.silent && ((options.data && options.data.soundName !== 'None' && options.data.soundName) || isWin7)); 39 | if (customSound) { 40 | options.silent = true; 41 | } 42 | super(options); 43 | 44 | this.customSound = customSound; 45 | this.channel = channel; 46 | this.teamId = teamId; 47 | } 48 | 49 | getNotificationSound = () => { 50 | return this.customSound; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /e2e/specs/deep_linking/deeplink.test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | 'use strict'; 5 | const fs = require('fs'); 6 | 7 | const env = require('../../modules/environment'); 8 | const {asyncSleep} = require('../../modules/utils'); 9 | 10 | describe('application', function desc() { 11 | this.timeout(30000); 12 | 13 | const config = env.demoConfig; 14 | 15 | beforeEach(async () => { 16 | env.cleanDataDir(); 17 | env.createTestUserDataDir(); 18 | env.cleanTestConfig(); 19 | fs.writeFileSync(env.configFilePath, JSON.stringify(config)); 20 | await asyncSleep(1000); 21 | }); 22 | 23 | afterEach(async () => { 24 | if (this.app) { 25 | try { 26 | await this.app.close(); 27 | // eslint-disable-next-line no-empty 28 | } catch (err) {} 29 | } 30 | await env.clearElectronInstances(); 31 | }); 32 | 33 | if (process.platform === 'win32') { 34 | it('MM-T1304/MM-T1306 should open the app on the requested deep link', async () => { 35 | this.app = await env.getApp(['mattermost://github.com/test/url']); 36 | this.serverMap = await env.getServerMap(this.app); 37 | const mainWindow = this.app.windows().find((window) => window.url().includes('index')); 38 | const browserWindow = await this.app.browserWindow(mainWindow); 39 | const webContentsId = this.serverMap[`${config.teams[1].name}___TAB_MESSAGING`].webContentsId; 40 | const isActive = await browserWindow.evaluate((window, id) => { 41 | return window.getBrowserViews().find((view) => view.webContents.id === id).webContents.getURL(); 42 | }, webContentsId); 43 | isActive.should.equal('https://github.com/test/url'); 44 | const dropdownButtonText = await mainWindow.innerText('.TeamDropdownButton'); 45 | dropdownButtonText.should.equal('github'); 46 | await this.app.close(); 47 | }); 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /src/renderer/components/RemoveServerModal.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | 5 | import React from 'react'; 6 | import {Modal} from 'react-bootstrap'; 7 | import {FormattedMessage, useIntl} from 'react-intl'; 8 | 9 | import DestructiveConfirmationModal from './DestructiveConfirmModal'; 10 | 11 | type Props = { 12 | show: boolean; 13 | serverName: string; 14 | onHide: () => void; 15 | onAccept: React.MouseEventHandler; 16 | onCancel: React.MouseEventHandler; 17 | }; 18 | 19 | function RemoveServerModal(props: Props) { 20 | const intl = useIntl(); 21 | const {serverName, ...rest} = props; 22 | return ( 23 | 30 |

31 | 35 |

36 |

37 | 42 |

43 | 44 | )} 45 | /> 46 | ); 47 | } 48 | 49 | export default RemoveServerModal; 50 | -------------------------------------------------------------------------------- /.github/workflows/scorecards-analysis.yml: -------------------------------------------------------------------------------- 1 | name: Scorecards supply-chain security 2 | on: 3 | # Only the default branch is supported. 4 | branch_protection_rule: 5 | schedule: 6 | - cron: '44 7 * * 5' 7 | push: 8 | branches: [ master ] 9 | 10 | # Declare default permissions as read only. 11 | permissions: read-all 12 | 13 | jobs: 14 | analysis: 15 | name: Scorecards analysis 16 | runs-on: ubuntu-latest 17 | permissions: 18 | # Needed to upload the results to code-scanning dashboard. 19 | security-events: write 20 | actions: read 21 | contents: read 22 | 23 | steps: 24 | - name: "Checkout code" 25 | uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 26 | with: 27 | persist-credentials: false 28 | 29 | - name: "Run analysis" 30 | uses: ossf/scorecard-action@c8416b0b2bf627c349ca92fc8e3de51a64b005cf # v1.0.2 31 | with: 32 | results_file: results.sarif 33 | results_format: sarif 34 | # Read-only PAT token. To create it, 35 | # follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation. 36 | repo_token: ${{ secrets.SCORECARD_READ_TOKEN }} 37 | # Publish the results to enable scorecard badges. For more details, see 38 | # https://github.com/ossf/scorecard-action#publishing-results. 39 | # For private repositories, `publish_results` will automatically be set to `false`, 40 | # regardless of the value entered here. 41 | publish_results: true 42 | 43 | # Upload the results as artifacts (optional). 44 | - name: "Upload artifact" 45 | uses: actions/upload-artifact@82c141cc518b40d92cc801eee768e7aafc9c2fa2 # v2.3.1 46 | with: 47 | name: SARIF file 48 | path: results.sarif 49 | retention-days: 5 50 | 51 | # Upload the results to GitHub's code scanning dashboard. 52 | - name: "Upload to code-scanning" 53 | uses: github/codeql-action/upload-sarif@5f532563584d71fdef14ee64d17bafb34f751ce5 # v1.0.26 54 | with: 55 | sarif_file: results.sarif 56 | -------------------------------------------------------------------------------- /e2e/specs/mattermost/copy_link.test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 'use strict'; 4 | 5 | const fs = require('fs'); 6 | 7 | const {clipboard} = require('electron'); 8 | 9 | const env = require('../../modules/environment'); 10 | const {asyncSleep} = require('../../modules/utils'); 11 | 12 | describe('copylink', function desc() { 13 | this.timeout(40000); 14 | 15 | const config = env.demoMattermostConfig; 16 | 17 | beforeEach(async () => { 18 | env.cleanDataDir(); 19 | env.createTestUserDataDir(); 20 | env.cleanTestConfig(); 21 | fs.writeFileSync(env.configFilePath, JSON.stringify(config)); 22 | await asyncSleep(1000); 23 | this.app = await env.getApp(); 24 | this.serverMap = await env.getServerMap(this.app); 25 | }); 26 | 27 | afterEach(async () => { 28 | if (this.app) { 29 | await this.app.close(); 30 | } 31 | await env.clearElectronInstances(); 32 | }); 33 | 34 | it('MM-T125 Copy Link can be used from channel LHS', async () => { 35 | const loadingScreen = this.app.windows().find((window) => window.url().includes('loadingScreen')); 36 | await loadingScreen.waitForSelector('.LoadingScreen', {state: 'hidden'}); 37 | const firstServer = this.serverMap[`${config.teams[0].name}___TAB_MESSAGING`].win; 38 | await env.loginToMattermost(firstServer); 39 | await firstServer.waitForSelector('#sidebarItem_suscipit-4'); 40 | await firstServer.click('#sidebarItem_suscipit-4'); 41 | await firstServer.click('#sidebarItem_suscipit-4', {button: 'right'}); 42 | await firstServer.click('text=Copy Linksint >> span'); 43 | await firstServer.click('#sidebarItem_town-square'); 44 | await firstServer.click('#post_textbox'); 45 | const clipboardText = clipboard.readText(); 46 | await firstServer.fill('#post_textbox', clipboardText); 47 | const content = await firstServer.locator('#post_textbox').textContent(); 48 | content.should.contain('/ad-1/channels/suscipit-4'); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/common/config/upgradePreferences.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | 5 | import {ConfigV3, ConfigV2, ConfigV1, ConfigV0, AnyConfig} from 'types/config'; 6 | 7 | import {getDefaultTeamWithTabsFromTeam} from 'common/tabs/TabView'; 8 | 9 | import pastDefaultPreferences from './pastDefaultPreferences'; 10 | 11 | function deepCopy(object: T): T { 12 | return JSON.parse(JSON.stringify(object)); 13 | } 14 | 15 | export function upgradeV0toV1(configV0: ConfigV0) { 16 | const config = deepCopy(pastDefaultPreferences[1]); 17 | config.teams.push({ 18 | name: 'Primary team', 19 | url: configV0.url, 20 | }); 21 | return config; 22 | } 23 | 24 | export function upgradeV1toV2(configV1: ConfigV1) { 25 | const config: ConfigV2 = Object.assign({}, deepCopy(pastDefaultPreferences[2]), configV1); 26 | config.version = 2; 27 | config.teams = configV1.teams.map((value, index) => { 28 | return { 29 | ...value, 30 | order: index, 31 | }; 32 | }); 33 | return config; 34 | } 35 | 36 | export function upgradeV2toV3(configV2: ConfigV2) { 37 | const config: ConfigV3 = Object.assign({}, deepCopy(pastDefaultPreferences[3]), configV2); 38 | config.version = 3; 39 | config.teams = configV2.teams.map((value) => { 40 | return { 41 | ...getDefaultTeamWithTabsFromTeam(value), 42 | lastActiveTab: 0, 43 | }; 44 | }); 45 | config.lastActiveTeam = 0; 46 | config.spellCheckerLocales = []; 47 | config.startInFullscreen = false; 48 | return config; 49 | } 50 | 51 | export default function upgradeToLatest(config: AnyConfig): ConfigV3 { 52 | switch (config.version) { 53 | case 3: 54 | return config as ConfigV3; 55 | case 2: 56 | return upgradeToLatest(upgradeV2toV3(config as ConfigV2)); 57 | case 1: 58 | return upgradeToLatest(upgradeV1toV2(config as ConfigV1)); 59 | default: 60 | return upgradeToLatest(upgradeV0toV1(config as ConfigV0)); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /resources/windows/gpo/mattermost.admx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /scripts/generate_release_markdown.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | # Requires sha256sum, on osx you can do 5 | # brew install coreutils 6 | 7 | function print_link { 8 | local URL="${1}" 9 | local CHECKSUM="$(curl -s -S -L "${URL}" | sha256sum | awk '{print $1}')" 10 | echo "- ${URL}" 11 | echo " - SHA-256 Checksum: \`${CHECKSUM}\`" 12 | } 13 | 14 | VERSION="$1" # such as 3.7.1, 4.0.0-rc1 15 | BASE_URL="https://releases.mattermost.com/desktop/${VERSION}" 16 | 17 | cat <<-MD 18 | ### Mattermost Desktop v${VERSION} has been cut! 19 | 20 | Release notes can be found here: https://docs.mattermost.com/install/desktop-app-changelog.html 21 | 22 | The download links can be found below. 23 | 24 | #### Windows - msi files (beta) 25 | $(print_link "${BASE_URL}/mattermost-desktop-${VERSION}-x64.msi") 26 | $(print_link "${BASE_URL}/mattermost-desktop-${VERSION}-x86.msi") 27 | 28 | #### Windows - setup exe files 29 | $(print_link "${BASE_URL}/mattermost-desktop-setup-${VERSION}-win.exe") 30 | 31 | #### Windows - zip files 32 | $(print_link "${BASE_URL}/mattermost-desktop-${VERSION}-win32.zip") 33 | $(print_link "${BASE_URL}/mattermost-desktop-${VERSION}-win64.zip") 34 | 35 | #### Mac 36 | $(print_link "${BASE_URL}/mattermost-desktop-${VERSION}-mac-universal.dmg") 37 | $(print_link "${BASE_URL}/mattermost-desktop-${VERSION}-mac-x64.dmg") 38 | $(print_link "${BASE_URL}/mattermost-desktop-${VERSION}-mac-m1.dmg") 39 | 40 | #### Linux 41 | $(print_link "${BASE_URL}/mattermost-desktop-${VERSION}-linux-ia32.tar.gz") 42 | $(print_link "${BASE_URL}/mattermost-desktop-${VERSION}-linux-x64.tar.gz") 43 | 44 | #### Linux (Unofficial) - deb files 45 | $(print_link "${BASE_URL}/mattermost-desktop_${VERSION}-1_i386.deb") 46 | $(print_link "${BASE_URL}/mattermost-desktop_${VERSION}-1_amd64.deb") 47 | 48 | #### Linux (Unofficial) - rpm files (beta) 49 | $(print_link "${BASE_URL}/mattermost-desktop-${VERSION}-linux-i686.rpm") 50 | $(print_link "${BASE_URL}/mattermost-desktop-${VERSION}-linux-x86_64.rpm") 51 | 52 | #### Linux (Unofficial) - AppImage files 53 | $(print_link "${BASE_URL}/mattermost-desktop-${VERSION}-linux-i386.AppImage") 54 | $(print_link "${BASE_URL}/mattermost-desktop-${VERSION}-linux-x86_64.AppImage") 55 | MD 56 | -------------------------------------------------------------------------------- /e2e/specs/menu_bar/history_menu.test.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 'use strict'; 4 | 5 | const fs = require('fs'); 6 | 7 | const env = require('../../modules/environment'); 8 | const {asyncSleep} = require('../../modules/utils'); 9 | 10 | describe('history_menu', function desc() { 11 | this.timeout(30000); 12 | 13 | const config = env.demoMattermostConfig; 14 | 15 | beforeEach(async () => { 16 | env.cleanDataDir(); 17 | env.createTestUserDataDir(); 18 | env.cleanTestConfig(); 19 | fs.writeFileSync(env.configFilePath, JSON.stringify(config)); 20 | await asyncSleep(1000); 21 | this.app = await env.getApp(); 22 | this.serverMap = await env.getServerMap(this.app); 23 | }); 24 | 25 | afterEach(async () => { 26 | if (this.app) { 27 | await this.app.close(); 28 | } 29 | await env.clearElectronInstances(); 30 | }); 31 | 32 | it('Click back and forward from history', async () => { 33 | const loadingScreen = this.app.windows().find((window) => window.url().includes('loadingScreen')); 34 | await loadingScreen.waitForSelector('.LoadingScreen', {state: 'hidden'}); 35 | const firstServer = this.serverMap[`${config.teams[0].name}___TAB_MESSAGING`].win; 36 | await env.loginToMattermost(firstServer); 37 | await firstServer.waitForSelector('#sidebarItem_suscipit-4'); 38 | 39 | // click on sint channel 40 | await firstServer.click('#sidebarItem_suscipit-4'); 41 | 42 | // click on town square channel 43 | await firstServer.click('#sidebarItem_town-square'); 44 | await firstServer.locator('[aria-label="Back"]').click(); 45 | let channelHeaderText = await firstServer.$eval('#channelHeaderTitle', (el) => el.firstChild.innerHTML); 46 | channelHeaderText.should.equal('sint'); 47 | await firstServer.locator('[aria-label="Forward"]').click(); 48 | channelHeaderText = await firstServer.$eval('#channelHeaderTitle', (el) => el.firstChild.innerHTML); 49 | channelHeaderText.should.equal('Town Square'); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | #### Summary 9 | 12 | 13 | #### Ticket Link 14 | 21 | 22 | #### Checklist 23 | 26 | - [ ] Added or updated unit tests (required for all new features) 27 | - [ ] Has UI changes 28 | - [ ] read and understood our [Contributing Guidelines](https://github.com/mattermost/desktop/blob/master/CONTRIBUTING.md) 29 | - [ ] completed [Mattermost Contributor Agreement](https://mattermost.com/contribute/) 30 | - [ ] executed `npm run lint:js` for proper code formatting 31 | 32 | #### Device Information 33 | This PR was tested on: 34 | 35 | #### Screenshots 36 | 39 | 40 | #### Release Note 41 | 60 | 61 | ```release-note 62 | 63 | ``` 64 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Debug Main Process", 8 | "cwd": "${workspaceRoot}", 9 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", 10 | "runtimeArgs": [ 11 | "src", 12 | "--disable-dev-mode" 13 | ], 14 | "windows": { 15 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" 16 | }, 17 | "program": "${workspaceRoot}/src/main.js", 18 | "outFiles": [ 19 | "${workspaceRoot}/src/main_bundle.js" 20 | ], 21 | "sourceMaps": true, 22 | "preLaunchTask": "Build sources" 23 | }, 24 | /* 25 | { 26 | "name": "Debug Renderer Process", 27 | "type": "chrome", 28 | "request": "launch", 29 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", 30 | "windows": { 31 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" 32 | }, 33 | "runtimeArgs": [ 34 | "${workspaceRoot}/src", 35 | "--disable-dev-mode", 36 | "--remote-debugging-port=9222" 37 | ], 38 | "webRoot": "${workspaceRoot}/src/browser", 39 | "sourceMaps": true, 40 | "preLaunchTask": "Build sources" 41 | }, 42 | */ 43 | { 44 | "type": "node", 45 | "request": "launch", 46 | "name": "E2E Tests", 47 | "program": "${workspaceRoot}/node_modules/electron-mocha/bin/electron-mocha", 48 | "args": [ 49 | "-r", 50 | "@babel/register", 51 | "--recursive", 52 | "--timeout", 53 | "999999", 54 | "--colors", 55 | "${workspaceRoot}/dist/tests/e2e_bundle.js" 56 | ], 57 | "internalConsoleOptions": "openOnSessionStart", 58 | "preLaunchTask": "prepare-e2e" 59 | } 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /src/renderer/hooks/useTransitionEnd.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import React from 'react'; 5 | 6 | /** 7 | * A custom hook to implement a transitionend listener on the provided ref 8 | * @param {object} ref - A reference to a DOM element to add the listener to 9 | * @param {function} callback - A callback function that will be run for matching animation events 10 | * @param {array} properties - An array of css property strings to listen for 11 | * @param {boolean} listenForEventBubbling - A parameter that when true, listens for events on the target element and 12 | * bubbled from all descendent elements but when false, only listens for events coming from the target element and 13 | * ignores events bubbling up from descendent elements 14 | */ 15 | function useTransitionend( 16 | ref: React.RefObject, 17 | callback: (event: Event) => void, 18 | properties: string[], 19 | listenForEventBubbling = true, 20 | ) { 21 | React.useEffect(() => { 22 | if (!ref.current) { 23 | return undefined; 24 | } 25 | 26 | function handleTransitionEnd(event: Event & {propertyName?: string}) { 27 | if (!listenForEventBubbling && event.target !== ref.current) { 28 | return; 29 | } 30 | 31 | if (properties && typeof properties === 'object') { 32 | const property = properties.find( 33 | (propertyName) => propertyName === event.propertyName, 34 | ); 35 | if (property) { 36 | callback(event); 37 | } 38 | return; 39 | } 40 | callback(event); 41 | } 42 | 43 | ref.current.addEventListener('transitionend', handleTransitionEnd); 44 | 45 | return () => { 46 | if (!ref.current) { 47 | return; 48 | } 49 | ref.current.removeEventListener( 50 | 'transitionend', 51 | handleTransitionEnd, 52 | ); 53 | }; 54 | }, [ref, callback, properties, listenForEventBubbling]); 55 | } 56 | 57 | export default useTransitionend; 58 | -------------------------------------------------------------------------------- /src/renderer/components/AutoSaveIndicator.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | 5 | import React from 'react'; 6 | import {Alert} from 'react-bootstrap'; 7 | import {IntlShape, useIntl} from 'react-intl'; 8 | 9 | const baseClassName = 'AutoSaveIndicator'; 10 | const leaveClassName = `${baseClassName}-Leave`; 11 | 12 | export enum SavingState { 13 | SAVING_STATE_SAVING = 'saving', 14 | SAVING_STATE_SAVED = 'saved', 15 | SAVING_STATE_ERROR = 'error', 16 | SAVING_STATE_DONE = 'done', 17 | } 18 | 19 | function getClassNameAndMessage(intl: IntlShape, savingState: SavingState, errorMessage?: React.ReactNode) { 20 | switch (savingState) { 21 | case SavingState.SAVING_STATE_SAVING: 22 | return {className: baseClassName, message: intl.formatMessage({id: 'renderer.components.autoSaveIndicator.saving', defaultMessage: 'Saving...'})}; 23 | case SavingState.SAVING_STATE_SAVED: 24 | return {className: baseClassName, message: intl.formatMessage({id: 'renderer.components.autoSaveIndicator.saved', defaultMessage: 'Saved'})}; 25 | case SavingState.SAVING_STATE_ERROR: 26 | return {className: `${baseClassName}`, message: errorMessage}; 27 | case SavingState.SAVING_STATE_DONE: 28 | return {className: `${baseClassName} ${leaveClassName}`, message: intl.formatMessage({id: 'renderer.components.autoSaveIndicator.saved', defaultMessage: 'Saved'})}; 29 | default: 30 | return {className: `${baseClassName} ${leaveClassName}`, message: ''}; 31 | } 32 | } 33 | 34 | type Props = { 35 | id?: string; 36 | savingState: SavingState; 37 | errorMessage?: React.ReactNode; 38 | }; 39 | 40 | const AutoSaveIndicator: React.FC = (props: Props) => { 41 | const intl = useIntl(); 42 | const {savingState, errorMessage, ...rest} = props; 43 | const {className, message} = getClassNameAndMessage(intl, savingState, errorMessage); 44 | return ( 45 | 50 | {message} 51 | 52 | ); 53 | }; 54 | 55 | export default AutoSaveIndicator; 56 | -------------------------------------------------------------------------------- /webpack.config.base.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 Yuya Ochiai 2 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 3 | // See LICENSE.txt for license information. 4 | 5 | // This file uses CommonJS. 6 | /* eslint-disable import/no-commonjs */ 7 | 'use strict'; 8 | 9 | const childProcess = require('child_process'); 10 | const path = require('path'); 11 | 12 | const webpack = require('webpack'); 13 | 14 | const VERSION = childProcess.execSync('git rev-parse --short HEAD').toString(); 15 | const isProduction = process.env.NODE_ENV === 'production'; 16 | const isTest = process.env.NODE_ENV === 'test'; 17 | const isRelease = process.env.CIRCLE_BRANCH && process.env.CIRCLE_BRANCH.startsWith('release-'); 18 | 19 | const codeDefinitions = { 20 | __HASH_VERSION__: !isRelease && JSON.stringify(VERSION), 21 | __CAN_UPGRADE__: isTest || JSON.stringify(process.env.CAN_UPGRADE === 'true'), 22 | __IS_NIGHTLY_BUILD__: JSON.stringify(process.env.CIRCLE_BRANCH === 'nightly'), 23 | __IS_MAC_APP_STORE__: JSON.stringify(process.env.IS_MAC_APP_STORE === 'true'), 24 | __SKIP_ONBOARDING_SCREENS__: JSON.stringify(process.env.MM_DESKTOP_BUILD_SKIPONBOARDINGSCREENS === 'true'), 25 | __DISABLE_GPU__: JSON.stringify(process.env.MM_DESKTOP_BUILD_DISABLEGPU === 'true'), 26 | }; 27 | codeDefinitions['process.env.NODE_ENV'] = JSON.stringify(process.env.NODE_ENV); 28 | if (isTest) { 29 | codeDefinitions['process.resourcesPath'] = 'process.env.RESOURCES_PATH'; 30 | } 31 | 32 | module.exports = { 33 | 34 | // Some plugins cause errors on the app, so use few plugins. 35 | // https://webpack.js.org/concepts/mode/#mode-production 36 | mode: isProduction ? 'none' : 'development', 37 | bail: true, 38 | plugins: [ 39 | new webpack.DefinePlugin(codeDefinitions), 40 | ], 41 | devtool: isProduction ? undefined : 'inline-source-map', 42 | resolve: { 43 | alias: { 44 | renderer: path.resolve(__dirname, 'src/renderer'), 45 | main: path.resolve(__dirname, './src/main'), 46 | common: path.resolve(__dirname, './src/common'), 47 | static: path.resolve(__dirname, './src/assets'), 48 | }, 49 | extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'], 50 | }, 51 | }; 52 | 53 | /* eslint-enable import/no-commonjs */ 54 | -------------------------------------------------------------------------------- /src/renderer/modals/removeServer/removeServer.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 | // See LICENSE.txt for license information. 3 | 4 | import 'bootstrap/dist/css/bootstrap.min.css'; 5 | import 'renderer/css/modals.css'; 6 | 7 | import React, {useEffect, useState} from 'react'; 8 | import ReactDOM from 'react-dom'; 9 | 10 | import {ModalMessage} from 'types/modals'; 11 | 12 | import {MODAL_CANCEL, MODAL_INFO, MODAL_RESULT, RETRIEVE_MODAL_INFO} from 'common/communication'; 13 | 14 | import IntlProvider from 'renderer/intl_provider'; 15 | 16 | import RemoveServerModal from '../../components/RemoveServerModal'; 17 | 18 | import setupDarkMode from '../darkMode'; 19 | 20 | setupDarkMode(); 21 | 22 | const onClose = () => { 23 | window.postMessage({type: MODAL_CANCEL}, window.location.href); 24 | }; 25 | 26 | const onSave = (data: boolean) => { 27 | window.postMessage({type: MODAL_RESULT, data}, window.location.href); 28 | }; 29 | 30 | const RemoveServerModalWrapper: React.FC = () => { 31 | const [serverName, setServerName] = useState(''); 32 | 33 | const handleRemoveServerMessage = (event: {data: ModalMessage}) => { 34 | switch (event.data.type) { 35 | case MODAL_INFO: { 36 | setServerName(event.data.data); 37 | break; 38 | } 39 | default: 40 | break; 41 | } 42 | }; 43 | 44 | useEffect(() => { 45 | window.addEventListener('message', handleRemoveServerMessage); 46 | window.postMessage({type: RETRIEVE_MODAL_INFO}, window.location.href); 47 | }, []); 48 | 49 | return ( 50 | 51 | { 55 | onClose(); 56 | }} 57 | onCancel={() => { 58 | onSave(false); 59 | }} 60 | onAccept={() => { 61 | onSave(true); 62 | }} 63 | /> 64 | 65 | ); 66 | }; 67 | 68 | const start = async () => { 69 | ReactDOM.render( 70 | , 71 | document.getElementById('app'), 72 | ); 73 | }; 74 | 75 | start(); 76 | --------------------------------------------------------------------------------