From 15bed586dcd1d10a6a05c664cf5bab72cdbf2a46 Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Sun, 15 Nov 2020 13:57:02 +0200 Subject: report notification wip --- src/services/entity_normalizer/entity_normalizer.service.js | 7 +++++++ src/services/notification_utils/notification_utils.js | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) (limited to 'src/services') diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index a4c1a1bf..093472e4 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -367,6 +367,13 @@ export const parseNotification = (data) => { : parseUser(data.target) output.from_profile = parseUser(data.account) output.emoji = data.emoji + if (data.report) { + output.report = data.report + output.report.content = data.report.content + output.report.acct = parseUser(data.report.acct) + output.report.actor = parseUser(data.report.actor) + output.report.statuses = data.report.statuses.map(parseStatus) + } } else { const parsedNotice = parseStatus(data.notice) output.type = data.ntype diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index d912d19f..dff97aa2 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -14,7 +14,8 @@ export const visibleTypes = store => { rootState.config.notificationVisibility.follows && 'follow', rootState.config.notificationVisibility.followRequest && 'follow_request', rootState.config.notificationVisibility.moves && 'move', - rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction' + rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction', + rootState.config.notificationVisibility.reports && 'pleroma:report' ].filter(_ => _)) } @@ -91,6 +92,9 @@ export const prepareNotificationObject = (notification, i18n) => { case 'follow_request': i18nString = 'follow_request' break + case 'pleroma:report': + i18nString = 'reported' + break } if (notification.type === 'pleroma:emoji_reaction') { -- cgit v1.2.3-70-g09d2 From 5e96260a4f855e2d93915c1b428a7209a882c8cb Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Fri, 4 Dec 2020 12:48:15 +0200 Subject: add test data for dev --- src/services/api/api.service.js | 282 +++++++++++++++++++++ .../entity_normalizer/entity_normalizer.service.js | 2 +- .../notifications_fetcher.service.js | 1 + 3 files changed, 284 insertions(+), 1 deletion(-) (limited to 'src/services') diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 8da933c4..6b825c37 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -484,6 +484,285 @@ const deleteUser = ({ credentials, user }) => { }) } +/* eslint-disable */ +const report = { + "account": { + "acct": "reporting_account", + "avatar": "https://develop.ilja.space/images/avi.png", + "avatar_static": "https://develop.ilja.space/images/avi.png", + "bot": false, + "created_at": "2020-09-03T14:17:00.000Z", + "display_name": "Reporting Account", + "emojis": [], + "fields": [], + "followers_count": 0, + "following_count": 0, + "header": "https://develop.ilja.space/images/banner.png", + "header_static": "https://develop.ilja.space/images/banner.png", + "id": "9ymg9LnKk74wuk9lXk", + "locked": false, + "note": "I'm an account made for an example report notification. I'm the one that will do the reporting.", + "pleroma": { + "accepts_chat_messages": true, + "ap_id": "https://develop.ilja.space/users/reporting_account", + "background_image": null, + "confirmation_pending": false, + "deactivated": false, + "favicon": null, + "hide_favorites": true, + "hide_followers": false, + "hide_followers_count": false, + "hide_follows": false, + "hide_follows_count": false, + "is_admin": false, + "is_moderator": false, + "relationship": {}, + "skip_thread_containment": false, + "tags": [] + }, + "source": { + "fields": [], + "note": "", + "pleroma": { + "actor_type": "Person", + "discoverable": false + }, + "sensitive": false + }, + "statuses_count": 0, + "url": "https://develop.ilja.space/users/reporting_account", + "username": "reporting_account" + }, + "created_at": "2020-09-03T14:22:59.000Z", + "id": "5", + "pleroma": { + "is_muted": false, + "is_seen": false + }, + "report": { + "account": { + "local": true, + "locked": false, + "acct": "reported_account", + "followers_count": 0, + "fields": [], + "avatar": "https://develop.ilja.space/images/avi.png", + "actor_type": "Person", + "url": "https://develop.ilja.space/users/reported_account", + "deactivated": false, + "id": "9ymgGklmHjZ2OpxVLM", + "bot": false, + "roles": { + "admin": false, + "moderator": false + }, + "statuses_count": 1, + "nickname": "reported_account", + "display_name": "Reported Account", + "source": { + "fields": [], + "note": "", + "pleroma": { + "actor_type": "Person", + "discoverable": false + }, + "sensitive": false + }, + "pleroma": { + "accepts_chat_messages": true, + "ap_id": "https://develop.ilja.space/users/reported_account", + "background_image": null, + "confirmation_pending": false, + "favicon": null, + "hide_favorites": true, + "hide_followers": false, + "hide_followers_count": false, + "hide_follows": false, + "hide_follows_count": false, + "is_admin": false, + "is_moderator": false, + "relationship": {}, + "skip_thread_containment": false, + "tags": [] + }, + "emojis": [], + "created_at": "2020-09-03T14:18:21.000Z", + "username": "reported_account", + "note": "I'm an account made for an example report notification. I'm the one that will be reported.", + "approval_pending": false, + "avatar_static": "https://develop.ilja.space/images/avi.png", + "header": "https://develop.ilja.space/images/banner.png", + "registration_reason": null, + "confirmation_pending": false, + "header_static": "https://develop.ilja.space/images/banner.png", + "following_count": 0, + "tags": [] + }, + "actor": { + "local": true, + "locked": false, + "acct": "reporting_account", + "followers_count": 0, + "fields": [], + "avatar": "https://develop.ilja.space/images/avi.png", + "actor_type": "Person", + "url": "https://develop.ilja.space/users/reporting_account", + "deactivated": false, + "id": "9ymg9LnKk74wuk9lXk", + "bot": false, + "roles": { + "admin": false, + "moderator": false + }, + "statuses_count": 0, + "nickname": "reporting_account", + "display_name": "Reporting Account", + "source": { + "fields": [], + "note": "", + "pleroma": { + "actor_type": "Person", + "discoverable": false + }, + "sensitive": false + }, + "pleroma": { + "accepts_chat_messages": true, + "ap_id": "https://develop.ilja.space/users/reporting_account", + "background_image": null, + "confirmation_pending": false, + "favicon": null, + "hide_favorites": true, + "hide_followers": false, + "hide_followers_count": false, + "hide_follows": false, + "hide_follows_count": false, + "is_admin": false, + "is_moderator": false, + "relationship": {}, + "skip_thread_containment": false, + "tags": [] + }, + "emojis": [], + "created_at": "2020-09-03T14:17:00.000Z", + "username": "reporting_account", + "note": "I'm an account made for an example report notification. I'm the one that will do the reporting.", + "approval_pending": false, + "avatar_static": "https://develop.ilja.space/images/avi.png", + "header": "https://develop.ilja.space/images/banner.png", + "registration_reason": null, + "confirmation_pending": false, + "header_static": "https://develop.ilja.space/images/banner.png", + "following_count": 0, + "tags": [] + }, + "content": "This is the report created by "reporting_account". It reports "reported_account".", + "created_at": "2020-09-03T14:22:59.000Z", + "id": "9ymggNcUyfIW8Cq1zM", + "notes": [], + "state": "open", + "statuses": [ + { + "account": { + "acct": "reported_account", + "avatar": "https://develop.ilja.space/images/avi.png", + "avatar_static": "https://develop.ilja.space/images/avi.png", + "bot": false, + "created_at": "2020-09-03T14:18:21.000Z", + "display_name": "Reported Account", + "emojis": [], + "fields": [], + "followers_count": 0, + "following_count": 0, + "header": "https://develop.ilja.space/images/banner.png", + "header_static": "https://develop.ilja.space/images/banner.png", + "id": "9ymgGklmHjZ2OpxVLM", + "locked": false, + "note": "I'm an account made for an example report notification. I'm the one that will be reported.", + "pleroma": { + "accepts_chat_messages": true, + "ap_id": "https://develop.ilja.space/users/reported_account", + "background_image": null, + "confirmation_pending": false, + "favicon": null, + "hide_favorites": true, + "hide_followers": false, + "hide_followers_count": false, + "hide_follows": false, + "hide_follows_count": false, + "is_admin": false, + "is_moderator": false, + "relationship": {}, + "skip_thread_containment": false, + "tags": [] + }, + "source": { + "fields": [], + "note": "", + "pleroma": { + "actor_type": "Person", + "discoverable": false + }, + "sensitive": false + }, + "statuses_count": 1, + "url": "https://develop.ilja.space/users/reported_account", + "username": "reported_account" + }, + "application": { + "name": "Web", + "website": null + }, + "bookmarked": false, + "card": null, + "content": "A post I made that will be included in the report", + "created_at": "2020-09-03T14:18:51.000Z", + "emojis": [], + "favourited": false, + "favourites_count": 0, + "id": "9ymgJaQxAbTzDDZMJs", + "in_reply_to_account_id": null, + "in_reply_to_id": null, + "language": null, + "media_attachments": [], + "mentions": [], + "muted": false, + "pinned": false, + "pleroma": { + "content": { + "text/plain": "A post I made that will be included in the report" + }, + "conversation_id": 7, + "direct_conversation_id": null, + "emoji_reactions": [], + "expires_at": null, + "in_reply_to_account_acct": null, + "local": true, + "parent_visible": false, + "spoiler_text": { + "text/plain": "" + }, + "thread_muted": false + }, + "poll": null, + "reblog": null, + "reblogged": false, + "reblogs_count": 0, + "replies_count": 0, + "sensitive": false, + "spoiler_text": "", + "tags": [], + "text": null, + "uri": "https://develop.ilja.space/objects/8fe7611a-07e7-403a-ae08-f74580b6cc53", + "url": "https://develop.ilja.space/notice/9ymgJaQxAbTzDDZMJs", + "visibility": "public" + } + ] + }, + "type": "pleroma:report" +} +/* eslint-enable */ + const fetchTimeline = ({ timeline, credentials, @@ -561,6 +840,9 @@ const fetchTimeline = ({ .then((data) => data.json()) .then((data) => { if (!data.errors) { + if (isNotifications) { + return { data: [parseNotification(report)], pagination } + } return { data: data.map(isNotifications ? parseNotification : parseStatus), pagination } } else { data.status = status diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 0fd01176..c71bc15a 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -376,7 +376,7 @@ export const parseNotification = (data) => { if (data.report) { output.report = data.report output.report.content = data.report.content - output.report.acct = parseUser(data.report.acct) + output.report.acct = parseUser(data.report.account) output.report.actor = parseUser(data.report.actor) output.report.statuses = data.report.statuses.map(parseStatus) } diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index beeb167c..91861b21 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -61,6 +61,7 @@ const fetchNotifications = ({ store, args, older }) => { messageArgs: [error.message], timeout: 5000 }) + console.error(error) }) } -- cgit v1.2.3-70-g09d2 From a4e3cccf1cba238e5bfd96ea8c60f0d12bc6b7aa Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Wed, 6 Jan 2021 18:31:34 +0200 Subject: somewhat workign version still with fixture --- src/components/notification/notification.js | 3 + src/components/notification/notification.scss | 28 +++ src/components/notification/notification.vue | 34 ++++ src/components/notifications/notifications.js | 1 + src/components/notifications/notifications.scss | 6 +- src/i18n/en.json | 3 +- src/modules/config.js | 3 +- src/services/api/api.service.js | 197 +++++++++++---------- .../entity_normalizer/entity_normalizer.service.js | 1 - 9 files changed, 177 insertions(+), 99 deletions(-) (limited to 'src/services') diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index 4aa9affd..a920bb3e 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -76,6 +76,9 @@ const Notification = { this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id }) this.$store.dispatch('removeFollowRequest', this.user) }) + }, + testlog (a) { + console.log(a) } }, computed: { diff --git a/src/components/notification/notification.scss b/src/components/notification/notification.scss index f5905560..b103db86 100644 --- a/src/components/notification/notification.scss +++ b/src/components/notification/notification.scss @@ -56,6 +56,34 @@ margin: 0 0.1em; } + .report-content { + margin: 0.5em 0; + } + + .reported-status { + border: 1px solid $fallback--faint; + border-color: var(--faint, $fallback--faint); + border-radius: $fallback--inputRadius; + border-radius: var(--inputRadius, $fallback--inputRadius); + color: $fallback--text; + color: var(--text, $fallback--text); + display: block; + padding: 0.5em; + margin: 0.5em 0; + + .status-content { + pointer-events: none; + } + + .reported-status-name { + font-weight: bold; + } + + .reported-status-timeago { + float: right; + } + } + &.-type--repeat .type-icon { color: $fallback--cGreen; color: var(--cGreen, $fallback--cGreen); diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue index f56aa977..39e3bda0 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -106,6 +106,9 @@ + + {{ $t('notifications.submitted_report') }} +
+
+ Reported user: + + @{{ notification.report.acct.screen_name }} + + +
+
+ Reported statuses: + + + {{ status.user.name }} + + + +
+
diff --git a/src/components/settings_modal/tabs/notifications_tab.js b/src/components/settings_modal/tabs/notifications_tab.js index 3e44c95d..3c6ab87f 100644 --- a/src/components/settings_modal/tabs/notifications_tab.js +++ b/src/components/settings_modal/tabs/notifications_tab.js @@ -1,4 +1,5 @@ -import Checkbox from 'src/components/checkbox/checkbox.vue' +import BooleanSetting from '../helpers/boolean_setting.vue' +import SharedComputedObject from '../helpers/shared_computed_object.js' const NotificationsTab = { data () { @@ -9,12 +10,13 @@ const NotificationsTab = { } }, components: { - Checkbox + BooleanSetting }, computed: { user () { return this.$store.state.users.currentUser - } + }, + ...SharedComputedObject() }, methods: { updateNotificationSettings () { diff --git a/src/components/settings_modal/tabs/notifications_tab.vue b/src/components/settings_modal/tabs/notifications_tab.vue index 7e0568ea..5e9ce91e 100644 --- a/src/components/settings_modal/tabs/notifications_tab.vue +++ b/src/components/settings_modal/tabs/notifications_tab.vue @@ -2,30 +2,68 @@

{{ $t('settings.notification_setting_filters') }}

-

- - {{ $t('settings.notification_setting_block_from_strangers') }} - -

+
    +
  • + + {{ $t('settings.notification_setting_block_from_strangers') }} + +
  • +
  • + {{ $t('settings.notification_visibility') }} +
      +
    • + + {{ $t('settings.notification_visibility_likes') }} + +
    • +
    • + + {{ $t('settings.notification_visibility_repeats') }} + +
    • +
    • + + {{ $t('settings.notification_visibility_follows') }} + +
    • +
    • + + {{ $t('settings.notification_visibility_mentions') }} + +
    • +
    • + + {{ $t('settings.notification_visibility_moves') }} + +
    • +
    • + + {{ $t('settings.notification_visibility_emoji_reactions') }} + +
    • +
    +
  • +
-
+

{{ $t('settings.notification_setting_privacy') }}

-

- - {{ $t('settings.notification_setting_hide_notification_contents') }} - -

+
    +
  • + + {{ $t('settings.enable_web_push_notifications') }} + +
  • +
  • + + {{ $t('settings.notification_setting_hide_notification_contents') }} + +
  • +

{{ $t('settings.notification_mutes') }}

{{ $t('settings.notification_blocks') }}

-
diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js index 64079fcd..bee8a7bb 100644 --- a/src/components/settings_modal/tabs/profile_tab.js +++ b/src/components/settings_modal/tabs/profile_tab.js @@ -8,6 +8,9 @@ import EmojiInput from 'src/components/emoji_input/emoji_input.vue' import suggestor from 'src/components/emoji_input/suggestor.js' import Autosuggest from 'src/components/autosuggest/autosuggest.vue' import Checkbox from 'src/components/checkbox/checkbox.vue' +import BooleanSetting from '../helpers/boolean_setting.vue' +import SharedComputedObject from '../helpers/shared_computed_object.js' + import { library } from '@fortawesome/fontawesome-svg-core' import { faTimes, @@ -27,18 +30,10 @@ const ProfileTab = { newName: this.$store.state.users.currentUser.name_unescaped, newBio: unescape(this.$store.state.users.currentUser.description), newLocked: this.$store.state.users.currentUser.locked, - newNoRichText: this.$store.state.users.currentUser.no_rich_text, - newDefaultScope: this.$store.state.users.currentUser.default_scope, newFields: this.$store.state.users.currentUser.fields.map(field => ({ name: field.name, value: field.value })), - hideFollows: this.$store.state.users.currentUser.hide_follows, - hideFollowers: this.$store.state.users.currentUser.hide_followers, - hideFollowsCount: this.$store.state.users.currentUser.hide_follows_count, - hideFollowersCount: this.$store.state.users.currentUser.hide_followers_count, showRole: this.$store.state.users.currentUser.show_role, role: this.$store.state.users.currentUser.role, - discoverable: this.$store.state.users.currentUser.discoverable, bot: this.$store.state.users.currentUser.bot, - allowFollowingMove: this.$store.state.users.currentUser.allow_following_move, pickAvatarBtnVisible: true, bannerUploading: false, backgroundUploading: false, @@ -54,12 +49,14 @@ const ProfileTab = { EmojiInput, Autosuggest, ProgressButton, - Checkbox + Checkbox, + BooleanSetting }, computed: { user () { return this.$store.state.users.currentUser }, + ...SharedComputedObject(), emojiUserSuggestor () { return suggestor({ emoji: [ @@ -123,15 +120,7 @@ const ProfileTab = { /* eslint-disable camelcase */ display_name: this.newName, fields_attributes: this.newFields.filter(el => el != null), - default_scope: this.newDefaultScope, - no_rich_text: this.newNoRichText, - hide_follows: this.hideFollows, - hide_followers: this.hideFollowers, - discoverable: this.discoverable, bot: this.bot, - allow_following_move: this.allowFollowingMove, - hide_follows_count: this.hideFollowsCount, - hide_followers_count: this.hideFollowersCount, show_role: this.showRole /* eslint-enable camelcase */ } }).then((user) => { diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue index bb3c301d..2cf3e8be 100644 --- a/src/components/settings_modal/tabs/profile_tab.vue +++ b/src/components/settings_modal/tabs/profile_tab.vue @@ -25,61 +25,6 @@ class="bio resize-height" /> -

- - {{ $t('settings.lock_account_description') }} - -

-
- -
- -
-
-

- - {{ $t('settings.no_rich_text_description') }} - -

-

- - {{ $t('settings.hide_follows_description') }} - -

-

- - {{ $t('settings.hide_follows_count_description') }} - -

-

- - {{ $t('settings.hide_followers_description') }} - -

-

- - {{ $t('settings.hide_followers_count_description') }} - -

-

- - {{ $t('settings.allow_following_move') }} - -

-

- - {{ $t('settings.discoverable') }} - -

{{ $t('settings.profile_fields.label') }}

{{ $t('settings.avatar_size_instruction') }}

-
- @@ -269,6 +208,67 @@ {{ $t('settings.save') }}
+
+

{{ $t('settings.account_privacy') }}

+
    +
  • + + {{ $t('settings.lock_account_description') }} + +
  • +
  • + + {{ $t('settings.discoverable') }} + +
  • +
  • + + {{ $t('settings.allow_following_move') }} + +
  • +
  • + + {{ $t('settings.hide_favorites_description') }} + +
  • +
  • + + {{ $t('settings.hide_followers_description') }} + +
      +
    • + + {{ $t('settings.hide_followers_count_description') }} + +
    • +
    +
  • +
  • + + {{ $t('settings.hide_follows_description') }} + +
      +
    • + + {{ $t('settings.hide_follows_count_description') }} + +
    • +
    +
  • +
+
diff --git a/src/i18n/en.json b/src/i18n/en.json index 8eb7fcc6..932e8af5 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -259,11 +259,14 @@ }, "settings": { "app_name": "App name", + "expert_mode": "Expert mode", "save": "Save changes", "security": "Security", "setting_changed": "Setting is different from default", + "setting_server_side": "This setting is tied to your profile and affects all sessions and clients", "enter_current_password_to_confirm": "Enter your current password to confirm your identity", "post_look_feel": "Posts Look & Feel", + "mention_links": "Mention links", "mfa": { "otp": "OTP", "setup_otp": "Setup OTP", @@ -400,6 +403,7 @@ "name": "Label", "value": "Content" }, + "account_privacy": "Privacy", "use_contain_fit": "Don't crop the attachment in thumbnails", "name": "Name", "name_bio": "Name & bio", @@ -417,6 +421,7 @@ "no_rich_text_description": "Strip rich text formatting from all posts", "no_blocks": "No blocks", "no_mutes": "No mutes", + "hide_favorites_description": "Don't show list of my favorites (people still get notified)", "hide_follows_description": "Don't show who I'm following", "hide_followers_description": "Don't show who's following me", "hide_follows_count_description": "Don't show follow count", diff --git a/src/main.js b/src/main.js index 3895da89..73bd49ed 100644 --- a/src/main.js +++ b/src/main.js @@ -11,6 +11,7 @@ import statusesModule from './modules/statuses.js' import usersModule from './modules/users.js' import apiModule from './modules/api.js' import configModule from './modules/config.js' +import serverSideConfigModule from './modules/serverSideConfig.js' import shoutModule from './modules/shout.js' import oauthModule from './modules/oauth.js' import authFlowModule from './modules/auth_flow.js' @@ -88,6 +89,7 @@ const persistedStateOptions = { users: usersModule, api: apiModule, config: configModule, + serverSideConfig: serverSideConfigModule, shout: shoutModule, oauth: oauthModule, authFlow: authFlowModule, diff --git a/src/modules/config.js b/src/modules/config.js index 20979174..775ee398 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -16,6 +16,7 @@ export const multiChoiceProperties = [ ] export const defaultState = { + expertLevel: 0, // used to track which settings to show and hide colors: {}, theme: undefined, customTheme: undefined, diff --git a/src/modules/serverSideConfig.js b/src/modules/serverSideConfig.js new file mode 100644 index 00000000..75ea91be --- /dev/null +++ b/src/modules/serverSideConfig.js @@ -0,0 +1,111 @@ +import { get, set } from 'lodash' + +export const settingsMapGet = { + 'defaultScope': 'source.privacy', + 'defaultNSFW': 'source.sensitive', + 'stripRichContent': 'source.pleroma.no_rich_content', + // Privacy + 'locked': 'locked', + 'acceptChatMessages': 'pleroma.accepts_chat_messages', + 'allowFollowingMove': 'pleroma.allow_following_move', + 'discoverable': 'source.discoverable', + 'hideFavorites': 'pleroma.hide_favorites', + 'hideFollowers': 'pleroma.hide_followers', + 'hideFollows': 'pleroma.hide_follows', + 'hideFollowersCount': 'pleroma.hide_followers_count', + 'hideFollowsCount': 'pleroma.hide_follows_count', + // NotificationSettingsAPIs + 'webPushHideContents': 'pleroma.notification_settings.hide_notification_contents', + 'blockNotificationsFromStrangers': 'pleroma.notification_settings.block_from_strangers' +} + +export const settingsMapSet = { + 'defaultScope': 'source.privacy', + 'defaultNSFW': 'source.sensitive', + 'stripRichContent': 'source.pleroma.no_rich_content', + // Privacy + 'locked': 'locked', + 'acceptChatMessages': 'accepts_chat_messages', + 'allowFollowingMove': 'allow_following_move', + 'discoverable': 'source.discoverable', + 'hideFavorites': 'hide_favorites', + 'hideFollowers': 'hide_followers', + 'hideFollows': 'hide_follows', + 'hideFollowersCount': 'hide_followers_count', + 'hideFollowsCount': 'hide_follows_count', + // NotificationSettingsAPIs + 'webPushHideContents': 'hide_notification_contents', + 'blockNotificationsFromStrangers': 'block_from_strangers' +} + +export const customAPIs = { + __defaultApi: 'updateProfile', + 'webPushHideContents': 'updateNotificationSettings', + 'blockNotificationsFromStrangers': 'updateNotificationSettings' +} + +export const defaultState = Object.fromEntries(Object.keys(settingsMapGet).map(key => [key, undefined])) + +const serverSideConfig = { + state: { ...defaultState }, + mutations: { + confirmServerSideOption (state, { name, value }) { + set(state, name, value) + }, + wipeServerSideOption (state, { name }) { + set(state, name, undefined) + }, + // Set the settings based on their path location + setCurrentUser (state, user) { + Object.entries(settingsMapGet).forEach(([name, path]) => { + set(state, name, get(user._original, path)) + }) + } + }, + actions: { + setServerSideOption ({ rootState, state, commit, dispatch }, { name, value }) { + const oldValue = get(state, name) + const params = {} + const path = settingsMapSet[name] + if (!path) throw new Error('Invalid server-side setting') + commit('wipeServerSideOption', { name }) + const customAPIName = customAPIs[name] || customAPIs.__defaultApi + const api = rootState.api.backendInteractor[customAPIName] + let prefix = '' + switch (customAPIName) { + case 'updateNotificationSettings': + prefix = 'settings.' + break + default: + prefix = 'params.' + break + } + + set(params, prefix + path, value) + api(params) + .then((result) => { + switch (customAPIName) { + case 'updateNotificationSettings': + console.log(result) + if (result.status === 'success') { + commit('confirmServerSideOption', { name, value }) + } else { + commit('confirmServerSideOption', { name, value: oldValue }) + } + break + default: + commit('addNewUsers', [result]) + commit('setCurrentUser', result) + break + } + console.log(state) + }) + .catch((e) => { + console.warn('Error setting server-side option:', e) + commit('confirmServerSideOption', { name, value: oldValue }) + }) + } + } +} + +export default serverSideConfig diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 7025d803..f219c161 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -44,6 +44,7 @@ export const parseUser = (data) => { const mastoShort = masto && !data.hasOwnProperty('avatar') output.id = String(data.id) + output._original = data // used for server-side settings if (masto) { output.screen_name = data.acct -- cgit v1.2.3-70-g09d2 From 819b76026101ddc0363118f240049a0019ebb4d6 Mon Sep 17 00:00:00 2001 From: Ilja Date: Sat, 26 Feb 2022 01:53:01 +0100 Subject: Fix up and code review * Check if it works properly * Notifs are shown as BE returns them * The Interaction view has Reports, but only when you're mod or admin * Do some extra translations * Fix some console spam --- src/components/interactions/interactions.js | 3 ++- src/i18n/en.json | 2 ++ src/i18n/nl.json | 13 +++++++++++++ src/modules/reports.js | 5 +---- src/services/api/api.service.js | 3 ++- src/services/notification_utils/notification_utils.js | 2 +- .../notifications_fetcher/notifications_fetcher.service.js | 4 +--- 7 files changed, 22 insertions(+), 10 deletions(-) (limited to 'src/services') diff --git a/src/components/interactions/interactions.js b/src/components/interactions/interactions.js index 54275a7b..53ec7d49 100644 --- a/src/components/interactions/interactions.js +++ b/src/components/interactions/interactions.js @@ -13,7 +13,8 @@ const Interactions = { data () { return { allowFollowingMove: this.$store.state.users.currentUser.allow_following_move, - filterMode: tabModeDict['mentions'] + filterMode: tabModeDict['mentions'], + canSeeReports: ['moderator', 'admin'].includes(this.$store.state.users.currentUser.role) } }, methods: { diff --git a/src/i18n/en.json b/src/i18n/en.json index 3bdd42e3..9e74840f 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -176,6 +176,8 @@ "interactions": { "favs_repeats": "Repeats and Favorites", "follows": "New follows", + "emoji_reactions": "Emoji Reactions", + "reports": "Reports", "moves": "User migrates", "load_older": "Load older interactions" }, diff --git a/src/i18n/nl.json b/src/i18n/nl.json index a01e57a0..8da07ac7 100644 --- a/src/i18n/nl.json +++ b/src/i18n/nl.json @@ -661,6 +661,8 @@ "interactions": { "favs_repeats": "Herhalingen en Favorieten", "follows": "Nieuwe volgingen", + "emoji_reactions": "Emoji Reacties", + "reports": "Rapportages", "moves": "Gebruiker migreert", "load_older": "Oudere interacties laden" }, @@ -669,6 +671,17 @@ "error": "Niet gevonden.", "remote_user_resolver": "Externe gebruikers zoeker" }, + "report": { + "reporter": "Reporteerder:", + "reported_user": "Gerapporteerde gebruiker:", + "reported_statuses": "Gerapporteerde statussen:", + "notes": "Notas:", + "state": "Status:", + "state_open": "Open", + "state_closed": "Gesloten", + "state_resolved": "Opgelost" + }, + "selectable_list": { "select_all": "Alles selecteren" }, diff --git a/src/modules/reports.js b/src/modules/reports.js index b25e9ee9..925792c0 100644 --- a/src/modules/reports.js +++ b/src/modules/reports.js @@ -43,11 +43,8 @@ const reports = { }, setReportState ({ commit, dispatch, rootState }, { id, state }) { const oldState = rootState.reports.reports[id].state - console.log(oldState, state) commit('setReportState', { id, state }) - rootState.api.backendInteractor.setReportState({ id, state }).then(report => { - console.log(report) - }).catch(e => { + rootState.api.backendInteractor.setReportState({ id, state }).catch(e => { console.error('Failed to set report state', e) dispatch('pushGlobalNotice', { level: 'error', diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index e60010fe..27ea5199 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -1270,7 +1270,8 @@ const deleteChatMessage = ({ chatId, messageId, credentials }) => { } const setReportState = ({ id, state, credentials }) => { - // Can't use promisedRequest because on OK this does not return json + // TODO: Can't use promisedRequest because on OK this does not return json + // See https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1322 return fetch(PLEROMA_ADMIN_REPORTS, { headers: { ...authHeaders(credentials), diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index dff97aa2..b338eb8b 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -93,7 +93,7 @@ export const prepareNotificationObject = (notification, i18n) => { i18nString = 'follow_request' break case 'pleroma:report': - i18nString = 'reported' + i18nString = 'submitted_report' break } diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index 2da6d646..4ecb348e 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -24,9 +24,7 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => { const timelineData = rootState.statuses.notifications const hideMutedPosts = getters.mergedConfig.hideMutedPosts - if (rootState.users.currentUser.role === 'admin') { - args['includeTypes'] = mastoApiNotificationTypes - } + args['includeTypes'] = mastoApiNotificationTypes args['withMuted'] = !hideMutedPosts args['timeline'] = 'notifications' -- cgit v1.2.3-70-g09d2 From f96e5882d16a8662ceb7b8cc6aa36fe131a2682f Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 1 Aug 2021 13:39:56 -0400 Subject: Make media modal be aware of multi-touch actions Originally the media viewer would think every touch is a swipe (one-finger touch event), so we would encounter the case where a two-finger scale event would incorrectly change the current media. This is now fixed. --- src/components/media_modal/media_modal.js | 25 ++++---- src/components/media_modal/media_modal.vue | 1 + src/services/gesture_service/gesture_service.js | 83 ++++++++++++++++++++++++- 3 files changed, 93 insertions(+), 16 deletions(-) (limited to 'src/services') diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index b8bce730..f3d381ee 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -53,28 +53,25 @@ const MediaModal = { } }, created () { - this.mediaSwipeGestureRight = GestureService.swipeGesture( - GestureService.DIRECTION_RIGHT, - this.goPrev, - 50 - ) - this.mediaSwipeGestureLeft = GestureService.swipeGesture( - GestureService.DIRECTION_LEFT, - this.goNext, - 50 - ) + this.mediaGesture = new GestureService.SwipeAndScaleGesture({ + direction: GestureService.DIRECTION_LEFT, + callbackPositive: this.goNext, + callbackNegative: this.goPrev, + threshold: 50 + }) }, methods: { getType (media) { return fileTypeService.fileType(media.mimetype) }, mediaTouchStart (e) { - GestureService.beginSwipe(e, this.mediaSwipeGestureRight) - GestureService.beginSwipe(e, this.mediaSwipeGestureLeft) + this.mediaGesture.start(e) }, mediaTouchMove (e) { - GestureService.updateSwipe(e, this.mediaSwipeGestureRight) - GestureService.updateSwipe(e, this.mediaSwipeGestureLeft) + this.mediaGesture.move(e) + }, + mediaTouchEnd (e) { + this.mediaGesture.end(e) }, hide () { this.$store.dispatch('closeMediaViewer') diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index 8680267b..b5a65d50 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -13,6 +13,7 @@ :title="currentMedia.description" @touchstart.stop="mediaTouchStart" @touchmove.stop="mediaTouchMove" + @touchend.stop="mediaTouchEnd" @click="hide" @load="onImageLoaded" > diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index 88a328f3..8df4f03d 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -4,9 +4,17 @@ const DIRECTION_RIGHT = [1, 0] const DIRECTION_UP = [0, -1] const DIRECTION_DOWN = [0, 1] +const isSwipeEvent = e => (e.touches.length === 1) +const isSwipeEventEnd = e => (e.changedTouches.length === 1) + +const isScaleEvent = e => (e.targetTouches.length === 2) +// const isScaleEventEnd = e => (e.changedTouches.length === 2) + const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]] -const touchEventCoord = e => ([e.touches[0].screenX, e.touches[0].screenY]) +const touchCoord = touch => [touch.screenX, touch.screenY] + +const touchEventCoord = e => touchCoord(e.touches[0]) const vectorLength = v => Math.sqrt(v[0] * v[0] + v[1] * v[1]) @@ -61,6 +69,76 @@ const updateSwipe = (event, gesture) => { gesture._swiping = false } +class SwipeAndScaleGesture { + constructor ({ + direction, callbackPositive, callbackNegative, + previewCallback, threshold = 30, perpendicularTolerance = 1.0 + }) { + this.direction = direction + this.previewCallback = previewCallback + this.callbackPositive = callbackPositive + this.callbackNegative = callbackNegative + this.threshold = threshold + this.perpendicularTolerance = perpendicularTolerance + this._startPos = [0, 0] + this._swiping = false + } + + start (event) { + console.log('start() called', event) + if (isSwipeEvent(event)) { + this._startPos = touchEventCoord(event) + console.log('start pos:', this._startPos) + this._swiping = true + } else if (isScaleEvent(event)) { + this._scalePoints = [...event.targetTouches] + this._swiping = false + } + } + + move (event) { + if (isScaleEvent(event)) { + } + } + + end (event) { + console.log('end() called', event) + if (!isSwipeEventEnd(event)) { + console.log('not swipe event') + return + } + if (!this._swiping) { + console.log('not swiping') + return + } + this.swiping = false + + console.log('is swipe event') + + // movement too small + const touch = event.changedTouches[0] + const delta = deltaCoord(this._startPos, touchCoord(touch)) + if (vectorLength(delta) < this.threshold) return + // movement is opposite from direction + const isPositive = dotProduct(delta, this.direction) > 0 + + // movement perpendicular to direction is too much + const towardsDir = project(delta, this.direction) + const perpendicularDir = perpendicular(this.direction) + const towardsPerpendicular = project(delta, perpendicularDir) + if ( + vectorLength(towardsDir) * this.perpendicularTolerance < + vectorLength(towardsPerpendicular) + ) return + + if (isPositive) { + this.callbackPositive() + } else { + this.callbackNegative() + } + } +} + const GestureService = { DIRECTION_LEFT, DIRECTION_RIGHT, @@ -68,7 +146,8 @@ const GestureService = { DIRECTION_DOWN, swipeGesture, beginSwipe, - updateSwipe + updateSwipe, + SwipeAndScaleGesture } export default GestureService -- cgit v1.2.3-70-g09d2 From a7570f5eb2b8bc576edbcc8e212b2c873ac99e7e Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 1 Aug 2021 19:46:27 -0400 Subject: Preview swipe action --- src/components/media_modal/media_modal.js | 26 +++++++++ src/components/media_modal/media_modal.vue | 1 + src/modules/media_viewer.js | 64 +++++++++++++++++++++- src/services/gesture_service/gesture_service.js | 73 +++++++++++++++++-------- 4 files changed, 138 insertions(+), 26 deletions(-) (limited to 'src/services') diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index f3d381ee..8f67db2b 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -4,6 +4,7 @@ import Modal from '../modal/modal.vue' import fileTypeService from '../../services/file_type/file_type.service.js' import GestureService from '../../services/gesture_service/gesture_service' import Flash from 'src/components/flash/flash.vue' +import Vuex from 'vuex' import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronLeft, @@ -17,6 +18,8 @@ library.add( faCircleNotch ) +const onlyXAxis = ([x, y]) => [x, 0] + const MediaModal = { components: { StillImage, @@ -50,6 +53,15 @@ const MediaModal = { }, type () { return this.currentMedia ? this.getType(this.currentMedia) : null + }, + scaling () { + return this.$store.state.mediaViewer.swipeScaler.scaling + }, + offsets () { + return this.$store.state.mediaViewer.swipeScaler.offsets + }, + transform () { + return `scale(${this.scaling}, ${this.scaling}) translate(${this.offsets[0]}px, ${this.offsets[1]}px)` } }, created () { @@ -57,6 +69,8 @@ const MediaModal = { direction: GestureService.DIRECTION_LEFT, callbackPositive: this.goNext, callbackNegative: this.goPrev, + swipePreviewCallback: this.handleSwipePreview, + swipeEndCallback: this.handleSwipeEnd, threshold: 50 }) }, @@ -99,6 +113,18 @@ const MediaModal = { onImageLoaded () { this.loading = false }, + handleSwipePreview (offsets) { + this.$store.dispatch('swipeScaler/apply', { offsets: onlyXAxis(offsets) }) + }, + handleSwipeEnd (sign) { + if (sign === 0) { + this.$store.dispatch('swipeScaler/revert') + } else if (sign > 0) { + this.goNext() + } else { + this.goPrev() + } + }, handleKeyupEvent (e) { if (this.showing && e.keyCode === 27) { // escape this.hide() diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index b5a65d50..728c3035 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -11,6 +11,7 @@ :src="currentMedia.url" :alt="currentMedia.description" :title="currentMedia.description" + :style="{ transform }" @touchstart.stop="mediaTouchStart" @touchmove.stop="mediaTouchMove" @touchend.stop="mediaTouchEnd" diff --git a/src/modules/media_viewer.js b/src/modules/media_viewer.js index ebcba01d..38723763 100644 --- a/src/modules/media_viewer.js +++ b/src/modules/media_viewer.js @@ -20,19 +20,79 @@ const mediaViewer = { } }, actions: { - setMedia ({ commit }, attachments) { + setMedia ({ commit, dispatch }, attachments) { const media = attachments.filter(attachment => { const type = fileTypeService.fileType(attachment.mimetype) return supportedTypes.has(type) }) commit('setMedia', media) + dispatch('swipeScaler/reset') }, setCurrentMedia ({ commit, state }, current) { const index = state.media.indexOf(current) commit('setCurrentMedia', index || 0) + dispatch('swipeScaler/reset') }, - closeMediaViewer ({ commit }) { + closeMediaViewer ({ commit, dispatch }) { commit('close') + dispatch('swipeScaler/reset') + } + }, + modules: { + swipeScaler: { + namespaced: true, + + state: { + origOffsets: [0, 0], + offsets: [0, 0], + origScaling: 1, + scaling: 1 + }, + + mutations: { + reset (state) { + state.origOffsets = [0, 0] + state.offsets = [0, 0] + state.origScaling = 1 + state.scaling = 1 + }, + applyOffsets (state, { offsets }) { + state.offsets = state.origOffsets.map((k, n) => k + offsets[n]) + }, + applyScaling (state, { scaling }) { + state.scaling = state.origScaling * scaling + }, + finishOffsets (state) { + state.origOffsets = [...state.offsets] + }, + finishScaling (state) { + state.origScaling = state.scaling + }, + revertOffsets (state) { + state.offsets = [...state.origOffsets] + }, + revertScaling (state) { + state.scaling = state.origScaling + } + }, + + actions: { + reset ({ commit }) { + commit('reset') + }, + apply ({ commit }, { offsets, scaling = 1 }) { + commit('applyOffsets', { offsets }) + commit('applyScaling', { scaling }) + }, + finish ({ commit }) { + commit('finishOffsets') + commit('finishScaling') + }, + revert ({ commit }) { + commit('revertOffsets') + commit('revertScaling') + } + } } } } diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index 8df4f03d..8f406762 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -70,14 +70,28 @@ const updateSwipe = (event, gesture) => { } class SwipeAndScaleGesture { + // swipePreviewCallback(offsets: Array[Number]) + // offsets: the offset vector which the underlying component should move, from the starting position + // pinchPreviewCallback(offsets: Array[Number], scaling: Number) + // offsets: the offset vector which the underlying component should move, from the starting position + // scaling: the scaling factor we should apply to the underlying component, from the starting position + // swipeEndcallback(sign: 0|-1|1) + // sign: if the swipe does not meet the threshold, 0 + // if the swipe meets the threshold in the positive direction, 1 + // if the swipe meets the threshold in the negative direction, -1 constructor ({ - direction, callbackPositive, callbackNegative, - previewCallback, threshold = 30, perpendicularTolerance = 1.0 + direction, + // swipeStartCallback, pinchStartCallback, + swipePreviewCallback, pinchPreviewCallback, + swipeEndCallback, pinchEndCallback, + threshold = 30, perpendicularTolerance = 1.0 }) { + const nop = () => {} this.direction = direction - this.previewCallback = previewCallback - this.callbackPositive = callbackPositive - this.callbackNegative = callbackNegative + this.swipePreviewCallback = swipePreviewCallback || nop + this.pinchPreviewCallback = pinchPreviewCallback || nop + this.swipeEndCallback = swipeEndCallback || nop + this.pinchEndCallback = pinchEndCallback || nop this.threshold = threshold this.perpendicularTolerance = perpendicularTolerance this._startPos = [0, 0] @@ -97,7 +111,12 @@ class SwipeAndScaleGesture { } move (event) { - if (isScaleEvent(event)) { + if (isSwipeEvent(event)) { + const touch = event.changedTouches[0] + const delta = deltaCoord(this._startPos, touchCoord(touch)) + + this.swipePreviewCallback(delta) + } else if (isScaleEvent(event)) { } } @@ -118,24 +137,30 @@ class SwipeAndScaleGesture { // movement too small const touch = event.changedTouches[0] const delta = deltaCoord(this._startPos, touchCoord(touch)) - if (vectorLength(delta) < this.threshold) return - // movement is opposite from direction - const isPositive = dotProduct(delta, this.direction) > 0 - - // movement perpendicular to direction is too much - const towardsDir = project(delta, this.direction) - const perpendicularDir = perpendicular(this.direction) - const towardsPerpendicular = project(delta, perpendicularDir) - if ( - vectorLength(towardsDir) * this.perpendicularTolerance < - vectorLength(towardsPerpendicular) - ) return - - if (isPositive) { - this.callbackPositive() - } else { - this.callbackNegative() - } + this.swipePreviewCallback(delta) + + const sign = (() => { + if (vectorLength(delta) < this.threshold) { + return 0 + } + // movement is opposite from direction + const isPositive = dotProduct(delta, this.direction) > 0 + + // movement perpendicular to direction is too much + const towardsDir = project(delta, this.direction) + const perpendicularDir = perpendicular(this.direction) + const towardsPerpendicular = project(delta, perpendicularDir) + if ( + vectorLength(towardsDir) * this.perpendicularTolerance < + vectorLength(towardsPerpendicular) + ) { + return 0 + } + + return isPositive ? 1 : -1 + })() + + this.swipeEndCallback(sign) } } -- cgit v1.2.3-70-g09d2 From d9030b4fddc9f47cb2f16a7a65f57a5703059e3f Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 1 Aug 2021 21:39:07 -0400 Subject: Handle pinch action --- src/components/media_modal/media_modal.js | 11 ++++++- src/components/media_modal/media_modal.vue | 6 ++-- src/modules/media_viewer.js | 14 +++------ src/services/gesture_service/gesture_service.js | 42 +++++++++++++++++++++++-- 4 files changed, 57 insertions(+), 16 deletions(-) (limited to 'src/services') diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 8f67db2b..04dd8658 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -61,7 +61,7 @@ const MediaModal = { return this.$store.state.mediaViewer.swipeScaler.offsets }, transform () { - return `scale(${this.scaling}, ${this.scaling}) translate(${this.offsets[0]}px, ${this.offsets[1]}px)` + return `translate(${this.offsets[0]}px, ${this.offsets[1]}px) scale(${this.scaling}, ${this.scaling})` } }, created () { @@ -71,6 +71,8 @@ const MediaModal = { callbackNegative: this.goPrev, swipePreviewCallback: this.handleSwipePreview, swipeEndCallback: this.handleSwipeEnd, + pinchPreviewCallback: this.handlePinchPreview, + pinchEndCallback: this.handlePinchEnd, threshold: 50 }) }, @@ -125,6 +127,13 @@ const MediaModal = { this.goPrev() } }, + handlePinchPreview (offsets, scaling) { + console.log('handle pinch preview:', offsets, scaling) + this.$store.dispatch('swipeScaler/apply', { offsets, scaling }) + }, + handlePinchEnd () { + this.$store.dispatch('swipeScaler/finish') + }, handleKeyupEvent (e) { if (this.showing && e.keyCode === 27) { // escape this.hide() diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index 728c3035..853dec1d 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -12,9 +12,9 @@ :alt="currentMedia.description" :title="currentMedia.description" :style="{ transform }" - @touchstart.stop="mediaTouchStart" - @touchmove.stop="mediaTouchMove" - @touchend.stop="mediaTouchEnd" + @touchstart.stop.prevent="mediaTouchStart" + @touchmove.stop.prevent="mediaTouchMove" + @touchend.stop.prevent="mediaTouchEnd" @click="hide" @load="onImageLoaded" > diff --git a/src/modules/media_viewer.js b/src/modules/media_viewer.js index 38723763..d62aa7c1 100644 --- a/src/modules/media_viewer.js +++ b/src/modules/media_viewer.js @@ -62,16 +62,12 @@ const mediaViewer = { applyScaling (state, { scaling }) { state.scaling = state.origScaling * scaling }, - finishOffsets (state) { + finish (state) { state.origOffsets = [...state.offsets] - }, - finishScaling (state) { state.origScaling = state.scaling }, - revertOffsets (state) { + revert (state) { state.offsets = [...state.origOffsets] - }, - revertScaling (state) { state.scaling = state.origScaling } }, @@ -85,12 +81,10 @@ const mediaViewer = { commit('applyScaling', { scaling }) }, finish ({ commit }) { - commit('finishOffsets') - commit('finishScaling') + commit('finish') }, revert ({ commit }) { - commit('revertOffsets') - commit('revertScaling') + commit('revert') } } } diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index 8f406762..82337bc6 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -4,14 +4,21 @@ const DIRECTION_RIGHT = [1, 0] const DIRECTION_UP = [0, -1] const DIRECTION_DOWN = [0, 1] +const DISTANCE_MIN = 1 + const isSwipeEvent = e => (e.touches.length === 1) const isSwipeEventEnd = e => (e.changedTouches.length === 1) const isScaleEvent = e => (e.targetTouches.length === 2) -// const isScaleEventEnd = e => (e.changedTouches.length === 2) +const isScaleEventEnd = e => (e.targetTouches.length === 1) const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]] +const vectorMinus = (a, b) => a.map((k, n) => k - b[n]) +const vectorAdd = (a, b) => a.map((k, n) => k + b[n]) + +const avgCoord = (coords) => [...coords].reduce(vectorAdd, [0, 0]).map(d => d / coords.length) + const touchCoord = touch => [touch.screenX, touch.screenY] const touchEventCoord = e => touchCoord(e.touches[0]) @@ -22,6 +29,8 @@ const perpendicular = v => [v[1], -v[0]] const dotProduct = (v1, v2) => v1[0] * v2[0] + v1[1] * v2[1] +// const numProduct = (num, v) => v.map(k => num * k) + const project = (v1, v2) => { const scalar = (dotProduct(v1, v2) / dotProduct(v2, v2)) return [scalar * v2[0], scalar * v2[1]] @@ -86,7 +95,7 @@ class SwipeAndScaleGesture { swipeEndCallback, pinchEndCallback, threshold = 30, perpendicularTolerance = 1.0 }) { - const nop = () => {} + const nop = () => { console.log('Warning: Not implemented') } this.direction = direction this.swipePreviewCallback = swipePreviewCallback || nop this.pinchPreviewCallback = pinchPreviewCallback || nop @@ -95,6 +104,7 @@ class SwipeAndScaleGesture { this.threshold = threshold this.perpendicularTolerance = perpendicularTolerance this._startPos = [0, 0] + this._startDistance = DISTANCE_MIN this._swiping = false } @@ -105,23 +115,51 @@ class SwipeAndScaleGesture { console.log('start pos:', this._startPos) this._swiping = true } else if (isScaleEvent(event)) { + const coords = [...event.targetTouches].map(touchCoord) + this._startPos = avgCoord(coords) + this._startDistance = vectorLength(deltaCoord(coords[0], coords[1])) + if (this._startDistance < DISTANCE_MIN) { + this._startDistance = DISTANCE_MIN + } this._scalePoints = [...event.targetTouches] this._swiping = false + console.log( + 'is scale event, start =', this._startPos, + 'dist =', this._startDistance) } } move (event) { + // console.log('move called', event) if (isSwipeEvent(event)) { const touch = event.changedTouches[0] const delta = deltaCoord(this._startPos, touchCoord(touch)) this.swipePreviewCallback(delta) } else if (isScaleEvent(event)) { + console.log('is scale event') + const coords = [...event.targetTouches].map(touchCoord) + const curPos = avgCoord(coords) + const curDistance = vectorLength(deltaCoord(coords[0], coords[1])) + const scaling = curDistance / this._startDistance + const posDiff = vectorMinus(curPos, this._startPos) + // const delta = vectorAdd(numProduct((1 - scaling), this._startPos), posDiff) + const delta = posDiff + // console.log( + // 'is scale event, cur =', curPos, + // 'dist =', curDistance, + // 'scale =', scaling, + // 'delta =', delta) + this.pinchPreviewCallback(delta, scaling) } } end (event) { console.log('end() called', event) + if (isScaleEventEnd(event)) { + this.pinchEndCallback() + } + if (!isSwipeEventEnd(event)) { console.log('not swipe event') return -- cgit v1.2.3-70-g09d2 From 29cd8fbd3bd9f936d2639a2edd773e1245b0b5a5 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 19:11:59 -0400 Subject: Add swipe-click handler to media modal Now swiping will correctly change the current media, and with a good preview. Clicking without swiping closes the overlay. --- src/components/media_modal/media_modal.js | 58 ++++------- src/components/media_modal/media_modal.vue | 13 ++- src/components/pinch_zoom/pinch_zoom.js | 5 + src/modules/media_viewer.js | 56 +--------- src/services/gesture_service/gesture_service.js | 130 +++++++++++------------- 5 files changed, 94 insertions(+), 168 deletions(-) (limited to 'src/services') diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 684fbe45..b4c0cfb4 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -2,10 +2,11 @@ import StillImage from '../still-image/still-image.vue' import VideoAttachment from '../video_attachment/video_attachment.vue' import Modal from '../modal/modal.vue' import PinchZoom from '../pinch_zoom/pinch_zoom.vue' -import fileTypeService from '../../services/file_type/file_type.service.js' +import SwipeClick from '../swipe_click/swipe_click.vue' import GestureService from '../../services/gesture_service/gesture_service' import Flash from 'src/components/flash/flash.vue' import Vuex from 'vuex' +import fileTypeService from '../../services/file_type/file_type.service.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronLeft, @@ -28,13 +29,15 @@ const MediaModal = { StillImage, VideoAttachment, PinchZoom, + SwipeClick, Modal, Flash }, data () { return { loading: false, - pinchZoomOptions: {} + swipeDirection: GestureService.DIRECTION_LEFT, + swipeThreshold: 50 } }, computed: { @@ -70,30 +73,19 @@ const MediaModal = { } }, created () { - this.mediaGesture = new GestureService.SwipeAndScaleGesture({ - direction: GestureService.DIRECTION_LEFT, - callbackPositive: this.goNext, - callbackNegative: this.goPrev, - swipePreviewCallback: this.handleSwipePreview, - swipeEndCallback: this.handleSwipeEnd, - pinchPreviewCallback: this.handlePinchPreview, - pinchEndCallback: this.handlePinchEnd, - threshold: 50 - }) + // this.mediaGesture = new GestureService.SwipeAndScaleGesture({ + // callbackPositive: this.goNext, + // callbackNegative: this.goPrev, + // swipePreviewCallback: this.handleSwipePreview, + // swipeEndCallback: this.handleSwipeEnd, + // pinchPreviewCallback: this.handlePinchPreview, + // pinchEndCallback: this.handlePinchEnd + // }) }, methods: { getType (media) { return fileTypeService.fileType(media.mimetype) }, - mediaTouchStart (e) { - this.mediaGesture.start(e) - }, - mediaTouchMove (e) { - this.mediaGesture.move(e) - }, - mediaTouchEnd (e) { - this.mediaGesture.end(e) - }, hide () { this.$store.dispatch('closeMediaViewer') }, @@ -105,6 +97,7 @@ const MediaModal = { this.loading = true } this.$store.dispatch('setCurrentMedia', newMedia) + this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 }) } }, goNext () { @@ -115,40 +108,25 @@ const MediaModal = { this.loading = true } this.$store.dispatch('setCurrentMedia', newMedia) + this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 }) } }, onImageLoaded () { this.loading = false }, handleSwipePreview (offsets) { - this.$store.dispatch('swipeScaler/apply', { - offsets: this.scaling > SCALING_ENABLE_MOVE_THRESHOLD ? offsets : onlyXAxis(offsets) - }) + this.$refs.pinchZoom.setTransform({ scale: 1, x: offsets[0], y: 0 }) }, handleSwipeEnd (sign) { - if (this.scaling > SCALING_ENABLE_MOVE_THRESHOLD) { - this.$store.dispatch('swipeScaler/finish') - return - } + console.log('handleSwipeEnd:', sign) if (sign === 0) { - this.$store.dispatch('swipeScaler/reset') + this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 }) } else if (sign > 0) { this.goNext() } else { this.goPrev() } }, - handlePinchPreview (offsets, scaling) { - console.log('handle pinch preview:', offsets, scaling) - this.$store.dispatch('swipeScaler/apply', { offsets, scaling }) - }, - handlePinchEnd () { - if (this.scaling > SCALING_RESET_MIN) { - this.$store.dispatch('swipeScaler/finish') - } else { - this.$store.dispatch('swipeScaler/reset') - } - }, handleKeyupEvent (e) { if (this.showing && e.keyCode === 27) { // escape this.hide() diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index a3fad4c5..e385024e 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -4,9 +4,16 @@ class="media-modal-view" @backdropClicked="hide" > - + k + offsets[n]) - }, - applyScaling (state, { scaling }) { - state.scaling = state.origScaling * scaling - }, - finish (state) { - state.origOffsets = [...state.offsets] - state.origScaling = state.scaling - }, - revert (state) { - state.offsets = [...state.origOffsets] - state.scaling = state.origScaling - } - }, - - actions: { - reset ({ commit }) { - commit('reset') - }, - apply ({ commit }, { offsets, scaling = 1 }) { - commit('applyOffsets', { offsets }) - commit('applyScaling', { scaling }) - }, - finish ({ commit }) { - commit('finish') - }, - revert ({ commit }) { - commit('revert') - } - } } } } diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index 82337bc6..f10dec3a 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -4,25 +4,27 @@ const DIRECTION_RIGHT = [1, 0] const DIRECTION_UP = [0, -1] const DIRECTION_DOWN = [0, 1] -const DISTANCE_MIN = 1 +// const DISTANCE_MIN = 1 -const isSwipeEvent = e => (e.touches.length === 1) -const isSwipeEventEnd = e => (e.changedTouches.length === 1) +// const isSwipeEvent = e => (e.touches.length === 1) +// const isSwipeEventEnd = e => (e.changedTouches.length === 1) -const isScaleEvent = e => (e.targetTouches.length === 2) -const isScaleEventEnd = e => (e.targetTouches.length === 1) +// const isScaleEvent = e => (e.targetTouches.length === 2) +// const isScaleEventEnd = e => (e.targetTouches.length === 1) const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]] -const vectorMinus = (a, b) => a.map((k, n) => k - b[n]) -const vectorAdd = (a, b) => a.map((k, n) => k + b[n]) +// const vectorMinus = (a, b) => a.map((k, n) => k - b[n]) +// const vectorAdd = (a, b) => a.map((k, n) => k + b[n]) -const avgCoord = (coords) => [...coords].reduce(vectorAdd, [0, 0]).map(d => d / coords.length) +// const avgCoord = (coords) => [...coords].reduce(vectorAdd, [0, 0]).map(d => d / coords.length) const touchCoord = touch => [touch.screenX, touch.screenY] const touchEventCoord = e => touchCoord(e.touches[0]) +const pointerEventCoord = e => [e.clientX, e.clientY] + const vectorLength = v => Math.sqrt(v[0] * v[0] + v[1] * v[1]) const perpendicular = v => [v[1], -v[0]] @@ -78,104 +80,87 @@ const updateSwipe = (event, gesture) => { gesture._swiping = false } -class SwipeAndScaleGesture { +class SwipeAndClickGesture { // swipePreviewCallback(offsets: Array[Number]) // offsets: the offset vector which the underlying component should move, from the starting position - // pinchPreviewCallback(offsets: Array[Number], scaling: Number) - // offsets: the offset vector which the underlying component should move, from the starting position - // scaling: the scaling factor we should apply to the underlying component, from the starting position - // swipeEndcallback(sign: 0|-1|1) + // swipeEndCallback(sign: 0|-1|1) // sign: if the swipe does not meet the threshold, 0 // if the swipe meets the threshold in the positive direction, 1 // if the swipe meets the threshold in the negative direction, -1 constructor ({ direction, - // swipeStartCallback, pinchStartCallback, - swipePreviewCallback, pinchPreviewCallback, - swipeEndCallback, pinchEndCallback, + // swipeStartCallback + swipePreviewCallback, + swipeEndCallback, + swipeCancelCallback, + swipelessClickCallback, threshold = 30, perpendicularTolerance = 1.0 }) { const nop = () => { console.log('Warning: Not implemented') } this.direction = direction this.swipePreviewCallback = swipePreviewCallback || nop - this.pinchPreviewCallback = pinchPreviewCallback || nop this.swipeEndCallback = swipeEndCallback || nop - this.pinchEndCallback = pinchEndCallback || nop + this.swipeCancelCallback = swipeCancelCallback || nop + this.swipelessClickCallback = swipelessClickCallback || nop this.threshold = threshold this.perpendicularTolerance = perpendicularTolerance + this._reset() + } + + _reset () { this._startPos = [0, 0] - this._startDistance = DISTANCE_MIN + this._pointerId = -1 this._swiping = false + this._swiped = false } start (event) { console.log('start() called', event) - if (isSwipeEvent(event)) { - this._startPos = touchEventCoord(event) - console.log('start pos:', this._startPos) - this._swiping = true - } else if (isScaleEvent(event)) { - const coords = [...event.targetTouches].map(touchCoord) - this._startPos = avgCoord(coords) - this._startDistance = vectorLength(deltaCoord(coords[0], coords[1])) - if (this._startDistance < DISTANCE_MIN) { - this._startDistance = DISTANCE_MIN - } - this._scalePoints = [...event.targetTouches] - this._swiping = false - console.log( - 'is scale event, start =', this._startPos, - 'dist =', this._startDistance) - } + + this._startPos = pointerEventCoord(event) + this._pointerId = event.pointerId + console.log('start pos:', this._startPos) + this._swiping = true + this._swiped = false } move (event) { - // console.log('move called', event) - if (isSwipeEvent(event)) { - const touch = event.changedTouches[0] - const delta = deltaCoord(this._startPos, touchCoord(touch)) + if (this._swiping && this._pointerId === event.pointerId) { + this._swiped = true + + const coord = pointerEventCoord(event) + const delta = deltaCoord(this._startPos, coord) this.swipePreviewCallback(delta) - } else if (isScaleEvent(event)) { - console.log('is scale event') - const coords = [...event.targetTouches].map(touchCoord) - const curPos = avgCoord(coords) - const curDistance = vectorLength(deltaCoord(coords[0], coords[1])) - const scaling = curDistance / this._startDistance - const posDiff = vectorMinus(curPos, this._startPos) - // const delta = vectorAdd(numProduct((1 - scaling), this._startPos), posDiff) - const delta = posDiff - // console.log( - // 'is scale event, cur =', curPos, - // 'dist =', curDistance, - // 'scale =', scaling, - // 'delta =', delta) - this.pinchPreviewCallback(delta, scaling) } } - end (event) { - console.log('end() called', event) - if (isScaleEventEnd(event)) { - this.pinchEndCallback() - } - - if (!isSwipeEventEnd(event)) { - console.log('not swipe event') + cancel (event) { + if (!this._swiping || this._pointerId !== event.pointerId) { return } + + this.swipeCancelCallback() + } + + end (event) { if (!this._swiping) { console.log('not swiping') return } - this.swiping = false - console.log('is swipe event') + if (this._pointerId !== event.pointerId) { + console.log('pointer id does not match') + return + } + + this._swiping = false + + console.log('end: is swipe event') // movement too small - const touch = event.changedTouches[0] - const delta = deltaCoord(this._startPos, touchCoord(touch)) - this.swipePreviewCallback(delta) + const coord = pointerEventCoord(event) + const delta = deltaCoord(this._startPos, coord) const sign = (() => { if (vectorLength(delta) < this.threshold) { @@ -198,7 +183,12 @@ class SwipeAndScaleGesture { return isPositive ? 1 : -1 })() - this.swipeEndCallback(sign) + if (this._swiped) { + this.swipeEndCallback(sign) + } else { + this.swipelessClickCallback() + } + this._reset() } } @@ -210,7 +200,7 @@ const GestureService = { swipeGesture, beginSwipe, updateSwipe, - SwipeAndScaleGesture + SwipeAndClickGesture } export default GestureService -- cgit v1.2.3-70-g09d2 From 23a6b86ef3c976509bad4fb4f9a223a5724ec7e5 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 19:21:18 -0400 Subject: Clean up --- src/components/media_modal/media_modal.js | 23 +++---------------- src/components/media_modal/media_modal.vue | 7 +++--- src/services/gesture_service/gesture_service.js | 30 ++++++++----------------- 3 files changed, 15 insertions(+), 45 deletions(-) (limited to 'src/services') diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index b4c0cfb4..2f00f5f9 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -37,7 +37,9 @@ const MediaModal = { return { loading: false, swipeDirection: GestureService.DIRECTION_LEFT, - swipeThreshold: 50 + swipeThreshold: 50, + pinchZoomMinScale: 1, + pinchZoomScaleResetLimit: 1.2 } }, computed: { @@ -62,25 +64,6 @@ const MediaModal = { type () { return this.currentMedia ? this.getType(this.currentMedia) : null }, - scaling () { - return this.$store.state.mediaViewer.swipeScaler.scaling - }, - offsets () { - return this.$store.state.mediaViewer.swipeScaler.offsets - }, - transform () { - return `translate(${this.offsets[0]}px, ${this.offsets[1]}px) scale(${this.scaling}, ${this.scaling})` - } - }, - created () { - // this.mediaGesture = new GestureService.SwipeAndScaleGesture({ - // callbackPositive: this.goNext, - // callbackNegative: this.goPrev, - // swipePreviewCallback: this.handleSwipePreview, - // swipeEndCallback: this.handleSwipeEnd, - // pinchPreviewCallback: this.handlePinchPreview, - // pinchEndCallback: this.handlePinchEnd - // }) }, methods: { getType (media) { diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index e385024e..76cf4319 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -16,12 +16,11 @@ ref="pinchZoom" class="modal-image-container-inner" selector=".modal-image" - allow-pan-min-scale="1" - min-scale="1" - reset-to-min-scale-limit="1.2" reach-min-scale-strategy="reset" stop-propagate-handled="stop-propgate-handled" - :inner-class="'modal-image-container-inner'" + :allow-pan-min-scale="pinchZoomMinScale" + :min-scale="pinchZoomMinScale" + :reset-to-min-scale-limit="pinchZoomScaleResetLimit" > (e.touches.length === 1) -// const isSwipeEventEnd = e => (e.changedTouches.length === 1) - -// const isScaleEvent = e => (e.targetTouches.length === 2) -// const isScaleEventEnd = e => (e.targetTouches.length === 1) - const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]] -// const vectorMinus = (a, b) => a.map((k, n) => k - b[n]) -// const vectorAdd = (a, b) => a.map((k, n) => k + b[n]) - -// const avgCoord = (coords) => [...coords].reduce(vectorAdd, [0, 0]).map(d => d / coords.length) - const touchCoord = touch => [touch.screenX, touch.screenY] const touchEventCoord = e => touchCoord(e.touches[0]) @@ -31,13 +18,14 @@ const perpendicular = v => [v[1], -v[0]] const dotProduct = (v1, v2) => v1[0] * v2[0] + v1[1] * v2[1] -// const numProduct = (num, v) => v.map(k => num * k) - const project = (v1, v2) => { const scalar = (dotProduct(v1, v2) / dotProduct(v2, v2)) return [scalar * v2[0], scalar * v2[1]] } +// const debug = console.log +const debug = () => {} + // direction: either use the constants above or an arbitrary 2d vector. // threshold: how many Px to move from touch origin before checking if the // callback should be called. @@ -96,7 +84,7 @@ class SwipeAndClickGesture { swipelessClickCallback, threshold = 30, perpendicularTolerance = 1.0 }) { - const nop = () => { console.log('Warning: Not implemented') } + const nop = () => { debug('Warning: Not implemented') } this.direction = direction this.swipePreviewCallback = swipePreviewCallback || nop this.swipeEndCallback = swipeEndCallback || nop @@ -115,11 +103,11 @@ class SwipeAndClickGesture { } start (event) { - console.log('start() called', event) + debug('start() called', event) this._startPos = pointerEventCoord(event) this._pointerId = event.pointerId - console.log('start pos:', this._startPos) + debug('start pos:', this._startPos) this._swiping = true this._swiped = false } @@ -145,18 +133,18 @@ class SwipeAndClickGesture { end (event) { if (!this._swiping) { - console.log('not swiping') + debug('not swiping') return } if (this._pointerId !== event.pointerId) { - console.log('pointer id does not match') + debug('pointer id does not match') return } this._swiping = false - console.log('end: is swipe event') + debug('end: is swipe event') // movement too small const coord = pointerEventCoord(event) -- cgit v1.2.3-70-g09d2 From 9f3a983fef9dfedef8f44855e1f939ea944cd0ba Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 20:32:02 -0400 Subject: Use native click for hiding overlay The pointerup strategy is unsuccessful, as some other overlays (Firefox's Inspect Element) will pass down pointerup events. --- src/components/swipe_click/swipe_click.js | 3 +-- src/services/gesture_service/gesture_service.js | 26 ++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) (limited to 'src/services') diff --git a/src/components/swipe_click/swipe_click.js b/src/components/swipe_click/swipe_click.js index ac77154a..b979f42a 100644 --- a/src/components/swipe_click/swipe_click.js +++ b/src/components/swipe_click/swipe_click.js @@ -53,8 +53,7 @@ const SwipeClick = { this.$gesture.cancel(event) }, handleNativeClick (event) { - event.stopPropagation() - event.preventDefault() + this.$gesture.click(event) }, preview (offsets) { this.$emit('preview-requested', offsets) diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index 238b7875..cd9e3ba2 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -4,6 +4,8 @@ const DIRECTION_RIGHT = [1, 0] const DIRECTION_UP = [0, -1] const DIRECTION_DOWN = [0, 1] +const BUTTON_LEFT = 0 + const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]] const touchCoord = touch => [touch.screenX, touch.screenY] @@ -23,8 +25,8 @@ const project = (v1, v2) => { return [scalar * v2[0], scalar * v2[1]] } -// const debug = console.log -const debug = () => {} +const debug = console.log +// const debug = () => {} // direction: either use the constants above or an arbitrary 2d vector. // threshold: how many Px to move from touch origin before checking if the @@ -100,11 +102,17 @@ class SwipeAndClickGesture { this._pointerId = -1 this._swiping = false this._swiped = false + this._preventNextClick = false } start (event) { debug('start() called', event) + // Only handle left click + if (event.button !== BUTTON_LEFT) { + return + } + this._startPos = pointerEventCoord(event) this._pointerId = event.pointerId debug('start pos:', this._startPos) @@ -124,6 +132,7 @@ class SwipeAndClickGesture { } cancel (event) { + debug('cancel called') if (!this._swiping || this._pointerId !== event.pointerId) { return } @@ -146,6 +155,8 @@ class SwipeAndClickGesture { debug('end: is swipe event') + debug('button = ', event.button) + // movement too small const coord = pointerEventCoord(event) const delta = deltaCoord(this._startPos, coord) @@ -171,9 +182,18 @@ class SwipeAndClickGesture { return isPositive ? 1 : -1 })() + const swiped = this._swiped if (this._swiped) { this.swipeEndCallback(sign) - } else { + } + this._reset() + if (swiped) { + this._preventNextClick = true + } + } + + click (event) { + if (!this._preventNextClick) { this.swipelessClickCallback() } this._reset() -- cgit v1.2.3-70-g09d2 From 6980e4ddf1aa8dfd8c3bba0ea6cc7de90f531ba9 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 21:19:04 -0400 Subject: Scale swipe threshold with viewport width --- src/components/media_modal/media_modal.js | 7 +++++-- src/components/swipe_click/swipe_click.js | 4 ++-- src/services/gesture_service/gesture_service.js | 11 +++++++---- 3 files changed, 14 insertions(+), 8 deletions(-) (limited to 'src/services') diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 91413563..6a368508 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -33,7 +33,10 @@ const MediaModal = { return { loading: false, swipeDirection: GestureService.DIRECTION_LEFT, - swipeThreshold: 50, + swipeThreshold: () => { + const considerableMoveRatio = 1 / 4 + return window.innerWidth * considerableMoveRatio + }, pinchZoomMinScale: 1, pinchZoomScaleResetLimit: 1.2 } @@ -104,7 +107,7 @@ const MediaModal = { this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 }) if (sign > 0) { this.goNext() - } else { + } else if (sign < 0) { this.goPrev() } }, diff --git a/src/components/swipe_click/swipe_click.js b/src/components/swipe_click/swipe_click.js index b979f42a..238e6df8 100644 --- a/src/components/swipe_click/swipe_click.js +++ b/src/components/swipe_click/swipe_click.js @@ -31,8 +31,8 @@ const SwipeClick = { type: Array }, threshold: { - type: Number, - default: 30 + type: Function, + default: () => 30 }, perpendicularTolerance: { type: Number, diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index cd9e3ba2..97a26ba7 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -25,8 +25,8 @@ const project = (v1, v2) => { return [scalar * v2[0], scalar * v2[1]] } -const debug = console.log -// const debug = () => {} +// const debug = console.log +const debug = () => {} // direction: either use the constants above or an arbitrary 2d vector. // threshold: how many Px to move from touch origin before checking if the @@ -92,7 +92,7 @@ class SwipeAndClickGesture { this.swipeEndCallback = swipeEndCallback || nop this.swipeCancelCallback = swipeCancelCallback || nop this.swipelessClickCallback = swipelessClickCallback || nop - this.threshold = threshold + this.threshold = typeof threshold === 'function' ? threshold : () => threshold this.perpendicularTolerance = perpendicularTolerance this._reset() } @@ -162,7 +162,10 @@ class SwipeAndClickGesture { const delta = deltaCoord(this._startPos, coord) const sign = (() => { - if (vectorLength(delta) < this.threshold) { + debug( + 'threshold = ', this.threshold(), + 'vector len =', vectorLength(delta)) + if (vectorLength(delta) < this.threshold()) { return 0 } // movement is opposite from direction -- cgit v1.2.3-70-g09d2 From a485ebc2bb120e7cc6eafb7d86a5f876608f7fe1 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 22:08:04 -0400 Subject: Check whether we swiped only for mouse pointer --- src/services/gesture_service/gesture_service.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/services') diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index 97a26ba7..741d4a12 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -190,7 +190,11 @@ class SwipeAndClickGesture { this.swipeEndCallback(sign) } this._reset() - if (swiped) { + // Only a mouse will fire click event when + // the end point is far from the starting point + // so for other kinds of pointers do not check + // whether we have swiped + if (swiped && event.pointerType === 'mouse') { this._preventNextClick = true } } -- cgit v1.2.3-70-g09d2 From 5829cd98af60b758ee3a39c35f76f4da95a630c3 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 8 Sep 2021 21:26:59 -0400 Subject: Clean up debug code for image pinch zoom --- src/components/media_modal/media_modal.js | 1 - src/services/gesture_service/gesture_service.js | 18 +----------------- 2 files changed, 1 insertion(+), 18 deletions(-) (limited to 'src/services') diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 6a368508..4ed0b66f 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -103,7 +103,6 @@ const MediaModal = { this.$refs.pinchZoom.setTransform({ scale: 1, x: offsets[0], y: 0 }) }, handleSwipeEnd (sign) { - console.log('handleSwipeEnd:', sign) this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 }) if (sign > 0) { this.goNext() diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index 741d4a12..94da3f43 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -25,9 +25,6 @@ const project = (v1, v2) => { return [scalar * v2[0], scalar * v2[1]] } -// const debug = console.log -const debug = () => {} - // direction: either use the constants above or an arbitrary 2d vector. // threshold: how many Px to move from touch origin before checking if the // callback should be called. @@ -86,7 +83,7 @@ class SwipeAndClickGesture { swipelessClickCallback, threshold = 30, perpendicularTolerance = 1.0 }) { - const nop = () => { debug('Warning: Not implemented') } + const nop = () => {} this.direction = direction this.swipePreviewCallback = swipePreviewCallback || nop this.swipeEndCallback = swipeEndCallback || nop @@ -106,8 +103,6 @@ class SwipeAndClickGesture { } start (event) { - debug('start() called', event) - // Only handle left click if (event.button !== BUTTON_LEFT) { return @@ -115,7 +110,6 @@ class SwipeAndClickGesture { this._startPos = pointerEventCoord(event) this._pointerId = event.pointerId - debug('start pos:', this._startPos) this._swiping = true this._swiped = false } @@ -132,7 +126,6 @@ class SwipeAndClickGesture { } cancel (event) { - debug('cancel called') if (!this._swiping || this._pointerId !== event.pointerId) { return } @@ -142,29 +135,20 @@ class SwipeAndClickGesture { end (event) { if (!this._swiping) { - debug('not swiping') return } if (this._pointerId !== event.pointerId) { - debug('pointer id does not match') return } this._swiping = false - debug('end: is swipe event') - - debug('button = ', event.button) - // movement too small const coord = pointerEventCoord(event) const delta = deltaCoord(this._startPos, coord) const sign = (() => { - debug( - 'threshold = ', this.threshold(), - 'vector len =', vectorLength(delta)) if (vectorLength(delta) < this.threshold()) { return 0 } -- cgit v1.2.3-70-g09d2 From 7dd1a0dd30773fd51508feaf93be53bdb744ec79 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 20 Feb 2022 22:45:58 -0500 Subject: Prevent hiding media viewer if swiped over SwipeClick --- src/components/media_modal/media_modal.js | 9 +++++++++ src/components/media_modal/media_modal.vue | 3 ++- src/services/gesture_service/gesture_service.js | 8 +++++--- 3 files changed, 16 insertions(+), 4 deletions(-) (limited to 'src/services') diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 4ed0b66f..0c19ed50 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -76,6 +76,15 @@ const MediaModal = { this.$store.dispatch('closeMediaViewer') }, transitionTime) }, + hideIfNotSwiped (event) { + // If we have swiped over SwipeClick, do not trigger hide + const comp = this.$refs.swipeClick + if (!comp) { + this.hide() + } else { + comp.$gesture.click(event) + } + }, goPrev () { if (this.canNavigate) { const prevIndex = this.currentIndex === 0 ? this.media.length - 1 : (this.currentIndex - 1) diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index ff9c4953..1aa66a55 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -2,10 +2,11 @@ {} this.direction = direction @@ -90,6 +92,7 @@ class SwipeAndClickGesture { this.swipeCancelCallback = swipeCancelCallback || nop this.swipelessClickCallback = swipelessClickCallback || nop this.threshold = typeof threshold === 'function' ? threshold : () => threshold + this.disableClickThreshold = typeof disableClickThreshold === 'function' ? disableClickThreshold : () => disableClickThreshold this.perpendicularTolerance = perpendicularTolerance this._reset() } @@ -169,7 +172,6 @@ class SwipeAndClickGesture { return isPositive ? 1 : -1 })() - const swiped = this._swiped if (this._swiped) { this.swipeEndCallback(sign) } @@ -178,7 +180,7 @@ class SwipeAndClickGesture { // the end point is far from the starting point // so for other kinds of pointers do not check // whether we have swiped - if (swiped && event.pointerType === 'mouse') { + if (vectorLength(delta) >= this.disableClickThreshold() && event.pointerType === 'mouse') { this._preventNextClick = true } } -- cgit v1.2.3-70-g09d2 From f21dc21a83cbcf4c502b8fbf56cabac797a0b9da Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 28 Mar 2022 23:55:26 +0300 Subject: properly implement resettableAsyncComponent --- .../async_component_error.vue | 1 + src/components/settings_modal/settings_modal.js | 4 ++-- src/services/resettable_async_component.js | 23 ++++++++++------------ 3 files changed, 13 insertions(+), 15 deletions(-) (limited to 'src/services') diff --git a/src/components/async_component_error/async_component_error.vue b/src/components/async_component_error/async_component_error.vue index b1b59638..26ab5d21 100644 --- a/src/components/async_component_error/async_component_error.vue +++ b/src/components/async_component_error/async_component_error.vue @@ -19,6 +19,7 @@ + + diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js index dd45b6b9..9508a707 100644 --- a/src/components/extra_buttons/extra_buttons.js +++ b/src/components/extra_buttons/extra_buttons.js @@ -71,6 +71,19 @@ const ExtraButtons = { }, reportStatus () { this.$store.dispatch('openUserReportingModal', { userId: this.status.user.id, statusIds: [this.status.id] }) + }, + editStatus () { + this.$store.dispatch('fetchStatusSource', { id: this.status.id }) + .then(data => this.$store.dispatch('openEditStatusModal', { + statusId: this.status.id, + subject: data.spoiler_text, + statusText: data.text, + statusIsSensitive: this.status.nsfw, + statusPoll: this.status.poll, + statusFiles: this.status.attachments, + visibility: this.status.visibility, + statusContentType: data.content_type + })) } }, computed: { diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue index a3c3c767..8e90ee27 100644 --- a/src/components/extra_buttons/extra_buttons.vue +++ b/src/components/extra_buttons/extra_buttons.vue @@ -73,6 +73,17 @@ icon="bookmark" />{{ $t("status.unbookmark") }} + +
- - @{{ user.screen_name_ui }} - + :user="user" + />
diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js index 5ba3907f..b664d6b3 100644 --- a/src/components/emoji_input/emoji_input.js +++ b/src/components/emoji_input/emoji_input.js @@ -1,5 +1,6 @@ import Completion from '../../services/completion/completion.js' import EmojiPicker from '../emoji_picker/emoji_picker.vue' +import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue' import { take } from 'lodash' import { findOffset } from '../../services/offset_finder/offset_finder.service.js' @@ -120,7 +121,8 @@ const EmojiInput = { } }, components: { - EmojiPicker + EmojiPicker, + UnicodeDomainIndicator }, computed: { padEmoji () { diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue index 7d95ab7e..81b81913 100644 --- a/src/components/emoji_input/emoji_input.vue +++ b/src/components/emoji_input/emoji_input.vue @@ -50,7 +50,21 @@ {{ suggestion.replacement }}
- {{ suggestion.displayText }} + + {{ suggestion.displayText }} + + + {{ suggestion.displayText }} + {{ suggestion.detailText }}
diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js index e8efbd1e..0ddb4d68 100644 --- a/src/components/emoji_input/suggestor.js +++ b/src/components/emoji_input/suggestor.js @@ -116,11 +116,12 @@ export const suggestUsers = ({ dispatch, state }) => { return diff + nameAlphabetically + screenNameAlphabetically /* eslint-disable camelcase */ - }).map(({ screen_name, screen_name_ui, name, profile_image_url_original }) => ({ - displayText: screen_name_ui, - detailText: name, - imageUrl: profile_image_url_original, - replacement: '@' + screen_name + ' ' + }).map((user) => ({ + user, + displayText: user.screen_name_ui, + detailText: user.name, + imageUrl: user.profile_image_url_original, + replacement: '@' + user.screen_name + ' ' })) /* eslint-enable camelcase */ diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js index 4a74fbe2..6515bd11 100644 --- a/src/components/mention_link/mention_link.js +++ b/src/components/mention_link/mention_link.js @@ -2,6 +2,7 @@ import generateProfileLink from 'src/services/user_profile_link_generator/user_p import { mapGetters, mapState } from 'vuex' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import UserAvatar from '../user_avatar/user_avatar.vue' +import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue' import { defineAsyncComponent } from 'vue' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -16,6 +17,7 @@ const MentionLink = { name: 'MentionLink', components: { UserAvatar, + UnicodeDomainIndicator, UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue')) }, props: { diff --git a/src/components/mention_link/mention_link.vue b/src/components/mention_link/mention_link.vue index 3af502ef..869a3257 100644 --- a/src/components/mention_link/mention_link.vue +++ b/src/components/mention_link/mention_link.vue @@ -47,6 +47,9 @@ class="serverName" :class="{ '-faded': shouldFadeDomain }" v-html="'@' + serverName" + /> - - {{ notification.from_profile.screen_name_ui }} - +
- - @{{ user.screen_name_ui }} - + :user="user" + /> + + diff --git a/src/components/user_list_popover/user_list_popover.js b/src/components/user_list_popover/user_list_popover.js index e24eb9f7..046e0abd 100644 --- a/src/components/user_list_popover/user_list_popover.js +++ b/src/components/user_list_popover/user_list_popover.js @@ -1,5 +1,6 @@ import { defineAsyncComponent } from 'vue' import RichContent from 'src/components/rich_content/rich_content.jsx' +import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' @@ -15,6 +16,7 @@ const UserListPopover = { ], components: { RichContent, + UnicodeDomainIndicator, Popover: defineAsyncComponent(() => import('../popover/popover.vue')), UserAvatar: defineAsyncComponent(() => import('../user_avatar/user_avatar.vue')) }, diff --git a/src/components/user_list_popover/user_list_popover.vue b/src/components/user_list_popover/user_list_popover.vue index a3ce54c3..635dc7f6 100644 --- a/src/components/user_list_popover/user_list_popover.vue +++ b/src/components/user_list_popover/user_list_popover.vue @@ -29,7 +29,7 @@ :emoji="user.emoji" /> - {{ user.screen_name_ui }} + {{ user.screen_name_ui }}
diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js index 85ffc661..67fde084 100644 --- a/src/components/user_reporting_modal/user_reporting_modal.js +++ b/src/components/user_reporting_modal/user_reporting_modal.js @@ -2,13 +2,15 @@ import Status from '../status/status.vue' import List from '../list/list.vue' import Checkbox from '../checkbox/checkbox.vue' import Modal from '../modal/modal.vue' +import UserLink from '../user_link/user_link.vue' const UserReportingModal = { components: { Status, List, Checkbox, - Modal + Modal, + UserLink }, data () { return { diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue index 429a66e2..8c42ab7b 100644 --- a/src/components/user_reporting_modal/user_reporting_modal.vue +++ b/src/components/user_reporting_modal/user_reporting_modal.vue @@ -5,9 +5,13 @@ >
-
- {{ $t('user_reporting.title', [user.screen_name_ui]) }} -
+ + +
diff --git a/src/i18n/en.json b/src/i18n/en.json index 91722d9a..4ce56678 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1006,5 +1006,8 @@ "update_changelog": "For more details on what's changed, see {theFullChangelog}.", "update_changelog_here": "the full changelog", "art_by": "Art by {linkToArtist}" + }, + "unicode_domain_indicator": { + "tooltip": "This domain contains non-ascii characters." } } diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index e9cbcfe6..ce316832 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -214,12 +214,14 @@ export const parseUser = (data) => { output.screen_name_ui = output.screen_name if (output.screen_name && output.screen_name.includes('@')) { const parts = output.screen_name.split('@') - let unicodeDomain = punycode.toUnicode(parts[1]) + const unicodeDomain = punycode.toUnicode(parts[1]) if (unicodeDomain !== parts[1]) { // Add some identifier so users can potentially spot spoofing attempts: // lain.com and xn--lin-6cd.com would appear identical otherwise. - unicodeDomain = '🌏' + unicodeDomain + output.screen_name_ui_contains_non_ascii = true output.screen_name_ui = [parts[0], unicodeDomain].join('@') + } else { + output.screen_name_ui_contains_non_ascii = false } } diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js index 98bb05a8..3923596b 100644 --- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js +++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js @@ -269,7 +269,8 @@ describe('API Entities normalizer', () => { it('converts IDN to unicode and marks it as internatonal', () => { const user = makeMockUserMasto({ acct: 'lain@xn--lin-6cd.com' }) - expect(parseUser(user)).to.have.property('screen_name_ui').that.equal('lain@🌏lаin.com') + expect(parseUser(user)).to.have.property('screen_name_ui').that.equal('lain@lаin.com') + expect(parseUser(user)).to.have.property('screen_name_ui_contains_non_ascii').that.equal(true) }) }) -- cgit v1.2.3-70-g09d2 From 12d8d1711bb41b14c35914cb82a6d5f41943e198 Mon Sep 17 00:00:00 2001 From: Sean King Date: Thu, 15 Sep 2022 22:02:58 -0600 Subject: Added support for removing users from followers --- src/components/account_actions/account_actions.js | 3 +++ src/components/account_actions/account_actions.vue | 7 +++++++ src/i18n/en.json | 1 + src/modules/users.js | 8 ++++++++ src/services/api/api.service.js | 9 +++++++++ 5 files changed, 28 insertions(+) (limited to 'src/services') diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js index 735dd81c..c23407f9 100644 --- a/src/components/account_actions/account_actions.js +++ b/src/components/account_actions/account_actions.js @@ -36,6 +36,9 @@ const AccountActions = { unblockUser () { this.$store.dispatch('unblockUser', this.user.id) }, + removeUserFromFollowers () { + this.$store.dispatch('removeUserFromFollowers', this.user.id) + }, reportUser () { this.$store.dispatch('openUserReportingModal', { userId: this.user.id }) }, diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue index 770740e0..9bcde9fe 100644 --- a/src/components/account_actions/account_actions.vue +++ b/src/components/account_actions/account_actions.vue @@ -29,6 +29,13 @@ /> +
+
+
+
+

{{ $t('announcements.post_form_header') }}

+
+
+