From 413ecf30bc6835b7ad209f5c0d90a78e393ebb96 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 12 May 2026 22:15:48 +0300 Subject: [PATCH 01/11] initial MFM support --- src/components/rich_content/rich_content.jsx | 83 ++++- src/components/rich_content/rich_content.scss | 291 ++++++++++++++++++ 2 files changed, 373 insertions(+), 1 deletion(-) diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx index 95ab102c6..322b4a8fa 100644 --- a/src/components/rich_content/rich_content.jsx +++ b/src/components/rich_content/rich_content.jsx @@ -313,7 +313,88 @@ 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' + newAttrs['data-mfm-operator'] = mfmOperator + switch(mfmOperator) { + case 'position': { + const x = fullAttrs['data-mfm-x'] || 0 + const y = fullAttrs['data-mfm-y'] || 0 + newAttrs.style = [ + 'transform:', + `translateX(calc(${x} * (var(--emoji-size) / 2)))`, + `translateY(calc(${y} * (var(--emoji-size) / 2)))`, + ].join(' ') + break + } + case 'scale': { + const x = fullAttrs['data-mfm-x'] || 1 + const y = fullAttrs['data-mfm-y'] || 1 + newAttrs.style = [ + 'transform:', + `scale(${x}, ${y})`, + ].join(' ') + break + } + case 'rotate': { + const deg = 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'] + 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] + newAttrs.style = `animation: ${speed} linear 3s infinite normal none running ${anim}` + break + } + case 'flip': { + newAttrs.style = 'transform: scaleX(-1)' + break + } + case 'jump': + case 'twitch': + case 'shake': + case 'bounce': + newAttrs.style = `animation: 0.75s linear 0s infinite normal none running mfm-${mfmOperator}` + break + default: + console.log(mfmOperator, opener) + console.log(mfmOperator) + 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..230ea8be5 100644 --- a/src/components/rich_content/rich_content.scss +++ b/src/components/rich_content/rich_content.scss @@ -1,6 +1,11 @@ .RichContent { font-family: var(--font); + .mfm { + display: inline-block; + font-size: calc(var(--emoji-size) / 2); + } + &.-faint { color: var(--text); /* stylelint-disable declaration-no-important */ @@ -106,3 +111,289 @@ 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(-16px) + } + + 50% { + transform: translateY(0) + } + + 75% { + transform: translateY(-8px) + } + + 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%) + } +} From 5e7fef8c7376bbbb04366d49a6955879fe9ed9ea Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 1 Jun 2026 22:24:11 +0300 Subject: [PATCH 02/11] delay --- src/components/rich_content/rich_content.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx index 322b4a8fa..60b546a83 100644 --- a/src/components/rich_content/rich_content.jsx +++ b/src/components/rich_content/rich_content.jsx @@ -367,7 +367,8 @@ export default { break } case 'spin': { - const speed = fullAttrs['data-mfm-speed'] + const speed = fullAttrs['data-mfm-speed'] = '1s' + const delay = fullAttrs['data-mfm-delay'] = 0 const y = fullAttrs['data-mfm-y'] != null const x = fullAttrs['data-mfm-x'] != null const anim = [ @@ -375,7 +376,7 @@ export default { y ? 'mfm-spinY' : null, 'mfm-spin' ].filter(a => a)[0] - newAttrs.style = `animation: ${speed} linear 3s infinite normal none running ${anim}` + newAttrs.style = `animation: ${speed} linear ${delay} infinite normal none running ${anim}` break } case 'flip': { From 4934feb33e2d0423fb50876062669e0f6efe6857 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 2 Jun 2026 16:30:38 +0300 Subject: [PATCH 03/11] =?UTF-8?q?=E8=B8=8A=E3=81=A3=E3=81=A6=E3=81=AA?= =?UTF-8?q?=E3=81=84=E5=A4=9C=E3=82=92=E7=9F=A5=E3=82=89=E3=81=AA=E3=81=84?= =?UTF-8?q?=20=E8=B8=8A=E3=81=A3=E3=81=A6=E3=81=AA=E3=81=84=E5=A4=9C?= =?UTF-8?q?=E3=81=8C=E6=B0=97=E3=81=AB=E5=85=A5=E3=82=89=E3=81=AA=E3=81=84?= =?UTF-8?q?=20https://shigusegubu.club/notice/B6Y2J5biTNp1VtN0pE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/rich_content/rich_content.jsx | 81 ++++++++++++++----- src/components/rich_content/rich_content.scss | 11 ++- 2 files changed, 72 insertions(+), 20 deletions(-) diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx index 60b546a83..1625f4a51 100644 --- a/src/components/rich_content/rich_content.jsx +++ b/src/components/rich_content/rich_content.jsx @@ -324,18 +324,18 @@ export default { newAttrs['data-mfm-operator'] = mfmOperator switch(mfmOperator) { case 'position': { - const x = fullAttrs['data-mfm-x'] || 0 - const y = fullAttrs['data-mfm-y'] || 0 + const x = fullAttrs['data-mfm-x'].replace(/\.+$/,'') || 0 + const y = fullAttrs['data-mfm-y'].replace(/\.+$/,'') || 0 newAttrs.style = [ 'transform:', - `translateX(calc(${x} * (var(--emoji-size) / 2)))`, - `translateY(calc(${y} * (var(--emoji-size) / 2)))`, + `translate(calc(${x} * (var(--emoji-size) / 2)), `, + `calc(${y} * (var(--emoji-size) / 2)))`, ].join(' ') break } case 'scale': { - const x = fullAttrs['data-mfm-x'] || 1 - const y = fullAttrs['data-mfm-y'] || 1 + const x = fullAttrs['data-mfm-x'].replace(/\.+$/,'') || 1 + const y = fullAttrs['data-mfm-y'].replace(/\.+$/,'') || 1 newAttrs.style = [ 'transform:', `scale(${x}, ${y})`, @@ -345,11 +345,9 @@ export default { case 'rotate': { const deg = fullAttrs['data-mfm-deg'] || 0 newAttrs.style = [ - 'transform:', - `rotate(${deg}deg);`, - 'transform-origin:', - 'center', - ].join(' ') + `transform: rotate(${deg}deg)`, + 'transform-origin: center', + ].join(';') break } case 'bg': { @@ -363,32 +361,79 @@ export default { const color = fullAttrs['data-mfm-color'] || 0 newAttrs.style = [ `color: #${color}`, - ].join(' ') + ].join(';') break } case 'spin': { - const speed = fullAttrs['data-mfm-speed'] = '1s' - const delay = fullAttrs['data-mfm-delay'] = 0 + 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] - newAttrs.style = `animation: ${speed} linear ${delay} infinite normal none running ${anim}` + + 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 'jump': + 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 'bounce': - newAttrs.style = `animation: 0.75s linear 0s infinite normal none running mfm-${mfmOperator}` + case 'jump': + case 'bounce': { + 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 + } default: console.log(mfmOperator, opener) console.log(mfmOperator) diff --git a/src/components/rich_content/rich_content.scss b/src/components/rich_content/rich_content.scss index 230ea8be5..4aad49477 100644 --- a/src/components/rich_content/rich_content.scss +++ b/src/components/rich_content/rich_content.scss @@ -1,9 +1,16 @@ .RichContent { font-family: var(--font); + text-align: center; .mfm { display: inline-block; font-size: calc(var(--emoji-size) / 2); + + .emoji { + /* Misskey's emoji width knows no bounds */ + /* stylelint-disable-next-line declaration-no-important */ + max-width: unset !important; + } } &.-faint { @@ -148,7 +155,7 @@ a .RichContent { } 25% { - transform: translateY(-16px) + transform: translateY(-1em) } 50% { @@ -156,7 +163,7 @@ a .RichContent { } 75% { - transform: translateY(-8px) + transform: translateY(-0.5em) } 100% { From a5179b6381f1ebad0d00b5aa9a89e3a4574e7795 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 2 Jun 2026 16:34:57 +0300 Subject: [PATCH 04/11] undo center --- src/components/rich_content/rich_content.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/rich_content/rich_content.scss b/src/components/rich_content/rich_content.scss index 4aad49477..f048e529e 100644 --- a/src/components/rich_content/rich_content.scss +++ b/src/components/rich_content/rich_content.scss @@ -1,6 +1,5 @@ .RichContent { font-family: var(--font); - text-align: center; .mfm { display: inline-block; From 4177981eb2fff58d01d4dbf391ac8b9cb82767db Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 7 Jul 2026 11:40:37 +0300 Subject: [PATCH 05/11] pause & scale MFM options --- .../basic_user_card/basic_user_card.js | 6 ++++ .../basic_user_card/basic_user_card.vue | 2 ++ src/components/chat_title/chat_title.js | 6 ++++ src/components/chat_title/chat_title.vue | 2 ++ src/components/notification/notification.js | 6 ++++ src/components/notification/notification.vue | 2 ++ src/components/poll/poll.js | 6 ++++ src/components/poll/poll.vue | 2 ++ src/components/rich_content/rich_content.jsx | 29 ++++++++++++++----- src/components/rich_content/rich_content.scss | 17 ++++++++++- .../settings_modal/tabs/posts_tab.vue | 10 +++++++ src/components/status/status.js | 6 ++++ src/components/status/status.vue | 2 ++ src/components/status_body/status_body.js | 6 ++++ src/components/status_body/status_body.vue | 4 +++ src/components/user_card/user_card.js | 6 ++++ src/components/user_card/user_card.vue | 8 +++++ .../user_list_popover/user_list_popover.js | 6 ++++ .../user_list_popover/user_list_popover.vue | 2 ++ src/i18n/en.json | 2 ++ src/modules/default_config_state.js | 8 +++++ 21 files changed, 130 insertions(+), 8 deletions(-) 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/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.vue b/src/components/notification/notification.vue index 39bd15426..daa208f09 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -72,6 +72,8 @@ :html="notification.from_profile.name_html" :emoji="notification.from_profile.emoji" :allow-non-square-emoji="allowNonSquareEmoji" + :pause-mfm="pauseMfm" + :scale-mfm="scaleMfm" :is-local="notification.from_profile.is_local" /> 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" />
x).join(' ') newAttrs['data-mfm-operator'] = mfmOperator switch(mfmOperator) { case 'position': { - const x = fullAttrs['data-mfm-x'].replace(/\.+$/,'') || 0 - const y = fullAttrs['data-mfm-y'].replace(/\.+$/,'') || 0 + 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)), `, @@ -334,8 +348,8 @@ export default { break } case 'scale': { - const x = fullAttrs['data-mfm-x'].replace(/\.+$/,'') || 1 - const y = fullAttrs['data-mfm-y'].replace(/\.+$/,'') || 1 + const x = Number.parseFloat(fullAttrs['data-mfm-x']) || 1 + const y = Number.parseFloat(fullAttrs['data-mfm-y']) || 1 newAttrs.style = [ 'transform:', `scale(${x}, ${y})`, @@ -343,7 +357,7 @@ export default { break } case 'rotate': { - const deg = fullAttrs['data-mfm-deg'] || 0 + const deg = Number.parseFloat(fullAttrs['data-mfm-deg']) || 0 newAttrs.style = [ `transform: rotate(${deg}deg)`, 'transform-origin: center', @@ -418,7 +432,8 @@ export default { case 'twitch': case 'shake': case 'jump': - case 'bounce': { + case 'bounce': + case 'rainbow': { const speed = fullAttrs['data-mfm-speed'] || '1s' const delay = fullAttrs['data-mfm-delay'] || 0 diff --git a/src/components/rich_content/rich_content.scss b/src/components/rich_content/rich_content.scss index f048e529e..3ef82c9ac 100644 --- a/src/components/rich_content/rich_content.scss +++ b/src/components/rich_content/rich_content.scss @@ -3,7 +3,18 @@ .mfm { display: inline-block; - font-size: calc(var(--emoji-size) / 2); + + &.-scale { + font-size: calc(var(--emoji-size) / 2); + } + + &:not(.-scale) { + --emoji-size: 2em; + } + + &.-pause { + animation-play-state: paused; + } .emoji { /* Misskey's emoji width knows no bounds */ @@ -12,6 +23,10 @@ } } + &:hover .mfm { + animation-play-state: running; + } + &.-faint { color: var(--text); /* stylelint-disable declaration-no-important */ 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_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" />

  • @@ -59,10 +61,12 @@ import { computed } from 'vue' import ColorInput from 'src/components/color_input/color_input.vue' import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue' -import { getContrastRatio, hex2rgb } from 'src/services/color_convert/color_convert.js' - import { useInterfaceStore } from 'src/stores/interface.js' +import { + getContrastRatio, + hex2rgb, +} from 'src/services/color_convert/color_convert.js' import { newExporter, newImporter, diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx index 990497007..432250548 100644 --- a/src/components/rich_content/rich_content.jsx +++ b/src/components/rich_content/rich_content.jsx @@ -334,9 +334,11 @@ export default { 'mfm', this.pauseMfm ? '-pause' : '', this.scaleMfm ? '-scale' : '', - ].filter(x => x).join(' ') + ] + .filter((x) => x) + .join(' ') newAttrs['data-mfm-operator'] = mfmOperator - switch(mfmOperator) { + switch (mfmOperator) { case 'position': { const x = Number.parseFloat(fullAttrs['data-mfm-x']) || 0 const y = Number.parseFloat(fullAttrs['data-mfm-y']) || 0 @@ -350,10 +352,7 @@ export default { 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(' ') + newAttrs.style = ['transform:', `scale(${x}, ${y})`].join(' ') break } case 'rotate': { @@ -366,16 +365,12 @@ export default { } case 'bg': { const color = fullAttrs['data-mfm-color'] || 0 - newAttrs.style = [ - `background-color: #${color}`, - ].join(' ') + newAttrs.style = [`background-color: #${color}`].join(' ') break } case 'fg': { const color = fullAttrs['data-mfm-color'] || 0 - newAttrs.style = [ - `color: #${color}`, - ].join(';') + newAttrs.style = [`color: #${color}`].join(';') break } case 'spin': { @@ -389,14 +384,14 @@ export default { const anim = [ x ? 'mfm-spinX' : null, y ? 'mfm-spinY' : null, - 'mfm-spin' - ].filter(a => a)[0] + 'mfm-spin', + ].filter((a) => a)[0] const direction = [ alternate ? 'alternate' : null, left ? 'reverse' : null, 'normal', - ].filter(a => a)[0] + ].filter((a) => a)[0] newAttrs.style = [ `animation-name: ${anim}`, @@ -423,7 +418,7 @@ export default { newAttrs.style = [ `border: ${width} ${style} ${color}`, `border-radius: ${radius}`, - `overflow: ${noclip ? 'visible' : 'clip'}` + `overflow: ${noclip ? 'visible' : 'clip'}`, ].join(';') break } From 122de7ab7edeb4f510f273898b12e662a6cf36d6 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 13 Jul 2026 17:43:03 +0300 Subject: [PATCH 11/11] lint --- src/components/rich_content/rich_content.scss | 128 +++++++++--------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/src/components/rich_content/rich_content.scss b/src/components/rich_content/rich_content.scss index 18ae9ac67..aa8845be4 100644 --- a/src/components/rich_content/rich_content.scss +++ b/src/components/rich_content/rich_content.scss @@ -1,70 +1,6 @@ .RichContent { font-family: var(--font); - .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; - } - &.-faint { color: var(--text); /* stylelint-disable declaration-no-important */ @@ -164,6 +100,70 @@ .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 {