From 4a2f676f2e7fe40727b0aac573b8c0f9b88eb0fe Mon Sep 17 00:00:00 2001 From: jasper Date: Mon, 1 Apr 2019 07:26:13 -0700 Subject: Fix user card in notification --- src/components/user_profile/user_profile.js | 3 --- 1 file changed, 3 deletions(-) (limited to 'src/components/user_profile/user_profile.js') diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 82df4510..1df06fe6 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -72,9 +72,6 @@ const UserProfile = { return this.$store.getters.findUser(this.fetchedUserId || routeParams.name || routeParams.id) }, user () { - if (this.timeline.statuses[0]) { - return this.timeline.statuses[0].user - } if (this.userInStore) { return this.userInStore } -- cgit v1.2.3-70-g09d2 From 6b6878bde06b375b1e715a3557f153acc73a8af0 Mon Sep 17 00:00:00 2001 From: eugenijm Date: Mon, 18 Feb 2019 17:49:32 +0300 Subject: Added moderation menu --- package.json | 2 + src/App.scss | 8 ++ src/boot/after_store.js | 1 + src/components/delete_button/delete_button.js | 6 +- src/components/dialog_modal/dialog_modal.js | 14 ++ src/components/dialog_modal/dialog_modal.vue | 92 ++++++++++++ .../moderation_tools/moderation_tools.js | 106 ++++++++++++++ .../moderation_tools/moderation_tools.vue | 158 +++++++++++++++++++++ src/components/notification/notification.js | 3 + src/components/notification/notification.vue | 2 +- src/components/popper/popper.scss | 70 +++++++++ src/components/user_card/user_card.js | 13 +- src/components/user_card/user_card.vue | 2 + src/components/user_profile/user_profile.js | 4 +- src/i18n/en.json | 24 +++- src/i18n/ru.json | 24 +++- src/modules/statuses.js | 10 ++ src/modules/users.js | 22 +++ src/services/api/api.service.js | 90 ++++++++++++ .../backend_interactor_service.js | 30 ++++ .../entity_normalizer/entity_normalizer.service.js | 19 ++- yarn.lock | 10 ++ 22 files changed, 697 insertions(+), 13 deletions(-) create mode 100644 src/components/dialog_modal/dialog_modal.js create mode 100644 src/components/dialog_modal/dialog_modal.vue create mode 100644 src/components/moderation_tools/moderation_tools.js create mode 100644 src/components/moderation_tools/moderation_tools.vue create mode 100644 src/components/popper/popper.scss (limited to 'src/components/user_profile/user_profile.js') diff --git a/package.json b/package.json index 03228133..c80e0f63 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,14 @@ "node-sass": "^3.10.1", "object-path": "^0.11.3", "phoenix": "^1.3.0", + "popper.js": "^1.14.7", "sanitize-html": "^1.13.0", "sass-loader": "^4.0.2", "vue": "^2.5.13", "vue-chat-scroll": "^1.2.1", "vue-compose": "^0.7.1", "vue-i18n": "^7.3.2", + "vue-popperjs": "^2.0.3", "vue-router": "^3.0.1", "vue-template-compiler": "^2.3.4", "vue-timeago": "^3.1.2", diff --git a/src/App.scss b/src/App.scss index 5fc0dd27..b1c65ade 100644 --- a/src/App.scss +++ b/src/App.scss @@ -101,6 +101,14 @@ button { background-color: $fallback--bg; background-color: var(--bg, $fallback--bg) } + + &.danger { + // TODO: add better color variable + color: $fallback--text; + color: var(--alertErrorPanelText, $fallback--text); + background-color: $fallback--alertError; + background-color: var(--alertError, $fallback--alertError); + } } label.select { diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 862a534d..bb593026 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -210,6 +210,7 @@ const getNodeInfo = async ({ store }) => { const frontendVersion = window.___pleromafe_commit_hash store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion }) + store.dispatch('setInstanceOption', { name: 'tagPolicyAvailable', value: metadata.federation.mrf_policies.includes('TagPolicy') }) } else { throw (res) } diff --git a/src/components/delete_button/delete_button.js b/src/components/delete_button/delete_button.js index f2920666..22f24625 100644 --- a/src/components/delete_button/delete_button.js +++ b/src/components/delete_button/delete_button.js @@ -10,7 +10,11 @@ const DeleteButton = { }, computed: { currentUser () { return this.$store.state.users.currentUser }, - canDelete () { return this.currentUser && this.currentUser.rights.delete_others_notice || this.status.user.id === this.currentUser.id } + canDelete () { + if (!this.currentUser) { return } + const superuser = this.currentUser.rights.moderator || this.currentUser.rights.admin + return superuser || this.status.user.id === this.currentUser.id + } } } diff --git a/src/components/dialog_modal/dialog_modal.js b/src/components/dialog_modal/dialog_modal.js new file mode 100644 index 00000000..f14e3fe9 --- /dev/null +++ b/src/components/dialog_modal/dialog_modal.js @@ -0,0 +1,14 @@ +const DialogModal = { + props: { + darkOverlay: { + default: true, + type: Boolean + }, + onCancel: { + default: () => {}, + type: Function + } + } +} + +export default DialogModal diff --git a/src/components/dialog_modal/dialog_modal.vue b/src/components/dialog_modal/dialog_modal.vue new file mode 100644 index 00000000..7621fb20 --- /dev/null +++ b/src/components/dialog_modal/dialog_modal.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js new file mode 100644 index 00000000..3eedeaa1 --- /dev/null +++ b/src/components/moderation_tools/moderation_tools.js @@ -0,0 +1,106 @@ +import DialogModal from '../dialog_modal/dialog_modal.vue' +import Popper from 'vue-popperjs/src/component/popper.js.vue' + +const FORCE_NSFW = 'mrf_tag:media-force-nsfw' +const STRIP_MEDIA = 'mrf_tag:media-strip' +const FORCE_UNLISTED = 'mrf_tag:force-unlisted' +const DISABLE_REMOTE_SUBSCRIPTION = 'mrf_tag:disable-remote-subscription' +const DISABLE_ANY_SUBSCRIPTION = 'mrf_tag:disable-any-subscription' +const SANDBOX = 'mrf_tag:sandbox' +const QUARANTINE = 'mrf_tag:quarantine' + +const ModerationTools = { + props: [ + 'user' + ], + data () { + return { + showDropDown: false, + tags: { + FORCE_NSFW, + STRIP_MEDIA, + FORCE_UNLISTED, + DISABLE_REMOTE_SUBSCRIPTION, + DISABLE_ANY_SUBSCRIPTION, + SANDBOX, + QUARANTINE + }, + showDeleteUserDialog: false + } + }, + components: { + DialogModal, + Popper + }, + computed: { + tagsSet () { + return new Set(this.user.tags) + }, + hasTagPolicy () { + return this.$store.state.instance.tagPolicyAvailable + } + }, + methods: { + toggleMenu () { + this.showDropDown = !this.showDropDown + }, + hasTag (tagName) { + return this.tagsSet.has(tagName) + }, + toggleTag (tag) { + const store = this.$store + if (this.tagsSet.has(tag)) { + store.state.api.backendInteractor.untagUser(this.user, tag).then(response => { + if (!response.ok) { return } + store.commit('untagUser', {user: this.user, tag}) + }) + } else { + store.state.api.backendInteractor.tagUser(this.user, tag).then(response => { + if (!response.ok) { return } + store.commit('tagUser', {user: this.user, tag}) + }) + } + }, + toggleRight (right) { + const store = this.$store + if (this.user.rights[right]) { + store.state.api.backendInteractor.deleteRight(this.user, right).then(response => { + if (!response.ok) { return } + store.commit('updateRight', {user: this.user, right: right, value: false}) + }) + } else { + store.state.api.backendInteractor.addRight(this.user, right).then(response => { + if (!response.ok) { return } + store.commit('updateRight', {user: this.user, right: right, value: true}) + }) + } + }, + toggleActivationStatus () { + const store = this.$store + const status = !!this.user.deactivated + store.state.api.backendInteractor.setActivationStatus(this.user, status).then(response => { + if (!response.ok) { return } + store.commit('updateActivationStatus', {user: this.user, status: status}) + }) + }, + deleteUserDialog (show) { + this.showDeleteUserDialog = show + }, + deleteUser () { + const store = this.$store + const user = this.user + const {id, name} = user + store.state.api.backendInteractor.deleteUser(user) + .then(e => { + this.$store.dispatch('markStatusesAsDeleted', status => user.id === status.user.id) + const isProfile = this.$route.name === 'external-user-profile' || this.$route.name === 'user-profile' + const isTargetUser = this.$route.params.name === name || this.$route.params.id === id + if (isProfile && isTargetUser) { + window.history.back() + } + }) + } + } +} + +export default ModerationTools diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue new file mode 100644 index 00000000..c24a2280 --- /dev/null +++ b/src/components/moderation_tools/moderation_tools.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index 42a48f3f..6aa6101f 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -21,6 +21,9 @@ const Notification = { }, userProfileLink (user) { return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames) + }, + getUser (notification) { + return this.$store.state.users.usersObject[notification.action.user.id] } }, computed: { diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue index 8f532747..cac335ce 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -5,7 +5,7 @@
- +
diff --git a/src/components/popper/popper.scss b/src/components/popper/popper.scss new file mode 100644 index 00000000..0c30d625 --- /dev/null +++ b/src/components/popper/popper.scss @@ -0,0 +1,70 @@ +@import '../../_variables.scss'; + +.popper-wrapper { + z-index: 8; +} + +.popper-wrapper .popper__arrow { + width: 0; + height: 0; + border-style: solid; + position: absolute; + margin: 5px; +} + +.popper-wrapper[x-placement^="top"] { + margin-bottom: 5px; +} + +.popper-wrapper[x-placement^="top"] .popper__arrow { + border-width: 5px 5px 0 5px; + border-color: $fallback--bg transparent transparent transparent; + border-color: var(--bg, $fallback--bg) transparent transparent transparent; + bottom: -5px; + left: calc(50% - 5px); + margin-top: 0; + margin-bottom: 0; +} + +.popper-wrapper[x-placement^="bottom"] { + margin-top: 5px; +} + +.popper-wrapper[x-placement^="bottom"] .popper__arrow { + border-width: 0 5px 5px 5px; + border-color: transparent transparent $fallback--bg transparent; + border-color: transparent transparent var(--bg, $fallback--bg) transparent; + top: -5px; + left: calc(50% - 5px); + margin-top: 0; + margin-bottom: 0; +} + +.popper-wrapper[x-placement^="right"] { + margin-left: 5px; +} + +.popper-wrapper[x-placement^="right"] .popper__arrow { + border-width: 5px 5px 5px 0; + border-color: transparent $fallback--bg transparent transparent; + border-color: transparent var(--bg, $fallback--bg) transparent transparent; + left: -5px; + top: calc(50% - 5px); + margin-left: 0; + margin-right: 0; +} + +.popper-wrapper[x-placement^="left"] { + margin-right: 5px; +} + +.popper-wrapper[x-placement^="left"] .popper__arrow { + border-width: 5px 0 5px 5px; + border-color: transparent transparent transparent $fallback--bg; + border-color: transparent transparent transparent var(--bg, $fallback--bg); + right: -5px; + top: calc(50% - 5px); + margin-left: 0; + margin-right: 0; +} + diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js index 197c61d5..1a100de3 100644 --- a/src/components/user_card/user_card.js +++ b/src/components/user_card/user_card.js @@ -1,5 +1,6 @@ import UserAvatar from '../user_avatar/user_avatar.vue' import RemoteFollow from '../remote_follow/remote_follow.vue' +import ModerationTools from '../moderation_tools/moderation_tools.vue' import { hex2rgb } from '../../services/color_convert/color_convert.js' import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' @@ -93,15 +94,17 @@ export default { } }, visibleRole () { - const validRole = (this.user.role === 'admin' || this.user.role === 'moderator') - const showRole = this.isOtherUser || this.user.show_role - - return validRole && showRole && this.user.role + const rights = this.user.rights + if (!rights) { return } + const validRole = rights.admin || rights.moderator + const roleTitle = rights.admin ? 'admin' : 'moderator' + return validRole && roleTitle } }, components: { UserAvatar, - RemoteFollow + RemoteFollow, + ModerationTools }, methods: { followUser () { diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index 3259d1c5..b5c7fa24 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -99,6 +99,8 @@
+ +
diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 1df06fe6..540de955 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -3,6 +3,7 @@ import get from 'lodash/get' import UserCard from '../user_card/user_card.vue' import FollowCard from '../follow_card/follow_card.vue' import Timeline from '../timeline/timeline.vue' +import ModerationTools from '../moderation_tools/moderation_tools.vue' import withLoadMore from '../../hocs/with_load_more/with_load_more' import withList from '../../hocs/with_list/with_list' @@ -155,7 +156,8 @@ const UserProfile = { UserCard, Timeline, FollowerList, - FriendList + FriendList, + ModerationTools } } diff --git a/src/i18n/en.json b/src/i18n/en.json index 026546cc..0bb58734 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -22,7 +22,8 @@ "generic_error": "An error occured", "optional": "optional", "show_more": "Show more", - "show_less": "Show less" + "show_less": "Show less", + "cancel": "Cancel" }, "image_cropper": { "crop_picture": "Crop picture", @@ -402,7 +403,26 @@ "block_progress": "Blocking...", "unmute": "Unmute", "unmute_progress": "Unmuting...", - "mute_progress": "Muting..." + "mute_progress": "Muting...", + "admin_menu": { + "moderation": "Moderation", + "grant_admin": "Grant Admin", + "revoke_admin": "Revoke Admin", + "grant_moderator": "Grant Moderator", + "revoke_moderator": "Revoke Moderator", + "activate_account": "Activate account", + "deactivate_account": "Deactivate account", + "delete_account": "Delete account", + "force_nsfw": "Mark all posts as NSFW", + "strip_media": "Remove media from posts", + "force_unlisted": "Force posts to be unlisted", + "sandbox": "Force posts to be followers-only", + "disable_remote_subscription": "Disallow following user from remote instances", + "disable_any_subscription": "Disallow following user at all", + "quarantine": "Disallow user posts from federating", + "delete_user": "Delete user", + "delete_user_confirmation": "Are you absolutely sure? This action cannot be undone." + } }, "user_profile": { "timeline_title": "User Timeline", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 89aa43f4..5450f154 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -8,7 +8,8 @@ }, "general": { "apply": "Применить", - "submit": "Отправить" + "submit": "Отправить", + "cancel": "Отмена" }, "login": { "login": "Войти", @@ -311,7 +312,26 @@ "muted": "Игнорирую", "per_day": "в день", "remote_follow": "Читать удалённо", - "statuses": "Статусы" + "statuses": "Статусы", + "admin_menu": { + "moderation": "Опции модератора", + "grant_admin": "Сделать администратором", + "revoke_admin": "Забрать права администратора", + "grant_moderator": "Сделать модератором", + "revoke_moderator": "Забрать права модератора", + "activate_account": "Активировать аккаунт", + "deactivate_account": "Деактивировать аккаунт", + "delete_account": "Удалить аккаунт", + "force_nsfw": "Отмечать посты пользователя как NSFW", + "strip_media": "Убирать вложения из постов пользователя", + "force_unlisted": "Не добавлять посты в публичные ленты", + "sandbox": "Посты доступны только для подписчиков", + "disable_remote_subscription": "Запретить подписываться с удаленных серверов", + "disable_any_subscription": "Запретить подписываться на пользователя", + "quarantine": "Не федерировать посты пользователя", + "delete_user": "Удалить пользователя", + "delete_user_confirmation": "Вы уверены? Это действие нельзя отменить." + } }, "user_profile": { "timeline_title": "Лента пользователя" diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 8e0203e3..4007b0fc 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -378,6 +378,13 @@ export const mutations = { const newStatus = state.allStatusesObject[status.id] newStatus.deleted = true }, + setManyDeleted (state, condition) { + Object.values(state.allStatusesObject).forEach(status => { + if (condition(status)) { + status.deleted = true + } + }) + }, setLoading (state, { timeline, value }) { state.timelines[timeline].loading = value }, @@ -438,6 +445,9 @@ const statuses = { commit('setDeleted', { status }) apiService.deleteStatus({ id: status.id, credentials: rootState.users.currentUser.credentials }) }, + markStatusesAsDeleted ({ commit }, condition) { + commit('setManyDeleted', condition) + }, favorite ({ rootState, commit }, status) { // Optimistic favoriting... commit('setFavorited', { status, value: true }) diff --git a/src/modules/users.js b/src/modules/users.js index 1a507d31..5839574b 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -37,6 +37,28 @@ export const mutations = { const user = state.usersObject[id] set(user, 'muted', muted) }, + tagUser (state, { user: { id }, tag }) { + const user = state.usersObject[id] + const tags = user.tags || [] + const newTags = tags.concat([tag]) + set(user, 'tags', newTags) + }, + untagUser (state, { user: { id }, tag }) { + const user = state.usersObject[id] + const tags = user.tags || [] + const newTags = tags.filter(t => t !== tag) + set(user, 'tags', newTags) + }, + updateRight (state, { user: { id }, right, value }) { + const user = state.usersObject[id] + let newRights = user.rights + newRights[right] = value + set(user, 'rights', newRights) + }, + updateActivationStatus (state, { user: { id }, status }) { + const user = state.usersObject[id] + set(user, 'deactivated', !status) + }, setCurrentUser (state, user) { state.lastLoginName = user.screen_name state.currentUser = merge(state.currentUser || {}, user) diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 030c2f5e..bd331062 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -16,6 +16,10 @@ const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' const FOLLOW_REQUESTS_URL = '/api/pleroma/friend_requests' const APPROVE_USER_URL = '/api/pleroma/friendships/approve' const DENY_USER_URL = '/api/pleroma/friendships/deny' +const TAG_USER_URL = '/api/pleroma/admin/users/tag' +const PERMISSION_GROUP_URL = '/api/pleroma/admin/permission_group' +const ACTIVATION_STATUS_URL = '/api/pleroma/admin/activation_status' +const ADMIN_USER_URL = '/api/pleroma/admin/user' const SUGGESTIONS_URL = '/api/v1/suggestions' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' @@ -352,6 +356,86 @@ const fetchStatus = ({id, credentials}) => { .then((data) => parseStatus(data)) } +const tagUser = ({tag, credentials, ...options}) => { + const screenName = options.screen_name + const form = { + nicknames: [screenName], + tags: [tag] + } + + const headers = authHeaders(credentials) + headers['Content-Type'] = 'application/json' + + return fetch(TAG_USER_URL, { + method: 'PUT', + headers: headers, + body: JSON.stringify(form) + }) +} + +const untagUser = ({tag, credentials, ...options}) => { + const screenName = options.screen_name + const body = { + nicknames: [screenName], + tags: [tag] + } + + const headers = authHeaders(credentials) + headers['Content-Type'] = 'application/json' + + return fetch(TAG_USER_URL, { + method: 'DELETE', + headers: headers, + body: JSON.stringify(body) + }) +} + +const addRight = ({right, credentials, ...user}) => { + const screenName = user.screen_name + + return fetch(`${PERMISSION_GROUP_URL}/${screenName}/${right}`, { + method: 'POST', + headers: authHeaders(credentials), + body: {} + }) +} + +const deleteRight = ({right, credentials, ...user}) => { + const screenName = user.screen_name + + return fetch(`${PERMISSION_GROUP_URL}/${screenName}/${right}`, { + method: 'DELETE', + headers: authHeaders(credentials), + body: {} + }) +} + +const setActivationStatus = ({status, credentials, ...user}) => { + const screenName = user.screen_name + const body = { + status: status + } + + const headers = authHeaders(credentials) + headers['Content-Type'] = 'application/json' + + return fetch(`${ACTIVATION_STATUS_URL}/${screenName}.json`, { + method: 'PUT', + headers: headers, + body: JSON.stringify(body) + }) +} + +const deleteUser = ({credentials, ...user}) => { + const screenName = user.screen_name + const headers = authHeaders(credentials) + + return fetch(`${ADMIN_USER_URL}.json?nickname=${screenName}`, { + method: 'DELETE', + headers: headers + }) +} + const fetchTimeline = ({timeline, credentials, since = false, until = false, userId = false, tag = false, withMuted = false}) => { const timelineUrls = { public: MASTODON_PUBLIC_TIMELINE, @@ -666,6 +750,12 @@ const apiService = { fetchBlocks, fetchOAuthTokens, revokeOAuthToken, + tagUser, + untagUser, + deleteUser, + addRight, + deleteRight, + setActivationStatus, register, getCaptcha, updateAvatar, diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index 71e78d2f..badcbb06 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -62,6 +62,30 @@ const backendInteractorService = (credentials) => { return timelineFetcherService.startFetching({timeline, store, credentials, userId, tag}) } + const tagUser = ({screen_name}, tag) => { + return apiService.tagUser({screen_name, tag, credentials}) + } + + const untagUser = ({screen_name}, tag) => { + return apiService.untagUser({screen_name, tag, credentials}) + } + + const addRight = ({screen_name}, right) => { + return apiService.addRight({screen_name, right, credentials}) + } + + const deleteRight = ({screen_name}, right) => { + return apiService.deleteRight({screen_name, right, credentials}) + } + + const setActivationStatus = ({screen_name}, status) => { + return apiService.setActivationStatus({screen_name, status, credentials}) + } + + const deleteUser = ({screen_name}) => { + return apiService.deleteUser({screen_name, credentials}) + } + const fetchMutes = () => apiService.fetchMutes({credentials}) const muteUser = (id) => apiService.muteUser({credentials, id}) const unmuteUser = (id) => apiService.unmuteUser({credentials, id}) @@ -104,6 +128,12 @@ const backendInteractorService = (credentials) => { fetchBlocks, fetchOAuthTokens, revokeOAuthToken, + tagUser, + untagUser, + addRight, + deleteRight, + deleteUser, + setActivationStatus, register, getCaptcha, updateAvatar, diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index ea57e6b2..8aa4b352 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -67,6 +67,11 @@ export const parseUser = (data) => { output.statusnet_blocking = relationship.blocking output.muted = relationship.muting } + + output.rights = { + moderator: data.pleroma.is_moderator, + admin: data.pleroma.is_admin + } } // Missing, trying to recover @@ -103,7 +108,12 @@ export const parseUser = (data) => { // QVITTER ONLY FOR NOW // Really only applies to logged in user, really.. I THINK - output.rights = data.rights + if (data.rights) { + output.rights = { + moderator: data.rights.delete_others_notice, + admin: data.rights.admin + } + } output.no_rich_text = data.no_rich_text output.default_scope = data.default_scope output.hide_follows = data.hide_follows @@ -125,6 +135,13 @@ export const parseUser = (data) => { output.follow_request_count = data.pleroma.follow_request_count } + if (data.pleroma) { + output.tags = data.pleroma.tags + output.deactivated = data.pleroma.deactivated + } + + output.tags = output.tags || [] + return output } diff --git a/yarn.lock b/yarn.lock index 0fe45479..58007622 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4831,6 +4831,10 @@ pluralize@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" +popper.js@^1.14.7: + version "1.14.7" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.7.tgz#e31ec06cfac6a97a53280c3e55e4e0c860e7738e" + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -6457,6 +6461,12 @@ vue-loader@^11.1.0: vue-style-loader "^2.0.0" vue-template-es2015-compiler "^1.2.2" +vue-popperjs@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/vue-popperjs/-/vue-popperjs-2.0.3.tgz#7c446d0ba7c63170ccb33a02669d0df4efc3d8cd" + dependencies: + popper.js "^1.14.7" + vue-router@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.2.tgz#dedc67afe6c4e2bc25682c8b1c2a8c0d7c7e56be" -- cgit v1.2.3-70-g09d2 From a1275be4c0c83dc848e402bb631990d0cb27bb8c Mon Sep 17 00:00:00 2001 From: jasper Date: Thu, 4 Apr 2019 09:03:56 -0700 Subject: Separate timeline and notification --- .../public_and_external_timeline.js | 2 +- src/components/public_timeline/public_timeline.js | 2 +- src/components/tag_timeline/tag_timeline.js | 4 ++-- src/components/user_profile/user_profile.js | 6 ++--- src/modules/api.js | 27 ++++++++++++++-------- src/modules/users.js | 4 ++-- .../backend_interactor_service.js | 12 ++++++---- 7 files changed, 34 insertions(+), 23 deletions(-) (limited to 'src/components/user_profile/user_profile.js') diff --git a/src/components/public_and_external_timeline/public_and_external_timeline.js b/src/components/public_and_external_timeline/public_and_external_timeline.js index d45677e0..f614c13b 100644 --- a/src/components/public_and_external_timeline/public_and_external_timeline.js +++ b/src/components/public_and_external_timeline/public_and_external_timeline.js @@ -7,7 +7,7 @@ const PublicAndExternalTimeline = { timeline () { return this.$store.state.statuses.timelines.publicAndExternal } }, created () { - this.$store.dispatch('startFetching', { timeline: 'publicAndExternal' }) + this.$store.dispatch('startFetchingTimeline', { timeline: 'publicAndExternal' }) }, destroyed () { this.$store.dispatch('stopFetching', 'publicAndExternal') diff --git a/src/components/public_timeline/public_timeline.js b/src/components/public_timeline/public_timeline.js index 64c951ac..8976a99c 100644 --- a/src/components/public_timeline/public_timeline.js +++ b/src/components/public_timeline/public_timeline.js @@ -7,7 +7,7 @@ const PublicTimeline = { timeline () { return this.$store.state.statuses.timelines.public } }, created () { - this.$store.dispatch('startFetching', { timeline: 'public' }) + this.$store.dispatch('startFetchingTimeline', { timeline: 'public' }) }, destroyed () { this.$store.dispatch('stopFetching', 'public') diff --git a/src/components/tag_timeline/tag_timeline.js b/src/components/tag_timeline/tag_timeline.js index 41b09706..458eb1c5 100644 --- a/src/components/tag_timeline/tag_timeline.js +++ b/src/components/tag_timeline/tag_timeline.js @@ -3,7 +3,7 @@ import Timeline from '../timeline/timeline.vue' const TagTimeline = { created () { this.$store.commit('clearTimeline', { timeline: 'tag' }) - this.$store.dispatch('startFetching', { timeline: 'tag', tag: this.tag }) + this.$store.dispatch('startFetchingTimeline', { timeline: 'tag', tag: this.tag }) }, components: { Timeline @@ -15,7 +15,7 @@ const TagTimeline = { watch: { tag () { this.$store.commit('clearTimeline', { timeline: 'tag' }) - this.$store.dispatch('startFetching', { timeline: 'tag', tag: this.tag }) + this.$store.dispatch('startFetchingTimeline', { timeline: 'tag', tag: this.tag }) } }, destroyed () { diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 1df06fe6..bac729c0 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -90,7 +90,7 @@ const UserProfile = { methods: { startFetchFavorites () { if (this.isUs) { - this.$store.dispatch('startFetching', { timeline: 'favorites', userId: this.userId }) + this.$store.dispatch('startFetchingTimeline', { timeline: 'favorites', userId: this.userId }) } }, fetchUserId () { @@ -118,8 +118,8 @@ const UserProfile = { }, startUp () { if (this.userId) { - this.$store.dispatch('startFetching', { timeline: 'user', userId: this.userId }) - this.$store.dispatch('startFetching', { timeline: 'media', userId: this.userId }) + this.$store.dispatch('startFetchingTimeline', { timeline: 'user', userId: this.userId }) + this.$store.dispatch('startFetchingTimeline', { timeline: 'media', userId: this.userId }) this.startFetchFavorites() } }, diff --git a/src/modules/api.js b/src/modules/api.js index 31cb55c6..6242dfa1 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -13,11 +13,11 @@ const api = { setBackendInteractor (state, backendInteractor) { state.backendInteractor = backendInteractor }, - addFetcher (state, {timeline, fetcher}) { - state.fetchers[timeline] = fetcher + addFetcher (state, {fetcherName, fetcher}) { + state.fetchers[fetcherName] = fetcher }, - removeFetcher (state, {timeline}) { - delete state.fetchers[timeline] + removeFetcher (state, { fetcherName }) { + delete state.fetchers[fetcherName] }, setWsToken (state, token) { state.wsToken = token @@ -33,17 +33,24 @@ const api = { } }, actions: { - startFetching (store, {timeline = 'friends', tag = false, userId = false}) { + startFetchingTimeline (store, {timeline = 'friends', tag = false, userId = false}) { // Don't start fetching if we already are. if (store.state.fetchers[timeline]) return - const fetcher = store.state.backendInteractor.startFetching({ timeline, store, userId, tag }) - store.commit('addFetcher', { timeline, fetcher }) + const fetcher = store.state.backendInteractor.startFetchingTimeline({ timeline, store, userId, tag }) + store.commit('addFetcher', { fetcherName: timeline, fetcher }) }, - stopFetching (store, timeline) { - const fetcher = store.state.fetchers[timeline] + startFetchingNotifications (store) { + // Don't start fetching if we already are. + if (store.state.fetchers['notifications']) return + + const fetcher = store.state.backendInteractor.startFetchingNotifications({ store }) + store.commit('addFetcher', { fetcherName: 'notifications', fetcher }) + }, + stopFetching (store, fetcherName) { + const fetcher = store.state.fetchers[fetcherName] window.clearInterval(fetcher) - store.commit('removeFetcher', {timeline}) + store.commit('removeFetcher', { fetcherName }) }, setWsToken (store, token) { store.commit('setWsToken', token) diff --git a/src/modules/users.js b/src/modules/users.js index 3cfae1fc..b0c7f2f6 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -364,10 +364,10 @@ const users = { } // Start getting fresh posts. - store.dispatch('startFetching', { timeline: 'friends' }) + store.dispatch('startFetchingTimeline', { timeline: 'friends' }) // Start fetching notifications - store.dispatch('startFetching', { timeline: 'notifications' }) + store.dispatch('startFetchingNotifications') // Get user mutes store.dispatch('fetchMutes') diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index f28686f8..7dd139c6 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -59,9 +59,12 @@ const backendInteractorService = (credentials) => { return apiService.denyUser({credentials, id}) } - const startFetching = ({timeline, store, userId = false, tag}) => { - if (timeline === 'notifications') { return notificationsFetcher.startFetching({store, credentials}) } - return timelineFetcherService.startFetching({timeline, store, credentials, userId, tag}) + const startFetchingTimeline = ({ timeline, store, userId = false, tag }) => { + return timelineFetcherService.startFetching({ timeline, store, credentials, userId, tag }) + } + + const startFetchingNotifications = ({ store }) => { + return notificationsFetcher.startFetching({ store, credentials }) } const fetchMutes = () => apiService.fetchMutes({credentials}) @@ -99,7 +102,8 @@ const backendInteractorService = (credentials) => { fetchUserRelationship, fetchAllFollowing, verifyCredentials: apiService.verifyCredentials, - startFetching, + startFetchingTimeline, + startFetchingNotifications, fetchMutes, muteUser, unmuteUser, -- cgit v1.2.3-70-g09d2 From e7010d73aca4a2555ef55c41de372ebdd28bd799 Mon Sep 17 00:00:00 2001 From: taehoon Date: Wed, 10 Apr 2019 13:49:39 -0400 Subject: store friends/followers in the global user repository --- src/components/user_profile/user_profile.js | 8 +-- src/modules/users.js | 65 ++++++++-------------- .../entity_normalizer/entity_normalizer.service.js | 4 +- 3 files changed, 30 insertions(+), 47 deletions(-) (limited to 'src/components/user_profile/user_profile.js') diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index d55d1517..4b0072ad 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -9,8 +9,8 @@ import withList from '../../hocs/with_list/with_list' const FollowerList = compose( withLoadMore({ - fetch: (props, $store) => $store.dispatch('addFollowers', props.userId), - select: (props, $store) => get($store.getters.findUser(props.userId), 'followers', []), + fetch: (props, $store) => $store.dispatch('fetchFollowers', props.userId), + select: (props, $store) => get($store.getters.findUser(props.userId), 'followerIds', []).map(id => $store.getters.findUser(id)), destory: (props, $store) => $store.dispatch('clearFollowers', props.userId), childPropName: 'entries', additionalPropNames: ['userId'] @@ -20,8 +20,8 @@ const FollowerList = compose( const FriendList = compose( withLoadMore({ - fetch: (props, $store) => $store.dispatch('addFriends', props.userId), - select: (props, $store) => get($store.getters.findUser(props.userId), 'friends', []), + fetch: (props, $store) => $store.dispatch('fetchFriends', props.userId), + select: (props, $store) => get($store.getters.findUser(props.userId), 'friendIds', []).map(id => $store.getters.findUser(id)), destory: (props, $store) => $store.dispatch('clearFriends', props.userId), childPropName: 'entries', additionalPropNames: ['userId'] diff --git a/src/modules/users.js b/src/modules/users.js index 6de50b80..74f33b81 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -1,5 +1,5 @@ import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' -import { compact, map, each, merge, find, last } from 'lodash' +import { compact, map, each, merge, last, concat, uniq } from 'lodash' import { set } from 'vue' import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js' import oauthApi from '../services/new_api/oauth' @@ -73,42 +73,27 @@ export const mutations = { endLogin (state) { state.loggingIn = false }, - // TODO Clean after ourselves? - addFriends (state, { id, friends }) { + saveFriendIds (state, { id, friendIds }) { const user = state.usersObject[id] - each(friends, friend => { - if (!find(user.friends, { id: friend.id })) { - user.friends.push(friend) - } - }) - user.lastFriendId = last(user.friends).id + user.friendIds = uniq(concat(user.friendIds, friendIds)) }, - addFollowers (state, { id, followers }) { + saveFollowerIds (state, { id, followerIds }) { const user = state.usersObject[id] - each(followers, follower => { - if (!find(user.followers, { id: follower.id })) { - user.followers.push(follower) - } - }) - user.lastFollowerId = last(user.followers).id + user.followerIds = uniq(concat(user.followerIds, followerIds)) }, // Because frontend doesn't have a reason to keep these stuff in memory // outside of viewing someones user profile. clearFriends (state, userId) { const user = state.usersObject[userId] - if (!user) { - return + if (user) { + set(user, 'friendIds', []) } - user.friends = [] - user.lastFriendId = null }, clearFollowers (state, userId) { const user = state.usersObject[userId] - if (!user) { - return + if (user) { + set(user, 'followerIds', []) } - user.followers = [] - user.lastFollowerId = null }, addNewUsers (state, users) { each(users, (user) => mergeOrAdd(state.users, state.usersObject, user)) @@ -240,25 +225,23 @@ const users = { return store.rootState.api.backendInteractor.unmuteUser(id) .then((relationship) => store.commit('updateUserRelationship', [relationship])) }, - addFriends ({ rootState, commit }, fetchBy) { - return new Promise((resolve, reject) => { - const user = rootState.users.usersObject[fetchBy] - const maxId = user.lastFriendId - rootState.api.backendInteractor.fetchFriends({ id: user.id, maxId }) - .then((friends) => { - commit('addFriends', { id: user.id, friends }) - resolve(friends) - }).catch(() => { - reject() - }) - }) + fetchFriends ({ rootState, commit }, id) { + const user = rootState.users.usersObject[id] + const maxId = last(user.friendIds) + return rootState.api.backendInteractor.fetchFriends({ id, maxId }) + .then((friends) => { + commit('addNewUsers', friends) + commit('saveFriendIds', { id, friendIds: map(friends, 'id') }) + return friends + }) }, - addFollowers ({ rootState, commit }, fetchBy) { - const user = rootState.users.usersObject[fetchBy] - const maxId = user.lastFollowerId - return rootState.api.backendInteractor.fetchFollowers({ id: user.id, maxId }) + fetchFollowers ({ rootState, commit }, id) { + const user = rootState.users.usersObject[id] + const maxId = last(user.followerIds) + return rootState.api.backendInteractor.fetchFollowers({ id, maxId }) .then((followers) => { - commit('addFollowers', { id: user.id, followers }) + commit('addNewUsers', followers) + commit('saveFollowerIds', { id, followerIds: map(followers, 'id') }) return followers }) }, diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index aef7062d..d68e5a98 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -129,8 +129,8 @@ export const parseUser = (data) => { output.locked = data.locked output.followers_count = data.followers_count output.statuses_count = data.statuses_count - output.friends = [] - output.followers = [] + output.friendIds = [] + output.followerIds = [] if (data.pleroma) { output.follow_request_count = data.pleroma.follow_request_count } -- cgit v1.2.3-70-g09d2 From 2e86a4eb4f7fe55626d2e05c7484b1d7cd0c1070 Mon Sep 17 00:00:00 2001 From: taehoon Date: Mon, 15 Apr 2019 11:08:28 -0400 Subject: fix mess in UserProfile component --- src/components/user_profile/user_profile.js | 91 +++++++++++----------------- src/components/user_profile/user_profile.vue | 2 +- 2 files changed, 35 insertions(+), 58 deletions(-) (limited to 'src/components/user_profile/user_profile.js') diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 4b0072ad..cb465d66 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -33,16 +33,12 @@ const UserProfile = { data () { return { error: false, - fetchedUserId: null + userId: null } }, created () { - if (!this.user.id) { - this.fetchUserId() - .then(() => this.startUp()) - } else { - this.startUp() - } + const routeParams = this.$route.params + this.load(routeParams.name || routeParams.id) }, destroyed () { this.cleanUp() @@ -57,26 +53,12 @@ const UserProfile = { media () { return this.$store.state.statuses.timelines.media }, - userId () { - return this.$route.params.id || this.user.id || this.fetchedUserId - }, - userName () { - return this.$route.params.name || this.user.screen_name - }, isUs () { return this.userId && this.$store.state.users.currentUser.id && this.userId === this.$store.state.users.currentUser.id }, - userInStore () { - const routeParams = this.$route.params - // This needs fetchedUserId so that computed will be refreshed when user is fetched - return this.$store.getters.findUser(this.fetchedUserId || routeParams.name || routeParams.id) - }, user () { - if (this.userInStore) { - return this.userInStore - } - return {} + return this.$store.getters.findUser(this.userId) }, isExternal () { return this.$route.name === 'external-user-profile' @@ -89,39 +71,36 @@ const UserProfile = { } }, methods: { - startFetchFavorites () { - if (this.isUs) { - this.$store.dispatch('startFetchingTimeline', { timeline: 'favorites', userId: this.userId }) - } - }, - fetchUserId () { - let fetchPromise - if (this.userId && !this.$route.params.name) { - fetchPromise = this.$store.dispatch('fetchUser', this.userId) + load (userNameOrId) { + // Check if user data is already loaded in store + const user = this.$store.getters.findUser(userNameOrId) + if (user) { + this.userId = user.id + this.fetchTimelines() } else { - fetchPromise = this.$store.dispatch('fetchUser', this.userName) + this.$store.dispatch('fetchUser', userNameOrId) .then(({ id }) => { - this.fetchedUserId = id + this.userId = id + this.fetchTimelines() + }) + .catch((reason) => { + const errorMessage = get(reason, 'error.error') + if (errorMessage === 'No user with such user_id') { // Known error + this.error = this.$t('user_profile.profile_does_not_exist') + } else if (errorMessage) { + this.error = errorMessage + } else { + this.error = this.$t('user_profile.profile_loading_error') + } }) } - return fetchPromise - .catch((reason) => { - const errorMessage = get(reason, 'error.error') - if (errorMessage === 'No user with such user_id') { // Known error - this.error = this.$t('user_profile.profile_does_not_exist') - } else if (errorMessage) { - this.error = errorMessage - } else { - this.error = this.$t('user_profile.profile_loading_error') - } - }) - .then(() => this.startUp()) }, - startUp () { - if (this.userId) { - this.$store.dispatch('startFetchingTimeline', { timeline: 'user', userId: this.userId }) - this.$store.dispatch('startFetchingTimeline', { timeline: 'media', userId: this.userId }) - this.startFetchFavorites() + fetchTimelines () { + const userId = this.userId + this.$store.dispatch('startFetchingTimeline', { timeline: 'user', userId }) + this.$store.dispatch('startFetchingTimeline', { timeline: 'media', userId }) + if (this.isUs) { + this.$store.dispatch('startFetchingTimeline', { timeline: 'favorites', userId }) } }, cleanUp () { @@ -134,18 +113,16 @@ const UserProfile = { } }, watch: { - // userId can be undefined if we don't know it yet - userId (newVal) { + '$route.params.id': function (newVal) { if (newVal) { this.cleanUp() - this.startUp() + this.load(newVal) } }, - userName () { - if (this.$route.params.name) { - this.fetchUserId() + '$route.params.name': function (newVal) { + if (newVal) { this.cleanUp() - this.startUp() + this.load(newVal) } }, $route () { diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index d449eb85..89900f60 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -1,6 +1,6 @@