feat(editor): inline spoiler mark (blur + click-reveal, lossless Markdown) (#246)
Add an inline spoiler (Telegram/Discord-style hidden text): a TipTap mark `spoiler` rendered as <span data-spoiler="true" class="spoiler">, blurred via CSS and revealed on click (UI-only is-revealed class, never persisted). - packages/editor-ext: the Spoiler mark (inclusive:false, set/toggle/unset commands, ||text|| input rule), exported; a lossless turndown rule emitting raw inline HTML; round-trip test. - apps/client: SpoilerView mark-view (ReactMarkViewRenderer, Link pattern), registration in extensions, bubble-menu toggle button (editable only), CSS (blur + @media print reveal), en/ru i18n. - apps/server: register Spoiler in collaboration.util tiptapExtensions so the mark survives HTML<->JSON export/index/import/Yjs; a test proving the public share keeps the spoiler (it isn't stripped with comments). No keyboard shortcut: the proposed Mod-Shift-s collides with Strike (and Mod-Shift-h with Highlight); the ||text|| input rule + the bubble-menu button cover ergonomics. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
IconStrikethrough,
|
||||
IconUnderline,
|
||||
IconMessage,
|
||||
IconEyeOff,
|
||||
} from "@tabler/icons-react";
|
||||
import clsx from "clsx";
|
||||
import classes from "./bubble-menu.module.css";
|
||||
@@ -74,6 +75,7 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
|
||||
isStrike: ctx.editor.isActive("strike"),
|
||||
isCode: ctx.editor.isActive("code"),
|
||||
isComment: ctx.editor.isActive("comment"),
|
||||
isSpoiler: ctx.editor.isActive("spoiler"),
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -109,6 +111,12 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
|
||||
command: () => props.editor.chain().focus().toggleCode().run(),
|
||||
icon: IconCode,
|
||||
},
|
||||
{
|
||||
name: "Spoiler",
|
||||
isActive: () => editorState?.isSpoiler,
|
||||
command: () => props.editor.chain().focus().toggleSpoiler().run(),
|
||||
icon: IconEyeOff,
|
||||
},
|
||||
];
|
||||
|
||||
const commentItem: BubbleMenuItem = {
|
||||
|
||||
Reference in New Issue
Block a user