diff --git a/CHANGELOG.md b/CHANGELOG.md index d9e69e8f0a..f8291a1357 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ unreleased * 外部サービス認証情報の配信 * 管理画面のモデレーションのUIを強化 * 管理画面からリモートユーザーの情報を更新できるように +* 回転構文の追加 * シンタックスハイライトの強化 * 引用投稿を削除したとき単なるRenoteとしてタイムラインに残る問題を修正 * イタリック構文の判定の改善 diff --git a/src/client/app/animation.styl b/src/client/app/animation.styl index a629165207..9cbd3ec6c8 100644 --- a/src/client/app/animation.styl +++ b/src/client/app/animation.styl @@ -26,3 +26,8 @@ transform: translateY(0); } } + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} diff --git a/src/client/app/common/views/components/mfm.ts b/src/client/app/common/views/components/mfm.ts index 8ffa566666..f6f95deb24 100644 --- a/src/client/app/common/views/components/mfm.ts +++ b/src/client/app/common/views/components/mfm.ts @@ -124,6 +124,17 @@ export default Vue.component('misskey-flavored-markdown', { }, genEl(token.children)); } + case 'spin': { + 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: spin 1.5s linear infinite;' + }, + }, genEl(token.children)); + } + case 'url': { return [createElement(MkUrl, { key: Math.random(), diff --git a/src/mfm/html.ts b/src/mfm/html.ts index 6af2833858..a40ff19ac8 100644 --- a/src/mfm/html.ts +++ b/src/mfm/html.ts @@ -55,6 +55,12 @@ export default (tokens: MfmForest, mentionedRemoteUsers: INote['mentionedRemoteU return el; }, + spin(token) { + const el = doc.createElement('i'); + appendChildren(token.children, el); + return el; + }, + blockCode(token) { const pre = doc.createElement('pre'); const inner = doc.createElement('code'); diff --git a/src/mfm/parser.ts b/src/mfm/parser.ts index 560e226af9..5cd9fc04c2 100644 --- a/src/mfm/parser.ts +++ b/src/mfm/parser.ts @@ -91,6 +91,7 @@ const mfm = P.createLanguage({ root: r => P.alt( r.big, r.small, + r.spin, r.bold, r.strike, r.italic, @@ -122,6 +123,7 @@ const mfm = P.createLanguage({ r.hashtag, r.emoji, r.math, + r.spin, r.text ).atLeast(1).tryParse(x), {})), //#endregion @@ -140,6 +142,15 @@ const mfm = P.createLanguage({ ).atLeast(1).tryParse(x), {})), //#endregion + //#region Spin + spin: r => + P.regexp(/(.+?)<\/spin>/, 1) + .map(x => createTree('spin', P.alt( + r.emoji, + r.text + ).atLeast(1).tryParse(x), {})), + //#endregion + //#region Block code blockCode: r => newline.then( @@ -173,6 +184,7 @@ const mfm = P.createLanguage({ .map(x => createTree('center', P.alt( r.big, r.small, + r.spin, r.bold, r.strike, r.italic, @@ -261,6 +273,7 @@ const mfm = P.createLanguage({ return createTree('link', P.alt( r.big, r.small, + r.spin, r.bold, r.strike, r.italic, @@ -304,6 +317,7 @@ const mfm = P.createLanguage({ .map(x => createTree('motion', P.alt( r.bold, r.small, + r.spin, r.strike, r.italic, r.mention, @@ -364,6 +378,7 @@ const mfm = P.createLanguage({ const contents = P.alt( r.big, r.small, + r.spin, r.bold, r.strike, r.italic, diff --git a/test/mfm.ts b/test/mfm.ts index a5ea4b2933..d8cba8ee15 100644 --- a/test/mfm.ts +++ b/test/mfm.ts @@ -244,6 +244,15 @@ describe('MFM', () => { ]); }); + it('spin', () => { + const tokens = analyze(':foo:'); + assert.deepStrictEqual(tokens, [ + tree('spin', [ + leaf('emoji', { name: 'foo' }) + ], {}), + ]); + }); + describe('motion', () => { it('by triple brackets', () => { const tokens = analyze('(((foo)))');