diff --git a/README.md b/README.md index 58703ff5..06b1dfb7 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,11 @@ This is a fork of Pleroma-FE from the Pleroma project, with support for new Akko # For Translators -To translate Pleroma-FE, add your language to [src/i18n/messages.js](https://akkoma.dev/AkkomaGang/pleroma-fe/src/branch/develop/src/i18n/messages.js). Pleroma-FE will set your language by your browser locale, but you can temporarily force it in the code by changing the locale in main.js. +The [Weblate UI](https://translate.akkoma.dev/projects/akkoma/pleroma-fe/) is recommended for adding or modifying translations for Pleroma-FE. + +Alternatively, edit/create `src/i18n/$LANGUAGE_CODE.json` (where `$LANGUAGE_CODE` is the [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) for your language), then add your language to [src/i18n/messages.js](https://akkoma.dev/AkkomaGang/pleroma-fe/src/branch/develop/src/i18n/messages.js) if it doesn't already exist there. + +Pleroma-FE will set your language by your browser locale, but you can temporarily force it in the code by changing the locale in main.js. # FOR ADMINS diff --git a/package.json b/package.json index e93692eb..727d7602 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,7 @@ "js-cookie": "^3.0.1", "localforage": "1.10.0", "marked": "^4.0.17", - "marked-mfm": "^0.4.0", - "mfm-js": "^0.22.1", + "marked-mfm": "^0.5.0", "parse-link-header": "1.0.1", "phoenix": "1.6.2", "punycode.js": "2.1.0", diff --git a/src/components/notification/notification.scss b/src/components/notification/notification.scss index 54cd5b58..b0da18f9 100644 --- a/src/components/notification/notification.scss +++ b/src/components/notification/notification.scss @@ -2,7 +2,8 @@ .notification-reaction-emoji { width: 40px; - display: flex; + display: inline-flex; + vertical-align: middle; flex-direction: column; } diff --git a/src/components/poll/poll_form.vue b/src/components/poll/poll_form.vue index b6da1c5f..146754db 100644 --- a/src/components/poll/poll_form.vue +++ b/src/components/poll/poll_form.vue @@ -84,7 +84,7 @@ :key="unit" :value="unit" > - {{ $t(`time.unit.${unit}_short`, ['']) }} + {{ $tc(`time.unit.${unit}_short`, expiryAmount, ['']) }} diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 19504169..4e59e430 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -56,6 +56,7 @@ const pxStringToNumber = (str) => { const PostStatusForm = { props: [ 'replyTo', + 'quoteId', 'repliedUser', 'attentions', 'copyMessageScope', @@ -99,12 +100,12 @@ const PostStatusForm = { this.updateIdempotencyKey() this.resize(this.$refs.textarea) - if (this.replyTo) { + if (this.replyTo || this.quoteId) { const textLength = this.$refs.textarea.value.length this.$refs.textarea.setSelectionRange(textLength, textLength) } - if (this.replyTo || this.autoFocus) { + if (this.replyTo || this.quoteId || this.autoFocus) { this.$refs.textarea.focus() } }, @@ -112,7 +113,7 @@ const PostStatusForm = { const preset = this.$route.query.message let statusText = preset || '' - if (this.replyTo) { + if (this.replyTo || this.quoteId) { const currentUser = this.$store.state.users.currentUser statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser) } @@ -314,6 +315,7 @@ const PostStatusForm = { media: newStatus.files, store: this.$store, inReplyToStatusId: this.replyTo, + quoteId: this.quoteId, contentType: newStatus.contentType, poll, idempotencyKey: this.idempotencyKey @@ -347,6 +349,7 @@ const PostStatusForm = { media: [], store: this.$store, inReplyToStatusId: this.replyTo, + quoteId: this.quoteId, contentType: newStatus.contentType, poll: {}, preview: true diff --git a/src/components/quote_button/quote_button.js b/src/components/quote_button/quote_button.js new file mode 100644 index 00000000..f5bf7e3a --- /dev/null +++ b/src/components/quote_button/quote_button.js @@ -0,0 +1,16 @@ +import { library } from '@fortawesome/fontawesome-svg-core' +import { faQuoteLeft } from '@fortawesome/free-solid-svg-icons' + +library.add(faQuoteLeft) + +const QuoteButton = { + name: 'QuoteButton', + props: ['status', 'quoting', 'visibility'], + computed: { + loggedIn () { + return !!this.$store.state.users.currentUser + } + } +} + +export default QuoteButton diff --git a/src/components/quote_button/quote_button.vue b/src/components/quote_button/quote_button.vue new file mode 100644 index 00000000..4367f495 --- /dev/null +++ b/src/components/quote_button/quote_button.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/src/components/quote_card/quote_card.js b/src/components/quote_card/quote_card.js new file mode 100644 index 00000000..48fdbe42 --- /dev/null +++ b/src/components/quote_card/quote_card.js @@ -0,0 +1,24 @@ +import { mapGetters } from 'vuex' +import QuoteCardContent from '../quote_card_content/quote_card_content.vue' + +const QuoteCard = { + name: 'QuoteCard', + props: [ + 'status' + ], + data () { + return { + imageLoaded: false + } + }, + computed: { + ...mapGetters([ + 'mergedConfig' + ]) + }, + components: { + QuoteCardContent + } +} + +export default QuoteCard diff --git a/src/components/quote_card/quote_card.vue b/src/components/quote_card/quote_card.vue new file mode 100644 index 00000000..7f114938 --- /dev/null +++ b/src/components/quote_card/quote_card.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/src/components/quote_card_content/quote_card_content.vue b/src/components/quote_card_content/quote_card_content.vue new file mode 100644 index 00000000..c5950547 --- /dev/null +++ b/src/components/quote_card_content/quote_card_content.vue @@ -0,0 +1,22 @@ + + + diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue index 97046ff0..7160b1fd 100644 --- a/src/components/settings_modal/tabs/filtering_tab.vue +++ b/src/components/settings_modal/tabs/filtering_tab.vue @@ -37,6 +37,15 @@ {{ $t('settings.hide_muted_posts') }} +
  • + + {{ $t('settings.hide_threads_with_blocked_users') }} + +
  • diff --git a/src/components/settings_modal/tabs/version_tab.js b/src/components/settings_modal/tabs/version_tab.js index ce0b4d35..d69b131d 100644 --- a/src/components/settings_modal/tabs/version_tab.js +++ b/src/components/settings_modal/tabs/version_tab.js @@ -1,7 +1,7 @@ import { extractCommit } from 'src/services/version/version.service' const pleromaFeCommitUrl = 'https://akkoma.dev/AkkomaGang/pleroma-fe/commit/' -const pleromaBeCommitUrl = 'https://akkoma.dev/AkkomaGang/akkoma/commits/' +const pleromaBeCommitUrl = 'https://akkoma.dev/AkkomaGang/akkoma/commit/' const VersionTab = { data () { diff --git a/src/components/status/status.js b/src/components/status/status.js index d1339652..2959c3fd 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -1,4 +1,5 @@ import ReplyButton from '../reply_button/reply_button.vue' +import QuoteButton from '../quote_button/quote_button.vue' import FavoriteButton from '../favorite_button/favorite_button.vue' import ReactButton from '../react_button/react_button.vue' import RetweetButton from '../retweet_button/retweet_button.vue' @@ -115,7 +116,8 @@ const Status = { StatusContent, RichContent, MentionLink, - MentionsLine + MentionsLine, + QuoteButton }, props: [ 'statusoid', @@ -145,6 +147,8 @@ const Status = { 'controlledToggleShowingLongSubject', 'controlledReplying', 'controlledToggleReplying', + 'controlledQuoting', + 'controlledToggleQuoting', 'controlledMediaPlaying', 'controlledSetMediaPlaying', 'dive' @@ -152,6 +156,7 @@ const Status = { data () { return { uncontrolledReplying: false, + uncontrolledQuoting: false, unmuted: false, userExpanded: false, uncontrolledMediaPlaying: [], @@ -161,7 +166,7 @@ const Status = { } }, computed: { - ...controlledOrUncontrolledGetters(['replying', 'mediaPlaying']), + ...controlledOrUncontrolledGetters(['replying', 'quoting', 'mediaPlaying']), muteWords () { return this.mergedConfig.muteWords }, @@ -256,6 +261,38 @@ const Status = { hasMentionsLine () { return this.mentionsLine.length > 0 }, + mentionsBlockedUser () { + // XXX: doesn't work on domain blocks, because users from blocked domains + // don't appear in `attentions' and therefore cannot be filtered. + let mentions = false + + // find if user in mentions list is blocked + this.status.attentions.forEach((attn) => { + if (attn.id === this.currentUser.id) return + const relationship = this.$store.getters.relationship(attn.id) + if (relationship.blocking) { + mentions = true + } + }) + + return mentions + }, + mentionsMutedUser () { + // XXX: doesn't work on domain blocks, because users from blocked domains + // don't appear in `attentions' and therefore cannot be filtered. + let mentions = false + + // find if user in mentions list is blocked + this.status.attentions.forEach((attn) => { + if (attn.id === this.currentUser.id) return + const relationship = this.$store.getters.relationship(attn.id) + if (relationship.muting) { + mentions = true + } + }) + + return mentions + }, muted () { if (this.statusoid.user.id === this.currentUser.id) return false const reasonsToMute = this.userIsMuted || @@ -264,7 +301,11 @@ const Status = { // Wordfiltered this.muteWordHits.length > 0 || // bot status - (this.muteBotStatuses && this.botStatus && !this.compact) + (this.muteBotStatuses && this.botStatus && !this.compact) || + // mentions blocked user + this.mentionsBlockedUser || + // mentions muted user + this.mentionsMutedUser return !this.unmuted && !this.shouldNotMute && reasonsToMute }, userIsMuted () { @@ -307,6 +348,9 @@ const Status = { hideFilteredStatuses () { return this.mergedConfig.hideFilteredStatuses }, + hideThreadsWithBlockedUsers () { + return this.mergedConfig.hideThreadsWithBlockedUsers + }, hideWordFilteredPosts () { return this.mergedConfig.hideWordFilteredPosts }, @@ -314,8 +358,9 @@ const Status = { return (!this.shouldNotMute) && ( (this.muted && this.hideFilteredStatuses) || (this.userIsMuted && this.hideMutedUsers) || - (this.status.thread_muted && this.hideMutedThreads) || - (this.muteWordHits.length > 0 && this.hideWordFilteredPosts) + ((this.status.thread_muted || this.mentionsMutedUser) && this.hideMutedThreads) || + (this.muteWordHits.length > 0 && this.hideWordFilteredPosts) || + (this.mentionsBlockedUser && this.hideThreadsWithBlockedUsers) ) }, isFocused () { @@ -418,6 +463,9 @@ const Status = { toggleReplying () { controlledOrUncontrolledToggle(this, 'replying') }, + toggleQuoting () { + controlledOrUncontrolledToggle(this, 'quoting') + }, gotoOriginal (id) { if (this.inConversation) { this.$emit('goto', id) diff --git a/src/components/status/status.scss b/src/components/status/status.scss index b3ad3818..cc9d4eb7 100644 --- a/src/components/status/status.scss +++ b/src/components/status/status.scss @@ -101,6 +101,10 @@ .status-heading { margin-bottom: 0.5em; + + .emoji { + --emoji-size: 16px; + } } .heading-name-row { @@ -355,6 +359,15 @@ flex: 1; } + .quote-form { + padding-top: 0; + padding-bottom: 0; + } + + .quote-body { + flex: 1; + } + .favs-repeated-users { margin-top: var(--status-margin, $status-margin); } diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 67ce999a..6c80e293 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -430,6 +430,12 @@ :status="status" @toggle="toggleReplying" /> + +
    + +
    diff --git a/src/components/status_body/status_body.scss b/src/components/status_body/status_body.scss index 8a5598b0..b1d9ecc9 100644 --- a/src/components/status_body/status_body.scss +++ b/src/components/status_body/status_body.scss @@ -6,9 +6,12 @@ .emoji { --_still_image-label-scale: 0.5; + --emoji-size: 38px; + } - width: 50px; - height: 50px; + .emoji:hover { + transform: scale(1.4); + transition: 0.05s; } ._mfm_x2_ { @@ -94,7 +97,7 @@ overflow-y: hidden; z-index: 1; - .media-body { + .media-body-wrapper { min-height: 0; mask: linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat, @@ -154,16 +157,19 @@ --emoji-size: 16px; - & .body, + & .body:not(:active), & .attachments { max-height: 3.25em; } .body { - overflow: hidden; white-space: normal; min-width: 5em; flex: 5 1 auto; + } + + .body:not(:active) { + overflow: hidden; mask-size: auto 3.5em, auto auto; mask-position: 0 0, 0 0; mask-repeat: repeat-x, repeat; diff --git a/src/components/status_body/status_body.vue b/src/components/status_body/status_body.vue index 4a44fbb7..321f3c4b 100644 --- a/src/components/status_body/status_body.vue +++ b/src/components/status_body/status_body.vue @@ -43,6 +43,7 @@
    - +
    + +