diff --git a/CHANGELOG.md b/CHANGELOG.md index a44fb163..49ec550c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,18 +4,50 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Changed +- Greentext now has separate color slot for it - Removed the use of with_move parameters when fetching notifications +- Push notifications now are the same as normal notfication, and are localized. + +### Fixed +- Weird bug related to post being sent seemingly after pasting with keyboard (hopefully) +- Multiple issues with muted statuses/notifications ## [Unreleased patch] ### Add - Added private notifications option for push notifications - 'Copy link' button for statuses (in the ellipsis menu) +- Autocomplete domains from list of known instances +- 'Bot' settings option and badge +- Added profile meta data fields that can be set in profile settings +- Added option to reset avatar/banner in profile settings +- Descriptions can be set on uploaded files before posting +- Added status preview option to preview your statuses before posting +- When a post is a reply to an unavailable post, the 'Reply to'-text has a strike-through style ### Changed - Registration page no longer requires email if the server is configured not to require it +- Change heart to thumbs up in reaction picker +- Close the media modal on navigation events +- Add colons to the emoji alt text, to make them copyable +- Add better visual indication for drag-and-drop for files +- When disabling attachments, the placeholder links now show an icon and the description instead of just IMAGE or VIDEO etc ### Fixed +- Custom Emoji will display in poll options now. - Status ellipsis menu closes properly when selecting certain options +- Cropped images look correct in Chrome +- Newlines in the muted words settings work again +- Clicking on non-latin hashtags won't open a new window +- Uploading and drag-dropping multiple files works correctly now. +- Subject field now appears disabled when posting +- Fix status ellipsis menu being cut off in notifications column +- Fixed autocomplete sometimes not returning the right user when there's already some results +- Videos and audio and misc files show description as alt/title properly now +- Clicking on non-image/video files no longer opens an empty modal +- Audio files can now be played back in the frontend with hidden attachments +- Videos are not cropped awkwardly in the uploads section anymore +- Reply filtering options in Settings -> Filtering now work again using filtering on server +- Don't show just blank-screen when cookies are disabled ## [2.0.3] - 2020-05-02 ### Fixed @@ -77,6 +109,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Ability to change user's email - About page - Added remote user redirect +- Bookmarks ### Changed - changed the way fading effects for user profile/long statuses works, now uses css-mask instead of gradient background hacks which weren't exactly compatible with semi-transparent themes ### Fixed diff --git a/README.md b/README.md index 889f0837..b66383ad 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# pleroma_fe +# Pleroma-FE -> A single column frontend for both Pleroma and GS servers. +> A single column frontend designed for Pleroma. ![screenshot](https://i.imgur.com/DJVqSJ0.png) @@ -11,7 +11,6 @@ To translate Pleroma-FE, add your language to [src/i18n/messages.js](https://git # FOR ADMINS You don't need to build Pleroma-FE yourself. Those using the Pleroma backend will be able to use it out of the box. -For the GNU social backend, check out https://git.pleroma.social/pleroma/pleroma-fe/wikis/dual-boot-with-qvitter to see how to run Pleroma-FE and Qvitter at the same time. ## Build Setup diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 076bfb1c..241ad331 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -8,8 +8,6 @@ > > --Catbag -Pleroma-FE user interface is modeled after Qvitter which is modeled after older Twitter design. It provides a simple 2-column interface for microblogging. While being simple by default it also provides many powerful customization options. - ## Posting, reading, basic functions. After registering and logging in you're presented with your timeline in right column and new post form with timeline list and notifications in the left column. @@ -33,7 +31,7 @@ will become Note that you can only use emoji defined on your instance, you cannot "copy" someone else's emoji, and will have to ask your administrator to copy emoji from other instance to yours. Lastly, there's two convenience options for emoji: an emoji picker (smiley face to the right of "submit" button) and autocomplete suggestions - when you start typing :shortcode: it will automatically try to suggest you emoj and complete the shortcode for you if you select one. **Note** that if emoji doesn't show up in suggestions nor in emoji picker it means there's no such emoji on your instance, if shortcode doesn't match any defined emoji it will appear as text. * **Attachments** are fairly simple - you can attach any file to a post as long as the file is within maximum size limits. If you're uploading explicit material you can mark all of your attachments as sensitive (or add `#nsfw` tag) - it will hide the images and videos behind a warning so that it won't be displayed instantly. -* **Subject line** also known as **CW** (Content Warning) could be used as a header to the post and/or to warn others about contents of the post having something that might upset somebody or something among those lines. Several applications allow to hide post content leaving only subject line visible. As a side-effect using subject line will also mark your images as sensitive (see above). +* **Subject line** also known as **CW** (Content Warning) could be used as a header to the post and/or to warn others about contents of the post having something that might upset somebody or something among those lines. Several applications allow to hide post content leaving only subject line visible. Using a subject line will not mark your images as sensitive, you will have to do that explicitly (see above). * **Visiblity scope** controls who will be able to see your posts. There are four scopes available: 1. `Public`: This is the default, and some fediverse software like GNU Social only supports this. This means that your post is accessible by anyone and will be shown in the public timelines. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..8764f9ab --- /dev/null +++ b/docs/index.md @@ -0,0 +1,8 @@ +# Introduction to Pleroma-FE +## What is Pleroma-FE? + +Pleroma-FE is the default user-facing frontend for Pleroma. It's user interface is modeled after Qvitter which is modeled after an older Twitter design. It provides a simple 2-column interface for microblogging. While being simple by default it also provides many powerful customization options. + +## How can I use it? + +If your instance uses Pleroma-FE, you can acces it by going to your instance (e.g. ). You can read more about it's basic functionality in the [Pleroma-FE User Guide](./USER_GUIDE.md). We also have [a guide for administrators](./CONFIGURATION.md) and for [hackers/contributors](./HACKING.md). diff --git a/package.json b/package.json index 542086b4..96231171 100644 --- a/package.json +++ b/package.json @@ -22,23 +22,21 @@ "cropperjs": "^1.4.3", "diff": "^3.0.1", "escape-html": "^1.0.3", - "karma-mocha-reporter": "^2.2.1", + "parse-link-header": "^1.0.1", "localforage": "^1.5.0", - "object-path": "^0.11.3", "phoenix": "^1.3.0", "portal-vue": "^2.1.4", - "sanitize-html": "^1.13.0", "v-click-outside": "^2.1.1", - "vue": "^2.5.13", + "vue": "^2.6.11", "vue-chat-scroll": "^1.2.1", "vue-i18n": "^7.3.2", "vue-router": "^3.0.1", - "vue-template-compiler": "^2.3.4", + "vue-template-compiler": "^2.6.11", "vuelidate": "^0.7.4", - "vuex": "^3.0.1", - "whatwg-fetch": "^2.0.3" + "vuex": "^3.0.1" }, "devDependencies": { + "karma-mocha-reporter": "^2.2.1", "@babel/core": "^7.7.5", "@babel/plugin-transform-runtime": "^7.7.6", "@babel/preset-env": "^7.7.6", diff --git a/src/App.js b/src/App.js index aaebaa75..e245615a 100644 --- a/src/App.js +++ b/src/App.js @@ -6,12 +6,14 @@ import InstanceSpecificPanel from './components/instance_specific_panel/instance import FeaturesPanel from './components/features_panel/features_panel.vue' import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue' import ChatPanel from './components/chat_panel/chat_panel.vue' +import SettingsModal from './components/settings_modal/settings_modal.vue' import MediaModal from './components/media_modal/media_modal.vue' import SideDrawer from './components/side_drawer/side_drawer.vue' import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue' import MobileNav from './components/mobile_nav/mobile_nav.vue' import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue' import PostStatusModal from './components/post_status_modal/post_status_modal.vue' +import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue' import { windowWidth, windowHeight } from './services/window_utils/window_utils' export default { @@ -29,8 +31,10 @@ export default { SideDrawer, MobilePostStatusButton, MobileNav, + SettingsModal, UserReportingModal, - PostStatusModal + PostStatusModal, + GlobalNoticeList }, data: () => ({ mobileActivePanel: 'timeline', @@ -46,7 +50,8 @@ export default { }), created () { // Load the locale from the storage - this.$i18n.locale = this.$store.getters.mergedConfig.interfaceLanguage + const val = this.$store.getters.mergedConfig.interfaceLanguage + this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val }) window.addEventListener('resize', this.updateMobileState) }, destroyed () { @@ -118,6 +123,9 @@ export default { onSearchBarToggled (hidden) { this.searchBarHidden = hidden }, + openSettingsModal () { + this.$store.dispatch('openSettingsModal') + }, updateMobileState () { const mobileLayout = windowWidth() <= 800 const layoutHeight = windowHeight() @@ -127,14 +135,5 @@ export default { } this.$store.dispatch('setLayoutHeight', layoutHeight) } - }, - watch: { - '$route' (to, from) { - if ((to.name === 'chat' && from.name === 'chats') || (to.name === 'chats' && from.name === 'chat')) { - this.transitionName = 'none' - } else { - this.transitionName = 'fade' - } - } } } diff --git a/src/App.scss b/src/App.scss index c39a3b07..e2e2d079 100644 --- a/src/App.scss +++ b/src/App.scss @@ -47,6 +47,7 @@ html { } body { + overscroll-behavior-y: none; font-family: sans-serif; font-family: var(--interfaceFont, sans-serif); margin: 0; @@ -319,7 +320,7 @@ option { i[class*=icon-] { color: $fallback--icon; - color: var(--icon, $fallback--icon) + color: var(--icon, $fallback--icon); } .btn-block { @@ -566,7 +567,7 @@ main-router { min-height: 0; box-sizing: border-box; margin: 0; - margin-left: .25em; + margin-left: .5em; min-width: 1px; align-self: stretch; } @@ -858,53 +859,12 @@ nav { display: block; margin-right: 0.8em; } -} -.setting-item { - border-bottom: 2px solid var(--fg, $fallback--fg); - margin: 1em 1em 1.4em; - padding-bottom: 1.4em; - - > div { - margin-bottom: .5em; - &:last-child { - margin-bottom: 0; - } - } - - &:last-child { - border-bottom: none; - padding-bottom: 0; - margin-bottom: 1em; - } - - select { - min-width: 10em; - } - - - textarea { - width: 100%; - max-width: 100%; - height: 100px; - } - - .unavailable, - .unavailable i { - color: var(--cRed, $fallback--cRed); - color: $fallback--cRed; - } - - .btn { - min-height: 28px; - min-width: 10em; - padding: 0 2em; - } - - .number-input { - max-width: 6em; + .main { + margin-bottom: 7em; } } + .select-multiple { display: flex; .option-list { @@ -970,24 +930,50 @@ nav { background-color: var(--panel, $fallback--fg); } -.alert-dot-number { - display: inline-block; - border-radius: 1em; +.unread-chat-count { + font-size: 0.9em; + font-weight: bolder; + font-style: normal; + position: absolute; + right: 0.6rem; + padding: 0 0.3em; min-width: 1.3rem; min-height: 1.3rem; max-height: 1.3rem; - font-size: 0.9em; - font-weight: bolder; line-height: 1.3rem; - text-align: center; - vertical-align: middle; - white-space: nowrap; - padding: 0 0.3em; - position: absolute; - right: 0.6rem; - background-color: $fallback--cRed; - background-color: var(--badgeNotification, $fallback--cRed); - color: white; - color: var(--badgeNotificationText, white); - font-style: normal; +} + +.chat-layout { + // Needed for smoother chat navigation in the desktop Safari (otherwise the chat layout "jumps" as the chat opens). + overflow: hidden; + height: 100%; + + // Ensures the fixed position of the mobile browser bars on scroll up / down events. + // Prevents the mobile browser bars from overlapping or hiding the message posting form. + @media all and (max-width: 800px) { + body { + height: 100%; + } + + #app { + height: 100%; + overflow: hidden; + min-height: auto; + } + + #app_bg_wrapper { + overflow: hidden; + } + + .main { + overflow: hidden; + height: 100%; + } + + #content { + padding-top: 0; + height: 100%; + overflow: visible; + } + } } diff --git a/src/App.vue b/src/App.vue index fe353eb5..0276c6a6 100644 --- a/src/App.vue +++ b/src/App.vue @@ -46,15 +46,16 @@ @toggled="onSearchBarToggled" @click.stop.native /> - - + - - - + @@ -126,7 +125,9 @@ + + diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 0db03547..7160434f 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -8,38 +8,64 @@ import backendInteractorService from '../services/backend_interactor_service/bac import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' import { applyTheme } from '../services/style_setter/style_setter.js' -const getStatusnetConfig = async ({ store }) => { +let staticInitialResults = null + +const parsedInitialResults = () => { + if (!document.getElementById('initial-results')) { + return null + } + if (!staticInitialResults) { + staticInitialResults = JSON.parse(document.getElementById('initial-results').textContent) + } + return staticInitialResults +} + +const preloadFetch = async (request) => { + const data = parsedInitialResults() + if (!data || !data[request]) { + return window.fetch(request) + } + const requestData = JSON.parse(atob(data[request])) + return { + ok: true, + json: () => requestData, + text: () => requestData + } +} + +const getInstanceConfig = async ({ store }) => { try { - const res = await window.fetch('/api/statusnet/config.json') + const res = await preloadFetch('/api/v1/instance') if (res.ok) { const data = await res.json() - const { name, closed: registrationClosed, textlimit, uploadlimit, server, vapidPublicKey, safeDMMentionsEnabled } = data.site + const textlimit = data.max_toot_chars + const vapidPublicKey = data.pleroma.vapid_public_key - store.dispatch('setInstanceOption', { name: 'name', value: name }) - store.dispatch('setInstanceOption', { name: 'registrationOpen', value: (registrationClosed === '0') }) - store.dispatch('setInstanceOption', { name: 'textlimit', value: parseInt(textlimit) }) - store.dispatch('setInstanceOption', { name: 'server', value: server }) - store.dispatch('setInstanceOption', { name: 'safeDM', value: safeDMMentionsEnabled !== '0' }) - - // TODO: default values for this stuff, added if to not make it break on - // my dev config out of the box. - if (uploadlimit) { - store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadlimit.uploadlimit) }) - store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadlimit.avatarlimit) }) - store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadlimit.backgroundlimit) }) - store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadlimit.bannerlimit) }) - } + store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit }) if (vapidPublicKey) { store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) } - - return data.site.pleromafe } else { throw (res) } } catch (error) { - console.error('Could not load statusnet config, potentially fatal') + console.error('Could not load instance config, potentially fatal') + console.error(error) + } +} + +const getBackendProvidedConfig = async ({ store }) => { + try { + const res = await window.fetch('/api/pleroma/frontend_configurations') + if (res.ok) { + const data = await res.json() + return data.pleroma_fe + } else { + throw (res) + } + } catch (error) { + console.error('Could not load backend-provided frontend config, potentially fatal') console.error(error) } } @@ -132,7 +158,7 @@ const getTOS = async ({ store }) => { const getInstancePanel = async ({ store }) => { try { - const res = await window.fetch('/instance/panel.html') + const res = await preloadFetch('/instance/panel.html') if (res.ok) { const html = await res.text() store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html }) @@ -189,24 +215,34 @@ const getAppSecret = async ({ store }) => { const resolveStaffAccounts = ({ store, accounts }) => { const nicknames = accounts.map(uri => uri.split('/').pop()) - nicknames.map(nickname => store.dispatch('fetchUser', nickname)) store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames }) } const getNodeInfo = async ({ store }) => { try { - const res = await window.fetch('/nodeinfo/2.0.json') + const res = await preloadFetch('/nodeinfo/2.0.json') if (res.ok) { const data = await res.json() const metadata = data.metadata const features = metadata.features + store.dispatch('setInstanceOption', { name: 'name', value: metadata.nodeName }) + store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations }) store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') }) + store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') }) store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') }) + store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') }) store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') }) store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits }) store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled }) + const uploadLimits = metadata.uploadLimits + store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) }) + store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadLimits.avatar) }) + store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadLimits.background) }) + store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadLimits.banner) }) + store.dispatch('setInstanceOption', { name: 'fieldsLimits', value: metadata.fieldsLimits }) + store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames }) store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats }) @@ -257,7 +293,7 @@ const getNodeInfo = async ({ store }) => { const setConfig = async ({ store }) => { // apiConfig, staticConfig - const configInfos = await Promise.all([getStatusnetConfig({ store }), getStaticConfig()]) + const configInfos = await Promise.all([getBackendProvidedConfig({ store }), getStaticConfig()]) const apiConfig = configInfos[0] const staticConfig = configInfos[1] @@ -280,6 +316,11 @@ const checkOAuthToken = async ({ store }) => { const afterStoreSetup = async ({ store, i18n }) => { const width = windowWidth() store.dispatch('setMobileLayout', width <= 800) + + const overrides = window.___pleromafe_dev_overrides || {} + const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin + store.dispatch('setInstanceOption', { name: 'server', value: server }) + await setConfig({ store }) const { customTheme, customThemeSource } = store.state.config @@ -299,16 +340,18 @@ const afterStoreSetup = async ({ store, i18n }) => { } // Now we can try getting the server settings and logging in + // Most of these are preloaded into the index.html so blocking is minimized await Promise.all([ checkOAuthToken({ store }), - getTOS({ store }), getInstancePanel({ store }), - getStickers({ store }), - getNodeInfo({ store }) + getNodeInfo({ store }), + getInstanceConfig({ store }) ]) // Start fetching things that don't need to block the UI store.dispatch('fetchMutes') + getTOS({ store }) + getStickers({ store }) const router = new VueRouter({ mode: 'history', diff --git a/src/boot/routes.js b/src/boot/routes.js index c770746f..b5d3c631 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -2,6 +2,7 @@ import PublicTimeline from 'components/public_timeline/public_timeline.vue' import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue' import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue' import TagTimeline from 'components/tag_timeline/tag_timeline.vue' +import BookmarkTimeline from 'components/bookmark_timeline/bookmark_timeline.vue' import ConversationPage from 'components/conversation-page/conversation-page.vue' import Interactions from 'components/interactions/interactions.vue' import DMs from 'components/dm_timeline/dm_timeline.vue' @@ -9,10 +10,8 @@ import ChatList from 'components/chat_list/chat_list.vue' import Chat from 'components/chat/chat.vue' import UserProfile from 'components/user_profile/user_profile.vue' import Search from 'components/search/search.vue' -import Settings from 'components/settings/settings.vue' import Registration from 'components/registration/registration.vue' import PasswordReset from 'components/password_reset/password_reset.vue' -import UserSettings from 'components/user_settings/user_settings.vue' import FollowRequests from 'components/follow_requests/follow_requests.vue' import OAuthCallback from 'components/oauth_callback/oauth_callback.vue' import Notifications from 'components/notifications/notifications.vue' @@ -31,7 +30,7 @@ export default (store) => { } } - return [ + let routes = [ { name: 'root', path: '/', redirect: _to => { @@ -44,6 +43,7 @@ export default (store) => { { name: 'public-timeline', path: '/main/public', component: PublicTimeline }, { name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute }, { name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline }, + { name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline }, { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, { name: 'remote-user-profile-acct', path: '/remote-users/(@?):username([^/@]+)@:hostname([^/@]+)', @@ -58,21 +58,26 @@ export default (store) => { { name: 'external-user-profile', path: '/users/:id', component: UserProfile }, { name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute }, { name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute }, - { name: 'chat', path: '/users/:username/chats/:recipient_id', component: Chat, meta: { dontScroll: false }, beforeEnter: validateAuthenticatedRoute }, - { name: 'chats', path: '/users/:username/chats', component: ChatList, meta: { dontScroll: false }, beforeEnter: validateAuthenticatedRoute }, - { name: 'settings', path: '/settings', component: Settings }, { name: 'registration', path: '/registration', component: Registration }, { name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true }, { name: 'registration-token', path: '/registration/:token', component: Registration }, { name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute }, - { name: 'user-settings', path: '/user-settings', component: UserSettings, beforeEnter: validateAuthenticatedRoute }, { name: 'notifications', path: '/:username/notifications', component: Notifications, beforeEnter: validateAuthenticatedRoute }, { name: 'login', path: '/login', component: AuthForm }, - { name: 'chat', path: '/chat', component: ChatPanel, props: () => ({ floating: false }) }, + { name: 'chat-panel', path: '/chat-panel', component: ChatPanel, props: () => ({ floating: false }) }, { name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) }, { name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) }, { name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute }, { name: 'about', path: '/about', component: About }, { name: 'user-profile', path: '/(users/)?:name', component: UserProfile } ] + + if (store.state.instance.pleromaChatMessagesAvailable) { + routes = routes.concat([ + { name: 'chat', path: '/users/:username/chats/:recipient_id', component: Chat, meta: { dontScroll: false }, beforeEnter: validateAuthenticatedRoute }, + { name: 'chats', path: '/users/:username/chats', component: ChatList, meta: { dontScroll: false }, beforeEnter: validateAuthenticatedRoute } + ]) + } + + return routes } diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js index 9ad8bd1d..6d345bc7 100644 --- a/src/components/account_actions/account_actions.js +++ b/src/components/account_actions/account_actions.js @@ -1,3 +1,4 @@ +import { mapState } from 'vuex' import ProgressButton from '../progress_button/progress_button.vue' import Popover from '../popover/popover.vue' @@ -34,6 +35,11 @@ const AccountActions = { params: { recipient_id: this.user.id } }) } + }, + computed: { + ...mapState({ + pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable + }) } } diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue index 308f1d6e..987e94b7 100644 --- a/src/components/account_actions/account_actions.vue +++ b/src/components/account_actions/account_actions.vue @@ -3,6 +3,7 @@