mirror of
https://akkoma.dev/AkkomaGang/akkoma-fe
synced 2025-04-30 11:09:30 +08:00
Merge branch 'develop' into stable
This commit is contained in:
commit
578ef52df6
21 changed files with 730 additions and 415 deletions
|
@ -6,7 +6,6 @@
|
|||
<title>Akkoma</title>
|
||||
<link rel="stylesheet" href="/static/font/tiresias.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/theme-holder.css" id="theme-holder">
|
||||
<!--server-generated-meta-->
|
||||
|
|
|
@ -59,7 +59,8 @@ export default {
|
|||
{
|
||||
'-reverse': this.reverseLayout,
|
||||
'-no-sticky-headers': this.noSticky,
|
||||
'-has-new-post-button': this.newPostButtonShown
|
||||
'-has-new-post-button': this.newPostButtonShown,
|
||||
'-wide-timeline': this.widenTimeline
|
||||
},
|
||||
'-' + this.layoutType
|
||||
]
|
||||
|
@ -93,6 +94,9 @@ export default {
|
|||
newPostButtonShown () {
|
||||
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
|
||||
},
|
||||
widenTimeline () {
|
||||
return this.$store.getters.mergedConfig.widenTimeline
|
||||
},
|
||||
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
|
||||
editingAvailable () { return this.$store.state.instance.editingAvailable },
|
||||
layoutType () { return this.$store.state.interface.layoutType },
|
||||
|
|
|
@ -172,6 +172,10 @@ nav {
|
|||
background-color: rgba(0, 0, 0, 0.15);
|
||||
background-color: var(--underlay, rgba(0, 0, 0, 0.15));
|
||||
z-index: -1000;
|
||||
|
||||
.-wide-timeline & {
|
||||
margin:0 calc(var(--columnGap) / -2);
|
||||
}
|
||||
}
|
||||
|
||||
.app-layout {
|
||||
|
@ -187,12 +191,17 @@ nav {
|
|||
grid-template-rows: 1fr;
|
||||
box-sizing: border-box;
|
||||
margin: 0 auto;
|
||||
padding: 0 calc(var(--columnGap) / 2);
|
||||
align-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
overflow-x: clip;
|
||||
|
||||
&.-wide-timeline {
|
||||
--maxiColumn: minmax(var(--miniColumn), 1fr);
|
||||
}
|
||||
|
||||
.column {
|
||||
--___columnMargin: var(--columnGap);
|
||||
|
||||
|
|
|
@ -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 }))
|
||||
|
||||
/**
|
||||
|
|
|
@ -88,10 +88,8 @@ const Gallery = {
|
|||
set(this.sizes, id, { width, height })
|
||||
},
|
||||
rowStyle (row) {
|
||||
if (row.audio) {
|
||||
return { 'padding-bottom': '25%' } // fixed reduced height for audio
|
||||
} else if (!row.minimal && !row.grid) {
|
||||
return { 'padding-bottom': `${(100 / (row.items.length + 0.6))}%` }
|
||||
if (!row.audio && !row.minimal && !row.grid) {
|
||||
return { 'aspect-ratio': `1/${(1 / (row.items.length + 0.6))}` }
|
||||
}
|
||||
},
|
||||
itemStyle (id, row) {
|
||||
|
|
|
@ -96,9 +96,15 @@
|
|||
|
||||
.gallery-row {
|
||||
position: relative;
|
||||
height: 0;
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
.Status & {
|
||||
max-height: 10em;
|
||||
}
|
||||
|
||||
&.-audio {
|
||||
aspect-ratio: 4/1; // this is terrible, but it's how it was before so I'm not changing it >:(
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: 0.5em;
|
||||
|
|
|
@ -121,6 +121,19 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
const mfmStyleFromDataAttributes = (attributes) => {
|
||||
// CSS selectors can check if a data-* attribute is true, but can't use other values, so we want to add them to the style attribute
|
||||
// Here we turn e.g. `{'data-mfm-some': '1deg', 'data-mfm-thing': '5s'}` to "--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 legitimate 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
|
||||
return Object.keys(attributes).filter(
|
||||
(key) => key.startsWith('data-mfm-') && attributes[key] !== true && /^[a-zA-Z0-9.\-]*$/.test(attributes[key])
|
||||
).map(
|
||||
(key) => '--mfm-' + key.substr(9) + (key === 'data-mfm-color' ? ': #' : ': ') + attributes[key] + ';'
|
||||
).reduce((a,v) => a+v, '')
|
||||
}
|
||||
|
||||
// Processor to use with html_tree_converter
|
||||
const processItem = (item, index, array, what) => {
|
||||
// Handle text nodes - just add emoji
|
||||
|
@ -191,6 +204,15 @@ export default {
|
|||
if (this.handleLinks && attrs?.['class']?.includes?.('h-card')) {
|
||||
return ['', children.map(processItem), '']
|
||||
}
|
||||
|
||||
let mfm_style = mfmStyleFromDataAttributes(attrs)
|
||||
if (mfm_style !== '') {
|
||||
return [
|
||||
opener.slice(0,-1) + ' style="' + mfm_style + '">',
|
||||
children.map(processItem),
|
||||
closer
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
if (children !== undefined) {
|
||||
|
|
|
@ -159,6 +159,16 @@
|
|||
{{ $t('settings.show_page_backgrounds') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="centerAlignBio">
|
||||
{{ $t('settings.center_align_bio') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="compactUserInfo">
|
||||
{{ $t('settings.compact_user_info') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="stopGifs">
|
||||
{{ $t('settings.stop_gifs') }}
|
||||
|
@ -269,6 +279,11 @@
|
|||
{{ $t('settings.right_sidebar') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="widenTimeline">
|
||||
{{ $t('settings.widen_timeline') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<ChoiceSetting
|
||||
v-if="user"
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
.StatusBody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.translation {
|
||||
border: 1px solid var(--accent, $fallback--link);
|
||||
|
@ -23,24 +24,6 @@
|
|||
transition: 0.05s;
|
||||
}
|
||||
|
||||
._mfm_x2_ {
|
||||
.emoji {
|
||||
height: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
._mfm_x3_ {
|
||||
.emoji {
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
._mfm_x4_ {
|
||||
.emoji {
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.attachments {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div
|
||||
class="StatusBody"
|
||||
:class="{ '-compact': compact, 'mfm-disabled': !renderMisskeyMarkdown }"
|
||||
:class="{ '-compact': compact }"
|
||||
>
|
||||
<div class="body">
|
||||
<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>
|
||||
|
||||
<script src="./status_content.js"></script>
|
||||
<style lang="scss" src="./mfm.scss" />
|
||||
<style lang="scss">
|
||||
.StatusContent {
|
||||
flex: 1;
|
||||
|
@ -75,23 +76,6 @@
|
|||
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,
|
||||
|
|
|
@ -117,6 +117,11 @@ export default {
|
|||
shouldConfirmMute () {
|
||||
return this.mergedConfig.modalOnMute
|
||||
},
|
||||
compactUserInfo () {
|
||||
return this.$store.getters.mergedConfig.compactUserInfo
|
||||
&& (this.$store.state.interface.layoutType !== 'mobile')
|
||||
&& this.switcher
|
||||
},
|
||||
...mapGetters(['mergedConfig'])
|
||||
},
|
||||
components: {
|
||||
|
|
|
@ -21,6 +21,13 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.user-buttons {
|
||||
grid-area: edit;
|
||||
display: flex;
|
||||
padding: .5em 0 .5em 0;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
word-wrap: break-word;
|
||||
border-bottom-right-radius: inherit;
|
||||
|
@ -53,7 +60,6 @@
|
|||
}
|
||||
|
||||
&-bio {
|
||||
text-align: center;
|
||||
display: block;
|
||||
line-height: 1.3;
|
||||
padding: 1em;
|
||||
|
@ -100,15 +106,14 @@
|
|||
padding: 0 26px;
|
||||
|
||||
.container {
|
||||
min-width: 0;
|
||||
padding: 16px 0 6px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
max-height: 56px;
|
||||
|
||||
> * {
|
||||
min-width: 0;
|
||||
}
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"pfp name edit"
|
||||
"pfp summary summary"
|
||||
"stats stats stats";
|
||||
grid-template-columns: auto 1fr auto;
|
||||
align-items: start;
|
||||
|
||||
.Avatar {
|
||||
--_avatarShadowBox: var(--avatarShadow);
|
||||
|
@ -123,6 +128,7 @@
|
|||
}
|
||||
|
||||
&-avatar-link {
|
||||
grid-area: pfp;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
|
@ -153,8 +159,8 @@
|
|||
|
||||
.external-link-button, .edit-profile-button {
|
||||
cursor: pointer;
|
||||
width: 2.5em;
|
||||
text-align: center;
|
||||
width: 2.3em;
|
||||
text-align: right;
|
||||
margin: -0.5em 0;
|
||||
padding: 0.5em 0;
|
||||
|
||||
|
@ -165,12 +171,16 @@
|
|||
}
|
||||
|
||||
.user-summary {
|
||||
display: block;
|
||||
grid-area: summary;
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"name name name name name"
|
||||
"hand role lock avg _";
|
||||
grid-template-columns:
|
||||
auto auto auto auto 1fr;
|
||||
justify-items: start;
|
||||
margin-left: 0.6em;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1 1 0;
|
||||
// This is so that text doesn't get overlapped by avatar's shadow if it has
|
||||
// big one
|
||||
z-index: 1;
|
||||
|
@ -178,55 +188,81 @@
|
|||
|
||||
--emoji-size: 1.7em;
|
||||
|
||||
.top-line,
|
||||
.bottom-line {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.user-name {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
flex: 1 1 auto;
|
||||
margin-right: 1em;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.bottom-line {
|
||||
font-weight: light;
|
||||
font-size: 1.1em;
|
||||
align-items: baseline;
|
||||
|
||||
.lock-icon {
|
||||
.user-locked {
|
||||
margin-left: 0.5em;
|
||||
grid-area: lock;
|
||||
}
|
||||
|
||||
.user-screen-name {
|
||||
min-width: 1px;
|
||||
flex: 0 1 auto;
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
color: $fallback--lightText;
|
||||
color: var(--lightText, $fallback--lightText);
|
||||
grid-area: hand;
|
||||
}
|
||||
|
||||
.dailyAvg {
|
||||
min-width: 1px;
|
||||
flex: 0 0 auto;
|
||||
margin-left: 1em;
|
||||
font-size: 0.7em;
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
grid-area: avg;
|
||||
}
|
||||
|
||||
.user-roles {
|
||||
display: flex;
|
||||
grid-area: role;
|
||||
|
||||
.user-role {
|
||||
flex: none;
|
||||
color: $fallback--text;
|
||||
color: var(--alertNeutralText, $fallback--text);
|
||||
background-color: $fallback--fg;
|
||||
background-color: var(--alertNeutral, $fallback--fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-counts {
|
||||
grid-area: stats;
|
||||
display: flex;
|
||||
line-height:16px;
|
||||
padding-top: 0.5em;
|
||||
text-align: center;
|
||||
justify-content: space-around;
|
||||
color: $fallback--lightText;
|
||||
color: var(--lightText, $fallback--lightText);
|
||||
align-self: center;
|
||||
|
||||
.user-count {
|
||||
padding: .5em 0 .5em 0;
|
||||
margin: 0 .5em;
|
||||
|
||||
h5 {
|
||||
font-size:1em;
|
||||
font-weight: bolder;
|
||||
margin: 0 0 0.25em;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-name {
|
||||
text-align: start;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
margin-left: 0.6em;
|
||||
font-size: 1.1em;
|
||||
grid-area: name;
|
||||
align-self: center;
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
z-index: 1; // so shadow from user avatar doesn't overlap it
|
||||
}
|
||||
|
||||
.user-meta {
|
||||
margin-bottom: .15em;
|
||||
|
@ -290,34 +326,21 @@
|
|||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.-compact {
|
||||
.container {
|
||||
grid-template-areas:
|
||||
"pfp name stats edit"
|
||||
"pfp summary stats edit";
|
||||
grid-template-columns: auto auto 1fr auto;
|
||||
}
|
||||
.user-counts {
|
||||
padding-top: 0;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar .edit-profile-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.user-counts {
|
||||
display: flex;
|
||||
line-height:16px;
|
||||
padding: .5em 1.5em 0em 1.5em;
|
||||
text-align: center;
|
||||
justify-content: space-between;
|
||||
color: $fallback--lightText;
|
||||
color: var(--lightText, $fallback--lightText);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.user-count {
|
||||
flex: 1 0 auto;
|
||||
padding: .5em 0 .5em 0;
|
||||
margin: 0 .5em;
|
||||
|
||||
h5 {
|
||||
font-size:1em;
|
||||
font-weight: bolder;
|
||||
margin: 0 0 0.25em;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,10 @@
|
|||
class="background-image"
|
||||
/>
|
||||
<div class="panel-heading -flexible-height">
|
||||
<div class="user-info">
|
||||
<div
|
||||
class="user-info"
|
||||
:class="{ '-compact': this.compactUserInfo }"
|
||||
>
|
||||
<div class="container">
|
||||
<a
|
||||
v-if="allowZoomingAvatar"
|
||||
|
@ -29,6 +32,7 @@
|
|||
</a>
|
||||
<router-link
|
||||
v-else
|
||||
class="user-info-avatar-link"
|
||||
:to="userProfileLink(user)"
|
||||
>
|
||||
<UserAvatar
|
||||
|
@ -36,14 +40,79 @@
|
|||
:user="user"
|
||||
/>
|
||||
</router-link>
|
||||
<div class="user-summary">
|
||||
<div class="top-line">
|
||||
<RichContent
|
||||
:title="user.name"
|
||||
class="user-name"
|
||||
:html="user.name"
|
||||
:emoji="user.emoji"
|
||||
/>
|
||||
<div class="user-summary">
|
||||
<router-link
|
||||
class="user-screen-name"
|
||||
:title="user.screen_name_ui"
|
||||
:to="userProfileLink(user)"
|
||||
>
|
||||
@{{ user.screen_name_ui }}
|
||||
</router-link>
|
||||
<span class="user-roles" v-if="!hideBio && (user.deactivated || !!visibleRole || user.bot)">
|
||||
<span
|
||||
v-if="user.deactivated"
|
||||
class="alert user-role"
|
||||
>
|
||||
{{ $t('user_card.deactivated') }}
|
||||
</span>
|
||||
<span
|
||||
v-if="!!visibleRole"
|
||||
class="alert user-role"
|
||||
>
|
||||
{{ $t(`general.role.${visibleRole}`) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="user.bot"
|
||||
class="alert user-role"
|
||||
>
|
||||
{{ $t('user_card.bot') }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="user-locked" v-if="user.locked">
|
||||
<FAIcon
|
||||
class="lock-icon"
|
||||
icon="lock"
|
||||
size="sm"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
v-if="!mergedConfig.hideUserStats && !hideBio"
|
||||
class="dailyAvg"
|
||||
>{{ dailyAvg }} {{ $t('user_card.per_day') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="!mergedConfig.hideUserStats && switcher"
|
||||
class="user-counts"
|
||||
>
|
||||
<div
|
||||
class="user-count"
|
||||
@click.prevent="setProfileView('statuses')"
|
||||
>
|
||||
<h5>{{ $t('user_card.statuses') }}</h5>
|
||||
<span>{{ user.statuses_count }} <br></span>
|
||||
</div>
|
||||
<div
|
||||
class="user-count"
|
||||
@click.prevent="setProfileView('friends')"
|
||||
>
|
||||
<h5>{{ $t('user_card.followees') }}</h5>
|
||||
<span>{{ hideFollowsCount ? $t('user_card.hidden') : user.friends_count }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="user-count"
|
||||
@click.prevent="setProfileView('followers')"
|
||||
>
|
||||
<h5>{{ $t('user_card.followers') }}</h5>
|
||||
<span>{{ hideFollowersCount ? $t('user_card.hidden') : user.followers_count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-buttons">
|
||||
<button
|
||||
v-if="!isOtherUser && user.is_local"
|
||||
class="button-unstyled edit-profile-button"
|
||||
|
@ -84,47 +153,6 @@
|
|||
:relationship="relationship"
|
||||
/>
|
||||
</div>
|
||||
<div class="bottom-line">
|
||||
<router-link
|
||||
class="user-screen-name"
|
||||
:title="user.screen_name_ui"
|
||||
:to="userProfileLink(user)"
|
||||
>
|
||||
@{{ user.screen_name_ui }}
|
||||
</router-link>
|
||||
<template v-if="!hideBio">
|
||||
<span
|
||||
v-if="user.deactivated"
|
||||
class="alert user-role"
|
||||
>
|
||||
{{ $t('user_card.deactivated') }}
|
||||
</span>
|
||||
<span
|
||||
v-if="!!visibleRole"
|
||||
class="alert user-role"
|
||||
>
|
||||
{{ $t(`general.role.${visibleRole}`) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="user.bot"
|
||||
class="alert user-role"
|
||||
>
|
||||
{{ $t('user_card.bot') }}
|
||||
</span>
|
||||
</template>
|
||||
<span v-if="user.locked">
|
||||
<FAIcon
|
||||
class="lock-icon"
|
||||
icon="lock"
|
||||
size="sm"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
v-if="!mergedConfig.hideUserStats && !hideBio"
|
||||
class="dailyAvg"
|
||||
>{{ dailyAvg }} {{ $t('user_card.per_day') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-meta">
|
||||
<div
|
||||
|
@ -269,38 +297,13 @@
|
|||
v-if="!hideBio"
|
||||
class="panel-body"
|
||||
>
|
||||
<div
|
||||
v-if="!mergedConfig.hideUserStats && switcher"
|
||||
class="user-counts"
|
||||
>
|
||||
<div
|
||||
class="user-count"
|
||||
@click.prevent="setProfileView('statuses')"
|
||||
>
|
||||
<h5>{{ $t('user_card.statuses') }}</h5>
|
||||
<span>{{ user.statuses_count }} <br></span>
|
||||
</div>
|
||||
<div
|
||||
class="user-count"
|
||||
@click.prevent="setProfileView('friends')"
|
||||
>
|
||||
<h5>{{ $t('user_card.followees') }}</h5>
|
||||
<span>{{ hideFollowsCount ? $t('user_card.hidden') : user.friends_count }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="user-count"
|
||||
@click.prevent="setProfileView('followers')"
|
||||
>
|
||||
<h5>{{ $t('user_card.followers') }}</h5>
|
||||
<span>{{ hideFollowersCount ? $t('user_card.hidden') : user.followers_count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<RichContent
|
||||
v-if="!hideBio"
|
||||
class="user-card-bio"
|
||||
:html="user.description_html"
|
||||
:emoji="user.emoji"
|
||||
:handle-links="true"
|
||||
:style='{"text-align": this.$store.getters.mergedConfig.centerAlignBio ? "center" : "start"}'
|
||||
/>
|
||||
</div>
|
||||
<teleport to="#modal">
|
||||
|
|
|
@ -482,6 +482,7 @@
|
|||
"blocks_tab": "Blocks",
|
||||
"bot": "Dies ist ein Bot Account",
|
||||
"btnRadius": "Knöpfe",
|
||||
"center_align_bio": "Zentrale Textausrichtung in der Bio",
|
||||
"cBlue": "Blau (Antworten, folgt dir)",
|
||||
"cGreen": "Grün (Retweet)",
|
||||
"cOrange": "Orange (Favorisieren)",
|
||||
|
@ -496,6 +497,7 @@
|
|||
"checkboxRadius": "Auswahlfelder",
|
||||
"collapse_subject": "Beiträge mit Inhaltswarnungen einklappen",
|
||||
"columns": "Spalten",
|
||||
"compact_user_info": "Kompakte Benutzerinfos wenn genug Platz",
|
||||
"composing": "Verfassen",
|
||||
"confirm_dialogs": "Bestätigung erforderlich für:",
|
||||
"confirm_dialogs_approve_follow": "Annehmen einer Followanfrage",
|
||||
|
@ -934,6 +936,7 @@
|
|||
"title": "Version"
|
||||
},
|
||||
"virtual_scrolling": "Anzeige der Zeitleiste optimieren",
|
||||
"widen_timeline": "Zeitleiste verbreitern, um horizontalen Platz zu füllen",
|
||||
"word_filter": "Wortfilter",
|
||||
"wordfilter": "Wortfilter"
|
||||
},
|
||||
|
|
|
@ -488,6 +488,7 @@
|
|||
"blocks_tab": "Blocks",
|
||||
"bot": "This is a bot account",
|
||||
"btnRadius": "Buttons",
|
||||
"center_align_bio": "Center text in user bio",
|
||||
"cBlue": "Blue (Reply, follow)",
|
||||
"cGreen": "Green (Retweet)",
|
||||
"cOrange": "Orange (Favorite)",
|
||||
|
@ -502,6 +503,7 @@
|
|||
"checkboxRadius": "Checkboxes",
|
||||
"collapse_subject": "Collapse posts with content warnings",
|
||||
"columns": "Columns",
|
||||
"compact_user_info": "Compact user info when enough space",
|
||||
"composing": "Composing",
|
||||
"confirm_dialogs": "Require confirmation for:",
|
||||
"confirm_dialogs_approve_follow": "Accepting a follow request",
|
||||
|
@ -948,6 +950,7 @@
|
|||
},
|
||||
"virtual_scrolling": "Optimize timeline rendering",
|
||||
"use_blurhash": "Use blurhashes for NSFW thumbnails",
|
||||
"widen_timeline": "Widen the Timeline to fill horizontal space",
|
||||
"word_filter": "Word filter",
|
||||
"wordfilter": "Wordfilter"
|
||||
},
|
||||
|
|
|
@ -56,6 +56,8 @@ export const defaultState = {
|
|||
autohideFloatingPostButton: false,
|
||||
pauseOnUnfocused: true,
|
||||
displayPageBackgrounds: true,
|
||||
centerAlignBio: false,
|
||||
compactUserInfo: true,
|
||||
stopGifs: undefined,
|
||||
replyVisibility: 'all',
|
||||
thirdColumnMode: 'notifications',
|
||||
|
@ -77,6 +79,7 @@ export const defaultState = {
|
|||
hideScopeNotice: false,
|
||||
useStreamingApi: false,
|
||||
sidebarRight: undefined, // instance default
|
||||
widenTimeline: undefined, // instance default
|
||||
subjectLineBehavior: undefined, // instance default
|
||||
alwaysShowSubjectInput: undefined, // instance default
|
||||
postContentType: undefined, // instance default
|
||||
|
|
|
@ -61,6 +61,7 @@ const defaultState = {
|
|||
showNavShortcuts: true,
|
||||
showWiderShortcuts: true,
|
||||
sidebarRight: false,
|
||||
widenTimeline: false,
|
||||
subjectLineBehavior: 'email',
|
||||
theme: 'pleroma-dark',
|
||||
virtualScrolling: true,
|
||||
|
|
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))
|
||||
})
|
||||
|
||||
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', () => {
|
||||
const html = [
|
||||
p('Testing 'em all'),
|
||||
|
|
Loading…
Reference in a new issue