diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx index fc17a529f..cdaebe2ff 100644 --- a/src/components/rich_content/rich_content.jsx +++ b/src/components/rich_content/rich_content.jsx @@ -313,7 +313,134 @@ 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'].replace(/\.+$/,'') || 0 + const y = fullAttrs['data-mfm-y'].replace(/\.+$/,'') || 0 + newAttrs.style = [ + 'transform:', + `translate(calc(${x} * (var(--emoji-size) / 2)), `, + `calc(${y} * (var(--emoji-size) / 2)))`, + ].join(' ') + break + } + case 'scale': { + const x = fullAttrs['data-mfm-x'].replace(/\.+$/,'') || 1 + const y = fullAttrs['data-mfm-y'].replace(/\.+$/,'') || 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'] || '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': { + 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) + 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..f048e529e 100644 --- a/src/components/rich_content/rich_content.scss +++ b/src/components/rich_content/rich_content.scss @@ -1,6 +1,17 @@ .RichContent { font-family: var(--font); + .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 { color: var(--text); /* stylelint-disable declaration-no-important */ @@ -106,3 +117,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(-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%) + } +}