diff --git a/CHANGELOG.md b/CHANGELOG.md index a44fb1630..ad03c7602 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,16 +6,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - Removed the use of with_move parameters when fetching notifications +### 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 ### 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 ### Fixed - 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. ## [2.0.3] - 2020-05-02 ### Fixed diff --git a/README.md b/README.md index 889f08376..b66383ade 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 076bfb1c3..f417f33d7 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -33,7 +33,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/package.json b/package.json index 542086b41..4d68cc6ee 100644 --- a/package.json +++ b/package.json @@ -29,11 +29,11 @@ "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" diff --git a/src/App.js b/src/App.js index bbb41409b..040138c97 100644 --- a/src/App.js +++ b/src/App.js @@ -6,6 +6,7 @@ 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' @@ -29,6 +30,7 @@ export default { SideDrawer, MobilePostStatusButton, MobileNav, + SettingsModal, UserReportingModal, PostStatusModal }, @@ -45,7 +47,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 () { @@ -117,6 +120,9 @@ export default { onSearchBarToggled (hidden) { this.searchBarHidden = hidden }, + openSettingsModal () { + this.$store.dispatch('openSettingsModal') + }, updateMobileState () { const mobileLayout = windowWidth() <= 800 const changed = mobileLayout !== this.isMobileLayout diff --git a/src/App.scss b/src/App.scss index 89aa32155..f2972eda5 100644 --- a/src/App.scss +++ b/src/App.scss @@ -566,7 +566,7 @@ main-router { min-height: 0; box-sizing: border-box; margin: 0; - margin-left: .25em; + margin-left: .5em; min-width: 1px; align-self: stretch; } @@ -860,51 +860,6 @@ nav { } } -.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; - } -} .select-multiple { display: flex; .option-list { diff --git a/src/App.vue b/src/App.vue index 7018a5a4e..7b9ad3dc1 100644 --- a/src/App.vue +++ b/src/App.vue @@ -46,15 +46,16 @@ @toggled="onSearchBarToggled" @click.stop.native /> - - + + diff --git a/src/boot/after_store.js b/src/boot/after_store.js index abdba305a..0db035475 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -110,6 +110,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { copyInstanceOption('alwaysShowSubjectInput') copyInstanceOption('showFeaturesPanel') copyInstanceOption('hideSitename') + copyInstanceOption('sidebarRight') return store.dispatch('setTheme', config['theme']) } diff --git a/src/boot/routes.js b/src/boot/routes.js index 7400a682c..d98a3b503 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -7,10 +7,8 @@ import Interactions from 'components/interactions/interactions.vue' import DMs from 'components/dm_timeline/dm_timeline.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' @@ -56,12 +54,10 @@ 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: '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 }) }, diff --git a/src/components/async_component_error/async_component_error.vue b/src/components/async_component_error/async_component_error.vue new file mode 100644 index 000000000..b68b98f97 --- /dev/null +++ b/src/components/async_component_error/async_component_error.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/components/domain_mute_card/domain_mute_card.js b/src/components/domain_mute_card/domain_mute_card.js index c8e838bac..f234dcb0f 100644 --- a/src/components/domain_mute_card/domain_mute_card.js +++ b/src/components/domain_mute_card/domain_mute_card.js @@ -5,9 +5,20 @@ const DomainMuteCard = { components: { ProgressButton }, + computed: { + user () { + return this.$store.state.users.currentUser + }, + muted () { + return this.user.domainMutes.includes(this.domain) + } + }, methods: { unmuteDomain () { return this.$store.dispatch('unmuteDomain', this.domain) + }, + muteDomain () { + return this.$store.dispatch('muteDomain', this.domain) } } } diff --git a/src/components/domain_mute_card/domain_mute_card.vue b/src/components/domain_mute_card/domain_mute_card.vue index 567d81c50..97aee2431 100644 --- a/src/components/domain_mute_card/domain_mute_card.vue +++ b/src/components/domain_mute_card/domain_mute_card.vue @@ -4,6 +4,7 @@ {{ domain }} @@ -12,6 +13,16 @@ {{ $t('domain_mute_card.unmute_progress') }} + + {{ $t('domain_mute_card.mute') }} + + @@ -34,5 +45,9 @@ button { width: 10em; } + + .autosuggest-results & { + padding-left: 1em; + } } diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue index f5ace0cce..dd6800a30 100644 --- a/src/components/interface_language_switcher/interface_language_switcher.vue +++ b/src/components/interface_language_switcher/interface_language_switcher.vue @@ -32,7 +32,7 @@ import _ from 'lodash' export default { computed: { languageCodes () { - return Object.keys(languagesObject) + return languagesObject.languages }, languageNames () { @@ -43,7 +43,6 @@ export default { get: function () { return this.$store.getters.mergedConfig.interfaceLanguage }, set: function (val) { this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val }) - this.$i18n.locale = val } } }, diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index abb18c7de..24764e800 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -84,10 +84,12 @@ const MediaModal = { } }, mounted () { + window.addEventListener('popstate', this.hide) document.addEventListener('keyup', this.handleKeyupEvent) document.addEventListener('keydown', this.handleKeydownEvent) }, destroyed () { + window.removeEventListener('popstate', this.hide) document.removeEventListener('keyup', this.handleKeyupEvent) document.removeEventListener('keydown', this.handleKeydownEvent) } diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js index f457d0220..fbb2d03db 100644 --- a/src/components/media_upload/media_upload.js +++ b/src/components/media_upload/media_upload.js @@ -5,10 +5,15 @@ import fileSizeFormatService from '../../services/file_size_format/file_size_for const mediaUpload = { data () { return { - uploading: false, + uploadCount: 0, uploadReady: true } }, + computed: { + uploading () { + return this.uploadCount > 0 + } + }, methods: { uploadFile (file) { const self = this @@ -23,29 +28,21 @@ const mediaUpload = { formData.append('file', file) self.$emit('uploading') - self.uploading = true + self.uploadCount++ statusPosterService.uploadMedia({ store, formData }) .then((fileData) => { self.$emit('uploaded', fileData) - self.uploading = false + self.decreaseUploadCount() }, (error) => { // eslint-disable-line handle-callback-err self.$emit('upload-failed', 'default') - self.uploading = false + self.decreaseUploadCount() }) }, - fileDrop (e) { - if (e.dataTransfer.files.length > 0) { - e.preventDefault() // allow dropping text like before - this.uploadFile(e.dataTransfer.files[0]) - } - }, - fileDrag (e) { - let types = e.dataTransfer.types - if (types.contains('Files')) { - e.dataTransfer.dropEffect = 'copy' - } else { - e.dataTransfer.dropEffect = 'none' + decreaseUploadCount () { + this.uploadCount-- + if (this.uploadCount === 0) { + this.$emit('all-uploaded') } }, clearFile () { @@ -54,11 +51,13 @@ const mediaUpload = { this.uploadReady = true }) }, - change ({ target }) { - for (var i = 0; i < target.files.length; i++) { - let file = target.files[i] + multiUpload (files) { + for (const file of files) { this.uploadFile(file) } + }, + change ({ target }) { + this.multiUpload(target.files) } }, props: [ @@ -67,7 +66,7 @@ const mediaUpload = { watch: { 'dropFiles': function (fileInfos) { if (!this.uploading) { - this.uploadFile(fileInfos[0]) + this.multiUpload(fileInfos) } } } diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue index 0fc305ac6..5e31730b5 100644 --- a/src/components/media_upload/media_upload.vue +++ b/src/components/media_upload/media_upload.vue @@ -1,10 +1,5 @@