diff --git a/src/client/app/animation.styl b/src/client/app/animation.styl index 9cbd3ec6c..9a0ec2729 100644 --- a/src/client/app/animation.styl +++ b/src/client/app/animation.styl @@ -31,3 +31,11 @@ 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } + +@keyframes jump { + 0% { transform: translateY(0); } + 25% { transform: translateY(-16px); } + 50% { transform: translateY(0); } + 75% { transform: translateY(-8px); } + 100% { transform: translateY(0); } +} diff --git a/src/client/app/common/views/components/mfm.ts b/src/client/app/common/views/components/mfm.ts index 199d6bb97..542f1e34c 100644 --- a/src/client/app/common/views/components/mfm.ts +++ b/src/client/app/common/views/components/mfm.ts @@ -135,6 +135,17 @@ export default Vue.component('misskey-flavored-markdown', { }, genEl(token.children)); } + case 'jump': { + motionCount++; + const isLong = sumTextsLength(token.children) > 5 || countNodesF(token.children) > 3; + const isMany = motionCount > 3; + return (createElement as any)('span', { + attrs: { + style: (this.$store.state.settings.disableAnimatedMfm || isLong || isMany) ? 'display: inline-block;' : 'display: inline-block; animation: jump 0.75s linear infinite;' + }, + }, genEl(token.children)); + } + case 'flip': { return (createElement as any)('span', { attrs: { diff --git a/src/mfm/html.ts b/src/mfm/html.ts index acd6891ab..6dba6defe 100644 --- a/src/mfm/html.ts +++ b/src/mfm/html.ts @@ -61,6 +61,12 @@ export default (tokens: MfmForest, mentionedRemoteUsers: INote['mentionedRemoteU return el; }, + jump(token) { + const el = doc.createElement('i'); + appendChildren(token.children, el); + return el; + }, + flip(token) { const el = doc.createElement('span'); appendChildren(token.children, el); diff --git a/src/mfm/parser.ts b/src/mfm/parser.ts index 1d72496a6..6b7c3c584 100644 --- a/src/mfm/parser.ts +++ b/src/mfm/parser.ts @@ -92,6 +92,7 @@ const mfm = P.createLanguage({ r.big, r.small, r.spin, + r.jump, r.bold, r.strike, r.italic, @@ -126,6 +127,7 @@ const mfm = P.createLanguage({ r.emoji, r.mathInline, r.spin, + r.jump, r.text ).atLeast(1).tryParse(x), {})), //#endregion @@ -154,6 +156,15 @@ const mfm = P.createLanguage({ ).atLeast(1).tryParse(x), {})), //#endregion + //#region Jump + jump: r => + P.regexp(/(.+?)<\/jump>/, 1) + .map(x => createTree('jump', P.alt( + r.emoji, + r.text + ).atLeast(1).tryParse(x), {})), + //#endregion + //#region Block code blockCode: r => newline.then( @@ -189,6 +200,7 @@ const mfm = P.createLanguage({ r.big, r.small, r.spin, + r.jump, r.bold, r.strike, r.italic, @@ -240,6 +252,7 @@ const mfm = P.createLanguage({ r.big, r.small, r.spin, + r.jump, r.bold, r.strike, r.link, @@ -297,6 +310,7 @@ const mfm = P.createLanguage({ r.big, r.small, r.spin, + r.jump, r.bold, r.strike, r.italic, @@ -347,6 +361,7 @@ const mfm = P.createLanguage({ r.bold, r.small, r.spin, + r.jump, r.strike, r.italic, r.mention, @@ -410,6 +425,7 @@ const mfm = P.createLanguage({ r.big, r.small, r.spin, + r.jump, r.bold, r.strike, r.italic, diff --git a/test/mfm.ts b/test/mfm.ts index 7070329f3..0d07add0e 100644 --- a/test/mfm.ts +++ b/test/mfm.ts @@ -262,6 +262,15 @@ describe('MFM', () => { ]); }); + it('jump', () => { + const tokens = analyze(':foo:'); + assert.deepStrictEqual(tokens, [ + tree('jump', [ + leaf('emoji', { name: 'foo' }) + ], {}), + ]); + }); + describe('motion', () => { it('by triple brackets', () => { const tokens = analyze('(((foo)))');