Text Formatter: Full support for spoilers
This commit is contained in:
parent
c12b58a9f1
commit
616a266cc4
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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(/ /g, ' ');
|
parsedHtml = parsedHtml.replace(/ /g, ' ');
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user