Use FEP-c16b: Formatting MFM functions #410
9 changed files with 503 additions and 265 deletions
|
@ -6,7 +6,6 @@
|
||||||
<title>Akkoma</title>
|
<title>Akkoma</title>
|
||||||
<link rel="stylesheet" href="/static/font/tiresias.css">
|
<link rel="stylesheet" href="/static/font/tiresias.css">
|
||||||
<link rel="stylesheet" href="/static/font/css/lato.css">
|
<link rel="stylesheet" href="/static/font/css/lato.css">
|
||||||
<link rel="stylesheet" href="/static/mfm.css">
|
|
||||||
<link rel="stylesheet" href="/static/custom.css">
|
<link rel="stylesheet" href="/static/custom.css">
|
||||||
<link rel="stylesheet" href="/static/theme-holder.css" id="theme-holder">
|
<link rel="stylesheet" href="/static/theme-holder.css" id="theme-holder">
|
||||||
<!--server-generated-meta-->
|
<!--server-generated-meta-->
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const MFM_TAGS = ['blur', 'bounce', 'flip', 'font', 'jelly', 'jump', 'rainbow', 'rotate', 'shake', 'sparkle', 'spin', 'tada', 'twitch', 'x2', 'x3', 'x4']
|
const MFM_TAGS = ['bg', 'blur', 'bounce', 'center', 'fg', 'flip', 'font', 'jelly', 'jump', 'position', 'rainbow', 'rotate', 'scale', 'shake', 'sparkle', 'spin', 'tada', 'twitch', 'x2', 'x3', 'x4']
|
||||||
.map(tag => ({ displayText: tag, detailText: '$[' + tag + ' ]', replacement: '$[' + tag + ' ]', mfm: true }))
|
.map(tag => ({ displayText: tag, detailText: '$[' + tag + ' ]', replacement: '$[' + tag + ' ]', mfm: true }))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -191,6 +191,24 @@ export default {
|
||||||
if (this.handleLinks && attrs?.['class']?.includes?.('h-card')) {
|
if (this.handleLinks && attrs?.['class']?.includes?.('h-card')) {
|
||||||
return ['', children.map(processItem), '']
|
return ['', children.map(processItem), '']
|
||||||
}
|
}
|
||||||
|
// Turn data-mfm- attributes into a string for the `style` attribute
|
||||||
|
// If they have a value different than `true`, they need to be added to `style`
|
||||||
|
// e.g. `attrs={'data-mfm-some': '1deg', 'data-mfm-thing': '5s'}` => "--mfm-some: 1deg;--mfm-thing: 5s;"
|
||||||
|
// Note that we only add the value to `style` when they contain only letters, numbers, dot, or minus signs
|
||||||
|
// At the moment of writing, this should be enough for legitimite purposes and reduces the chance of injection by using special characters
|
||||||
|
// There is a special case for the `color` value, who is provided without `#`, but requires this in the `style` attribute
|
||||||
|
let mfm_style = Object.keys(attrs).filter(
|
||||||
|
(key) => key.startsWith('data-mfm-') && attrs[key] !== true && /^[a-zA-Z0-9.\-]*$/.test(attrs[key])
|
||||||
|
).map(
|
||||||
|
(key) => '--mfm-' + key.substr(9) + (key === 'data-mfm-color' ? ': #' : ': ') + attrs[key] + ';'
|
||||||
|
).reduce((a,v) => a+v, '')
|
||||||
|
if (mfm_style !== '') {
|
||||||
|
return [
|
||||||
|
opener.slice(0,-1) + ' style="' + mfm_style + '">',
|
||||||
|
children.map(processItem),
|
||||||
|
closer
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (children !== undefined) {
|
if (children !== undefined) {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
.StatusBody {
|
.StatusBody {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.translation {
|
.translation {
|
||||||
border: 1px solid var(--accent, $fallback--link);
|
border: 1px solid var(--accent, $fallback--link);
|
||||||
|
@ -23,24 +24,6 @@
|
||||||
transition: 0.05s;
|
transition: 0.05s;
|
||||||
}
|
}
|
||||||
|
|
||||||
._mfm_x2_ {
|
|
||||||
.emoji {
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
._mfm_x3_ {
|
|
||||||
.emoji {
|
|
||||||
height: 150px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
._mfm_x4_ {
|
|
||||||
.emoji {
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.attachments {
|
.attachments {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="StatusBody"
|
class="StatusBody"
|
||||||
:class="{ '-compact': compact, 'mfm-disabled': !renderMisskeyMarkdown }"
|
:class="{ '-compact': compact }"
|
||||||
>
|
>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div
|
<div
|
||||||
|
|
423
src/components/status_content/mfm.scss
Normal file
423
src/components/status_content/mfm.scss
Normal file
|
@ -0,0 +1,423 @@
|
||||||
|
/**
|
||||||
|
* "FEP-c16b: Formatting MFM functions" attributes that Akkoma supports
|
||||||
|
*/
|
||||||
|
|
||||||
|
.StatusContent:not(.mfm-disabled) {
|
||||||
|
/* The following are the non-animated MFM */
|
||||||
|
.mfm-center {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-flip {
|
||||||
|
display: inline-block;
|
||||||
|
transform: scaleX(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-flip[data-mfm-v] {
|
||||||
|
transform: scaleY(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-flip[data-mfm-v][data-mfm-h] {
|
||||||
|
transform: scale(-1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-font[data-mfm-serif] {
|
||||||
|
font-family: serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-font[data-mfm-monospace] {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-font[data-mfm-cursive] {
|
||||||
|
font-family: cursive;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-font[data-mfm-fantasy] {
|
||||||
|
font-family: fantasy;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-font[data-mfm-emoji] {
|
||||||
|
font-family: emoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-font[data-mfm-math] {
|
||||||
|
font-family: math;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-blur {
|
||||||
|
filter: blur(6px);
|
||||||
|
transition: filter 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
filter: blur(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-rotate {
|
||||||
|
display: inline-block;
|
||||||
|
transform: rotate(calc(var(--mfm-deg, 90) * 1deg));
|
||||||
|
transform-origin: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-x2 {
|
||||||
|
--mfm-zoom-size: 200%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-x3 {
|
||||||
|
--mfm-zoom-size: 400%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-x4 {
|
||||||
|
|||||||
|
--mfm-zoom-size: 600%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-x2,
|
||||||
|
.mfm-x3,
|
||||||
|
.mfm-x4,
|
||||||
|
.mfm-tada {
|
||||||
|
.emoji {
|
||||||
|
--emoji-size: 2em;
|
||||||
|
}
|
||||||
|
font-size: var(--mfm-zoom-size);
|
||||||
|
|
||||||
|
.mfm-x2,
|
||||||
|
.mfm-x3,
|
||||||
|
.mfm-x4,
|
||||||
|
.mfm-tada {
|
||||||
|
/* only half effective */
|
||||||
|
font-size: calc(var(--mfm-zoom-size) / 2 + 50%);
|
||||||
|
|
||||||
|
.mfm-x2,
|
||||||
|
.mfm-x3,
|
||||||
|
.mfm-x4,
|
||||||
|
.mfm-tada {
|
||||||
|
/* disabled */
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-position {
|
||||||
|
display: inline-block;
|
||||||
|
transform: translate(calc(var(--mfm-x, 0) * 1em), calc(var(--mfm-y, 0) * 1em));
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-scale {
|
||||||
|
display: inline-block;
|
||||||
|
transform: scale(var(--mfm-x, 1), var(--mfm-y, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-fg {
|
||||||
|
color: var(--mfm-color, #f00);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-bg {
|
||||||
|
background-color: var(--mfm-color, #0f0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The following are the animated MFM */
|
||||||
|
|
||||||
|
/* .mfm-hover means that we should only play animation when hovering over the StatusContent
|
||||||
|
* So either StatusContent does not have this class,
|
||||||
|
* or it has the class and we are hovering over StatusContent
|
||||||
|
*/
|
||||||
|
&:not(.mfm-hover:not(:hover)) {
|
||||||
|
.mfm-jelly {
|
||||||
|
display: inline-block;
|
||||||
|
animation: mfm-rubberBand var(--mfm-speed, 1s) linear infinite both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-twitch {
|
||||||
|
display: inline-block;
|
||||||
|
animation: mfm-twitch var(--mfm-speed, 0.5s) ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-shake {
|
||||||
|
display: inline-block;
|
||||||
|
animation: mfm-shake var(--mfm-speed, 0.5s) ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-spin {
|
||||||
|
display: inline-block;
|
||||||
|
animation: mfm-spin var(--mfm-speed, 1.5s) linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-spin[data-mfm-y] {
|
||||||
|
animation-name: mfm-spinY;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-spin[data-mfm-x] {
|
||||||
|
animation-name: mfm-spinX;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-spin[data-mfm-alternate] {
|
||||||
|
animation-direction: alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-spin[data-mfm-left] {
|
||||||
|
animation-direction: reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-jump {
|
||||||
|
display: inline-block;
|
||||||
|
animation: mfm-jump var(--mfm-speed, 0.75s) linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-bounce {
|
||||||
|
display: inline-block;
|
||||||
|
animation: mfm-bounce var(--mfm-speed, 0.75s) linear infinite;
|
||||||
|
transform-origin: center bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-rainbow {
|
||||||
|
animation: mfm-rainbow var(--mfm-speed, 1s) linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mfm-tada {
|
||||||
|
display: inline-block;
|
||||||
|
animation: mfm-tada var(--mfm-speed, 1s) linear infinite both;
|
||||||
|
|
||||||
|
--mfm-zoom-size: 150%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* animation keyframes */
|
||||||
|
|
||||||
|
@keyframes mfm-spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mfm-spinX {
|
||||||
|
0% { transform: perspective(128px) rotateX(0deg); }
|
||||||
|
100% { transform: perspective(128px) rotateX(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mfm-spinY {
|
||||||
|
0% { transform: perspective(128px) rotateY(0deg); }
|
||||||
|
100% { transform: perspective(128px) 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, 1); }
|
||||||
|
25% { transform: translateY(-16px) scale(1, 1); }
|
||||||
|
50% { transform: translateY(0) scale(1, 1); }
|
||||||
|
75% { transform: translateY(0) scale(1.5, 0.75); }
|
||||||
|
100% { transform: translateY(0) scale(1, 1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mfm-twitch {
|
||||||
|
0% { transform: translate(7px, -2px); }
|
||||||
|
5% { transform: translate(-3px, 1px); }
|
||||||
|
10% { transform: translate(-7px, -1px); }
|
||||||
|
15% { transform: translate(0, -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, 0); }
|
||||||
|
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: translate(0, -1px) rotate(-10deg); }
|
||||||
|
10% { transform: translate(1px, -3px) rotate(0deg); }
|
||||||
|
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: translate(0, -1px) rotate(-12deg); }
|
||||||
|
50% { transform: translate(1px, 2px) rotate(10deg); }
|
||||||
|
55% { transform: translate(0, -3px) rotate(8deg); }
|
||||||
|
60% { transform: translate(1px, -1px) rotate(8deg); }
|
||||||
|
65% { transform: translate(0, -1px) rotate(-7deg); }
|
||||||
|
70% { transform: translate(-1px, -3px) rotate(6deg); }
|
||||||
|
75% { transform: translate(0, -2px) rotate(4deg); }
|
||||||
|
80% { transform: translate(-2px, -1px) rotate(3deg); }
|
||||||
|
85% { transform: translate(1px, -3px) rotate(-10deg); }
|
||||||
|
90% { transform: translate(1px, 0) rotate(3deg); }
|
||||||
|
95% { transform: translate(-2px, 0) rotate(-3deg); }
|
||||||
|
100% { transform: translate(2px, 1px) rotate(2deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mfm-rubberBand {
|
||||||
|
0% { transform: scale3d(1, 1, 1); }
|
||||||
|
30% { transform: scale3d(1.25, 0.75, 1); }
|
||||||
|
40% { transform: scale3d(0.75, 1.25, 1); }
|
||||||
|
50% { transform: scale3d(1.15, 0.85, 1); }
|
||||||
|
65% { transform: scale3d(0.95, 1.05, 1); }
|
||||||
|
75% { transform: scale3d(1.05, 0.95, 1); }
|
||||||
|
100% { transform: scale3d(1, 1, 1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mfm-rainbow {
|
||||||
|
0% { filter: hue-rotate(0deg) contrast(150%) saturate(150%); }
|
||||||
|
100% { filter: hue-rotate(360deg) contrast(150%) saturate(150%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mfm-tada {
|
||||||
|
0%,
|
||||||
|
100% { transform: scale3d(1, 1, 1); }
|
||||||
|
|
||||||
|
10%,
|
||||||
|
20% { transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); }
|
||||||
|
|
||||||
|
30%,
|
||||||
|
50%,
|
||||||
|
70%,
|
||||||
|
90% { transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); }
|
||||||
|
|
||||||
|
40%,
|
||||||
|
60%,
|
||||||
|
80% { transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy MFM
|
||||||
|
* This is for backwards compatibility with posts formatted on Akkoma before support for FEP-c16b
|
||||||
|
* Note that it uses the keyframes as defined above for the FEP-c16b compatible MFM representation
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mfm {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The following are the legacy non-animated MFM */
|
||||||
|
._mfm_flip_[data-h][data-v] {
|
||||||
|
transform: scale(-1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_flip_[data-v] {
|
||||||
|
transform: scaleY(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_flip_:not([data-v]) {
|
||||||
|
transform: scaleX(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_x2_ {
|
||||||
|
font-size: 200%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_x3_ {
|
||||||
|
font-size: 400%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_x4_ {
|
||||||
|
font-size: 600%;
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_x2_ {
|
||||||
|
.emoji {
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_x3_ {
|
||||||
|
.emoji {
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_x4_ {
|
||||||
|
.emoji {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_blur_ {
|
||||||
|
filter: blur(6px);
|
||||||
|
transition: filter 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_blur_:hover {
|
||||||
|
filter: blur(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_rotate_ {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
transform-origin: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The following are the legacy animated MFM */
|
||||||
|
|
||||||
|
/* .mfm-hover means that we should only play animation when hovering over the StatusContent
|
||||||
|
* So either StatusContent does not have this class,
|
||||||
|
* or it has the class and we are hovering over StatusContent
|
||||||
|
*/
|
||||||
|
&:not(.mfm-hover:not(:hover)) {
|
||||||
|
._mfm_tada_ {
|
||||||
|
font-size: 150%;
|
||||||
|
animation: mfm-tada 1s linear infinite both;
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_jelly_ {
|
||||||
|
animation: mfm-rubberBand 1s linear infinite both;
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_twitch_ {
|
||||||
|
animation: mfm-twitch 0.5s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_shake_ {
|
||||||
|
animation: mfm-shake 0.5s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_spin_ {
|
||||||
|
animation: mfm-spin 0.5s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_spin_[data-x] {
|
||||||
|
animation-name: mfm-spinX;
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_spin_[data-y] {
|
||||||
|
animation-name: mfm-spinY;
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_spin_[left] {
|
||||||
|
animation-direction: reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_spin_[alternate] {
|
||||||
|
animation-direction: alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_jump_ {
|
||||||
|
animation: mfm-jump 0.75s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_bounce_ {
|
||||||
|
animation: mfm-bounce 0.75s linear infinite;
|
||||||
|
transform-origin: center bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
._mfm_rainbow_ {
|
||||||
|
animation: mfm-rainbow 1s linear infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,6 +64,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./status_content.js"></script>
|
<script src="./status_content.js"></script>
|
||||||
|
<style lang="scss" src="./mfm.scss" />
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.StatusContent {
|
.StatusContent {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
@ -75,23 +76,6 @@
|
||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mfm-hover:not(:hover) {
|
|
||||||
.mfm {
|
|
||||||
animation: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.mfm-disabled {
|
|
||||||
span {
|
|
||||||
font-size: 100% !important;
|
|
||||||
}
|
|
||||||
.mfm {
|
|
||||||
animation: none !important;
|
|
||||||
}
|
|
||||||
.emoji {
|
|
||||||
height: 32px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.quote-inline,
|
.quote-inline,
|
||||||
|
|
227
static/mfm.css
227
static/mfm.css
|
@ -1,227 +0,0 @@
|
||||||
.mfm {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
._mfm_tada_ {
|
|
||||||
font-size: 150%;
|
|
||||||
animation: mfm-tada 1s linear infinite both;
|
|
||||||
}
|
|
||||||
|
|
||||||
._mfm_jelly_ {
|
|
||||||
animation: mfm-jelly 1s linear infinite both;
|
|
||||||
}
|
|
||||||
|
|
||||||
._mfm_twitch_ {
|
|
||||||
animation: mfm-twitch 0.5s ease infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
._mfm_shake_ {
|
|
||||||
animation: mfm-shake 0.5s ease infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
._mfm_spin_ {
|
|
||||||
animation: mfm-spin 0.5s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
._mfm_spin_[data-x] {
|
|
||||||
animation-name: mfm-spinX;
|
|
||||||
}
|
|
||||||
._mfm_spin_[data-y] {
|
|
||||||
animation-name: mfm-spinY;
|
|
||||||
}
|
|
||||||
._mfm_spin_[left] {
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
._mfm_spin_[alternate] {
|
|
||||||
animation-direction: alternate;
|
|
||||||
}
|
|
||||||
|
|
||||||
._mfm_jump_ {
|
|
||||||
animation: mfm-jump 0.75s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
._mfm_bounce_ {
|
|
||||||
animation: mfm-bounce 0.75s linear infinite;
|
|
||||||
transform-origin: center bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
._mfm_flip_[data-h][data-v] {
|
|
||||||
transform: scale(-1, -1);
|
|
||||||
}
|
|
||||||
._mfm_flip_[data-v] {
|
|
||||||
transform: scaleY(-1);
|
|
||||||
}
|
|
||||||
._mfm_flip_:not([data-v]) {
|
|
||||||
transform: scaleX(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
._mfm_x2_ {
|
|
||||||
font-size: 200%;
|
|
||||||
}
|
|
||||||
|
|
||||||
._mfm_x3_ {
|
|
||||||
font-size: 400%;
|
|
||||||
}
|
|
||||||
|
|
||||||
._mfm_x4_ {
|
|
||||||
font-size: 600%;
|
|
||||||
}
|
|
||||||
|
|
||||||
._mfm_blur_ {
|
|
||||||
filter: blur(6px);
|
|
||||||
transition: filter 0.3s
|
|
||||||
}
|
|
||||||
._mfm_blur_:hover {
|
|
||||||
filter: blur(0px);
|
|
||||||
}
|
|
||||||
|
|
||||||
._mfm_rainbow_ {
|
|
||||||
animation: mfm-rainbow 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
._mfm_rotate_ {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
transform-origin: center center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* sparkle */
|
|
||||||
|
|
||||||
@keyframes mfm-tada {
|
|
||||||
from {
|
|
||||||
transform: scale3d(1, 1, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
10%,
|
|
||||||
20% {
|
|
||||||
transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
30%,
|
|
||||||
50%,
|
|
||||||
70%,
|
|
||||||
90% {
|
|
||||||
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
40%,
|
|
||||||
60%,
|
|
||||||
80% {
|
|
||||||
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
transform: scale3d(1, 1, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes bounce {
|
|
||||||
0% {
|
|
||||||
transform: scaleX(0.9) scaleY(0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
19% {
|
|
||||||
transform: scaleX(1.1) scaleY(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
48% {
|
|
||||||
transform: scaleX(0.95) scaleY(0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: scaleX(1) scaleY(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes mfm-spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes mfm-spinX {
|
|
||||||
0% { transform: perspective(128px) rotateX(0deg); }
|
|
||||||
100% { transform: perspective(128px) rotateX(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes mfm-spinY {
|
|
||||||
0% { transform: perspective(128px) rotateY(0deg); }
|
|
||||||
100% { transform: perspective(128px) 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, 1); }
|
|
||||||
25% { transform: translateY(-16px) scale(1, 1); }
|
|
||||||
50% { transform: translateY(0) scale(1, 1); }
|
|
||||||
75% { transform: translateY(0) scale(1.5, 0.75); }
|
|
||||||
100% { transform: translateY(0) scale(1, 1); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes mfm-twitch {
|
|
||||||
0% { transform: translate(7px, -2px); }
|
|
||||||
5% { transform: translate(-3px, 1px); }
|
|
||||||
10% { transform: translate(-7px, -1px); }
|
|
||||||
15% { transform: translate(0, -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, 0); }
|
|
||||||
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: translate(0, -1px) rotate(-10deg); }
|
|
||||||
10% { transform: translate(1px, -3px) rotate(0deg); }
|
|
||||||
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: translate(0, -1px) rotate(-12deg); }
|
|
||||||
50% { transform: translate(1px, 2px) rotate(10deg); }
|
|
||||||
55% { transform: translate(0, -3px) rotate(8deg); }
|
|
||||||
60% { transform: translate(1px, -1px) rotate(8deg); }
|
|
||||||
65% { transform: translate(0, -1px) rotate(-7deg); }
|
|
||||||
70% { transform: translate(-1px, -3px) rotate(6deg); }
|
|
||||||
75% { transform: translate(0, -2px) rotate(4deg); }
|
|
||||||
80% { transform: translate(-2px, -1px) rotate(3deg); }
|
|
||||||
85% { transform: translate(1px, -3px) rotate(-10deg); }
|
|
||||||
90% { transform: translate(1px, 0) rotate(3deg); }
|
|
||||||
95% { transform: translate(-2px, 0) rotate(-3deg); }
|
|
||||||
100% { transform: translate(2px, 1px) rotate(2deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes mfm-jelly {
|
|
||||||
from { transform: scale3d(1, 1, 1); }
|
|
||||||
30% { transform: scale3d(1.25, 0.75, 1); }
|
|
||||||
40% { transform: scale3d(0.75, 1.25, 1); }
|
|
||||||
50% { transform: scale3d(1.15, 0.85, 1); }
|
|
||||||
65% { transform: scale3d(0.95, 1.05, 1); }
|
|
||||||
75% { transform: scale3d(1.05, 0.95, 1); }
|
|
||||||
to { transform: scale3d(1, 1, 1); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes mfm-rainbow {
|
|
||||||
0% { filter: hue-rotate(0deg) contrast(150%) saturate(150%); }
|
|
||||||
100% { filter: hue-rotate(360deg) contrast(150%) saturate(150%); }
|
|
||||||
}
|
|
|
@ -40,6 +40,64 @@ describe('RichContent', () => {
|
||||||
expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(html))
|
expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(html))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('it adds a # to the MFM color style value', () => {
|
||||||
|
const html_ok = '<span class="mfm-fg" data-mfm-color="fff">this text is not white</span>'
|
||||||
|
const expected_ok = '<span class="mfm-fg" data-mfm-color="fff" style="--mfm-color: #fff;">this text is not white</span>'
|
||||||
|
const wrapper_ok = shallowMount(RichContent, {
|
||||||
|
global,
|
||||||
|
props: {
|
||||||
|
attentions,
|
||||||
|
handleLinks: true,
|
||||||
|
greentext: true,
|
||||||
|
emoji: [],
|
||||||
|
html: html_ok
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper_ok.html()).to.eql(compwrap(expected_ok))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not allow injection through MFM data- attributes', () => {
|
||||||
|
const html_ok = '<span class="mfm-spin" data-mfm-speed="-0.2s">brrr</span>'
|
||||||
|
const expected_ok = '<span class="mfm-spin" data-mfm-speed="-0.2s" style="--mfm-speed: -0.2s;">brrr</span>'
|
||||||
|
const wrapper_ok = shallowMount(RichContent, {
|
||||||
|
global,
|
||||||
|
props: {
|
||||||
|
attentions,
|
||||||
|
handleLinks: true,
|
||||||
|
greentext: true,
|
||||||
|
emoji: [],
|
||||||
|
html: html_ok
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const html_nok1 = '<span class="mfm-spin" data-mfm-speed="<">brrr</span>'
|
||||||
|
const wrapper_nok1 = shallowMount(RichContent, {
|
||||||
|
global,
|
||||||
|
props: {
|
||||||
|
attentions,
|
||||||
|
handleLinks: true,
|
||||||
|
greentext: true,
|
||||||
|
emoji: [],
|
||||||
|
html: html_nok1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const html_nok2 = '<span class="mfm-spin" data-mfm-speed="\\">brrr</span>'
|
||||||
|
const wrapper_nok2 = shallowMount(RichContent, {
|
||||||
|
global,
|
||||||
|
props: {
|
||||||
|
attentions,
|
||||||
|
handleLinks: true,
|
||||||
|
greentext: true,
|
||||||
|
emoji: [],
|
||||||
|
html: html_nok2
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper_ok.html()).to.eql(compwrap(expected_ok))
|
||||||
|
expect(wrapper_nok1.html()).to.eql(compwrap(html_nok1))
|
||||||
|
expect(wrapper_nok2.html()).to.eql(compwrap(html_nok2))
|
||||||
|
})
|
||||||
|
|
||||||
it('unescapes everything as needed', () => {
|
it('unescapes everything as needed', () => {
|
||||||
const html = [
|
const html = [
|
||||||
p('Testing 'em all'),
|
p('Testing 'em all'),
|
||||||
|
|
Loading…
Reference in a new issue
I think the whole emoji size scaling setup is overly complicated. Just setting
--mfm-emoji-zoom-size: 2.63;
etc as here in all relevant cases and using this with a default of1.0
directly in the base emojiheight
definition would achieve the same effect as now but avoid manually overridingheight
directly and--nested-base-emoji-size
for some MFM effects.However, while i appreciate the effort which went into preserving Akkoma’s old emoji scaling exactly (except for nested scaling which previously was a noop), this scaling behaviour is not compatible with Foundkey, iceshrimp and likely all other keys and imho leads to a jarring discrepancy as emoji get scaled noticeable more than text. Foundkey and IceShrimp set the emoji height to
2em
instead of a fixed pixel value which means they’ll get automatically be scaled together with and by the same amount as text without any emoji-specific scaling logic. Imho we should adopt this too; improving portability and simplifying our logic at the same time.Yes! I didn't understand why I needed extra CSS for emoji while Foundey didn't ^^' Using
em
works perfectly, thx!One problem I saw with this, as you say, *key uses 2em. But Akkoma uses 38px, which corresponds to ~2.9px. So that means that we'd have smaller emoji than what people are used to on Akkoma-fe. I see two options
--emoji-size: 2em;
. In normal situations, emoji will be 38px as they are now. When a zoom is used, the emoji will be e.g.2em * 200%
. For tada, it will start at2em
, so smaller than "normal", but then it grows bigger bc that's what tada does.When I try, this looks OK for me. It may cause things to be slightly off for things like mfmArt, but if we want to be completely compatible with the most advanced mfmArt, we have to change a lot more I'm afraid. So the question then becomes how far we want to go with that.
I now pushed a new commit with the second option, because it looks best to me, but it can still be changed if needed.
i was thinking of continuing to retain the current base size for non-MFM posts, but use
2em
for MFM posts only.This way scaling remains striaghtforward and we’re more compatible while the majority of posts (not-MFM) doesn't change at all
That one has crossed my mind too, but also has its problems (and worse imo).
From a code-complexity perspective; We now need a way for css to know what posts are mfm and not. It's doable, we can add a class somewhere with what content they were written in (or at least that it's MFM). But that's more complexity than what I did now (just one extra line in the css that already handles the zoom), so not an advantage in that regard, on the contrary.
It also means that input method now has an effect on the end result. But input method should purely be a client thing imo, transforming input into a proper representation. With MFM we couldn't do that, hence the work that this PR is part of. Bringing back the requirenment for knowing the input method and doing special handling goes against the "raison d'être" of this work for me.
There's also the fact that it may not be obvious to people why a custom emoji is smaller in some cases and bigger in others. Not all posts who are marked as having MFM are always MfmArt. So in those cases you'd have a perfectly normal-looking post, but suddenly the emoji have a different size than what people expect. This is internally inconsistent and I fear this will bring more confusion.
If it wasn’t dropped as part of this change we already have that via the
.mfm
class. In fact this change is part of the client-side overrides I’m applying atm to make MFM post behave better.current MFM CSS overrides for reference
Input methods already have an effect on the end result, e.g. MFM is rendered as an inline block, line spacing differs between MFM,Markdown and BBCode,plain text, plain text quotes get coloured while in Markdown and MFM it gets shifted, set in faint text /Or italics atm) and a bar is added, etc
Each input method is a different typeset language and to get the same result across different input methods, different input is required (if possible at all).
MFM in particular comes with stricter expectations on what constitutes a correct display and is defined by *key instances. One of those requirements being
2em
-sized emoji.I should maybe rephrase myself. We get an object who has a
content
field. It also has asource
field. Currently we use thesource
field, and that causes problems. This whole excercise here is about making it so that thecontent
can be used without requiring thesource
field.Afaik, the differences you mention don't have anything to do with the
source
(i.e. stated input method) field at all, they are purely derived from the content. The only exception is MFM, and that's what this is trying to fix.I certainly do not have the expectation of emoji to suddenly look different, and I very much believe it will only cause more confusion. Emoji are federated just like any one else federates them, so there's no special expectation there. There's is also no specific mention of
2em
on https://misskey-hub.net/en/docs/for-users/features/mfm/Besides, if that's really the case to make, that the content should look like it does on Misskey; how far should this be taken? Because there's more than just "emoji are
2em
size". Should the text font be different? Should the spacings be different? Should we render the spacing differend? Heck, should the width of the content be changed? And so on... If yes, then we dissagree on a very fundamental level which I do not see how to resolve.Maybe a more important question; Do you consider this a blocking issue? If not, I'd like to keep it for now, as I do consider it a strict improvement regardless, and then see what people think.