diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eb5a9cb4..d3b899d25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,50 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## 2.11 +### Added +- Initial MFM rendering support +- Button to remove all drafts +- Option to remove forced aspect ratio for user profiles (requested) +- Showing user tags (MRF policies for user + custom if present) +- Version information now is also in about page +- Mention autosuggest now sorts by recent activity +- Non-square emoji support (toggleable by user) +- Displaying other user's backgrounds (if supported by BE) +- Add quoting by URL and in replies +- Settings synchronization +- User highlight synchronization +- User administration + post scope/sensitivity admin change support + +### Changed +- Migrated to Vite 8 and optimized our imports, more stuff is loaded on-demand, reducing the initial load time and transfer size +- Overall improved spacing in status action buttons and post form +- Logout confirm button is now dangerous +- Reply/quote now is a radio group and wraps, fixes overflow on languages where labels are too wide +- Personal note input is now bigger +- Moved "edit pinned" to the bottom for status action buttons. +- Dots status action button drops down instead of up to avoid hiding the action buttons +- Improved attachment description (alt text) input and display + +### Fixed +- Fix HTML attribute parsing for escaped quotes +- Fix emojis breaking user bio/description editing +- Navbar wide logo cropping search input +- Danger buttons being too bright +- User background upload failure no longer breaks new uploads + displays an error +- Importing theme from old theme editor +- Removed duplicate federationpolicy entry in admin tab +- Repeater name overflowing content +- Reply popover is now shown if replied-to status is muted +- Second language input not having header +- Post form's bottom left buttons not showing their toggled state +- Some font overrides not working +- Popovers opening outside of window's boundaries +- Occasional blank page when showing new posts +- Fixed status action mute hiding itself on click +- Fix reply form crash when quote-reply settings are unavailable + + ## 2.10.1 ### Fixed - fixed being unable to set actor type from profile page diff --git a/changelog.d/attrs-parsing.fix b/changelog.d/attrs-parsing.fix deleted file mode 100644 index e36e59a86..000000000 --- a/changelog.d/attrs-parsing.fix +++ /dev/null @@ -1 +0,0 @@ -Fix HTML attribute parsing for escaped quotes \ No newline at end of file diff --git a/changelog.d/ci-pr-uploads-removal.skip b/changelog.d/ci-pr-uploads-removal.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/fast.change b/changelog.d/fast.change deleted file mode 100644 index 1f0a89092..000000000 --- a/changelog.d/fast.change +++ /dev/null @@ -1 +0,0 @@ -Migrated to Vite 8 and optimized our imports, more stuff is loaded on-demand, reducing the initial load time and transfer size diff --git a/changelog.d/fix-emojis-breaking-bio.fix b/changelog.d/fix-emojis-breaking-bio.fix deleted file mode 100644 index 62a607d8a..000000000 --- a/changelog.d/fix-emojis-breaking-bio.fix +++ /dev/null @@ -1 +0,0 @@ -Fix emojis breaking user bio/description editing diff --git a/changelog.d/instance-store-migration.skip b/changelog.d/instance-store-migration.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/minor.add b/changelog.d/minor.add deleted file mode 100644 index 5f4934173..000000000 --- a/changelog.d/minor.add +++ /dev/null @@ -1,6 +0,0 @@ -button to remove all drafts -option to remove forced aspect ratio for user profiles (requested) -showing user tags (mrf policies for user + custom if present) -version information now is also in about page -mention autosuggest now sorts by recent activity -non-square emoji support (toggleable by user) diff --git a/changelog.d/minor.change b/changelog.d/minor.change deleted file mode 100644 index 979d36955..000000000 --- a/changelog.d/minor.change +++ /dev/null @@ -1,7 +0,0 @@ -overall improved spacings in status action buttons and post form -logout confirm button is now dangerous -reply/quote now is a radio group and wraps, fixes overflow on languages where labels are too wide -personal note input is now bigger -moved "edit pinned" to the bottom for status action buttons. -dots status action button drops down instead of up to avoid hiding the action buttons -improved attachment description (alt text) input and display diff --git a/changelog.d/minor.fix b/changelog.d/minor.fix deleted file mode 100644 index 420836364..000000000 --- a/changelog.d/minor.fix +++ /dev/null @@ -1,12 +0,0 @@ -navbar wide logo cropping search input -danger buttons being too bright -user background upload failure no longer breaks new uploads + displays an error -importing theme from old theme editor -removed duplicate federationpolicy entry in admin tab -repeater name overflowing content -reply popover is now shown if replied-to status is muted -second language input not having header -post form's bottom left buttons not showing their toggled state -some font overrides not working -popovers opening outside of window's boundaries -occasional blank page when showing new posts diff --git a/changelog.d/more-fixes.skip b/changelog.d/more-fixes.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/mute-dropdown.fix b/changelog.d/mute-dropdown.fix deleted file mode 100644 index 33f12a571..000000000 --- a/changelog.d/mute-dropdown.fix +++ /dev/null @@ -1 +0,0 @@ -Fixed status action mute hiding itself on click diff --git a/changelog.d/profilebg.add b/changelog.d/profilebg.add deleted file mode 100644 index a2c79074a..000000000 --- a/changelog.d/profilebg.add +++ /dev/null @@ -1 +0,0 @@ -displaying other user's backgrounds (if supported by BE) diff --git a/changelog.d/quote-by-url.add b/changelog.d/quote-by-url.add deleted file mode 100644 index ef401f93c..000000000 --- a/changelog.d/quote-by-url.add +++ /dev/null @@ -1 +0,0 @@ -Add quoting by URL and in replies diff --git a/changelog.d/api-refactor.skip b/changelog.d/release-fixes.skip similarity index 100% rename from changelog.d/api-refactor.skip rename to changelog.d/release-fixes.skip diff --git a/changelog.d/reply-quote-config.fix b/changelog.d/reply-quote-config.fix deleted file mode 100644 index b6ac4e5e9..000000000 --- a/changelog.d/reply-quote-config.fix +++ /dev/null @@ -1 +0,0 @@ -Fix reply form crash when quote-reply settings are unavailable diff --git a/changelog.d/sync-config.add b/changelog.d/sync-config.add deleted file mode 100644 index 76a5a1cca..000000000 --- a/changelog.d/sync-config.add +++ /dev/null @@ -1,2 +0,0 @@ -settings synchronization -user highlight synchronization diff --git a/changelog.d/user-management.add b/changelog.d/user-management.add deleted file mode 100644 index ccd217f19..000000000 --- a/changelog.d/user-management.add +++ /dev/null @@ -1 +0,0 @@ -User administration + post scope/sensitivity admin change support diff --git a/changelog.d/woodpecker-pr-pipeline.skip b/changelog.d/woodpecker-pr-pipeline.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/package.json b/package.json index f594d7a99..1d94ac22f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pleroma_fe", - "version": "2.10.1", + "version": "2.11.0", "description": "Pleroma frontend, the default frontend of Pleroma social network server", "author": "Pleroma contributors ", "private": false, diff --git a/src/boot/routes.js b/src/boot/routes.js index 4f21246e5..0dfb1e744 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -1,5 +1,6 @@ import { defineAsyncComponent } from 'vue' +import AuthForm from 'src/components/auth_form/auth_form.js' import BookmarkTimeline from 'src/components/bookmark_timeline/bookmark_timeline.vue' import BubbleTimeline from 'src/components/bubble_timeline/bubble_timeline.vue' import ConversationPage from 'src/components/conversation-page/conversation-page.vue' @@ -147,9 +148,7 @@ export default (store) => { { name: 'login', path: '/login', - component: defineAsyncComponent( - () => import('src/components/auth_form/auth_form.js'), - ), + component: AuthForm, }, { name: 'shout-panel', diff --git a/src/components/basic_user_card/basic_user_card.js b/src/components/basic_user_card/basic_user_card.js index ae1f1c9c6..27e275bfb 100644 --- a/src/components/basic_user_card/basic_user_card.js +++ b/src/components/basic_user_card/basic_user_card.js @@ -36,6 +36,12 @@ const BasicUserCard = { allowNonSquareEmoji() { return useMergedConfigStore().mergedConfig.nonSquareEmoji }, + pauseMfm() { + return useMergedConfigStore().mergedConfig.pauseMfm + }, + scaleMfm() { + return useMergedConfigStore().mergedConfig.scaleMfm + }, }, } diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue index 49786bf1b..39b269ea8 100644 --- a/src/components/basic_user_card/basic_user_card.vue +++ b/src/components/basic_user_card/basic_user_card.vue @@ -32,6 +32,8 @@ :html="user.name" :emoji="user.emoji" :allow-non-square-emoji="allowNonSquareEmoji" + :pause-mfm="pauseMfm" + :scale-mfm="scaleMfm" />
diff --git a/src/components/chat_title/chat_title.js b/src/components/chat_title/chat_title.js index 1d12384cc..e552ce9fd 100644 --- a/src/components/chat_title/chat_title.js +++ b/src/components/chat_title/chat_title.js @@ -21,5 +21,11 @@ export default { allowNonSquareEmoji() { return useMergedConfigStore().mergedConfig.nonSquareEmoji }, + pauseMfm() { + return useMergedConfigStore().mergedConfig.pauseMfm + }, + scaleMfm() { + return useMergedConfigStore().mergedConfig.scaleMfm + }, }, } diff --git a/src/components/chat_title/chat_title.vue b/src/components/chat_title/chat_title.vue index 313e66ce3..764e0de61 100644 --- a/src/components/chat_title/chat_title.vue +++ b/src/components/chat_title/chat_title.vue @@ -20,6 +20,8 @@ :html="htmlTitle" :emoji="user.emoji || []" :allow-non-square-emoji="allowNonSquareEmoji" + :pause-mfm="pauseMfm" + :scale-mfm="scaleMfm" :is-local="user.is_local" />
diff --git a/src/components/global_error/global_error.js b/src/components/global_error/global_error.js index cd4593521..b4768b8ad 100644 --- a/src/components/global_error/global_error.js +++ b/src/components/global_error/global_error.js @@ -1,12 +1,13 @@ import { mapActions, mapState } from 'pinia' - -import ErrorModal from 'src/components/error_modal/error_modal.vue' +import { defineAsyncComponent } from 'vue' import { useInterfaceStore } from 'src/stores/interface.js' const GlobalError = { components: { - ErrorModal, + ErrorModal: defineAsyncComponent( + () => import('src/components/error_modal/error_modal.vue'), + ), }, computed: { title() { diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js index 48f6d1d8e..f1861748b 100644 --- a/src/components/mention_link/mention_link.js +++ b/src/components/mention_link/mention_link.js @@ -9,6 +9,7 @@ import { highlightStyle, } from '../../services/user_highlighter/user_highlighter.js' +import { useInstanceStore } from 'src/stores/instance.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js' @@ -89,8 +90,7 @@ const MentionLink = { // XXX assumed that domain does not contain @ return ( this.user && - (this.userNameFullUi.split('@')[1] || - this.$store.getters.instanceDomain) + (this.userNameFullUi.split('@')[1] || useInstanceStore().instanceDomain) ) }, userNameFull() { diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index b6ee27f9c..1bfe540a0 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -217,6 +217,12 @@ const Notification = { allowNonSquareEmoji() { return this.mergedConfig.nonSquareEmoji }, + pauseMfm() { + return this.mergedConfig.pauseMfm + }, + scaleMfm() { + return this.mergedConfig.scaleMfm + }, shouldConfirmApprove() { return this.mergedConfig.modalOnApproveFollow }, diff --git a/src/components/notification/notification.scss b/src/components/notification/notification.scss index 934d3e58d..3e2fa54a5 100644 --- a/src/components/notification/notification.scss +++ b/src/components/notification/notification.scss @@ -10,11 +10,6 @@ cursor: pointer; } - &.Status { - /* stylelint-disable-next-line declaration-no-important */ - background-color: transparent !important; - } - --emoji-size: 1em; &:hover { diff --git a/src/components/notification/notification.style.js b/src/components/notification/notification.style.js index 49e28cf2e..db5fc5b94 100644 --- a/src/components/notification/notification.style.js +++ b/src/components/notification/notification.style.js @@ -1,6 +1,6 @@ export default { name: 'Notification', - selector: '.Notification', + selector: '.NotificationParent', validInnerComponents: [ 'Text', 'Link', @@ -9,5 +9,11 @@ export default { 'Avatar', 'PollGraph', ], - defaultRules: [], + defaultRules: [ + { + directives: { + background: '--bg', + }, + }, + ], } diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue index 39bd15426..c526eef79 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -4,7 +4,7 @@ ref="root" >
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue index 387c3a847..66819317b 100644 --- a/src/components/notifications/notifications.vue +++ b/src/components/notifications/notifications.vue @@ -51,7 +51,6 @@
-
{{ ' ' }}
+
+ {{ ' ' }} +
diff --git a/src/components/poll/poll.js b/src/components/poll/poll.js index 3ea9a1890..b93a6699d 100644 --- a/src/components/poll/poll.js +++ b/src/components/poll/poll.js @@ -57,6 +57,12 @@ export default { allowNonSquareEmoji() { return useMergedConfigStore().mergedConfig.nonSquareEmoji }, + pauseMfm() { + return useMergedConfigStore().mergedConfig.pauseMfm + }, + scaleMfm() { + return useMergedConfigStore().mergedConfig.scaleMfm + }, loggedIn() { return this.$store.state.users.currentUser }, diff --git a/src/components/poll/poll.vue b/src/components/poll/poll.vue index 9ac859824..89bb1324e 100644 --- a/src/components/poll/poll.vue +++ b/src/components/poll/poll.vue @@ -25,6 +25,8 @@ :handle-links="false" :emoji="emoji" :allow-non-square-emoji="allowNonSquareEmoji" + :pause-mfm="pauseMfm" + :scale-mfm="scaleMfm" />
{ - this.error = data.error + this.error = error }) .finally(() => { this.posting = false diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx index fc17a529f..432250548 100644 --- a/src/components/rich_content/rich_content.jsx +++ b/src/components/rich_content/rich_content.jsx @@ -118,6 +118,16 @@ export default { type: Boolean, default: false, }, + pauseMfm: { + required: false, + type: Boolean, + default: false, + }, + scaleMfm: { + required: false, + type: Boolean, + default: false, + }, }, // NEVER EVER TOUCH DATA INSIDE RENDER render() { @@ -313,7 +323,139 @@ export default { const newChildren = Array.isArray(children) ? [...children].reverse().map(processItemReverse).reverse() : children - return {newChildren} + const attrs = getAttrs(opener) + const newAttrs = { ...attrs } + const fullAttrs = getAttrs(opener, () => true) + const classname = fullAttrs['class'] + const isMFM = classname?.startsWith('mfm-') + if (isMFM) { + const mfmOperator = /^mfm-(\w+)$/.exec(classname)?.[1] + newAttrs['class'] = [ + 'mfm', + this.pauseMfm ? '-pause' : '', + this.scaleMfm ? '-scale' : '', + ] + .filter((x) => x) + .join(' ') + newAttrs['data-mfm-operator'] = mfmOperator + switch (mfmOperator) { + case 'position': { + const x = Number.parseFloat(fullAttrs['data-mfm-x']) || 0 + const y = Number.parseFloat(fullAttrs['data-mfm-y']) || 0 + newAttrs.style = [ + 'transform:', + `translate(calc(${x} * (var(--emoji-size) / 2)), `, + `calc(${y} * (var(--emoji-size) / 2)))`, + ].join(' ') + break + } + case 'scale': { + const x = Number.parseFloat(fullAttrs['data-mfm-x']) || 1 + const y = Number.parseFloat(fullAttrs['data-mfm-y']) || 1 + newAttrs.style = ['transform:', `scale(${x}, ${y})`].join(' ') + break + } + case 'rotate': { + const deg = Number.parseFloat(fullAttrs['data-mfm-deg']) || 0 + newAttrs.style = [ + `transform: rotate(${deg}deg)`, + 'transform-origin: center', + ].join(';') + break + } + case 'bg': { + const color = fullAttrs['data-mfm-color'] || 0 + newAttrs.style = [`background-color: #${color}`].join(' ') + break + } + case 'fg': { + const color = fullAttrs['data-mfm-color'] || 0 + newAttrs.style = [`color: #${color}`].join(';') + break + } + case 'spin': { + const speed = fullAttrs['data-mfm-speed'] || '1s' + const delay = fullAttrs['data-mfm-delay'] || 0 + const left = fullAttrs['data-mfm-left'] != null + const alternate = fullAttrs['data-mfm-alternate'] != null + const y = fullAttrs['data-mfm-y'] != null + const x = fullAttrs['data-mfm-x'] != null + + const anim = [ + x ? 'mfm-spinX' : null, + y ? 'mfm-spinY' : null, + 'mfm-spin', + ].filter((a) => a)[0] + + const direction = [ + alternate ? 'alternate' : null, + left ? 'reverse' : null, + 'normal', + ].filter((a) => a)[0] + + newAttrs.style = [ + `animation-name: ${anim}`, + `animation-duration: ${speed}`, + 'animation-iteration-count: infinite', + `animation-delay: ${delay}`, + `animation-direction: ${direction}`, + 'animation-fill-mode: none', + 'animation-timing-function: linear', + ].join(';') + break + } + case 'flip': { + newAttrs.style = 'transform: scaleX(-1)' + break + } + case 'border': { + const width = fullAttrs['data-mfm-width'] || '0' + const style = fullAttrs['data-mfm-style'] || 'solid' + const color = fullAttrs['data-mfm-color'] || 'transparent' + const radius = fullAttrs['data-mfm-radius'] || '0' + const noclip = fullAttrs['data-mfm-noclip'] || false + + newAttrs.style = [ + `border: ${width} ${style} ${color}`, + `border-radius: ${radius}`, + `overflow: ${noclip ? 'visible' : 'clip'}`, + ].join(';') + break + } + case 'tada': + case 'jelly': + case 'twitch': + case 'shake': + case 'jump': + case 'bounce': + case 'rainbow': { + const speed = fullAttrs['data-mfm-speed'] || '1s' + const delay = fullAttrs['data-mfm-delay'] || 0 + + const rules = [ + `animation-name: mfm-${mfmOperator}`, + `animation-duration: ${speed}`, + 'animation-iteration-count: infinite', + `animation-delay: ${delay}`, + 'animation-direction: normal', + 'animation-fill-mode: none', + 'animation-timing-function: linear', + ].join(';') + newAttrs.style = rules + break + } + case 'sparkle': + case 'x2': + case 'x3': + case 'x4': + // handled by css + break + default: + console.warn('Unsupported MFM operator:', mfmOperator, opener) + break + } + } + return {newChildren} } else { return } diff --git a/src/components/rich_content/rich_content.scss b/src/components/rich_content/rich_content.scss index 33effc623..aa8845be4 100644 --- a/src/components/rich_content/rich_content.scss +++ b/src/components/rich_content/rich_content.scss @@ -100,9 +100,371 @@ .cyantext { color: var(--funtextCyantext); } + + .mfm { + display: inline-block; + + &.-scale { + font-size: calc(var(--emoji-size) / 2); + } + + &:not(.-scale) { + --emoji-size: 2em; + } + + &.-pause { + animation-play-state: paused; + } + + &[data-mfm-operator="x2"] { + font-size: 300% + } + + &[data-mfm-operator="x3"] { + font-size: 400% + } + + &[data-mfm-operator="x4"] { + font-size: 600% + } + + &[data-mfm-operator="sparkle"] { + position: relative; + + &::before, + &::after { + content: '✨'; + position: absolute; + opacity: 0.4; + z-index: -1; + animation-name: cheap-sparkle; + animation-duration: 1s; + animation-timing-function: ease-in-out; + animation-iteration-count: infinite; + } + + &::before { + left: 0; + animation-direction: alternate; + } + + &::after { + right: 0; + animation-direction: alternate-reverse; + } + } + + .emoji { + /* Misskey's emoji width knows no bounds */ + /* stylelint-disable-next-line declaration-no-important */ + max-width: unset !important; + } + } + + &:hover .mfm { + animation-play-state: running; + } } a .RichContent { /* stylelint-disable-next-line declaration-no-important */ color: var(--link) !important; } + +@keyframes mfm-spin { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +@keyframes mfm-spinX { + 0% { + transform: perspective(8em) rotateX(0); + } + + 100% { + transform: perspective(8em) rotateX(360deg); + } +} + +@keyframes mfm-spinY { + 0% { + transform: perspective(8em) rotateY(0); + } + + 100% { + transform: perspective(8em) rotateY(360deg); + } +} + +@keyframes mfm-jump { + 0% { + transform: translateY(0); + } + + 25% { + transform: translateY(-1em); + } + + 50% { + transform: translateY(0); + } + + 75% { + transform: translateY(-0.5em); + } + + 100% { + transform: translateY(0); + } +} + +@keyframes mfm-bounce { + 0% { + transform: translateY(0) scale(1); + } + + 25% { + transform: translateY(-16px) scale(1); + } + + 50% { + transform: translateY(0) scale(1); + } + + 75% { + transform: translateY(0) scale(1.5,.75); + } + + 100% { + transform: translateY(0) scale(1); + } +} + +@keyframes mfm-twitch { + 0% { + transform: translate(7px, -2px); + } + + 5% { + transform: translate(-3px, 1px); + } + + 10% { + transform: translate(-7px, -1px); + } + + 15% { + transform: translateY(-1px); + } + + 20% { + transform: translate(-8px, 6px); + } + + 25% { + transform: translate(-4px, -3px); + } + + 30% { + transform: translate(-4px, -6px); + } + + 35% { + transform: translate(-8px, -8px); + } + + 40% { + transform: translate(4px, 6px); + } + + 45% { + transform: translate(-3px, 1px); + } + + 50% { + transform: translate(2px, -10px); + } + + 55% { + transform: translate(-7px); + } + + 60% { + transform: translate(-2px, 4px); + } + + 65% { + transform: translate(3px, -8px); + } + + 70% { + transform: translate(6px, 7px); + } + + 75% { + transform: translate(-7px, -2px); + } + + 80% { + transform: translate(-7px, -8px); + } + + 85% { + transform: translate(9px, 3px); + } + + 90% { + transform: translate(-3px, -2px); + } + + 95% { + transform: translate(-10px, 2px); + } + + 100% { + transform: translate(-2px, -6px); + } +} + +@keyframes mfm-shake { + 0% { + transform: translate(-3px, -1px) rotate(-8deg); + } + + 5% { + transform: translateY(-1px) rotate(-10deg); + } + + 10% { + transform: translate(1px, -3px) rotate(0); + } + + 15% { + transform: translate(1px, 1px) rotate(11deg); + } + + 20% { + transform: translate(-2px, 1px) rotate(1deg); + } + + 25% { + transform: translate(-1px, -2px) rotate(-2deg); + } + + 30% { + transform: translate(-1px, 2px) rotate(-3deg); + } + + 35% { + transform: translate(2px, 1px) rotate(6deg); + } + + 40% { + transform: translate(-2px, -3px) rotate(-9deg); + } + + 45% { + transform: translateY(-1px) rotate(-12deg); + } + + 50% { + transform: translate(1px, 2px) rotate(10deg); + } + + 55% { + transform: translateY(-3px) rotate(8deg); + } + + 60% { + transform: translate(1px, -1px) rotate(8deg); + } + + 65% { + transform: translateY(-1px) rotate(-7deg); + } + + 70% { + transform: translate(-1px, -3px) rotate(6deg); + } + + 75% { + transform: translateY(-2px) rotate(4deg); + } + + 80% { + transform: translate(-2px, -1px) rotate(3deg); + } + + 85% { + transform: translate(1px, -3px) rotate(-10deg); + } + + 90% { + transform: translate(1px) rotate(3deg); + } + + 95% { + transform: translate(-2px) rotate(-3deg); + } + + 100% { + transform: translate(2px, 1px) rotate(2deg); + } +} + +@keyframes mfm-rubberBand { + 0% { + transform: scale(1); + } + + 30% { + transform: scale(1.25, .75); + } + + 40% { + transform: scale(.75, 1.25); + } + + 50% { + transform: scale(1.15, .85); + } + + 65% { + transform: scale(.95, 1.05); + } + + 75% { + transform: scale(1.05, .95); + } + + 100% { + transform: scale(1); + } +} + +@keyframes mfm-rainbow { + 0% { + filter: hue-rotate() contrast(150%) saturate(150%); + } + + 100% { + filter: hue-rotate(360deg) contrast(150%) saturate(150%); + } +} + +@keyframes cheap-sparkle { + 0% { + filter: hue-rotate() contrast(150%) saturate(150%); + transform: translateY(-0.5em); + } + + 100% { + filter: hue-rotate(360deg) contrast(150%) saturate(150%); + transform: translateY(0.5em); + } +} diff --git a/src/components/settings_modal/tabs/posts_tab.vue b/src/components/settings_modal/tabs/posts_tab.vue index fc33d5476..cbba67f12 100644 --- a/src/components/settings_modal/tabs/posts_tab.vue +++ b/src/components/settings_modal/tabs/posts_tab.vue @@ -174,6 +174,16 @@ {{ $t('settings.non_square_emoji') }} +
  • + + {{ $t('settings.scale_mfm') }} + +
  • +
  • + + {{ $t('settings.pause_mfm') }} + +
  • diff --git a/src/components/status_action_buttons/status_action_buttons.js b/src/components/status_action_buttons/status_action_buttons.js index 5309f44a3..d9c7943b7 100644 --- a/src/components/status_action_buttons/status_action_buttons.js +++ b/src/components/status_action_buttons/status_action_buttons.js @@ -134,8 +134,8 @@ const StatusActionButtons = { } }, doActionReal(button) { - button - .action?.(this.funcArg) + const promise = button.action?.(this.funcArg) ?? Promise.resolve() + promise .then(() => this.$emit('onSuccess')) .catch((err) => this.$emit('onError', err)) }, diff --git a/src/components/status_body/status_body.js b/src/components/status_body/status_body.js index 7a46a1a3b..e6752cb1a 100644 --- a/src/components/status_body/status_body.js +++ b/src/components/status_body/status_body.js @@ -59,6 +59,12 @@ const StatusBody = { allowNonSquareEmoji() { return this.mergedConfig.nonSquareEmoji }, + pauseMfm() { + return this.mergedConfig.pauseMfm + }, + scaleMfm() { + return this.mergedConfig.scaleMfm + }, // This is a bit hacky, but we want to approximate post height before rendering // so we count newlines (masto uses

    for paragraphs, GS uses
    between them) // as well as approximate line count by counting characters and approximating ~80 diff --git a/src/components/status_body/status_body.vue b/src/components/status_body/status_body.vue index b611d69b8..e8994180a 100644 --- a/src/components/status_body/status_body.vue +++ b/src/components/status_body/status_body.vue @@ -16,6 +16,8 @@ :emoji="status.emojis" :is-local="status.isLocal" :allow-non-square-emoji="allowNonSquareEmoji" + :pause-mfm="pauseMfm" + :scale-mfm="scaleMfm" />