Text Formatter: Full support for spoilers

This commit is contained in:
Alexander Zinchuk 2022-01-28 02:11:09 +01:00
parent c12b58a9f1
commit 616a266cc4
4 changed files with 56 additions and 15 deletions

View File

@ -18,7 +18,9 @@ import SafeLink from '../SafeLink';
import Spoiler from '../spoiler/Spoiler'; import Spoiler from '../spoiler/Spoiler';
import { LangFn } from '../../../hooks/useLang'; import { LangFn } from '../../../hooks/useLang';
export type TextPart = string | Element; export type TextPart =
string
| Element;
export function renderMessageSummary( export function renderMessageSummary(
lang: LangFn, lang: LangFn,
@ -500,7 +502,10 @@ function processEntityAsHtml(
dir="auto" dir="auto"
>${renderedContent}</a>`; >${renderedContent}</a>`;
case ApiMessageEntityTypes.Spoiler: case ApiMessageEntityTypes.Spoiler:
return `||${renderedContent}||`; return `<span
class="spoiler"
data-entity-type="${ApiMessageEntityTypes.Spoiler}"
>${renderedContent}</span>`;
default: default:
return renderedContent; return renderedContent;
} }

View File

@ -399,6 +399,18 @@
} }
} }
.spoiler {
background-image: url('../../../assets/spoiler-dots-black.png');
background-size: auto min(100%, 1.125rem);
border-radius: 0.5rem;
padding: 0 0.3125rem 0.125rem 0.3125rem;
text-shadow: -2px -2px 0 var(--color-background), 2px -2px 0 var(--color-background), -2px 2px 0 var(--color-background), 2px 2px 0 var(--color-background);
}
html.theme-dark & .spoiler {
background-image: url('../../../assets/spoiler-dots-white.png');
}
.clone { .clone {
position: absolute; position: absolute;
top: 0; top: 0;

View File

@ -3,6 +3,7 @@ import React, {
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { IAnchorPosition } from '../../../types'; import { IAnchorPosition } from '../../../types';
import { ApiMessageEntityTypes } from '../../../api/types';
import { EDITABLE_INPUT_ID } from '../../../config'; import { EDITABLE_INPUT_ID } from '../../../config';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
@ -32,6 +33,7 @@ interface ISelectedTextFormats {
underline?: boolean; underline?: boolean;
strikethrough?: boolean; strikethrough?: boolean;
monospace?: boolean; monospace?: boolean;
spoiler?: boolean;
} }
const TEXT_FORMAT_BY_TAG_NAME: Record<string, keyof ISelectedTextFormats> = { const TEXT_FORMAT_BY_TAG_NAME: Record<string, keyof ISelectedTextFormats> = {
@ -42,6 +44,7 @@ const TEXT_FORMAT_BY_TAG_NAME: Record<string, keyof ISelectedTextFormats> = {
U: 'underline', U: 'underline',
DEL: 'strikethrough', DEL: 'strikethrough',
CODE: 'monospace', CODE: 'monospace',
SPAN: 'spoiler',
}; };
const fragmentEl = document.createElement('div'); const fragmentEl = document.createElement('div');
@ -188,6 +191,34 @@ const TextFormatter: FC<OwnProps> = ({
return undefined; return undefined;
} }
const handleSpoilerText = useCallback(() => {
if (selectedTextFormats.spoiler) {
const element = getSelectedElement();
if (
!selectedRange
|| !element
|| element.dataset.entityType !== ApiMessageEntityTypes.Spoiler
|| !element.textContent
) {
return;
}
element.replaceWith(element.textContent);
setSelectedTextFormats((selectedFormats) => ({
...selectedFormats,
spoiler: false,
}));
return;
}
const text = getSelectedText();
document.execCommand(
'insertHTML', false, `<span class="spoiler" data-entity-type="${ApiMessageEntityTypes.Spoiler}">${text}</span>`,
);
onClose();
}, [getSelectedElement, getSelectedText, onClose, selectedRange, selectedTextFormats.spoiler]);
const handleBoldText = useCallback(() => { const handleBoldText = useCallback(() => {
setSelectedTextFormats((selectedFormats) => { setSelectedTextFormats((selectedFormats) => {
// Somehow re-applying 'bold' command to already bold text doesn't work // Somehow re-applying 'bold' command to already bold text doesn't work
@ -249,16 +280,9 @@ const TextFormatter: FC<OwnProps> = ({
document.execCommand('insertHTML', false, `<del>${text}</del>`); document.execCommand('insertHTML', false, `<del>${text}</del>`);
onClose(); onClose();
}, [ }, [
getSelectedElement, getSelectedText, onClose, getSelectedElement, getSelectedText, onClose, selectedRange, selectedTextFormats.strikethrough,
selectedRange, selectedTextFormats.strikethrough,
]); ]);
const handleSpoilerText = useCallback(() => {
const text = getSelectedText();
document.execCommand('insertHTML', false, `||${text}||`);
onClose();
}, [getSelectedText, onClose]);
const handleMonospaceText = useCallback(() => { const handleMonospaceText = useCallback(() => {
if (selectedTextFormats.monospace) { if (selectedTextFormats.monospace) {
const element = getSelectedElement(); const element = getSelectedElement();
@ -276,6 +300,7 @@ const TextFormatter: FC<OwnProps> = ({
...selectedFormats, ...selectedFormats,
monospace: false, monospace: false,
})); }));
return; return;
} }
@ -283,8 +308,7 @@ const TextFormatter: FC<OwnProps> = ({
document.execCommand('insertHTML', false, `<code class="text-entity-code" dir="auto">${text}</code>`); document.execCommand('insertHTML', false, `<code class="text-entity-code" dir="auto">${text}</code>`);
onClose(); onClose();
}, [ }, [
getSelectedElement, getSelectedText, onClose, getSelectedElement, getSelectedText, onClose, selectedRange, selectedTextFormats.monospace,
selectedRange, selectedTextFormats.monospace,
]); ]);
function handleLinkUrlConfirm() { function handleLinkUrlConfirm() {
@ -299,6 +323,7 @@ const TextFormatter: FC<OwnProps> = ({
(element as HTMLAnchorElement).href = formattedLinkUrl; (element as HTMLAnchorElement).href = formattedLinkUrl;
onClose(); onClose();
return; return;
} }
@ -389,10 +414,12 @@ const TextFormatter: FC<OwnProps> = ({
<Button <Button
color="translucent" color="translucent"
ariaLabel="Spoiler text" ariaLabel="Spoiler text"
className={getFormatButtonClassName('spoiler')}
onClick={handleSpoilerText} onClick={handleSpoilerText}
> >
<i className="icon-eye-closed" /> <i className="icon-eye-closed" />
</Button> </Button>
<div className="TextFormatter-divider" />
<Button <Button
color="translucent" color="translucent"
ariaLabel="Bold text" ariaLabel="Bold text"

View File

@ -60,9 +60,6 @@ function parseMarkdown(html: string) {
parsedHtml = parsedHtml.replace(/<img[^>]+alt="([^"]+)"[^>]*>/gm, '$1'); parsedHtml = parsedHtml.replace(/<img[^>]+alt="([^"]+)"[^>]*>/gm, '$1');
} }
// Strip redundant <span> tags
parsedHtml = parsedHtml.replace(/<\/?span([^>]*)?>/g, '');
// Strip redundant nbsp's // Strip redundant nbsp's
parsedHtml = parsedHtml.replace(/&nbsp;/g, ' '); parsedHtml = parsedHtml.replace(/&nbsp;/g, ' ');