diff --git a/apps/blog/components/md-editor.tsx b/apps/blog/components/md-editor.tsx
index 0d9d9f415d5a4e10cfe8315590e3cc53620d766a..900c26c5223d78d5ad5b4d6f8707bf8d990511ea 100644
--- a/apps/blog/components/md-editor.tsx
+++ b/apps/blog/components/md-editor.tsx
@@ -21,6 +21,7 @@ import { useTranslation } from 'next-i18next';
 import imageUserBlocklist from '@ui/config/lists/image-user-blocklist';
 import { cn } from '@ui/lib/utils';
 import { useSignerContext } from '@smart-signer/components/signer-provider';
+import { Button } from '@ui/components';
 
 const logger = getLogger('app');
 
@@ -240,7 +241,38 @@ const MdEditor: FC<MdEditorProps> = ({ onChange, persistedValue = '', placeholde
     }
   });
 
-  const editChoice = (inputRef: MutableRefObject<HTMLInputElement>) => [imgBtn(inputRef)];
+  const spoilerBtn = (): commands.ICommand => ({
+    name: "Add Spoiler",
+    keyCommand: "spoiler",
+    render: (
+      command: commands.ICommand,
+      disabled: boolean | undefined,
+      executeCommand: (
+        arg0: commands.ICommand<string>,
+        arg1: string | undefined
+      ) => void
+    ) => {
+      return (
+        <Button
+          variant="basic"
+          onClick={() => executeCommand(command, command.groupName)}
+          disabled={disabled}
+        >
+          Spoiler
+        </Button>
+      );
+    },
+    execute: (_: commands.ExecuteState, api: TextAreaTextApi) => {
+      const spoilerTemplate = ">! [Click to reveal] Your spoiler content";
+      const newState = api.replaceSelection(spoilerTemplate);
+      api.setSelectionRange({
+        start:
+          newState.selection.start +
+          spoilerTemplate.indexOf("Your spoiler content"),
+        end: newState.selection.end,
+      });
+    },
+  });
 
   return !imageUserBlocklist?.includes(user.username) ? (
     <div>
@@ -264,7 +296,7 @@ const MdEditor: FC<MdEditorProps> = ({ onChange, persistedValue = '', placeholde
             onChange={(value) => {
               setFormValue(value || '');
             }}
-            commands={[...(commands.getCommands() as ICommand[]), imgBtn(inputRef)]}
+            commands={[...(commands.getCommands() as ICommand[]), imgBtn(inputRef), spoilerBtn()]}
             extraCommands={[]}
             className={cn({ '!bg-red-400 !bg-opacity-20': isDrag })}
             onDrop={dropHandler}
@@ -287,7 +319,7 @@ const MdEditor: FC<MdEditorProps> = ({ onChange, persistedValue = '', placeholde
       onChange={(value) => {
         setFormValue(value || '');
       }}
-      commands={[...(commands.getCommands() as ICommand[]), imgBtn(inputRef)]}
+      commands={[...(commands.getCommands() as ICommand[]), imgBtn(inputRef), spoilerBtn()]}
       extraCommands={[]}
       //@ts-ignore
       style={{ '--color-canvas-default': 'var(--background)' }}
diff --git a/apps/blog/lib/renderer.ts b/apps/blog/lib/renderer.ts
index 76d01e2d7fb570bb1ee9f7a59c753f83173e277e..d042cc378d3709d6192524426ac7d063ae2b4026 100644
--- a/apps/blog/lib/renderer.ts
+++ b/apps/blog/lib/renderer.ts
@@ -1,4 +1,4 @@
-import { DefaultRenderer } from '@hive/renderer';
+import { DefaultRenderer, SpoilerPlugin } from '@hive/renderer';
 import { getDoubleSize, proxifyImageUrl } from '@ui/lib/old-profixy';
 import env from '@beam-australia/react-env';
 import imageUserBlocklist from '@hive/ui/config/lists/image-user-blocklist';
@@ -17,6 +17,7 @@ const renderDefaultOptions = {
   ipfsPrefix: '',
   assetsWidth: 640,
   assetsHeight: 480,
+  plugins: [new SpoilerPlugin()],
   imageProxyFn: (url: string) => getDoubleSize(proxifyImageUrl(url, true).replace(/ /g, '%20')),
   usertagUrlFn: (account: string) => '/@' + account,
   hashtagUrlFn: (hashtag: string) => '/trending/' + hashtag,
diff --git a/packages/renderer/src/renderers/default/embedder/HtmlDOMParser.ts b/packages/renderer/src/renderers/default/embedder/HtmlDOMParser.ts
index 160a0ebd65480c2bb2d70edeff45da0ed814b649..faaa26d6b4018062b3ccc918f7fec0cb1a833e57 100644
--- a/packages/renderer/src/renderers/default/embedder/HtmlDOMParser.ts
+++ b/packages/renderer/src/renderers/default/embedder/HtmlDOMParser.ts
@@ -72,7 +72,12 @@ export class HtmlDOMParser {
 
     public parse(html: string): HtmlDOMParser {
         try {
-            const doc: Document = this.domParser.parseFromString(html, 'text/html');
+            const fixedHtml = html
+                // Remove wrapping <p> from details
+                .replace(/<p>\s*(<details>[\s\S]*?<\/details>)\s*<\/p>/g, '$1')
+                // Move content after details outside of it
+                .replace(/(<details>[\s\S]*?<\/pre>)([\s\S]*?)(<\/details>)/g, '$1$3$2');
+            const doc: Document = this.domParser.parseFromString(fixedHtml, 'text/html');
             this.traverseDOMNode(doc);
             if (this.mutate) this.postprocessDOM(doc);
             this.parsedDocument = doc;
diff --git a/packages/renderer/src/renderers/default/embedder/embedders/ThreeSpeakEmbedder.ts b/packages/renderer/src/renderers/default/embedder/embedders/ThreeSpeakEmbedder.ts
index 62793dc398ba3b0c9a2c1b04661f18e2dadff083..bad58ea4b10b6a2b7b6b24649354d9a6dda0c6b2 100644
--- a/packages/renderer/src/renderers/default/embedder/embedders/ThreeSpeakEmbedder.ts
+++ b/packages/renderer/src/renderers/default/embedder/embedders/ThreeSpeakEmbedder.ts
@@ -10,7 +10,7 @@ export class ThreeSpeakEmbedder extends AbstractEmbedder {
     public getEmbedMetadata(input: string | HTMLObjectElement): EmbedMetadata | undefined {
         const url = typeof input === 'string' ? input : input.data;
         try {
-            const match = url.match(ThreeSpeakEmbedder.linkRegex);
+            const match = (url.startsWith('\n') ? url.replace('\n', '') : url).match(ThreeSpeakEmbedder.linkRegex);
             if (match && match[1]) {
                 const id = match[1];
                 return {
diff --git a/packages/renderer/src/renderers/default/embedder/embedders/TwitterEmbedder.ts b/packages/renderer/src/renderers/default/embedder/embedders/TwitterEmbedder.ts
index 5aa55d521a218cb81f10bcb0c3e71e8db8da0643..1db23deee02a8149d0fc414ab331e5dad4d9c57f 100644
--- a/packages/renderer/src/renderers/default/embedder/embedders/TwitterEmbedder.ts
+++ b/packages/renderer/src/renderers/default/embedder/embedders/TwitterEmbedder.ts
@@ -10,7 +10,8 @@ export class TwitterEmbedder extends AbstractEmbedder {
     public getEmbedMetadata(input: string | HTMLObjectElement): EmbedMetadata | undefined {
         const url = typeof input === 'string' ? input : input.data;
         try {
-            const metadata = TwitterEmbedder.getTwitterMetadataFromLink(url);
+            const metadata = TwitterEmbedder.getTwitterMetadataFromLink(url.startsWith('\n') ? url.replace('\n', '') : url);
+
             if (!metadata) {
                 return undefined;
             }
diff --git a/packages/renderer/src/renderers/default/plugins/SpoilerPlugin.ts b/packages/renderer/src/renderers/default/plugins/SpoilerPlugin.ts
index 52ed65904026e45a73fadabb9d221029527d3773..59a12cddcc39ce2aa0f8da62813f56fb963b719f 100644
--- a/packages/renderer/src/renderers/default/plugins/SpoilerPlugin.ts
+++ b/packages/renderer/src/renderers/default/plugins/SpoilerPlugin.ts
@@ -4,23 +4,22 @@ export class SpoilerPlugin implements RendererPlugin {
     name = 'spoiler';
 
     preProcess(text: string): string {
-        return text.replace(/^>! *\[(.*?)\] *(.*?)(?:\n|$)([\s\S]*?)(?=^>! *\[|$)/gm, (_, title, firstLine, rest) => {
-            // Get the first line content (after the title)
-            const content = firstLine.trim();
+        // Matches spoiler blocks with optional title and multiple lines
+        return text.replace(/^>!(?:\s*\[(.*?)\])?\s*(.*?)(?:\n|$)((?:\n> ?.*)*)$/gm, (_, title, firstLine, rest) => {
+            // Combines first line and additional lines into single content string
+            const content = [
+                firstLine.trim(),
+                ...rest
+                    .split('\n') // Split additional lines
+                    .map((line: string) => line.trim()) // Remove whitespace
+                    .filter((line: string) => line.startsWith('>')) // Keep only quote lines
+                    .map((line: string) => line.replace(/^> ?/, '')) // Remove quote markers
+            ]
+                .join(' ') // Join all lines with spaces
+                .trim(); // Remove extra whitespace
 
-            // Get the rest of the content (lines starting with >)
-            const restContent = rest
-                .split('\n')
-                .map((line: string) => line.trim())
-                .filter((line: string) => line.startsWith('>'))
-                .map((line: string) => line.replace(/^> ?/, ''))
-                .join(' ')
-                .trim();
-
-            // Combine all content
-            const fullContent = [content, restContent].filter(Boolean).join(' ');
-
-            return `<details class="spoiler"><summary>${title || 'Reveal spoiler'}</summary><p>${fullContent}</p></details>`;
+            // Generate HTML details/summary structure
+            return `<details class="spoiler"><summary>${title || 'Reveal spoiler'}</summary><p>${content}</p></details>`;
         });
     }
 }