From a8d2103980da04aa473f4ba7c5c20b9392c23027 Mon Sep 17 00:00:00 2001
From: Mar0xy <marie@kaifa.ch>
Date: Wed, 4 Oct 2023 03:36:26 +0200
Subject: [PATCH] upd: add MFM Cheatsheet

Closes transfem-org/Sharkey#21
---
 locales/en-US.yml                             |  73 +++
 .../frontend/src/components/MkMfmWindow.vue   | 476 ++++++++++++++++++
 .../frontend/src/components/MkPostForm.vue    |   7 +-
 3 files changed, 555 insertions(+), 1 deletion(-)
 create mode 100644 packages/frontend/src/components/MkMfmWindow.vue

diff --git a/locales/en-US.yml b/locales/en-US.yml
index 24fece3e40..2e6cb32e31 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -2138,3 +2138,76 @@ _moderationLogTypes:
   createAd: "Ad created"
   deleteAd: "Ad deleted"
   updateAd: "Ad updated"
+_mfm:
+  intro: "MFM is a markup language used on Misskey, Sharkey, Firefish, Akkoma, and more that can be used in many places. Here you can view a list of all available MFM syntax."
+  dummy: "Sharkey expands the world of the Fediverse"
+  mention: "Mention"
+  mentionDescription: "You can specify a user by using an At-Symbol and a username."
+  hashtag: "Hashtag"
+  hashtagDescription: "You can specify a hashtag using a number sign and text."
+  url: "URL"
+  urlDescription: "URLs can be displayed."
+  link: "Link"
+  linkDescription: "Specific parts of text can be displayed as a URL."
+  bold: "Bold"
+  boldDescription: "Highlights letters by making them thicker."
+  small: "Small"
+  smallDescription: "Displays content small and thin."
+  center: "Center"
+  centerDescription: "Displays content centered."
+  inlineCode: "Code (Inline)"
+  inlineCodeDescription: "Displays inline syntax highlighting for (program) code."
+  blockCode: "Code (Block)"
+  blockCodeDescription: "Displays syntax highlighting for multi-line (program) code in a block."
+  inlineMath: "Math (Inline)"
+  inlineMathDescription: "Display math formulas (KaTeX) in-line"
+  blockMath: "Math (Block)"
+  blockMathDescription: "Display math formulas (KaTeX) in a block"
+  quote: "Quote"
+  quoteDescription: "Displays content as a quote."
+  emoji: "Custom Emoji"
+  emojiDescription: "By surrounding a custom emoji name with colons, custom emoji can be displayed."
+  search: "Search"
+  searchDescription: "Displays a search box with pre-entered text."
+  flip: "Flip"
+  flipDescription: "Flips content horizontally or vertically."
+  jelly: "Animation (Jelly)"
+  jellyDescription: "Gives content a jelly-like animation."
+  tada: "Animation (Tada)"
+  tadaDescription: "Gives content a \"Tada!\"-like animation."
+  jump: "Animation (Jump)"
+  jumpDescription: "Gives content a jumping animation."
+  bounce: "Animation (Bounce)"
+  bounceDescription: "Gives content a bouncy animation."
+  shake: "Animation (Shake)"
+  shakeDescription: "Gives content a shaking animation."
+  twitch: "Animation (Twitch)"
+  twitchDescription: "Gives content a strongly twitching animation."
+  spin: "Animation (Spin)"
+  spinDescription: "Gives content a spinning animation."
+  x2: "Big"
+  x2Description: "Displays content bigger."
+  x3: "Very big"
+  x3Description: "Displays content even bigger."
+  x4: "Unbelievably big"
+  x4Description: "Displays content even bigger than bigger than big."
+  blur: "Blur"
+  blurDescription: "Blurs content. It will be displayed clearly when hovered over."
+  font: "Font"
+  fontDescription: "Sets the font to display content in."
+  rainbow: "Rainbow"
+  rainbowDescription: "Makes the content appear in rainbow colors."
+  sparkle: "Sparkle"
+  sparkleDescription: "Gives content a sparkling particle effect."
+  rotate: "Rotate"
+  rotateDescription: "Turns content by a specified angle."
+  position: "Position"
+  positionDescription: "Move content by a specified amount."
+  scale: "Scale"
+  scaleDescription: "Scale content by a specified amount."
+  foreground: "Foreground color"
+  foregroundDescription: "Change the foreground color of text."
+  background: "Background color"
+  backgroundDescription: "Change the background color of text."
+  plain: "Plain"
+  plainDescription: "Deactivates the effects of all MFM contained within this MFM effect."
diff --git a/packages/frontend/src/components/MkMfmWindow.vue b/packages/frontend/src/components/MkMfmWindow.vue
new file mode 100644
index 0000000000..eb0ccc560a
--- /dev/null
+++ b/packages/frontend/src/components/MkMfmWindow.vue
@@ -0,0 +1,476 @@
+<template>
+<MkWindow
+	ref="window"
+	:initialWidth="600"
+	:initialHeight="800"
+	:canResize="true"
+	@closed="emit('closed')"
+>
+	<template #header>
+		MFM Cheatsheet
+	</template>
+    <MkStickyContainer>
+		<MkSpacer :content-max="800">
+			<div class="mfm-cheat-sheet">
+				<div>{{ i18n.ts._mfm.intro }}</div>
+				<br />
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.mention }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.mentionDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_mention" />
+							<MkTextarea v-model="preview_mention"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.hashtag }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.hashtagDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_hashtag" />
+							<MkTextarea v-model="preview_hashtag"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.link }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.linkDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_link" />
+							<MkTextarea v-model="preview_link"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.emoji }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.emojiDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_emoji" />
+							<MkTextarea v-model="preview_emoji"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.bold }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.boldDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_bold" />
+							<MkTextarea v-model="preview_bold"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.small }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.smallDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_small" />
+							<MkTextarea v-model="preview_small"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.quote }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.quoteDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_quote" />
+							<MkTextarea v-model="preview_quote"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.center }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.centerDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_center" />
+							<MkTextarea v-model="preview_center"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.inlineCode }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.inlineCodeDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_inlineCode" />
+							<MkTextarea v-model="preview_inlineCode"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.blockCode }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.blockCodeDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_blockCode" />
+							<MkTextarea v-model="preview_blockCode"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.inlineMath }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.inlineMathDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_inlineMath" />
+							<MkTextarea v-model="preview_inlineMath"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.blockMath }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.blockMathDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_blockMath" />
+							<MkTextarea v-model="preview_blockMath"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.search }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.searchDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_search" />
+							<MkTextarea v-model="preview_search"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.flip }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.flipDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_flip" />
+							<MkTextarea v-model="preview_flip"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.font }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.fontDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_font" />
+							<MkTextarea v-model="preview_font"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.x2 }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.x2Description }}</p>
+						<div class="preview">
+							<Mfm :text="preview_x2" />
+							<MkTextarea v-model="preview_x2"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.x3 }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.x3Description }}</p>
+						<div class="preview">
+							<Mfm :text="preview_x3" />
+							<MkTextarea v-model="preview_x3"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.x4 }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.x4Description }}</p>
+						<div class="preview">
+							<Mfm :text="preview_x4" />
+							<MkTextarea v-model="preview_x4"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.blur }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.blurDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_blur" />
+							<MkTextarea v-model="preview_blur"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.jelly }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.jellyDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_jelly" />
+							<MkTextarea v-model="preview_jelly"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.tada }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.tadaDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_tada" />
+							<MkTextarea v-model="preview_tada"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.jump }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.jumpDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_jump" />
+							<MkTextarea v-model="preview_jump"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.bounce }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.bounceDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_bounce" />
+							<MkTextarea v-model="preview_bounce"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.spin }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.spinDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_spin" />
+							<MkTextarea v-model="preview_spin"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.shake }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.shakeDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_shake" />
+							<MkTextarea v-model="preview_shake"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.twitch }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.twitchDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_twitch" />
+							<MkTextarea v-model="preview_twitch"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.rainbow }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.rainbowDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_rainbow" />
+							<MkTextarea v-model="preview_rainbow"><template #label>MFM</template></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.sparkle }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.sparkleDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_sparkle" />
+							<MkTextarea v-model="preview_sparkle"><span>MFM</span></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.rotate }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.rotateDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_rotate" />
+							<MkTextarea v-model="preview_rotate"><span>MFM</span></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.position }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.positionDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_position" />
+							<MkTextarea v-model="preview_position"><span>MFM</span></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.scale }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.scaleDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_scale" />
+							<MkTextarea v-model="preview_scale"><span>MFM</span></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.foreground }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.foregroundDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_fg" />
+							<MkTextarea v-model="preview_fg"><span>MFM</span></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.background }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.backgroundDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_bg" />
+							<MkTextarea v-model="preview_bg"><span>MFM</span></MkTextarea>
+						</div>
+					</div>
+				</div>
+				<div class="section _block">
+					<div class="title">{{ i18n.ts._mfm.plain }}</div>
+					<div class="content">
+						<p>{{ i18n.ts._mfm.plainDescription }}</p>
+						<div class="preview">
+							<Mfm :text="preview_plain" />
+							<MkTextarea v-model="preview_plain"><span>MFM</span></MkTextarea>
+						</div>
+					</div>
+				</div>
+			</div>
+		</MkSpacer>
+	</MkStickyContainer>
+</MkWindow>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue';
+import MkWindow from '@/components/MkWindow.vue';
+import MkTextarea from '@/components/MkTextarea.vue';
+import { i18n } from "@/i18n.js";
+import { instance } from "@/instance.js";
+
+const emit = defineEmits<{
+	(ev: 'closed'): void;
+}>();
+
+const preview_mention = ref("@example");
+const preview_hashtag = ref("#test");
+const preview_link = ref(`[${i18n.ts._mfm.dummy}](https://joinsharkey.org)`);
+const preview_emoji = ref(
+	instance.emojis.length ? `:${instance.emojis[0].name}:` : ":emojiname:",
+);
+const preview_bold = ref(`**${i18n.ts._mfm.dummy}**`);
+const preview_small = ref(
+	`<small>${i18n.ts._mfm.dummy}</small> $[small ${i18n.ts._mfm.dummy}]`,
+);
+const preview_center = ref(
+	`<center>${i18n.ts._mfm.dummy}</center>`,
+);
+const preview_inlineCode = ref('`<: "Hello, world!"`');
+const preview_blockCode = ref(
+	'```\n~ (#i, 100) {\n\t<: ? ((i % 15) = 0) "FizzBuzz"\n\t\t.? ((i % 3) = 0) "Fizz"\n\t\t.? ((i % 5) = 0) "Buzz"\n\t\t. i\n}\n```',
+);
+const preview_inlineMath = ref(
+	"\\(x= \\frac{-b' \\pm \\sqrt{(b')^2-ac}}{a}\\)",
+);
+const preview_blockMath = ref("\\[x= \\frac{-b' \\pm \\sqrt{(b')^2-ac}}{a}\\]");
+const preview_quote = ref(`> ${i18n.ts._mfm.dummy}`);
+const preview_search = ref(
+	`${i18n.ts._mfm.dummy} [search]\n${i18n.ts._mfm.dummy} [ๆคœ็ดข]\n${i18n.ts._mfm.dummy} ๆคœ็ดข`,
+);
+const preview_jelly = ref(
+	"$[jelly ๐Ÿฎ] $[jelly.speed=3s ๐Ÿฎ] $[jelly.delay=3s ๐Ÿฎ] $[jelly.loop=3 ๐Ÿฎ]",
+);
+const preview_tada = ref(
+	"$[tada ๐Ÿฎ] $[tada.speed=3s ๐Ÿฎ] $[tada.delay=3s ๐Ÿฎ] $[tada.loop=3 ๐Ÿฎ]",
+);
+const preview_jump = ref(
+	"$[jump ๐Ÿฎ] $[jump.speed=3s ๐Ÿฎ] $[jump.delay=3s ๐Ÿฎ] $[jump.loop=3 ๐Ÿฎ]",
+);
+const preview_bounce = ref(
+	"$[bounce ๐Ÿฎ] $[bounce.speed=3s ๐Ÿฎ] $[bounce.delay=3s ๐Ÿฎ] $[bounce.loop=3 ๐Ÿฎ]",
+);
+const preview_shake = ref(
+	"$[shake ๐Ÿฎ] $[shake.speed=3s ๐Ÿฎ] $[shake.delay=3s ๐Ÿฎ] $[shake.loop=3 ๐Ÿฎ]",
+);
+const preview_twitch = ref(
+	"$[twitch ๐Ÿฎ] $[twitch.speed=3s ๐Ÿฎ] $[twitch.delay=3s ๐Ÿฎ] $[twitch.loop=3 ๐Ÿฎ]",
+);
+const preview_spin = ref(
+	"$[spin ๐Ÿฎ] $[spin.left ๐Ÿฎ] $[spin.alternate ๐Ÿฎ]\n$[spin.x ๐Ÿฎ] $[spin.x,left ๐Ÿฎ] $[spin.x,alternate ๐Ÿฎ]\n$[spin.y ๐Ÿฎ] $[spin.y,left ๐Ÿฎ] $[spin.y,alternate ๐Ÿฎ]\n\n$[spin.speed=3s ๐Ÿฎ] $[spin.delay=3s ๐Ÿฎ] $[spin.loop=3 ๐Ÿฎ]",
+);
+const preview_flip = ref(
+	`$[flip ${i18n.ts._mfm.dummy}]\n$[flip.v ${i18n.ts._mfm.dummy}]\n$[flip.h,v ${i18n.ts._mfm.dummy}]`,
+);
+const preview_font = ref(
+	`$[font.serif ${i18n.ts._mfm.dummy}]\n$[font.monospace ${i18n.ts._mfm.dummy}]\n$[font.cursive ${i18n.ts._mfm.dummy}]\n$[font.fantasy ${i18n.ts._mfm.dummy}]`,
+);
+const preview_x2 = ref("$[x2 ๐Ÿฎ]");
+const preview_x3 = ref("$[x3 ๐Ÿฎ]");
+const preview_x4 = ref("$[x4 ๐Ÿฎ]");
+const preview_blur = ref(`$[blur ${i18n.ts._mfm.dummy}]`);
+const preview_rainbow = ref(
+	"$[rainbow ๐Ÿฎ] $[rainbow.speed=3s ๐Ÿฎ] $[rainbow.delay=3s ๐Ÿฎ] $[rainbow.loop=3 ๐Ÿฎ]",
+);
+const preview_sparkle = ref("$[sparkle ๐Ÿฎ]");
+const preview_rotate = ref(
+	"$[rotate ๐Ÿฎ]\n$[rotate.deg=45 ๐Ÿฎ]\n$[rotate.x,deg=45 Hello, world!]",
+);
+const preview_position = ref("$[position.y=-1 ๐Ÿฎ]\n$[position.x=-1 ๐Ÿฎ]");
+const preview_scale = ref(
+	"$[scale.x=1.3 ๐Ÿฎ]\n$[scale.x=1.5,y=3 ๐Ÿฎ]\n$[scale.y=0.3 ๐Ÿฎ]",
+);
+const preview_fg = ref("$[fg.color=eb6f92 Text color]");
+const preview_bg = ref("$[bg.color=31748f Background color]");
+const preview_plain = ref(
+	"<plain>**bold** @mention #hashtag `code` $[x2 ๐Ÿฎ]</plain>",
+);
+</script>
+
+<style lang="scss" scoped>
+.mfm-cheat-sheet {
+	> .section {
+		> .title {
+			position: sticky;
+			z-index: 1;
+			top: var(--stickyTop, 0px);
+			padding: 16px;
+			font-weight: bold;
+			-webkit-backdrop-filter: var(--blur, blur(10px));
+			backdrop-filter: var(--blur, blur(10px));
+			background-color: var(--X16);
+		}
+
+		> .content {
+			> p {
+				margin: 0;
+				padding: 16px;
+				padding-top: 0;
+			}
+
+			> .preview {
+				border-top: solid 0.5px var(--divider);
+				padding: 16px;
+			}
+		}
+	}
+}
+</style>
\ No newline at end of file
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 623e79d4cc..613d93dfa7 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -88,6 +88,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</div>
 		<div :class="$style.footerRight">
 			<button v-tooltip="i18n.ts.previewNoteText" class="_button" :class="[$style.footerButton, { [$style.previewButtonActive]: showPreview }]" @click="showPreview = !showPreview"><i class="ph-eye ph-bold ph-lg"></i></button>
+			<button v-tooltip="'MFM Cheatsheet'" class="_button" :class="$style.footerButton" @click="MFMWindow"><i class="ph-notebook ph-bold ph-lg"></i></button>
 			<!--<button v-tooltip="i18n.ts.more" class="_button" :class="$style.footerButton" @click="showingOptions = !showingOptions"><i class="ph-dots-three ph-bold ph-lg"></i></button>-->
 		</div>
 	</footer>
@@ -346,6 +347,10 @@ function watchForDraft() {
 	watch($$(localOnly), () => saveDraft());
 }
 
+function MFMWindow() {
+	os.popup(defineAsyncComponent(() => import('@/components/MkMfmWindow.vue')), {}, {}, 'closed');
+}
+
 function checkMissingMention() {
 	if (visibility === 'specified') {
 		const ast = mfm.parse(text);
@@ -1159,7 +1164,7 @@ defineExpose({
 }
 
 .footerRight {
-	flex: 0;
+	flex: 0.3;
 	margin-left: auto;
 	display: grid;
 	grid-auto-flow: row;